Browse Source

Merge branch 'master' into patch-task-after-pack

pull/4146/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
42a018747b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitmodules
  2. 13
      native/Avalonia.Native/inc/avalonia-native.h
  3. 13
      native/Avalonia.Native/src/OSX/window.h
  4. 289
      native/Avalonia.Native/src/OSX/window.mm
  5. 4
      samples/ControlCatalog/MainView.xaml
  6. 7
      samples/ControlCatalog/MainView.xaml.cs
  7. 42
      samples/ControlCatalog/MainWindow.xaml
  8. 123
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  9. 30
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
  10. 21
      samples/ControlCatalog/Pages/SliderPage.xaml
  11. 19
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  12. 19
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs
  13. 65
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  14. 4
      src/Avalonia.Base/AvaloniaProperty.cs
  15. 4
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  16. 2
      src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs
  17. 3
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  18. 34
      src/Avalonia.Base/Data/BindingValue.cs
  19. 36
      src/Avalonia.Base/Data/Core/LogicalNotNode.cs
  20. 16
      src/Avalonia.Base/Data/Optional.cs
  21. 2
      src/Avalonia.Base/DirectPropertyBase.cs
  22. 2
      src/Avalonia.Base/IStyledPropertyMetadata.cs
  23. 140
      src/Avalonia.Base/Metadata/NullableAttributes.cs
  24. 7
      src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
  25. 7
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  26. 3
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  27. 45
      src/Avalonia.Base/Utilities/StyleClassParser.cs
  28. 2
      src/Avalonia.Base/ValueStore.cs
  29. 13
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  30. 2
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
  31. 47
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  32. 86
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  33. 117
      src/Avalonia.Controls/Chrome/TitleBar.cs
  34. 412
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  35. 531
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  36. 19
      src/Avalonia.Controls/DateTimePickers/DatePickerSelectedValueChangedEventArgs.cs
  37. 566
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  38. 25
      src/Avalonia.Controls/DateTimePickers/PickerPresenterBase.cs
  39. 292
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  40. 262
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  41. 15
      src/Avalonia.Controls/DateTimePickers/TimePickerSelectedValueChangedEventArgs.cs
  42. 38
      src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
  43. 46
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  44. 29
      src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
  45. 15
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  46. 1
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  47. 66
      src/Avalonia.Controls/Slider.cs
  48. 12
      src/Avalonia.Controls/TickBar.cs
  49. 2
      src/Avalonia.Controls/TopLevel.cs
  50. 135
      src/Avalonia.Controls/Window.cs
  51. 24
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  52. 24
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  53. 7
      src/Avalonia.Input/InputElement.cs
  54. 4
      src/Avalonia.Input/Navigation/TabNavigation.cs
  55. 86
      src/Avalonia.Native/WindowImpl.cs
  56. 14
      src/Avalonia.Native/WindowImplBase.cs
  57. 5
      src/Avalonia.Styling/IStyledElement.cs
  58. 1
      src/Avalonia.Styling/StyledElement.cs
  59. 1
      src/Avalonia.Styling/Styling/Styles.cs
  60. 69
      src/Avalonia.Themes.Default/CaptionButtons.xaml
  61. 1
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  62. 9
      src/Avalonia.Themes.Default/Slider.xaml
  63. 53
      src/Avalonia.Themes.Default/TitleBar.xaml
  64. 4
      src/Avalonia.Themes.Default/Window.xaml
  65. 61
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  66. 61
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  67. 68
      src/Avalonia.Themes.Fluent/CaptionButtons.xaml
  68. 338
      src/Avalonia.Themes.Fluent/DatePicker.xaml
  69. 6
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  70. 1
      src/Avalonia.Themes.Fluent/Slider.xaml
  71. 284
      src/Avalonia.Themes.Fluent/TimePicker.xaml
  72. 53
      src/Avalonia.Themes.Fluent/TitleBar.xaml
  73. 2
      src/Avalonia.Themes.Fluent/Window.xaml
  74. 24
      src/Avalonia.X11/X11Window.cs
  75. 6
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  76. 36
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  77. 75
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  78. 78
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  79. 98
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
  80. 10
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs
  81. 10
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs
  82. 28
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs
  83. 28
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs
  84. 26
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs
  85. 8
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
  86. 118
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  87. 68
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
  88. 78
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs
  89. 12
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs
  90. 96
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  91. 14
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs
  92. 19
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs
  93. 103
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs
  94. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  95. 4
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  96. 9
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  97. 539
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  98. 140
      src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs
  99. 511
      src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs
  100. 167
      src/Windows/Avalonia.Win32/WindowImpl.cs

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

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

4
samples/ControlCatalog/MainView.xaml

@ -29,6 +29,9 @@
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:DataGridPage/>
</TabItem>
<TabItem Header="Date/Time Picker">
<pages:DateTimePickerPage/>
</TabItem>
<TabItem Header="CalendarDatePicker">
<pages:CalendarDatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
@ -63,6 +66,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">

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)

42
samples/ControlCatalog/MainWindow.xaml

@ -7,7 +7,13 @@
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}"
TransparencyBackgroundFallback="Transparent"
x:Name="MainWindow"
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{x:Null}">
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="File">
@ -56,20 +62,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 Background="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" 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>

123
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@ -0,0 +1,123 @@
<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.DateTimePickerPage">
<StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Stretch">
<TextBlock Classes="h1">DatePicker and TimePicker</TextBlock>
<TextBlock Name="DatePickerDesc" Classes="h2" TextWrapping="Wrap"/>
<TextBlock Name="TimePickerDesc" Classes="h2" TextWrapping="Wrap"/>
<StackPanel Orientation="Vertical"
Margin="16"
HorizontalAlignment="Stretch"
Spacing="16">
<TextBlock FontSize="18">A simple DatePicker with a header</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<DatePicker Header="Pick a date" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;DatePicker Header="Pick a date" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
<TextBlock FontSize="18">A DatePicker with day formatted and year hidden.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<DatePicker x:Name="Control2" DayFormat="d (ddd)"
YearVisible="False" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;DatePicker DayFormat="d (ddd)" YearVisible="False" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
<Border Background="{DynamicResource SystemControlHighlightBaseLowBrush}" BorderThickness="1" Margin="15" />
<TextBlock FontSize="18">A simple TimePicker.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<TimePicker />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;TimePicker /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
<TextBlock FontSize="18">A TimePicker with a header and minute increments specified.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<TimePicker Header="Arrival time" MinuteIncrement="15" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;TimePicker Header="Arrival time" MinuteIncrement="15" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
<TextBlock FontSize="18">A TimePicker using a 12-hour clock.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
<TextBlock FontSize="18">A TimePicker using a 24-hour clock.</TextBlock>
<StackPanel Orientation="Vertical">
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1" Padding="15">
<TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" />
</Border>
<Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
<TextBlock Padding="15">
<TextBlock.Text>
<x:String>
&lt;TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /&gt;
</x:String>
</TextBlock.Text>
</TextBlock>
</Panel>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

30
samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs

@ -0,0 +1,30 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class DateTimePickerPage : UserControl
{
public DateTimePickerPage()
{
this.InitializeComponent();
this.FindControl<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " +
"for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " +
"These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
"Order of month, day, and year is dynamically set based on user date settings";
this.FindControl<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
"to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
"are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
"12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden.";
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

21
samples/ControlCatalog/Pages/SliderPage.xaml

@ -6,11 +6,22 @@
<TextBlock Classes="h2">A control that lets the user select from a range of values by moving a Thumb control along a Track.</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 16 0 0" Spacing="16">
<Slider Value="0"
Minimum="0"
Maximum="100"
TickFrequency="10"
Width="300"/>
<StackPanel Orientation="Vertical"
HorizontalAlignment="Center">
<Slider Value="0"
Minimum="0"
Maximum="100"
TickFrequency="10"
Width="300" />
<Slider Name="CustomTickedSlider"
Value="0"
Minimum="0"
Maximum="100"
TickPlacement="BottomRight"
IsSnapToTickEnabled="True"
Ticks="0,20,25,40,75,100"
Width="300" />
</StackPanel>
<Slider Value="0"
Minimum="0"
Maximum="100"

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

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
src/Avalonia.Base/AvaloniaProperty.cs

@ -159,8 +159,6 @@ namespace Avalonia
/// </summary>
internal int Id { get; }
internal bool HasChangedSubscriptions => _changed?.HasObservers ?? false;
/// <summary>
/// Provides access to a property's binding via the <see cref="AvaloniaObject"/>
/// indexer.
@ -512,7 +510,7 @@ namespace Avalonia
/// <returns>
/// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
/// </returns>
internal abstract IDisposable? RouteSetValue(
internal abstract IDisposable RouteSetValue(
IAvaloniaObject o,
object value,
BindingPriority priority);

4
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -362,7 +362,7 @@ namespace Avalonia
/// <param name="property">The property.</param>
/// <remarks>
/// You won't usually want to call this method directly, instead use the
/// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TOwner, TValue, TValue}, Action{IAvaloniaObject, bool})"/>
/// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{IAvaloniaObject, TValue, TValue}, Action{IAvaloniaObject, bool})"/>
/// method.
/// </remarks>
public void Register(Type type, AvaloniaProperty property)
@ -413,7 +413,7 @@ namespace Avalonia
/// <param name="property">The property.</param>
/// <remarks>
/// You won't usually want to call this method directly, instead use the
/// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{THost, TValue, TValue})"/>
/// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{IAvaloniaObject, TValue, TValue})"/>
/// method.
/// </remarks>
public void RegisterAttached(Type type, AvaloniaProperty property)

2
src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs

@ -13,9 +13,11 @@ namespace Avalonia.Collections.Pooled
public interface IReadOnlyPooledList<T> : IReadOnlyList<T>
{
#pragma warning disable CS0419
/// <summary>
/// Gets a <see cref="System.ReadOnlySpan{T}"/> for the items currently in the collection.
/// </summary>
#pragma warning restore CS0419
ReadOnlySpan<T> Span { get; }
}
}

3
src/Avalonia.Base/Collections/Pooled/PooledList.cs

@ -138,7 +138,6 @@ namespace Avalonia.Collections.Pooled
/// initially empty, but will have room for the given number of elements
/// before any reallocations are required.
/// </summary>
/// <param name="sizeToCapacity">If true, Count of list equals capacity. Depending on ClearMode, rented items may or may not hold dirty values.</param>
public PooledList(int capacity, ClearMode clearMode, ArrayPool<T> customPool, bool sizeToCapacity)
{
if (capacity < 0)
@ -499,11 +498,13 @@ namespace Avalonia.Collections.Pooled
public void AddRange(T[] array)
=> AddRange(array.AsSpan());
#pragma warning disable CS0419
/// <summary>
/// Adds the elements of the given <see cref="ReadOnlySpan{T}"/> to the end of this list. If
/// required, the capacity of the list is increased to twice the previous
/// capacity or the new size, whichever is larger.
/// </summary>
#pragma warning restore CS0419
public void AddRange(ReadOnlySpan<T> span)
{
var newSpan = InsertSpan(_size, span.Length, false);

34
src/Avalonia.Base/Data/BindingValue.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Utilities;
#nullable enable
@ -81,14 +82,14 @@ namespace Avalonia.Data
/// </remarks>
public readonly struct BindingValue<T>
{
private readonly T _value;
[AllowNull] private readonly T _value;
/// <summary>
/// Initializes a new instance of the <see cref="BindingValue{T}"/> struct with a type of
/// <see cref="BindingValueType.Value"/>
/// </summary>
/// <param name="value">The value.</param>
public BindingValue(T value)
public BindingValue([AllowNull] T value)
{
ValidateValue(value);
_value = value;
@ -96,7 +97,7 @@ namespace Avalonia.Data
Error = null;
}
private BindingValue(BindingValueType type, T value, Exception? error)
private BindingValue(BindingValueType type, [AllowNull] T value, Exception? error)
{
_value = value;
Type = type;
@ -154,7 +155,7 @@ namespace Avalonia.Data
BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue,
BindingValueType.DoNothing => BindingOperations.DoNothing,
BindingValueType.Value => _value,
BindingValueType.BindingError =>
BindingValueType.BindingError =>
new BindingNotification(Error, BindingErrorType.Error),
BindingValueType.BindingErrorWithFallback =>
new BindingNotification(Error, BindingErrorType.Error, Value),
@ -175,7 +176,7 @@ namespace Avalonia.Data
/// The binding type is <see cref="BindingValueType.UnsetValue"/> or
/// <see cref="BindingValueType.DoNothing"/>.
/// </exception>
public BindingValue<T> WithValue(T value)
public BindingValue<T> WithValue([AllowNull] T value)
{
if (Type == BindingValueType.DoNothing)
{
@ -190,6 +191,7 @@ namespace Avalonia.Data
/// Gets the value of the binding value if present, otherwise the default value.
/// </summary>
/// <returns>The value.</returns>
[return: MaybeNull]
public T GetValueOrDefault() => HasValue ? _value : default;
/// <summary>
@ -206,6 +208,7 @@ namespace Avalonia.Data
/// The value if present and of the correct type, `default(TResult)` if the value is
/// not present or of an incorrect type.
/// </returns>
[return: MaybeNull]
public TResult GetValueOrDefault<TResult>()
{
return HasValue ?
@ -222,7 +225,8 @@ namespace Avalonia.Data
/// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
/// value is not present.
/// </returns>
public TResult GetValueOrDefault<TResult>(TResult defaultValue)
[return: MaybeNull]
public TResult GetValueOrDefault<TResult>([AllowNull] TResult defaultValue)
{
return HasValue ?
_value is TResult result ? result : default
@ -242,7 +246,7 @@ namespace Avalonia.Data
UnsetValueType _ => Unset,
DoNothingType _ => DoNothing,
BindingNotification n => n.ToBindingValue().Cast<T>(),
_ => (T)value
_ => new BindingValue<T>((T)value)
};
}
@ -250,7 +254,7 @@ namespace Avalonia.Data
/// Creates a binding value from an instance of the underlying value type.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator BindingValue<T>(T value) => new BindingValue<T>(value);
public static implicit operator BindingValue<T>([AllowNull] T value) => new BindingValue<T>(value);
/// <summary>
/// Creates a binding value from an <see cref="Optional{T}"/>.
@ -278,7 +282,7 @@ namespace Avalonia.Data
/// <param name="e">The binding error.</param>
public static BindingValue<T> BindingError(Exception e)
{
e = e ?? throw new ArgumentNullException("e");
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue<T>(BindingValueType.BindingError, default, e);
}
@ -290,7 +294,7 @@ namespace Avalonia.Data
/// <param name="fallbackValue">The fallback value.</param>
public static BindingValue<T> BindingError(Exception e, T fallbackValue)
{
e = e ?? throw new ArgumentNullException("e");
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue<T>(BindingValueType.BindingErrorWithFallback, fallbackValue, e);
}
@ -303,7 +307,7 @@ namespace Avalonia.Data
/// <param name="fallbackValue">The fallback value.</param>
public static BindingValue<T> BindingError(Exception e, Optional<T> fallbackValue)
{
e = e ?? throw new ArgumentNullException("e");
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue<T>(
fallbackValue.HasValue ?
@ -319,7 +323,7 @@ namespace Avalonia.Data
/// <param name="e">The data validation error.</param>
public static BindingValue<T> DataValidationError(Exception e)
{
e = e ?? throw new ArgumentNullException("e");
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue<T>(BindingValueType.DataValidationError, default, e);
}
@ -331,7 +335,7 @@ namespace Avalonia.Data
/// <param name="fallbackValue">The fallback value.</param>
public static BindingValue<T> DataValidationError(Exception e, T fallbackValue)
{
e = e ?? throw new ArgumentNullException("e");
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue<T>(BindingValueType.DataValidationErrorWithFallback, fallbackValue, e);
}
@ -344,7 +348,7 @@ namespace Avalonia.Data
/// <param name="fallbackValue">The fallback value.</param>
public static BindingValue<T> DataValidationError(Exception e, Optional<T> fallbackValue)
{
e = e ?? throw new ArgumentNullException("e");
e = e ?? throw new ArgumentNullException(nameof(e));
return new BindingValue<T>(
fallbackValue.HasValue ?
@ -354,7 +358,7 @@ namespace Avalonia.Data
e);
}
private static void ValidateValue(T value)
private static void ValidateValue([AllowNull] T value)
{
if (value is UnsetValueType)
{

36
src/Avalonia.Base/Data/Core/LogicalNotNode.cs

@ -12,8 +12,19 @@ namespace Avalonia.Data.Core
base.NextValueChanged(Negate(value));
}
private static object Negate(object v)
private static object Negate(object value)
{
var notification = value as BindingNotification;
var v = BindingNotification.ExtractValue(value);
BindingNotification GenerateError(Exception e)
{
notification ??= new BindingNotification(AvaloniaProperty.UnsetValue);
notification.AddError(e, BindingErrorType.Error);
notification.ClearValue();
return notification;
}
if (v != AvaloniaProperty.UnsetValue)
{
var s = v as string;
@ -28,9 +39,7 @@ namespace Avalonia.Data.Core
}
else
{
return new BindingNotification(
new InvalidCastException($"Unable to convert '{s}' to bool."),
BindingErrorType.Error);
return GenerateError(new InvalidCastException($"Unable to convert '{s}' to bool."));
}
}
else
@ -38,24 +47,31 @@ namespace Avalonia.Data.Core
try
{
var boolean = Convert.ToBoolean(v, CultureInfo.InvariantCulture);
return !boolean;
if (notification is object)
{
notification.SetValue(!boolean);
return notification;
}
else
{
return !boolean;
}
}
catch (InvalidCastException)
{
// The error message here is "Unable to cast object of type 'System.Object'
// to type 'System.IConvertible'" which is kinda useless so provide our own.
return new BindingNotification(
new InvalidCastException($"Unable to convert '{v}' to bool."),
BindingErrorType.Error);
return GenerateError(new InvalidCastException($"Unable to convert '{v}' to bool."));
}
catch (Exception e)
{
return new BindingNotification(e, BindingErrorType.Error);
return GenerateError(e);
}
}
}
return AvaloniaProperty.UnsetValue;
return notification ?? AvaloniaProperty.UnsetValue;
}
public object Transform(object value)

16
src/Avalonia.Base/Data/Optional.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
#nullable enable
@ -22,13 +23,13 @@ namespace Avalonia.Data
/// </remarks>
public readonly struct Optional<T> : IEquatable<Optional<T>>
{
private readonly T _value;
[AllowNull] private readonly T _value;
/// <summary>
/// Initializes a new instance of the <see cref="Optional{T}"/> struct with value.
/// </summary>
/// <param name="value">The value.</param>
public Optional(T value)
public Optional([AllowNull] T value)
{
_value = value;
HasValue = true;
@ -48,7 +49,7 @@ namespace Avalonia.Data
public T Value => HasValue ? _value : throw new InvalidOperationException("Optional has no value.");
/// <inheritdoc/>
public override bool Equals(object obj) => obj is Optional<T> o && this == o;
public override bool Equals(object? obj) => obj is Optional<T> o && this == o;
/// <inheritdoc/>
public bool Equals(Optional<T> other) => this == other;
@ -69,6 +70,7 @@ namespace Avalonia.Data
/// Gets the value if present, otherwise the default value.
/// </summary>
/// <returns>The value.</returns>
[return: MaybeNull]
public T GetValueOrDefault() => HasValue ? _value : default;
/// <summary>
@ -85,6 +87,7 @@ namespace Avalonia.Data
/// The value if present and of the correct type, `default(TResult)` if the value is
/// not present or of an incorrect type.
/// </returns>
[return: MaybeNull]
public TResult GetValueOrDefault<TResult>()
{
return HasValue ?
@ -101,7 +104,8 @@ namespace Avalonia.Data
/// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
/// value is not present.
/// </returns>
public TResult GetValueOrDefault<TResult>(TResult defaultValue)
[return: MaybeNull]
public TResult GetValueOrDefault<TResult>([AllowNull] TResult defaultValue)
{
return HasValue ?
_value is TResult result ? result : default
@ -112,7 +116,7 @@ namespace Avalonia.Data
/// Creates an <see cref="Optional{T}"/> from an instance of the underlying value type.
/// </summary>
/// <param name="value">The value.</param>
public static implicit operator Optional<T>(T value) => new Optional<T>(value);
public static implicit operator Optional<T>([AllowNull] T value) => new Optional<T>(value);
/// <summary>
/// Compares two <see cref="Optional{T}"/>s for inequality.
@ -128,7 +132,7 @@ namespace Avalonia.Data
/// <param name="x">The first value.</param>
/// <param name="y">The second value.</param>
/// <returns>True if the values are equal; otherwise false.</returns>
public static bool operator==(Optional<T> x, Optional<T> y)
public static bool operator ==(Optional<T> x, Optional<T> y)
{
if (!x.HasValue && !y.HasValue)
{

2
src/Avalonia.Base/DirectPropertyBase.cs

@ -120,7 +120,7 @@ namespace Avalonia
return o.GetValue<TValue>(this);
}
internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
internal override object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
{
return o.GetValue<TValue>(this);
}

2
src/Avalonia.Base/IStyledPropertyMetadata.cs

@ -1,5 +1,3 @@
using System;
namespace Avalonia
{
/// <summary>

140
src/Avalonia.Base/Metadata/NullableAttributes.cs

@ -0,0 +1,140 @@
#pragma warning disable MA0048 // File name must match type name
#define INTERNAL_NULLABLE_ATTRIBUTES
#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
// https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
// 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.
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class AllowNullAttribute : Attribute
{ }
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class DisallowNullAttribute : Attribute
{ }
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class MaybeNullAttribute : Attribute
{ }
/// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class NotNullAttribute : Attribute
{ }
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class MaybeNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter may be null.
/// </param>
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class NotNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter will not be null.
/// </param>
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class NotNullIfNotNullAttribute : Attribute
{
/// <summary>Initializes the attribute with the associated parameter name.</summary>
/// <param name="parameterName">
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
/// </param>
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
/// <summary>Gets the associated parameter name.</summary>
public string ParameterName { get; }
}
/// <summary>Applied to a method that will never return under any circumstance.</summary>
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class DoesNotReturnAttribute : Attribute
{ }
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
#if INTERNAL_NULLABLE_ATTRIBUTES
internal
#else
public
#endif
sealed class DoesNotReturnIfAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified parameter value.</summary>
/// <param name="parameterValue">
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
/// the associated parameter matches this value.
/// </param>
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
/// <summary>Gets the condition parameter value.</summary>
public bool ParameterValue { get; }
}
}
#endif

7
src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs

@ -1,7 +1,4 @@
using System;
using Avalonia.Data;
#nullable enable
#nullable enable
namespace Avalonia.PropertyStore
{
@ -10,8 +7,6 @@ namespace Avalonia.PropertyStore
/// </summary>
internal interface IPriorityValueEntry : IValue
{
BindingPriority Priority { get; }
void Reparent(IValueSink sink);
}

7
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@ -1,4 +1,5 @@
using Avalonia.Data;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
#nullable enable
@ -11,9 +12,9 @@ namespace Avalonia.PropertyStore
/// <typeparam name="T">The property type.</typeparam>
internal class LocalValueEntry<T> : IValue<T>
{
private T _value;
[AllowNull] private T _value;
public LocalValueEntry(T value) => _value = value;
public LocalValueEntry([AllowNull] T value) => _value = value;
public BindingPriority Priority => BindingPriority.LocalValue;
Optional<object> IValue.GetValue() => new Optional<object>(_value);

3
src/Avalonia.Base/StyledPropertyMetadata`1.cs

@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using Avalonia.Data;
namespace Avalonia
@ -35,7 +34,7 @@ namespace Avalonia
/// <summary>
/// Gets the value coercion callback, if any.
/// </summary>
public Func<IAvaloniaObject, TValue, TValue>? CoerceValue { get; private set; }
public Func<IAvaloniaObject, TValue, TValue> CoerceValue { get; private set; }
object IStyledPropertyMetadata.DefaultValue => DefaultValue;

45
src/Avalonia.Base/Utilities/StyleClassParser.cs

@ -0,0 +1,45 @@
using System;
using System.Globalization;
namespace Avalonia.Utilities
{
#if !BUILDTASK
public
#endif
static class StyleClassParser
{
public static ReadOnlySpan<char> ParseStyleClass(this ref CharacterReader r)
{
if (IsValidIdentifierStart(r.Peek))
{
return r.TakeWhile(c => IsValidIdentifierChar(c));
}
else
{
return ReadOnlySpan<char>.Empty;
}
}
private static bool IsValidIdentifierStart(char c)
{
return char.IsLetter(c) || c == '_';
}
private static bool IsValidIdentifierChar(char c)
{
if (IsValidIdentifierStart(c) || c == '-')
{
return true;
}
else
{
var cat = CharUnicodeInfo.GetUnicodeCategory(c);
return cat == UnicodeCategory.NonSpacingMark ||
cat == UnicodeCategory.SpacingCombiningMark ||
cat == UnicodeCategory.ConnectorPunctuation ||
cat == UnicodeCategory.Format ||
cat == UnicodeCategory.DecimalDigitNumber;
}
}
}
}

2
src/Avalonia.Base/ValueStore.cs

@ -162,7 +162,7 @@ namespace Avalonia
_sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
property,
old,
new Optional<T>(old),
default,
BindingPriority.Unset));
}

13
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,10 +21,10 @@
<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">
@ -42,9 +42,12 @@
<Compile Include="../Avalonia.Base/Utilities/IdentifierParser.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 Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
</ItemGroup>

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
{

47
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
{
@ -51,11 +52,11 @@ namespace Avalonia.Build.Tasks
// Nothing to do
return new CompileResult(true);
var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new XamlIlTransformerConfiguration(typeSystem,
var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new TransformerConfiguration(typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage),
AvaloniaXamlIlLanguage.CustomValueConverter);
@ -63,10 +64,10 @@ namespace Avalonia.Build.Tasks
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 +127,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 +324,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;

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

412
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@ -0,0 +1,412 @@
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Interactivity;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Avalonia.Controls
{
/// <summary>
/// A control to allow the user to select a date
/// </summary>
public class DatePicker : TemplatedControl
{
/// <summary>
/// Define the <see cref="DayFormat"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, string> DayFormatProperty =
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(DayFormat),
x => x.DayFormat, (x, v) => x.DayFormat = v);
/// <summary>
/// Defines the <see cref="DayVisible"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, bool> DayVisibleProperty =
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible),
x => x.DayVisible, (x, v) => x.DayVisible = v);
/// <summary>
/// Defines the <see cref="Header"/> Property
/// </summary>
public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<DatePicker, object>(nameof(Header));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> Property
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate));
/// <summary>
/// Defines the <see cref="MaxYear"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, DateTimeOffset> MaxYearProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MaxYear),
x => x.MaxYear, (x, v) => x.MaxYear = v);
/// <summary>
/// Defines the <see cref="MinYear"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, DateTimeOffset> MinYearProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MinYear),
x => x.MinYear, (x, v) => x.MinYear = v);
/// <summary>
/// Defines the <see cref="MonthFormat"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, string> MonthFormatProperty =
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(MonthFormat),
x => x.MonthFormat, (x, v) => x.MonthFormat = v);
/// <summary>
/// Defines the <see cref="MonthVisible"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, bool> MonthVisibleProperty =
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(MonthVisible),
x => x.MonthVisible, (x, v) => x.MonthVisible = v);
/// <summary>
/// Defiens the <see cref="YearFormat"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, string> YearFormatProperty =
AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(YearFormat),
x => x.YearFormat, (x, v) => x.YearFormat = v);
/// <summary>
/// Defines the <see cref="YearVisible"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, bool> YearVisibleProperty =
AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(YearVisible),
x => x.YearVisible, (x, v) => x.YearVisible = v);
/// <summary>
/// Defines the <see cref="SelectedDate"/> Property
/// </summary>
public static readonly DirectProperty<DatePicker, DateTimeOffset?> SelectedDateProperty =
AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate),
x => x.SelectedDate, (x, v) => x.SelectedDate = v);
//Template Items
private Button _flyoutButton;
private TextBlock _dayText;
private TextBlock _monthText;
private TextBlock _yearText;
private Grid _container;
private Rectangle _spacer1;
private Rectangle _spacer2;
private Popup _popup;
private DatePickerPresenter _presenter;
private bool _areControlsAvailable;
private string _dayFormat = "%d";
private bool _dayVisible = true;
private DateTimeOffset _maxYear;
private DateTimeOffset _minYear;
private string _monthFormat = "MMMM";
private bool _monthVisible = true;
private string _yearFormat = "yyyy";
private bool _yearVisible = true;
private DateTimeOffset? _selectedDate;
public DatePicker()
{
PseudoClasses.Set(":hasnodate", true);
var now = DateTimeOffset.Now;
_minYear = new DateTimeOffset(now.Date.Year - 100, 1, 1, 0, 0, 0, now.Offset);
_maxYear = new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset);
}
public string DayFormat
{
get => _dayFormat;
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value);
}
/// <summary>
/// Gets or sets whether the day is visible
/// </summary>
public bool DayVisible
{
get => _dayVisible;
set
{
SetAndRaise(DayVisibleProperty, ref _dayVisible, value);
SetGrid();
}
}
/// <summary>
/// Gets or sets the DatePicker header
/// </summary>
public object Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
/// <summary>
/// Gets or sets the header template
/// </summary>
public IDataTemplate HeaderTemplate
{
get => GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
/// <summary>
/// Gets or sets the maximum year for the picker
/// </summary>
public DateTimeOffset MaxYear
{
get => _maxYear;
set
{
if (value < MinYear)
throw new InvalidOperationException("MaxDate cannot be less than MinDate");
SetAndRaise(MaxYearProperty, ref _maxYear, value);
if (SelectedDate.HasValue && SelectedDate.Value > value)
SelectedDate = value;
}
}
/// <summary>
/// Gets or sets the minimum year for the picker
/// </summary>
public DateTimeOffset MinYear
{
get => _minYear;
set
{
if (value > MaxYear)
throw new InvalidOperationException("MinDate cannot be greater than MaxDate");
SetAndRaise(MinYearProperty, ref _minYear, value);
if (SelectedDate.HasValue && SelectedDate.Value < value)
SelectedDate = value;
}
}
/// <summary>
/// Gets or sets the month format
/// </summary>
public string MonthFormat
{
get => _monthFormat;
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value);
}
/// <summary>
/// Gets or sets whether the month is visible
/// </summary>
public bool MonthVisible
{
get => _monthVisible;
set
{
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value);
SetGrid();
}
}
/// <summary>
/// Gets or sets the year format
/// </summary>
public string YearFormat
{
get => _yearFormat;
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value);
}
/// <summary>
/// Gets or sets whether the year is visible
/// </summary>
public bool YearVisible
{
get => _yearVisible;
set
{
SetAndRaise(YearVisibleProperty, ref _yearVisible, value);
SetGrid();
}
}
/// <summary>
/// Gets or sets the Selected Date for the picker, can be null
/// </summary>
public DateTimeOffset? SelectedDate
{
get => _selectedDate;
set
{
var old = _selectedDate;
SetAndRaise(SelectedDateProperty, ref _selectedDate, value);
SetSelectedDateText();
OnSelectedDateChanged(this, new DatePickerSelectedValueChangedEventArgs(old, value));
}
}
/// <summary>
/// Raised when the <see cref="SelectedDate"/> changes
/// </summary>
public event EventHandler<DatePickerSelectedValueChangedEventArgs> SelectedDateChanged;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_areControlsAvailable = false;
if (_flyoutButton != null)
_flyoutButton.Click -= OnFlyoutButtonClicked;
if (_presenter != null)
{
_presenter.Confirmed -= OnConfirmed;
_presenter.Dismissed -= OnDismissPicker;
}
base.OnApplyTemplate(e);
_flyoutButton = e.NameScope.Find<Button>("FlyoutButton");
_dayText = e.NameScope.Find<TextBlock>("DayText");
_monthText = e.NameScope.Find<TextBlock>("MonthText");
_yearText = e.NameScope.Find<TextBlock>("YearText");
_container = e.NameScope.Find<Grid>("ButtonContentGrid");
_spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer");
_spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer");
_popup = e.NameScope.Find<Popup>("Popup");
_presenter = e.NameScope.Find<DatePickerPresenter>("PickerPresenter");
_areControlsAvailable = true;
SetGrid();
SetSelectedDateText();
if (_flyoutButton != null)
_flyoutButton.Click += OnFlyoutButtonClicked;
if (_presenter != null)
{
_presenter.Confirmed += OnConfirmed;
_presenter.Dismissed += OnDismissPicker;
_presenter[!DatePickerPresenter.MaxYearProperty] = this[!MaxYearProperty];
_presenter[!DatePickerPresenter.MinYearProperty] = this[!MinYearProperty];
_presenter[!DatePickerPresenter.MonthVisibleProperty] = this[!MonthVisibleProperty];
_presenter[!DatePickerPresenter.MonthFormatProperty] = this[!MonthFormatProperty];
_presenter[!DatePickerPresenter.DayVisibleProperty] = this[!DayVisibleProperty];
_presenter[!DatePickerPresenter.DayFormatProperty] = this[!DayFormatProperty];
_presenter[!DatePickerPresenter.YearVisibleProperty] = this[!YearVisibleProperty];
_presenter[!DatePickerPresenter.YearFormatProperty] = this[!YearFormatProperty];
}
}
private void OnDismissPicker(object sender, EventArgs e)
{
_popup.Close();
Focus();
}
private void OnConfirmed(object sender, EventArgs e)
{
_popup.Close();
SelectedDate = _presenter.Date;
}
private void SetGrid()
{
if (_container == null)
return;
var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
var columns = new List<(TextBlock, int)>
{
(_monthText, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1),
(_yearText, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1),
(_dayText, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1),
};
columns.Sort((x, y) => x.Item2 - y.Item2);
_container.ColumnDefinitions.Clear();
var columnIndex = 0;
foreach (var column in columns)
{
if (column.Item1 is null)
continue;
column.Item1.IsVisible = column.Item2 != -1;
if (column.Item2 != -1)
{
if (columnIndex > 0)
{
_container.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto));
}
_container.ColumnDefinitions.Add(
new ColumnDefinition(column.Item1 == _monthText ? 138 : 78, GridUnitType.Star));
if (column.Item1.Parent is null)
{
_container.Children.Add(column.Item1);
}
Grid.SetColumn(column.Item1, (columnIndex++ * 2));
}
}
Grid.SetColumn(_spacer1, 1);
Grid.SetColumn(_spacer2, 3);
_spacer1.IsVisible = columnIndex > 1;
_spacer2.IsVisible = columnIndex > 2;
}
private void SetSelectedDateText()
{
if (!_areControlsAvailable)
return;
if (SelectedDate.HasValue)
{
PseudoClasses.Set(":hasnodate", false);
var selDate = SelectedDate.Value;
_monthText.Text = selDate.ToString(MonthFormat);
_yearText.Text = selDate.ToString(YearFormat);
_dayText.Text = selDate.ToString(DayFormat);
}
else
{
PseudoClasses.Set(":hasnodate", true);
_monthText.Text = "month";
_yearText.Text = "year";
_dayText.Text = "day";
}
}
private void OnFlyoutButtonClicked(object sender, RoutedEventArgs e)
{
if (_presenter == null)
throw new InvalidOperationException("No DatePickerPresenter found");
_presenter.Date = SelectedDate ?? DateTimeOffset.Now;
_popup.IsOpen = true;
var deltaY = _presenter.GetOffsetForPopup();
//The extra 5 px I think is related to default popup placement behavior
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
}
protected virtual void OnSelectedDateChanged(object sender, DatePickerSelectedValueChangedEventArgs e)
{
SelectedDateChanged?.Invoke(sender, e);
}
}
}

531
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@ -0,0 +1,531 @@
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Interactivity;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Avalonia.Controls
{
/// <summary>
/// Defines the presenter used for selecting a date for a
/// <see cref="DatePicker"/>
/// </summary>
public class DatePickerPresenter : PickerPresenterBase
{
/// <summary>
/// Defines the <see cref="Date"/> Property
/// </summary>
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> DateProperty =
AvaloniaProperty.RegisterDirect<DatePickerPresenter, DateTimeOffset>(nameof(Date),
x => x.Date, (x, v) => x.Date = v);
/// <summary>
/// Defines the <see cref="DayFormat"/> Property
/// </summary>
public static readonly DirectProperty<DatePickerPresenter, string> DayFormatProperty =
DatePicker.DayFormatProperty.AddOwner<DatePickerPresenter>(x =>
x.DayFormat, (x, v) => x.DayFormat = v);
/// <summary>
/// Defines the <see cref="DayVisible"/> Property
/// </summary>
public static readonly DirectProperty<DatePickerPresenter, bool> DayVisibleProperty =
DatePicker.DayVisibleProperty.AddOwner<DatePickerPresenter>(x =>
x.DayVisible, (x, v) => x.DayVisible = v);
/// <summary>
/// Defines the <see cref="MaxYear"/> Property
/// </summary>
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MaxYearProperty =
DatePicker.MaxYearProperty.AddOwner<DatePickerPresenter>(x =>
x.MaxYear, (x, v) => x.MaxYear = v);
/// <summary>
/// Defines the <see cref="MinYear"/> Property
/// </summary>
public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MinYearProperty =
DatePicker.MinYearProperty.AddOwner<DatePickerPresenter>(x =>
x.MinYear, (x, v) => x.MinYear = v);
/// <summary>
/// Defines the <see cref="MonthFormat"/> Property
/// </summary>
public static readonly DirectProperty<DatePickerPresenter, string> MonthFormatProperty =
DatePicker.MonthFormatProperty.AddOwner<DatePickerPresenter>(x =>
x.MonthFormat, (x, v) => x.MonthFormat = v);
/// <summary>
/// Defines the <see cref="MonthVisible"/> Property
/// </summary>
public static readonly DirectProperty<DatePickerPresenter, bool> MonthVisibleProperty =
DatePicker.MonthVisibleProperty.AddOwner<DatePickerPresenter>(x =>
x.MonthVisible, (x, v) => x.MonthVisible = v);
/// <summary>
/// Defines the <see cref="YearFormat"/> Property
/// </summary>
public static readonly DirectProperty<DatePickerPresenter, string> YearFormatProperty =
DatePicker.YearFormatProperty.AddOwner<DatePickerPresenter>(x =>
x.YearFormat, (x, v) => x.YearFormat = v);
/// <summary>
/// Defines the <see cref="YearVisible"/> Property
/// </summary>
public static readonly DirectProperty<DatePickerPresenter, bool> YearVisibleProperty =
DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x =>
x.YearVisible, (x, v) => x.YearVisible = v);
//Template Items
private Grid _pickerContainer;
private Button _acceptButton;
private Button _dismissButton;
private Rectangle _spacer1;
private Rectangle _spacer2;
private Panel _monthHost;
private Panel _yearHost;
private Panel _dayHost;
private DateTimePickerPanel _monthSelector;
private DateTimePickerPanel _yearSelector;
private DateTimePickerPanel _daySelector;
private Button _monthUpButton;
private Button _dayUpButton;
private Button _yearUpButton;
private Button _monthDownButton;
private Button _dayDownButton;
private Button _yearDownButton;
private DateTimeOffset _date;
private string _dayFormat = "%d";
private bool _dayVisible = true;
private DateTimeOffset _maxYear;
private DateTimeOffset _minYear;
private string _monthFormat = "MMMM";
private bool _monthVisible = true;
private string _yearFormat = "yyyy";
private bool _yearVisible = true;
private DateTimeOffset _syncDate;
private GregorianCalendar _calendar;
private bool _suppressUpdateSelection;
public DatePickerPresenter()
{
var now = DateTimeOffset.Now;
_minYear = new DateTimeOffset(now.Year - 100, 1, 1, 0, 0, 0, now.Offset);
_maxYear = new DateTimeOffset(now.Year + 100, 12, 31, 0, 0, 0, now.Offset);
_date = now;
_calendar = new GregorianCalendar();
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
}
/// <summary>
/// Gets or sets the current Date for the picker
/// </summary>
public DateTimeOffset Date
{
get => _date;
set
{
SetAndRaise(DateProperty, ref _date, value);
_syncDate = Date;
InitPicker();
}
}
/// <summary>
/// Gets or sets the DayFormat
/// </summary>
public string DayFormat
{
get => _dayFormat;
set => SetAndRaise(DayFormatProperty, ref _dayFormat, value);
}
/// <summary>
/// Get or sets whether the Day selector is visible
/// </summary>
public bool DayVisible
{
get => _dayVisible;
set
{
SetAndRaise(DayVisibleProperty, ref _dayVisible, value);
}
}
/// <summary>
/// Gets or sets the maximum pickable year
/// </summary>
public DateTimeOffset MaxYear
{
get => _maxYear;
set
{
if (value < MinYear)
throw new InvalidOperationException("MaxDate cannot be less than MinDate");
SetAndRaise(MaxYearProperty, ref _maxYear, value);
if (Date > value)
Date = value;
}
}
/// <summary>
/// Gets or sets the minimum pickable year
/// </summary>
public DateTimeOffset MinYear
{
get => _minYear;
set
{
if (value > MaxYear)
throw new InvalidOperationException("MinDate cannot be greater than MaxDate");
SetAndRaise(MinYearProperty, ref _minYear, value);
if (Date < value)
Date = value;
}
}
/// <summary>
/// Gets or sets the month format
/// </summary>
public string MonthFormat
{
get => _monthFormat;
set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value);
}
/// <summary>
/// Gets or sets whether the month selector is visible
/// </summary>
public bool MonthVisible
{
get => _monthVisible;
set
{
SetAndRaise(MonthVisibleProperty, ref _monthVisible, value);
}
}
/// <summary>
/// Gets or sets the year format
/// </summary>
public string YearFormat
{
get => _yearFormat;
set => SetAndRaise(YearFormatProperty, ref _yearFormat, value);
}
/// <summary>
/// Gets or sets whether the year selector is visible
/// </summary>
public bool YearVisible
{
get => _yearVisible;
set
{
SetAndRaise(YearVisibleProperty, ref _yearVisible, value);
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
//These are requirements, so throw if not found
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer");
_monthHost = e.NameScope.Get<Panel>("MonthHost");
_dayHost = e.NameScope.Get<Panel>("DayHost");
_yearHost = e.NameScope.Get<Panel>("YearHost");
_monthSelector = e.NameScope.Get<DateTimePickerPanel>("MonthSelector");
_monthSelector.SelectionChanged += OnMonthChanged;
_daySelector = e.NameScope.Get<DateTimePickerPanel>("DaySelector");
_daySelector.SelectionChanged += OnDayChanged;
_yearSelector = e.NameScope.Get<DateTimePickerPanel>("YearSelector");
_yearSelector.SelectionChanged += OnYearChanged;
_acceptButton = e.NameScope.Get<Button>("AcceptButton");
_monthUpButton = e.NameScope.Find<RepeatButton>("MonthUpButton");
if (_monthUpButton != null)
{
_monthUpButton.Click += OnSelectorButtonClick;
}
_monthDownButton = e.NameScope.Find<RepeatButton>("MonthDownButton");
if (_monthDownButton != null)
{
_monthDownButton.Click += OnSelectorButtonClick;
}
_dayUpButton = e.NameScope.Find<RepeatButton>("DayUpButton");
if (_dayUpButton != null)
{
_dayUpButton.Click += OnSelectorButtonClick;
}
_dayDownButton = e.NameScope.Find<RepeatButton>("DayDownButton");
if (_dayDownButton != null)
{
_dayDownButton.Click += OnSelectorButtonClick;
}
_yearUpButton = e.NameScope.Find<RepeatButton>("YearUpButton");
if (_yearUpButton != null)
{
_yearUpButton.Click += OnSelectorButtonClick;
}
_yearDownButton = e.NameScope.Find<RepeatButton>("YearDownButton");
if (_yearDownButton != null)
{
_yearDownButton.Click += OnSelectorButtonClick;
}
_dismissButton = e.NameScope.Find<Button>("DismissButton");
_spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer");
_spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer");
if (_acceptButton != null)
{
_acceptButton.Click += OnAcceptButtonClicked;
}
if (_dismissButton != null)
{
_dismissButton.Click += OnDismissButtonClicked;
}
InitPicker();
}
protected override void OnKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Escape:
OnDismiss();
e.Handled = true;
break;
case Key.Tab:
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next);
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
e.Handled = true;
break;
case Key.Enter:
Date = _syncDate;
OnConfirmed();
e.Handled = true;
break;
}
base.OnKeyDown(e);
}
/// <summary>
/// Initializes the picker selectors.
/// </summary>
private void InitPicker()
{
//OnApplyTemplate must've been called before we can init here...
if (_pickerContainer == null)
return;
_suppressUpdateSelection = true;
_monthSelector.MaximumValue = 12;
_monthSelector.MinimumValue = 1;
_monthSelector.ItemFormat = MonthFormat;
_daySelector.ItemFormat = DayFormat;
_yearSelector.MaximumValue = MaxYear.Year;
_yearSelector.MinimumValue = MinYear.Year;
_yearSelector.ItemFormat = YearFormat;
SetGrid();
//Date should've been set when we reach this point
var dt = Date;
if (DayVisible)
{
GregorianCalendar gc = new GregorianCalendar();
var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month);
_daySelector.MaximumValue = maxDays;
_daySelector.MinimumValue = 1;
_daySelector.SelectedValue = dt.Day;
_daySelector.FormatDate = dt.Date;
}
if (MonthVisible)
_monthSelector.SelectedValue = dt.Month;
if (YearVisible)
_yearSelector.SelectedValue = dt.Year;
_suppressUpdateSelection = false;
SetInitialFocus();
}
private void SetGrid()
{
var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
var columns = new List<(Panel, int)>
{
(_monthHost, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1),
(_yearHost, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1),
(_dayHost, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1),
};
columns.Sort((x, y) => x.Item2 - y.Item2);
_pickerContainer.ColumnDefinitions.Clear();
var columnIndex = 0;
foreach (var column in columns)
{
if (column.Item1 is null)
continue;
column.Item1.IsVisible = column.Item2 != -1;
if (column.Item2 != -1)
{
if (columnIndex > 0)
{
_pickerContainer.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto));
}
_pickerContainer.ColumnDefinitions.Add(
new ColumnDefinition(column.Item1 == _monthHost ? 138 : 78, GridUnitType.Star));
if (column.Item1.Parent is null)
{
_pickerContainer.Children.Add(column.Item1);
}
Grid.SetColumn(column.Item1, (columnIndex++ * 2));
}
}
Grid.SetColumn(_spacer1, 1);
Grid.SetColumn(_spacer2, 3);
_spacer1.IsVisible = columnIndex > 1;
_spacer2.IsVisible = columnIndex > 2;
}
private void SetInitialFocus()
{
int monthCol = MonthVisible ? Grid.GetColumn(_monthHost) : int.MaxValue;
int dayCol = DayVisible ? Grid.GetColumn(_dayHost) : int.MaxValue;
int yearCol = YearVisible ? Grid.GetColumn(_yearHost) : int.MaxValue;
if (monthCol < dayCol && monthCol < yearCol)
{
KeyboardDevice.Instance?.SetFocusedElement(_monthSelector, NavigationMethod.Pointer, KeyModifiers.None);
}
else if (dayCol < monthCol && dayCol < yearCol)
{
KeyboardDevice.Instance?.SetFocusedElement(_daySelector, NavigationMethod.Pointer, KeyModifiers.None);
}
else if (yearCol < monthCol && yearCol < dayCol)
{
KeyboardDevice.Instance?.SetFocusedElement(_yearSelector, NavigationMethod.Pointer, KeyModifiers.None);
}
}
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
OnDismiss();
}
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Date = _syncDate;
OnConfirmed();
}
private void OnSelectorButtonClick(object sender, RoutedEventArgs e)
{
if (sender == _monthUpButton)
_monthSelector.ScrollUp();
else if (sender == _monthDownButton)
_monthSelector.ScrollDown();
else if (sender == _yearUpButton)
_yearSelector.ScrollUp();
else if (sender == _yearDownButton)
_yearSelector.ScrollDown();
else if (sender == _dayUpButton)
_daySelector.ScrollUp();
else if (sender == _dayDownButton)
_daySelector.ScrollDown();
}
private void OnYearChanged(object sender, EventArgs e)
{
if (_suppressUpdateSelection)
return;
int maxDays = _calendar.GetDaysInMonth(_yearSelector.SelectedValue, _syncDate.Month);
var newDate = new DateTimeOffset(_yearSelector.SelectedValue, _syncDate.Month,
_syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset);
_syncDate = newDate;
//We don't need to update the days if not displaying day, not february
if (!DayVisible || _syncDate.Month != 2)
return;
_suppressUpdateSelection = true;
_daySelector.FormatDate = newDate.Date;
if (_daySelector.MaximumValue != maxDays)
_daySelector.MaximumValue = maxDays;
else
_daySelector.RefreshItems();
_suppressUpdateSelection = false;
}
private void OnDayChanged(object sender, EventArgs e)
{
if (_suppressUpdateSelection)
return;
_syncDate = new DateTimeOffset(_syncDate.Year, _syncDate.Month, _daySelector.SelectedValue, 0, 0, 0, _syncDate.Offset);
}
private void OnMonthChanged(object sender, EventArgs e)
{
if (_suppressUpdateSelection)
return;
int maxDays = _calendar.GetDaysInMonth(_syncDate.Year, _monthSelector.SelectedValue);
var newDate = new DateTimeOffset(_syncDate.Year, _monthSelector.SelectedValue,
_syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset);
if (!DayVisible)
{
_syncDate = newDate;
return;
}
_suppressUpdateSelection = true;
_daySelector.FormatDate = newDate.Date;
_syncDate = newDate;
if (_daySelector.MaximumValue != maxDays)
_daySelector.MaximumValue = maxDays;
else
_daySelector.RefreshItems();
_suppressUpdateSelection = false;
}
internal double GetOffsetForPopup()
{
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41;
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2);
}
}
}

19
src/Avalonia.Controls/DateTimePickers/DatePickerSelectedValueChangedEventArgs.cs

@ -0,0 +1,19 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Defines the argument passed when the <see cref="DatePicker"/> SelectedDate changes
/// </summary>
public class DatePickerSelectedValueChangedEventArgs
{
public DateTimeOffset? NewDate { get; }
public DateTimeOffset? OldDate { get; }
public DatePickerSelectedValueChangedEventArgs(DateTimeOffset? oldDate, DateTimeOffset? newDate)
{
NewDate = newDate;
OldDate = oldDate;
}
}
}

566
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@ -0,0 +1,566 @@
using System;
using System.Globalization;
using System.Linq;
using Avalonia.Input;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
public enum DateTimePickerPanelType
{
Year,
Month,
Day,
Hour,
Minute,
TimePeriod //AM or PM
}
public class DateTimePickerPanel : Panel, ILogicalScrollable
{
/// <summary>
/// Defines the <see cref="ItemHeight"/> property
/// </summary>
public static readonly StyledProperty<double> ItemHeightProperty =
AvaloniaProperty.Register<DateTimePickerPanel, double>(nameof(ItemHeight), 40.0);
/// <summary>
/// Defines the <see cref="PanelType"/> property
/// </summary>
public static readonly StyledProperty<DateTimePickerPanelType> PanelTypeProperty =
AvaloniaProperty.Register<DateTimePickerPanel, DateTimePickerPanelType>(nameof(PanelType));
/// <summary>
/// Defines the <see cref="ItemFormat"/> property
/// </summary>
public static readonly StyledProperty<string> ItemFormatProperty =
AvaloniaProperty.Register<DateTimePickerPanel, string>(nameof(ItemFormat), "yyyy");
/// <summary>
/// Defines the <see cref="ShouldLoop"/> property
/// </summary>
public static readonly StyledProperty<bool> ShouldLoopProperty =
AvaloniaProperty.Register<DateTimePickerPanel, bool>(nameof(ShouldLoop));
//Backing fields for properties
private int _minimumValue = 1;
private int _maximumValue = 2;
private int _selectedValue = 1;
private int _increment = 1;
//Helper fields
private int _selectedIndex = 0;
private int _totalItems;
private int _numItemsAboveBelowSelected;
private int _range;
private double _extentOne;
private Size _extent;
private Vector _offset;
private bool _hasInit;
private bool _suppressUpdateOffset;
private ListBoxItem _pressedItem;
public DateTimePickerPanel()
{
FormatDate = DateTime.Now;
AddHandler(ListBoxItem.PointerPressedEvent, OnItemPointerDown, Avalonia.Interactivity.RoutingStrategies.Bubble);
AddHandler(ListBoxItem.PointerReleasedEvent, OnItemPointerUp, Avalonia.Interactivity.RoutingStrategies.Bubble);
}
static DateTimePickerPanel()
{
FocusableProperty.OverrideDefaultValue<DateTimePickerPanel>(true);
AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty);
}
/// <summary>
/// Gets or sets what this panel displays in date or time units
/// </summary>
public DateTimePickerPanelType PanelType
{
get => GetValue(PanelTypeProperty);
set => SetValue(PanelTypeProperty, value);
}
/// <summary>
/// Gets or sets the height of each item
/// </summary>
public double ItemHeight
{
get => GetValue(ItemHeightProperty);
set => SetValue(ItemHeightProperty, value);
}
/// <summary>
/// Gets or sets the string format for the items, using standard
/// .net DateTime or TimeSpan formatting. Format must match panel type
/// </summary>
public string ItemFormat
{
get => GetValue(ItemFormatProperty);
set => SetValue(ItemFormatProperty, value);
}
/// <summary>
/// Gets or sets whether the panel should loop
/// </summary>
public bool ShouldLoop
{
get => GetValue(ShouldLoopProperty);
set => SetValue(ShouldLoopProperty, value);
}
/// <summary>
/// Gets or sets the minimum value
/// </summary>
public int MinimumValue
{
get => _minimumValue;
set
{
if (value > MaximumValue)
throw new InvalidOperationException("Minimum cannot be greater than Maximum");
_minimumValue = value;
UpdateHelperInfo();
var sel = CoerceSelected(SelectedValue);
if (sel != SelectedValue)
SelectedValue = sel;
UpdateItems();
InvalidateArrange();
RaiseScrollInvalidated(EventArgs.Empty);
}
}
/// <summary>
/// Gets or sets the maximum value
/// </summary>
public int MaximumValue
{
get => _maximumValue;
set
{
if (value < MinimumValue)
throw new InvalidOperationException("Maximum cannot be less than Minimum");
_maximumValue = value;
UpdateHelperInfo();
var sel = CoerceSelected(SelectedValue);
if (sel != SelectedValue)
SelectedValue = sel;
UpdateItems();
InvalidateArrange();
RaiseScrollInvalidated(EventArgs.Empty);
}
}
/// <summary>
/// Gets or sets the selected value
/// </summary>
public int SelectedValue
{
get => _selectedValue;
set
{
if (value > MaximumValue || value < MinimumValue)
throw new ArgumentOutOfRangeException("SelectedValue");
var sel = CoerceSelected(value);
_selectedValue = sel;
_selectedIndex = (value - MinimumValue) / Increment;
if (!ShouldLoop)
CreateOrDestroyItems(Children);
if (!_suppressUpdateOffset)
_offset = new Vector(0, ShouldLoop ? _selectedIndex * ItemHeight + (_extentOne * 50) :
_selectedIndex * ItemHeight);
UpdateItems();
InvalidateArrange();
RaiseScrollInvalidated(EventArgs.Empty);
SelectionChanged?.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
/// Gets or sets the increment
/// </summary>
public int Increment
{
get => _increment;
set
{
if (value <= 0 || value > _range)
throw new ArgumentOutOfRangeException("Increment");
_increment = value;
UpdateHelperInfo();
var sel = CoerceSelected(SelectedValue);
if (sel != SelectedValue)
SelectedValue = sel;
UpdateItems();
InvalidateArrange();
RaiseScrollInvalidated(EventArgs.Empty);
}
}
//Used to help format the date (if applicable), for ex.,
//if we're want to display the day of week, we need context
//for the month/year, this is our context
internal DateTime FormatDate { get; set; }
public Vector Offset
{
get => _offset;
set
{
var old = _offset;
_offset = value;
var dy = _offset.Y - old.Y;
var children = Children;
if (dy > 0) // Scroll Down
{
int numContsToMove = 0;
for (int i = 0; i < children.Count; i++)
{
if (children[i].Bounds.Bottom - dy < 0)
numContsToMove++;
else
break;
}
children.MoveRange(0, numContsToMove, children.Count);
var scrollHeight = _extent.Height - Viewport.Height;
if (ShouldLoop && value.Y >= scrollHeight - _extentOne)
_offset = new Vector(0, value.Y - (_extentOne * 50));
}
else if (dy < 0) // Scroll Up
{
int numContsToMove = 0;
for (int i = children.Count - 1; i >= 0; i--)
{
if (children[i].Bounds.Top - dy > Bounds.Height)
numContsToMove++;
else
break;
}
children.MoveRange(children.Count - numContsToMove, numContsToMove, 0);
if (ShouldLoop && value.Y < _extentOne)
_offset = new Vector(0, value.Y + (_extentOne * 50));
}
//Setting selection will handle all invalidation
var newSel = (Offset.Y / ItemHeight) % _totalItems;
_suppressUpdateOffset = true;
SelectedValue = (int)newSel * Increment + MinimumValue;
_suppressUpdateOffset = false;
}
}
public bool CanHorizontallyScroll { get => false; set { } }
public bool CanVerticallyScroll { get => true; set { } }
public bool IsLogicalScrollEnabled => true;
public Size ScrollSize => new Size(0, ItemHeight);
public Size PageScrollSize => new Size(0, ItemHeight * 4);
public Size Extent => _extent;
public Size Viewport => new Size(0, ItemHeight);
public event EventHandler ScrollInvalidated;
public event EventHandler SelectionChanged;
protected override Size MeasureOverride(Size availableSize)
{
if (double.IsInfinity(availableSize.Width) ||
double.IsInfinity(availableSize.Height))
throw new InvalidOperationException("Panel must have finite height");
if (!_hasInit)
UpdateHelperInfo();
double initY = (availableSize.Height / 2.0) - (ItemHeight / 2.0);
_numItemsAboveBelowSelected = (int)Math.Ceiling(initY / ItemHeight) + 1;
var children = Children;
CreateOrDestroyItems(children);
for (int i = 0; i < children.Count; i++)
children[i].Measure(availableSize);
if (!_hasInit)
{
UpdateItems();
RaiseScrollInvalidated(EventArgs.Empty);
_hasInit = true;
}
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (Children.Count == 0)
return base.ArrangeOverride(finalSize);
var itemHgt = ItemHeight;
var children = Children;
Rect rc;
double initY = (finalSize.Height / 2.0) - (itemHgt / 2.0);
if (ShouldLoop)
{
var currentSet = Math.Truncate(Offset.Y / _extentOne);
initY += (_extentOne * currentSet) + (_selectedIndex - _numItemsAboveBelowSelected) * ItemHeight;
for (int i = 0; i < children.Count; i++)
{
rc = new Rect(0, initY - Offset.Y, finalSize.Width, itemHgt);
children[i].Arrange(rc);
initY += itemHgt;
}
}
else
{
var first = Math.Max(0, (_selectedIndex - _numItemsAboveBelowSelected));
for (int i = 0; i < children.Count; i++)
{
rc = new Rect(0, (initY + first * itemHgt) - Offset.Y, finalSize.Width, itemHgt);
children[i].Arrange(rc);
initY += itemHgt;
}
}
return finalSize;
}
protected override void OnKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
ScrollUp();
e.Handled = true;
break;
case Key.Down:
ScrollDown();
e.Handled = true;
break;
case Key.PageUp:
ScrollUp(4);
e.Handled = true;
break;
case Key.PageDown:
ScrollDown(4);
e.Handled = true;
break;
}
base.OnKeyDown(e);
}
/// <summary>
/// Refreshes the content of the visible items
/// </summary>
public void RefreshItems()
{
UpdateItems();
}
/// <summary>
/// Scrolls up the specified number of items
/// </summary>
public void ScrollUp(int numItems = 1)
{
var newY = Math.Max(Offset.Y - (numItems * ItemHeight), 0);
Offset = new Vector(0, newY);
}
/// <summary>
/// Scrolls down the specified number of items
/// </summary>
public void ScrollDown(int numItems = 1)
{
var scrollHeight = _extent.Height - Viewport.Height;
var newY = Math.Min(Offset.Y + (numItems * ItemHeight), scrollHeight);
Offset = new Vector(0, newY);
}
/// <summary>
/// Updates helper fields used in various calculations
/// </summary>
private void UpdateHelperInfo()
{
_range = _maximumValue - _minimumValue + 1;
_totalItems = (int)Math.Ceiling((double)_range / _increment);
var itemHgt = ItemHeight;
//If looping, measure 100x as many items as we actually have
_extent = new Size(0, ShouldLoop ? _totalItems * itemHgt * 100 : _totalItems * itemHgt);
//Height of 1 "set" of items
_extentOne = _totalItems * itemHgt;
_offset = new Vector(0, ShouldLoop ? _extentOne * 50 + _selectedIndex * itemHgt : _selectedIndex * itemHgt);
}
/// <summary>
/// Ensures enough containers are visible in the viewport
/// </summary>
/// <param name="children"></param>
private void CreateOrDestroyItems(Controls children)
{
int totalItemsInViewport = _numItemsAboveBelowSelected * 2 + 1;
if (!ShouldLoop)
{
int numItemAboveSelect = _numItemsAboveBelowSelected;
if (_selectedIndex - _numItemsAboveBelowSelected < 0)
numItemAboveSelect = _selectedIndex;
int numItemBelowSelect = _numItemsAboveBelowSelected;
if (_selectedIndex + _numItemsAboveBelowSelected >= _totalItems)
numItemBelowSelect = _totalItems - _selectedIndex - 1;
totalItemsInViewport = numItemBelowSelect + numItemAboveSelect + 1;
}
while (children.Count < totalItemsInViewport)
{
children.Add(new ListBoxItem
{
Height = ItemHeight,
Classes = new Classes("DateTimePickerItem", $"{PanelType}Item"),
VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center,
Focusable = false
});
}
if (children.Count > totalItemsInViewport)
{
var numToRemove = children.Count - totalItemsInViewport;
children.RemoveRange(children.Count - numToRemove, numToRemove);
}
}
/// <summary>
/// Updates item content based on the current selection
/// and the panel type
/// </summary>
private void UpdateItems()
{
var children = Children;
var min = MinimumValue;
var panelType = PanelType;
var selected = SelectedValue;
var max = MaximumValue;
int first;
if (ShouldLoop)
{
first = (_selectedIndex - _numItemsAboveBelowSelected) % _totalItems;
first = first < 0 ? min + (first + _totalItems) * Increment : min + first * Increment;
}
else
{
first = min + Math.Max(0, _selectedIndex - _numItemsAboveBelowSelected) * Increment;
}
for (int i = 0; i < children.Count; i++)
{
ListBoxItem item = (ListBoxItem)children[i];
item.Content = FormatContent(first, panelType);
item.Tag = first;
item.IsSelected = first == selected;
first += Increment;
if (first > max)
first = min;
}
}
private string FormatContent(int value, DateTimePickerPanelType panelType)
{
switch (panelType)
{
case DateTimePickerPanelType.Year:
return new DateTime(value, FormatDate.Month, FormatDate.Day).ToString(ItemFormat);
case DateTimePickerPanelType.Month:
return new DateTime(FormatDate.Year, value, FormatDate.Day).ToString(ItemFormat);
case DateTimePickerPanelType.Day:
return new DateTime(FormatDate.Year, FormatDate.Month, value).ToString(ItemFormat);
case DateTimePickerPanelType.Hour:
return new TimeSpan(value, 0, 0).ToString(ItemFormat);
case DateTimePickerPanelType.Minute:
return new TimeSpan(0, value, 0).ToString(ItemFormat);
case DateTimePickerPanelType.TimePeriod:
return value == MinimumValue ? CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator :
CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator;
default:
return "";
}
}
/// <summary>
/// Ensures the <see cref="SelectedValue"/> is within the bounds and
/// follows the current Increment
/// </summary>
private int CoerceSelected(int newValue)
{
if (newValue < MinimumValue)
return MinimumValue;
if (newValue > MaximumValue)
return MaximumValue;
if (newValue % Increment != 0)
{
var items = Enumerable.Range(MinimumValue, MaximumValue + 1).Where(i => i % Increment == 0).ToList();
var nearest = items.Aggregate((x, y) => Math.Abs(x - newValue) > Math.Abs(y - newValue) ? y : x);
return items.IndexOf(nearest) * Increment;
}
return newValue;
}
private void OnItemPointerDown(object sender, PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
_pressedItem = GetItemFromSource((IVisual)e.Source);
e.Handled = true;
}
}
private void OnItemPointerUp(object sender, PointerReleasedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased &&
_pressedItem != null)
{
SelectedValue = (int)GetItemFromSource((IVisual)e.Source).Tag;
_pressedItem = null;
e.Handled = true;
}
}
//Helper to get ListBoxItem from pointerevent source
private ListBoxItem GetItemFromSource(IVisual src)
{
var item = src;
while (item != null && !(item is ListBoxItem))
{
item = item.VisualParent;
}
return (ListBoxItem)item;
}
public bool BringIntoView(IControl target, Rect targetRect) { return false; }
public IControl GetControlInDirection(NavigationDirection direction, IControl from) { return null; }
public void RaiseScrollInvalidated(EventArgs e)
{
ScrollInvalidated?.Invoke(this, e);
}
}
}

25
src/Avalonia.Controls/DateTimePickers/PickerPresenterBase.cs

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Defines the base class for Date and Time PickerPresenters
/// </summary>
public abstract class PickerPresenterBase : TemplatedControl
{
protected virtual void OnConfirmed()
{
Confirmed?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnDismiss()
{
Dismissed.Invoke(this, EventArgs.Empty);
}
public event EventHandler Confirmed;
public event EventHandler Dismissed;
}
}

292
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@ -0,0 +1,292 @@
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using System;
using System.Globalization;
namespace Avalonia.Controls
{
/// <summary>
/// A control to allow the user to select a time
/// </summary>
public class TimePicker : TemplatedControl
{
/// <summary>
/// Defines the <see cref="MinuteIncrement"/> property
/// </summary>
public static readonly DirectProperty<TimePicker, int> MinuteIncrementProperty =
AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement),
x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v);
/// <summary>
/// Defines the <see cref="Header"/> property
/// </summary>
public static readonly StyledProperty<object> HeaderProperty =
AvaloniaProperty.Register<DatePicker, object>(nameof(Header));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property
/// </summary>
public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate));
/// <summary>
/// Defines the <see cref="ClockIdentifier"/> property
/// </summary>
public static readonly DirectProperty<TimePicker, string> ClockIdentifierProperty =
AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier),
x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v);
/// <summary>
/// Defines the <see cref="SelectedTime"/> property
/// </summary>
public static readonly DirectProperty<TimePicker, TimeSpan?> SelectedTimeProperty =
AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime),
x => x.SelectedTime, (x, v) => x.SelectedTime = v);
//Template Items
private TimePickerPresenter _presenter;
private Button _flyoutButton;
private Border _firstPickerHost;
private Border _secondPickerHost;
private Border _thirdPickerHost;
private TextBlock _hourText;
private TextBlock _minuteText;
public TextBlock _periodText;
private Rectangle _firstSplitter;
private Rectangle _secondSplitter;
private Grid _contentGrid;
private Popup _popup;
private TimeSpan? _selectedTime;
private int _minuteIncrement = 1;
private string _clockIdentifier = "12HourClock";
public TimePicker()
{
PseudoClasses.Set(":hasnotime", true);
var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
if (timePattern.IndexOf("H") != -1)
_clockIdentifier = "24HourClock";
}
/// <summary>
/// Gets or sets the minute increment in the picker
/// </summary>
public int MinuteIncrement
{
get => _minuteIncrement;
set
{
if (value < 1 || value > 59)
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59");
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value);
SetSelectedTimeText();
}
}
/// <summary>
/// Gets or sets the header
/// </summary>
public object Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
/// <summary>
/// Gets or sets the header template
/// </summary>
public IDataTemplate HeaderTemplate
{
get => GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
/// <summary>
/// Gets or sets the clock identifier, either 12HourClock or 24HourClock
/// </summary>
public string ClockIdentifier
{
get => _clockIdentifier;
set
{
if (!(string.IsNullOrEmpty(value) || value == "" || value == "12HourClock" || value == "24HourClock"))
throw new ArgumentException("Invalid ClockIdentifier");
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
SetGrid();
SetSelectedTimeText();
}
}
/// <summary>
/// Gets or sets the selected time. Can be null.
/// </summary>
public TimeSpan? SelectedTime
{
get => _selectedTime;
set
{
var old = _selectedTime;
SetAndRaise(SelectedTimeProperty, ref _selectedTime, value);
OnSelectedTimeChanged(old, value);
SetSelectedTimeText();
}
}
/// <summary>
/// Raised when the <see cref="SelectedTime"/> property changes
/// </summary>
public event EventHandler<TimePickerSelectedValueChangedEventArgs> SelectedTimeChanged;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_flyoutButton != null)
_flyoutButton.Click -= OnFlyoutButtonClicked;
if(_presenter != null)
{
_presenter.Confirmed -= OnConfirmed;
_presenter.Dismissed -= OnDismissPicker;
}
base.OnApplyTemplate(e);
_flyoutButton = e.NameScope.Find<Button>("FlyoutButton");
_firstPickerHost = e.NameScope.Find<Border>("FirstPickerHost");
_secondPickerHost = e.NameScope.Find<Border>("SecondPickerHost");
_thirdPickerHost = e.NameScope.Find<Border>("ThirdPickerHost");
_hourText = e.NameScope.Find<TextBlock>("HourTextBlock");
_minuteText = e.NameScope.Find<TextBlock>("MinuteTextBlock");
_periodText = e.NameScope.Find<TextBlock>("PeriodTextBlock");
_firstSplitter = e.NameScope.Find<Rectangle>("FirstColumnDivider");
_secondSplitter = e.NameScope.Find<Rectangle>("SecondColumnDivider");
_contentGrid = e.NameScope.Find<Grid>("FlyoutButtonContentGrid");
_popup = e.NameScope.Find<Popup>("Popup");
_presenter = e.NameScope.Find<TimePickerPresenter>("PickerPresenter");
if (_flyoutButton != null)
_flyoutButton.Click += OnFlyoutButtonClicked;
SetGrid();
SetSelectedTimeText();
if (_presenter != null)
{
_presenter.Confirmed += OnConfirmed;
_presenter.Dismissed += OnDismissPicker;
_presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty];
_presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty];
}
}
private void SetGrid()
{
if (_contentGrid == null)
return;
bool use24HourClock = ClockIdentifier == "24HourClock";
if (!use24HourClock)
{
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
_thirdPickerHost.IsVisible = true;
_secondSplitter.IsVisible = true;
Grid.SetColumn(_firstPickerHost, 0);
Grid.SetColumn(_secondPickerHost, 2);
Grid.SetColumn(_thirdPickerHost, 4);
Grid.SetColumn(_firstSplitter, 1);
Grid.SetColumn(_secondSplitter, 3);
}
else
{
_contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
_thirdPickerHost.IsVisible = false;
_secondSplitter.IsVisible = false;
Grid.SetColumn(_firstPickerHost, 0);
Grid.SetColumn(_secondPickerHost, 2);
Grid.SetColumn(_firstSplitter, 1);
}
}
private void SetSelectedTimeText()
{
if (_hourText == null || _minuteText == null || _periodText == null)
return;
var time = SelectedTime;
if (time.HasValue)
{
var newTime = SelectedTime.Value;
if (ClockIdentifier == "12HourClock")
{
var hr = newTime.Hours;
hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr;
newTime = new TimeSpan(hr, newTime.Minutes, 0);
}
_hourText.Text = newTime.ToString("%h");
_minuteText.Text = newTime.ToString("mm");
PseudoClasses.Set(":hasnotime", false);
_periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator :
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
}
else
{
_hourText.Text = "hour";
_minuteText.Text = "minute";
PseudoClasses.Set(":hasnotime", true);
_periodText.Text = DateTime.Now.Hour >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator :
CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
}
}
protected virtual void OnSelectedTimeChanged(TimeSpan? oldTime, TimeSpan? newTime)
{
SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime));
}
private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
_popup.IsOpen = true;
var deltaY = _presenter.GetOffsetForPopup();
//The extra 5 px I think is related to default popup placement behavior
_popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
}
private void OnDismissPicker(object sender, EventArgs e)
{
_popup.Close();
Focus();
}
private void OnConfirmed(object sender, EventArgs e)
{
_popup.Close();
SelectedTime = _presenter.Time;
}
}
}

262
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@ -0,0 +1,262 @@
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Interactivity;
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Defines the presenter used for selecting a time. Intended for use with
/// <see cref="TimePicker"/> but can be used independently
/// </summary>
public class TimePickerPresenter : PickerPresenterBase
{
/// <summary>
/// Defines the <see cref="MinuteIncrement"/> property
/// </summary>
public static readonly DirectProperty<TimePickerPresenter, int> MinuteIncrementProperty =
TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>(x => x.MinuteIncrement,
(x, v) => x.MinuteIncrement = v);
/// <summary>
/// Defines the <see cref="ClockIdentifier"/> property
/// </summary>
public static readonly DirectProperty<TimePickerPresenter, string> ClockIdentifierProperty =
TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>(x => x.ClockIdentifier,
(x, v) => x.ClockIdentifier = v);
/// <summary>
/// Defines the <see cref="Time"/> property
/// </summary>
public static readonly DirectProperty<TimePickerPresenter, TimeSpan> TimeProperty =
AvaloniaProperty.RegisterDirect<TimePickerPresenter, TimeSpan>(nameof(Time),
x => x.Time, (x, v) => x.Time = v);
public TimePickerPresenter()
{
Time = DateTime.Now.TimeOfDay;
KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
}
//TemplateItems
private Grid _pickerContainer;
private Button _acceptButton;
private Button _dismissButton;
private Rectangle _spacer2;
private Panel _periodHost;
private DateTimePickerPanel _hourSelector;
private DateTimePickerPanel _minuteSelector;
private DateTimePickerPanel _periodSelector;
private Button _hourUpButton;
private Button _minuteUpButton;
private Button _periodUpButton;
private Button _hourDownButton;
private Button _minuteDownButton;
private Button _periodDownButton;
//Backing Fields
private TimeSpan _Time;
private int _minuteIncrement = 1;
private string _clockIdentifier = "12HourClock";
/// <summary>
/// Gets or sets the minute increment in the selector
/// </summary>
public int MinuteIncrement
{
get => _minuteIncrement;
set
{
if (value < 1 || value > 59)
throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59");
SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value);
InitPicker();
}
}
/// <summary>
/// Gets or sets the current clock identifier, either 12HourClock or 24HourClock
/// </summary>
public string ClockIdentifier
{
get => _clockIdentifier;
set
{
if (string.IsNullOrEmpty(value) || value == "" || !(value == "12HourClock" || value == "24HourClock"))
throw new ArgumentException("Invalid ClockIdentifier");
SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
InitPicker();
}
}
/// <summary>
/// Gets or sets the current time
/// </summary>
public TimeSpan Time
{
get => _Time;
set
{
SetAndRaise(TimeProperty, ref _Time, value);
InitPicker();
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_pickerContainer = e.NameScope.Get<Grid>("PickerContainer");
_periodHost = e.NameScope.Get<Panel>("PeriodHost");
_hourSelector = e.NameScope.Get<DateTimePickerPanel>("HourSelector");
_minuteSelector = e.NameScope.Get<DateTimePickerPanel>("MinuteSelector");
_periodSelector = e.NameScope.Get<DateTimePickerPanel>("PeriodSelector");
_spacer2 = e.NameScope.Get<Rectangle>("SecondSpacer");
_acceptButton = e.NameScope.Get<Button>("AcceptButton");
_acceptButton.Click += OnAcceptButtonClicked;
_hourUpButton = e.NameScope.Find<RepeatButton>("HourUpButton");
if (_hourUpButton != null)
_hourUpButton.Click += OnSelectorButtonClick;
_hourDownButton = e.NameScope.Find<RepeatButton>("HourDownButton");
if (_hourDownButton != null)
_hourDownButton.Click += OnSelectorButtonClick;
_minuteUpButton = e.NameScope.Find<RepeatButton>("MinuteUpButton");
if (_minuteUpButton != null)
_minuteUpButton.Click += OnSelectorButtonClick;
_minuteDownButton = e.NameScope.Find<RepeatButton>("MinuteDownButton");
if (_minuteDownButton != null)
_minuteDownButton.Click += OnSelectorButtonClick;
_periodUpButton = e.NameScope.Find<RepeatButton>("PeriodUpButton");
if (_periodUpButton != null)
_periodUpButton.Click += OnSelectorButtonClick;
_periodDownButton = e.NameScope.Find<RepeatButton>("PeriodDownButton");
if (_periodDownButton != null)
_periodDownButton.Click += OnSelectorButtonClick;
_dismissButton = e.NameScope.Find<Button>("DismissButton");
if (_dismissButton != null)
_dismissButton.Click += OnDismissButtonClicked;
InitPicker();
}
protected override void OnKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Escape:
OnDismiss();
e.Handled = true;
break;
case Key.Tab:
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next);
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
e.Handled = true;
break;
case Key.Enter:
OnConfirmed();
e.Handled = true;
break;
}
base.OnKeyDown(e);
}
protected override void OnConfirmed()
{
var hr = _hourSelector.SelectedValue;
var min = _minuteSelector.SelectedValue;
var per = _periodSelector.SelectedValue;
if (ClockIdentifier == "12HourClock")
{
hr = per == 1 ? hr + 12 : per == 0 && hr == 12 ? 0 : hr;
}
Time = new TimeSpan(hr, min, 0);
base.OnConfirmed();
}
private void InitPicker()
{
if (_pickerContainer == null)
return;
bool clock12 = ClockIdentifier == "12HourClock";
_hourSelector.MaximumValue = clock12 ? 12 : 23;
_hourSelector.MinimumValue = clock12 ? 1 : 0;
_hourSelector.ItemFormat = "%h";
var hr = Time.Hours;
_hourSelector.SelectedValue = !clock12 ? hr :
hr > 12 ? hr - 12 : hr == 0 ? 12 : hr;
_minuteSelector.MaximumValue = 59;
_minuteSelector.MinimumValue = 0;
_minuteSelector.Increment = MinuteIncrement;
_minuteSelector.SelectedValue = Time.Minutes;
_minuteSelector.ItemFormat = "mm";
_periodSelector.MaximumValue = 1;
_periodSelector.MinimumValue = 0;
_periodSelector.SelectedValue = hr >= 12 ? 1 : 0;
SetGrid();
KeyboardDevice.Instance?.SetFocusedElement(_hourSelector, NavigationMethod.Pointer, KeyModifiers.None);
}
private void SetGrid()
{
if (ClockIdentifier == "12HourClock")
{
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
_spacer2.IsVisible = true;
_periodHost.IsVisible = true;
}
else
{
_pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
_spacer2.IsVisible = false;
_periodHost.IsVisible = false;
}
}
private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
OnDismiss();
}
private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
OnConfirmed();
}
private void OnSelectorButtonClick(object sender, RoutedEventArgs e)
{
if (sender == _hourUpButton)
_hourSelector.ScrollUp();
else if (sender == _hourDownButton)
_hourSelector.ScrollDown();
else if (sender == _minuteUpButton)
_minuteSelector.ScrollUp();
else if (sender == _minuteDownButton)
_minuteSelector.ScrollDown();
else if (sender == _periodUpButton)
_periodSelector.ScrollUp();
else if (sender == _periodDownButton)
_periodSelector.ScrollDown();
}
internal double GetOffsetForPopup()
{
var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41;
return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector.ItemHeight / 2);
}
}
}

15
src/Avalonia.Controls/DateTimePickers/TimePickerSelectedValueChangedEventArgs.cs

@ -0,0 +1,15 @@
using System;
namespace Avalonia.Controls
{
public class TimePickerSelectedValueChangedEventArgs
{
public TimeSpan? OldTime { get; }
public TimeSpan? NewTime { get; }
public TimePickerSelectedValueChangedEventArgs(TimeSpan? old, TimeSpan? newT)
{
OldTime = old;
NewTime = newT;
}
}
}

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,
}
}

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

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")]

66
src/Avalonia.Controls/Slider.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Collections;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
@ -64,6 +65,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TickPlacement> TickPlacementProperty =
AvaloniaProperty.Register<TickBar, TickPlacement>(nameof(TickPlacement), 0d);
/// <summary>
/// Defines the <see cref="TicksProperty"/> property.
/// </summary>
public static readonly StyledProperty<AvaloniaList<double>> TicksProperty =
TickBar.TicksProperty.AddOwner<Slider>();
// Slider required parts
private bool _isDragging = false;
private Track _track;
@ -83,7 +90,8 @@ namespace Avalonia.Controls
PressedMixin.Attach<Slider>();
OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e),
RoutingStrategies.Bubble);
}
/// <summary>
@ -94,6 +102,15 @@ namespace Avalonia.Controls
UpdatePseudoClasses(Orientation);
}
/// <summary>
/// Defines the ticks to be drawn on the tick bar.
/// </summary>
public AvaloniaList<double> Ticks
{
get => GetValue(TicksProperty);
set => SetValue(TicksProperty, value);
}
/// <summary>
/// Gets or sets the orientation of a <see cref="Slider"/>.
/// </summary>
@ -240,19 +257,50 @@ namespace Avalonia.Controls
/// <param name="value">Value that want to snap to closest Tick.</param>
private double SnapToTick(double value)
{
var previous = Minimum;
var next = Maximum;
if (TickFrequency > 0.0)
if (IsSnapToTickEnabled)
{
previous = Minimum + (Math.Round((value - Minimum) / TickFrequency) * TickFrequency);
next = Math.Min(Maximum, previous + TickFrequency);
double previous = Minimum;
double next = Maximum;
// This property is rarely set so let's try to avoid the GetValue
var ticks = Ticks;
// If ticks collection is available, use it.
// Note that ticks may be unsorted.
if ((ticks != null) && (ticks.Count > 0))
{
for (int i = 0; i < ticks.Count; i++)
{
double tick = ticks[i];
if (MathUtilities.AreClose(tick, value))
{
return value;
}
if (MathUtilities.LessThan(tick, value) && MathUtilities.GreaterThan(tick, previous))
{
previous = tick;
}
else if (MathUtilities.GreaterThan(tick, value) && MathUtilities.LessThan(tick, next))
{
next = tick;
}
}
}
else if (MathUtilities.GreaterThan(TickFrequency, 0.0))
{
previous = Minimum + (Math.Round(((value - Minimum) / TickFrequency)) * TickFrequency);
next = Math.Min(Maximum, previous + TickFrequency);
}
// Choose the closest value between previous and next. If tie, snap to 'next'.
value = MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
}
// Choose the closest value between previous and next. If tie, snap to 'next'.
return MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
return value;
}
private void UpdatePseudoClasses(Orientation o)
{
PseudoClasses.Set(":vertical", o == Orientation.Vertical);

12
src/Avalonia.Controls/TickBar.cs

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Collections;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Utilities;
@ -135,15 +131,15 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Ticks"/> property.
/// </summary>
public static readonly StyledProperty<List<double>> TicksProperty =
AvaloniaProperty.Register<TickBar, List<double>>(nameof(Ticks));
public static readonly StyledProperty<AvaloniaList<double>> TicksProperty =
AvaloniaProperty.Register<TickBar, AvaloniaList<double>>(nameof(Ticks));
/// <summary>
/// The Ticks property contains collection of value of type Double which
/// are the logical positions use to draw the ticks.
/// The property value is a <see cref="DoubleCollection" />.
/// </summary>
public List<double> Ticks
public AvaloniaList<double> Ticks
{
get { return GetValue(TicksProperty); }
set { SetValue(TicksProperty, value); }

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

24
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,10 @@ namespace Avalonia.DesignerSupport.Remote
}
public WindowTransparencyLevel TransparencyLevel { get; private set; }
public bool IsClientAreaExtendedToDecorations { get; }
public bool NeedsManagedDecorations => false;
}
class ClipboardStub : IClipboard

7
src/Avalonia.Input/InputElement.cs

@ -158,6 +158,7 @@ namespace Avalonia.Input
private bool _isEffectivelyEnabled = true;
private bool _isFocused;
private bool _isFocusVisible;
private bool _isPointerOver;
private GestureRecognizerCollection _gestureRecognizers;
@ -427,7 +428,9 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnGotFocus(GotFocusEventArgs e)
{
IsFocused = e.Source == this;
var isFocused = e.Source == this;
_isFocusVisible = isFocused && (e.NavigationMethod == NavigationMethod.Directional || e.NavigationMethod == NavigationMethod.Tab);
IsFocused = isFocused;
}
/// <summary>
@ -436,6 +439,7 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnLostFocus(RoutedEventArgs e)
{
_isFocusVisible = false;
IsFocused = false;
}
@ -602,6 +606,7 @@ namespace Avalonia.Input
if (isFocused.HasValue)
{
PseudoClasses.Set(":focus", isFocused.Value);
PseudoClasses.Set(":focus-visible", _isFocusVisible);
}
if (isPointerOver.HasValue)

4
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -219,7 +219,9 @@ namespace Avalonia.Input.Navigation
if (parent != null)
{
if (direction == NavigationDirection.Previous && parent.CanFocus())
if (direction == NavigationDirection.Previous &&
parent.CanFocus() &&
KeyboardNavigation.GetIsTabStop((InputElement) parent))
{
return parent;
}

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

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

5
src/Avalonia.Styling/IStyledElement.cs

@ -17,11 +17,6 @@ namespace Avalonia
/// </summary>
event EventHandler Initialized;
/// <summary>
/// Raised when resources on the element are changed.
/// </summary>
event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>

1
src/Avalonia.Styling/StyledElement.cs

@ -67,7 +67,6 @@ namespace Avalonia
private List<IStyleInstance>? _appliedStyles;
private ITemplatedControl? _templatedParent;
private bool _dataContextUpdating;
private bool _notifyingResourcesChanged;
/// <summary>
/// Initializes static members of the <see cref="StyledElement"/> class.

1
src/Avalonia.Styling/Styling/Styles.cs

@ -21,7 +21,6 @@ namespace Avalonia.Styling
private IResourceHost? _owner;
private IResourceDictionary? _resources;
private Dictionary<Type, List<IStyle>?>? _cache;
private bool _notifyingResourcesChanged;
public Styles()
{

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>

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

9
src/Avalonia.Themes.Default/Slider.xaml

@ -87,7 +87,10 @@
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Slider:disabled /template/ Grid#grid">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>
<Style Selector="Slider /template/ TickBar">
<Setter Property="Ticks" Value="{TemplateBinding Ticks}" />
</Style>
<Style Selector="Slider:disabled /template/ Grid#grid">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>
</Styles>

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>

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"

61
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@ -667,6 +667,67 @@
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness>
<!-- Resources for DatePicker.xaml-->
<StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="DatePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DatePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="DatePickerButtonBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
<StaticResource x:Key="DatePickerButtonBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
<StaticResource x:Key="DatePickerButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="DatePickerButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="DatePickerButtonBackgroundFocused" ResourceKey="SystemControlHighlightListAccentLowBrush" />
<StaticResource x:Key="DatePickerButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="DatePickerButtonForegroundFocused" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="DatePickerFlyoutPresenterBackground" ResourceKey="SystemControlTransientBackgroundBrush" />
<StaticResource x:Key="DatePickerFlyoutPresenterBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
<StaticResource x:Key="DatePickerFlyoutPresenterSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="DatePickerFlyoutPresenterHighlightFill" ResourceKey="SystemControlHighlightListAccentLowBrush" />
<StaticResource x:Key="DatePickerLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightListLowBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBackgroundPressed" ResourceKey="SystemControlHighlightListMediumBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrushPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonForegroundPressed" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<!-- Resources for TimePicker.xaml -->
<StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="TimePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TimePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="TimePickerButtonBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
<StaticResource x:Key="TimePickerButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="TimePickerButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="TimePickerButtonBackgroundFocused" ResourceKey="SystemControlHighlightListAccentLowBrush" />
<StaticResource x:Key="TimePickerButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TimePickerButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="TimePickerButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="TimePickerButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonForegroundFocused" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="TimePickerFlyoutPresenterBackground" ResourceKey="SystemControlTransientBackgroundBrush" />
<StaticResource x:Key="TimePickerFlyoutPresenterBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
<StaticResource x:Key="TimePickerFlyoutPresenterSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="TimePickerFlyoutPresenterHighlightFill" ResourceKey="SystemControlHighlightListAccentLowBrush" />
<StaticResource x:Key="TimePickerLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
<SolidColorBrush x:Key="TimePickerHeaderForegroundThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="TimePickerForegroundThemeBrush" Color="#FF000000" />
<!-- Resources for Pivot.xaml -->
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>

61
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@ -664,6 +664,67 @@
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness>
<!-- Resources for DatePicker.xaml-->
<StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="DatePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DatePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="DatePickerButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="DatePickerButtonBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
<StaticResource x:Key="DatePickerButtonBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
<StaticResource x:Key="DatePickerButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="DatePickerButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="DatePickerButtonBackgroundFocused" ResourceKey="SystemControlHighlightListAccentLowBrush" />
<StaticResource x:Key="DatePickerButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="DatePickerButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="DatePickerButtonForegroundFocused" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="DatePickerFlyoutPresenterBackground" ResourceKey="SystemControlTransientBackgroundBrush" />
<StaticResource x:Key="DatePickerFlyoutPresenterBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
<StaticResource x:Key="DatePickerFlyoutPresenterSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="DatePickerFlyoutPresenterHighlightFill" ResourceKey="SystemControlHighlightListAccentLowBrush" />
<StaticResource x:Key="DatePickerLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBackground" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightListLowBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBackgroundPressed" ResourceKey="SystemControlHighlightListMediumBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrushPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="DateTimePickerFlyoutButtonForegroundPressed" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<!-- Resources for TimePicker.xaml -->
<StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="TimePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TimePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
<StaticResource x:Key="TimePickerButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
<StaticResource x:Key="TimePickerButtonBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
<StaticResource x:Key="TimePickerButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="TimePickerButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
<StaticResource x:Key="TimePickerButtonBackgroundFocused" ResourceKey="SystemControlHighlightListAccentLowBrush" />
<StaticResource x:Key="TimePickerButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TimePickerButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="TimePickerButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
<StaticResource x:Key="TimePickerButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
<StaticResource x:Key="TimePickerButtonForegroundFocused" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
<StaticResource x:Key="TimePickerFlyoutPresenterBackground" ResourceKey="SystemControlTransientBackgroundBrush" />
<StaticResource x:Key="TimePickerFlyoutPresenterBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
<StaticResource x:Key="TimePickerFlyoutPresenterSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
<StaticResource x:Key="TimePickerFlyoutPresenterHighlightFill" ResourceKey="SystemControlHighlightListAccentLowBrush" />
<StaticResource x:Key="TimePickerLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
<SolidColorBrush x:Key="TimePickerForegroundThemeBrush" Color="#FF000000" />
<SolidColorBrush x:Key="TimePickerHeaderForegroundThemeBrush" Color="#FF000000" />
<!-- Resources for Pivot.xaml -->
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>

68
src/Avalonia.Themes.Fluent/CaptionButtons.xaml

@ -0,0 +1,68 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="CaptionButtons">
<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>

338
src/Avalonia.Themes.Fluent/DatePicker.xaml

@ -0,0 +1,338 @@
<!--
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-->
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Styles.Resources>
<Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
<x:Double x:Key="DatePickerThemeMinWidth">296</x:Double>
<x:Double x:Key="DatePickerThemeMaxWidth">456</x:Double>
<Thickness x:Key="DatePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness>
<Thickness x:Key="DatePickerFlyoutPresenterMonthPadding">9,3,0,6</Thickness>
<Thickness x:Key="DatePickerHostPadding">0,3,0,6</Thickness>
<Thickness x:Key="DatePickerHostMonthPadding">9,3,0,6</Thickness>
<x:Double x:Key="DatePickerSpacerThemeWidth">1</x:Double>
</Styles.Resources>
<!-- Styles for the items displayed in the selectors -->
<Style Selector="ListBoxItem.DateTimePickerItem">
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterItemPadding}"/>
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem:selected">
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ Rectangle#PressedBackground">
<Setter Property="Fill" Value="Transparent"/>
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem.MonthItem">
<Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterMonthPadding}"/>
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
</Style>
<!-- This is used for both the accept/dismiss & repeatbuttons in the presenter-->
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle">
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackground}" />
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<ControlTemplate>
<!--
The background is doubled here for the loopingselector up/down repeat buttons
that appear opaque. Not sure how MS does it though I suspect this is it
but source isn't MIT yet, so this is my solution -->
<Border Background="{TemplateBinding Background}">
<ContentPresenter x:Name="ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource DateTimePickerFlyoutButtonBorderBrush}"
BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{DynamicResource SystemControlHighlightAltBaseHighBrush}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
CornerRadius="{DynamicResource ControlCornerRadius}"/>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPointerOver}"/>
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPointerOver}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPointerOver}"/>
</Style>
<Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPressed}"/>
<Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPressed}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPressed}"/>
</Style>
<Style Selector="RepeatButton.UpButton">
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="Height" Value="22" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" />
<Setter Property="Content">
<Template>
<Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,9 L 9,0 L 18,9"/>
</Viewbox>
</Template>
</Setter>
</Style>
<Style Selector="RepeatButton.DownButton">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="Height" Value="22" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" />
<Setter Property="Content">
<Template>
<Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,0 L 9,9 L 18,0"/>
</Viewbox>
</Template>
</Setter>
</Style>
<Style Selector="DatePicker">
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForeground}" />
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackground}"/>
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<ControlTemplate>
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*">
<ContentPresenter Name="HeaderContentPresenter" Grid.Row="0"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{DynamicResource DatePickerTopHeaderMargin}"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"/>
<Button Name="FlyoutButton" Grid.Row="1"
Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
IsEnabled="{TemplateBinding IsEnabled}"
MinWidth="{StaticResource DatePickerThemeMinWidth}"
MaxWidth="{StaticResource DatePickerThemeMaxWidth}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
TemplatedControl.IsTemplateFocusTarget="True">
<Button.Template>
<ControlTemplate>
<ContentPresenter Name="ContentPresenter"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
CornerRadius="{DynamicResource ControlCornerRadius}"/>
</ControlTemplate>
</Button.Template>
<Grid Name="ButtonContentGrid" ColumnDefinitions="78*,Auto,132*,Auto,78*">
<TextBlock Name="DayText" Text="day" HorizontalAlignment="Center"
Padding="{DynamicResource DatePickerHostPadding}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"/>
<TextBlock Name="MonthText" Text="month" TextAlignment="Left"
Padding="{DynamicResource DatePickerHostMonthPadding}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"/>
<TextBlock Name="YearText" Text="year" HorizontalAlignment="Center"
Padding="{DynamicResource DatePickerHostPadding}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"/>
<Rectangle x:Name="FirstSpacer"
Fill="{DynamicResource DatePickerSpacerFill}"
HorizontalAlignment="Center"
Width="1"
Grid.Column="1" />
<Rectangle x:Name="SecondSpacer"
Fill="{DynamicResource DatePickerSpacerFill}"
HorizontalAlignment="Center"
Width="1"
Grid.Column="3" />
</Grid>
</Button>
<Popup Name="Popup" WindowManagerAddShadowHint="False"
StaysOpen="False" PlacementTarget="{TemplateBinding}"
PlacementMode="Bottom">
<DatePickerPresenter Name="PickerPresenter" />
</Popup>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DatePicker /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerHeaderForeground}"/>
</Style>
<Style Selector="DatePicker:disabled /template/ Rectangle">
<Setter Property="Fill" Value="{DynamicResource DatePickerSpacerFillDisabled}"/>
</Style>
<Style Selector="DatePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPointerOver}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPointerOver}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPointerOver}"/>
</Style>
<Style Selector="DatePicker /template/ Button#FlyoutButton:pressed /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPressed}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPressed}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPressed}"/>
</Style>
<Style Selector="DatePicker /template/ Button#FlyoutButton:disabled /template/ ContentPresenter">
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushDisabled}"/>
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundDisabled}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/>
</Style>
<!-- Changes foreground for watermark text when SelectedDate is null-->
<Style Selector="DatePicker:hasnodate /template/ Button#FlyoutButton TextBlock">
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/>
</Style>
<!--WinUI: DatePickerFlyoutPresenter-->
<Style Selector="DatePickerPresenter">
<Setter Property="Width" Value="296" />
<Setter Property="MinWidth" Value="296" />
<Setter Property="MaxHeight" Value="398" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Background" Value="{DynamicResource DatePickerFlyoutPresenterBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerFlyoutPresenterBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="Background" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{DynamicResource DateTimeFlyoutBorderPadding}"
MaxHeight="398" CornerRadius="{DynamicResource OverlayCornerRadius}">
<Grid Name="ContentRoot" RowDefinitions="*,Auto">
<Grid Name="PickerContainer">
<!--Column Definitions set in code, ignore here-->
<Panel Name="MonthHost">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="MonthSelector"
PanelType="Month"
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}"
ShouldLoop="True" />
</ScrollViewer>
<RepeatButton Name="MonthUpButton"
Classes="DateTimeFlyoutButtonStyle UpButton"/>
<RepeatButton Name="MonthDownButton"
Classes="DateTimeFlyoutButtonStyle DownButton"/>
</Panel>
<Panel Name="DayHost">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="DaySelector"
PanelType="Day"
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}"
ShouldLoop="True" />
</ScrollViewer>
<RepeatButton Name="DayUpButton"
Classes="DateTimeFlyoutButtonStyle UpButton"/>
<RepeatButton Name="DayDownButton"
Classes="DateTimeFlyoutButtonStyle DownButton"/>
</Panel>
<Panel Name="YearHost">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="YearSelector"
PanelType="Year"
ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}"
ShouldLoop="False" />
</ScrollViewer>
<RepeatButton Name="YearUpButton"
Classes="DateTimeFlyoutButtonStyle UpButton"/>
<RepeatButton Name="YearDownButton"
Classes="DateTimeFlyoutButtonStyle DownButton"/>
</Panel>
<Rectangle Name="HighlightRect" IsHitTestVisible="False" ZIndex="-1"
Fill="{DynamicResource DatePickerFlyoutPresenterHighlightFill}"
Grid.Column="0" Grid.ColumnSpan="5" VerticalAlignment="Center"
Height="{DynamicResource DatePickerFlyoutPresenterHighlightHeight}" />
<Rectangle Name="FirstSpacer"
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}"
HorizontalAlignment="Center"
Width="{DynamicResource DatePickerSpacerThemeWidth}"
Grid.Column="1" />
<Rectangle Name="SecondSpacer"
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}"
HorizontalAlignment="Center"
Width="{DynamicResource DatePickerSpacerThemeWidth}"
Grid.Column="3" />
</Grid>
<Grid Grid.Row="1" Height="{DynamicResource DatePickerFlyoutPresenterAcceptDismissHostGridHeight}"
Name="AcceptDismissGrid" ColumnDefinitions="*,*">
<Rectangle Height="{DynamicResource DatePickerSpacerThemeWidth}" VerticalAlignment="Top"
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}"
Grid.ColumnSpan="2"/>
<Button Name="AcceptButton" Grid.Column="0" Classes="DateTimeFlyoutButtonStyle"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round"
StrokeThickness="0.75" Data="M0.5,8.5 5,13.5 15.5,3" />
</Button>
<Button Name="DismissButton" Grid.Column="1" Classes="DateTimeFlyoutButtonStyle"
FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round"
StrokeThickness="0.75" Data="M2,2 14,14 M2,14 14 2" />
</Button>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DatePickerPresenter /template/ Panel RepeatButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DatePickerPresenter /template/ Panel:pointerover RepeatButton">
<Setter Property="IsVisible" Value="True" />
</Style>
</Styles>

6
src/Avalonia.Themes.Fluent/FluentTheme.xaml

@ -7,7 +7,8 @@
<StyleInclude Source="resm:Avalonia.Themes.Fluent.FocusAdorner.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Button.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Carousel.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.CaptionButtons.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Carousel.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.CheckBox.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.ComboBox.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.ComboBoxItem.xaml?assembly=Avalonia.Themes.Fluent"/>
@ -36,6 +37,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Fluent.TextBox.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.ToggleButton.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.Expander.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.TitleBar.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.TreeView.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.TreeViewItem.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.UserControl.xaml?assembly=Avalonia.Themes.Fluent"/>
@ -54,4 +56,6 @@
<StyleInclude Source="resm:Avalonia.Themes.Fluent.NativeMenuBar.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.ToggleSwitch.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.SplitView.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.DatePicker.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.TimePicker.xaml?assembly=Avalonia.Themes.Fluent"/>
</Styles>

1
src/Avalonia.Themes.Fluent/Slider.xaml

@ -182,6 +182,7 @@
<Style Selector="Slider /template/ TickBar">
<Setter Property="IsVisible" Value="False" />
<Setter Property="Ticks" Value="{TemplateBinding Ticks}" />
</Style>
<!-- TickBar Placement States -->

284
src/Avalonia.Themes.Fluent/TimePicker.xaml

@ -0,0 +1,284 @@
<!--
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
-->
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Styles.Resources>
<x:Double x:Key="TimePickerFlyoutPresenterItemHeight">40</x:Double>
<x:Double x:Key="TimePickerSpacerThemeWidth">1</x:Double>
<Thickness x:Key="TimePickerBorderThemeThickness">1</Thickness>
<Thickness x:Key="TimePickerTopHeaderMargin">0,0,0,4</Thickness>
<x:Double x:Key="TimePickerFlyoutPresenterHighlightHeight">40</x:Double>
<x:Double x:Key="TimePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
<x:Double x:Key="TimePickerThemeMinWidth">242</x:Double>
<x:Double x:Key="TimePickerThemeMaxWidth">456</x:Double>
<Thickness x:Key="TimePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness>
<Thickness x:Key="TimePickerHostPadding">0,3,0,6</Thickness>
</Styles.Resources>
<Style Selector="ListBoxItem.DateTimePickerItem.HourItem">
<Setter Property="Padding" Value="{DynamicResource TimePickerFlyoutPresenterItemPadding}" />
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem.MinuteItem">
<Setter Property="Padding" Value="{DynamicResource TimePickerFlyoutPresenterItemPadding}" />
</Style>
<Style Selector="ListBoxItem.DateTimePickerItem.TimePeriodItem">
<Setter Property="Padding" Value="{DynamicResource TimePickerFlyoutPresenterItemPadding}" />
</Style>
<Style Selector="TimePicker">
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Foreground" Value="{DynamicResource TimePickerButtonForeground}" />
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackground}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource TimePickerBorderThemeThickness}"/>
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<ControlTemplate>
<Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*">
<ContentPresenter x:Name="HeaderContentPresenter"
Grid.Row="0"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{DynamicResource TimePickerTopHeaderMargin}"
MaxWidth="{DynamicResource TimePickerThemeMaxWidth}"
TextBlock.Foreground="{DynamicResource TimePickerHeaderForeground}"
HorizontalAlignment="Stretch"
VerticalAlignment="Top" />
<Button x:Name="FlyoutButton"
Grid.Row="1"
Foreground="{TemplateBinding Foreground}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
IsEnabled="{TemplateBinding IsEnabled}"
MinWidth="{StaticResource TimePickerThemeMinWidth}"
MaxWidth="{StaticResource TimePickerThemeMaxWidth}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalAlignment="Top"
VerticalContentAlignment="Stretch">
<Button.Template>
<ControlTemplate>
<ContentPresenter Name="ContentPresenter"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
CornerRadius="{DynamicResource ControlCornerRadius}" />
</ControlTemplate>
</Button.Template>
<Grid Name="FlyoutButtonContentGrid">
<!--Ignore col defs here, set in code-->
<Border x:Name="FirstPickerHost" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock x:Name="HourTextBlock"
HorizontalAlignment="Center"
Padding="{DynamicResource TimePickerHostPadding}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}" />
</Border>
<Rectangle Name="FirstColumnDivider"
Fill="{DynamicResource TimePickerSpacerFill}"
HorizontalAlignment="Center"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
Grid.Column="1" />
<Border x:Name="SecondPickerHost" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock x:Name="MinuteTextBlock"
HorizontalAlignment="Center"
Padding="{DynamicResource TimePickerHostPadding}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"/>
</Border>
<Rectangle Name="SecondColumnDivider"
Fill="{DynamicResource TimePickerSpacerFill}"
HorizontalAlignment="Center"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
Grid.Column="3" />
<Border x:Name="ThirdPickerHost" Grid.Column="4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock x:Name="PeriodTextBlock"
HorizontalAlignment="Center"
Padding="{DynamicResource TimePickerHostPadding}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}" />
</Border>
</Grid>
</Button>
<Popup Name="Popup" WindowManagerAddShadowHint="False"
StaysOpen="False" PlacementTarget="{TemplateBinding}"
PlacementMode="Bottom">
<TimePickerPresenter Name="PickerPresenter" />
</Popup>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TimePicker:disabled /template/ ContentPresenter#HeaderContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerHeaderForegroundDisabled}"/>
</Style>
<Style Selector="TimePicker:disabled /template/ Rectangle">
<Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/>
</Style>
<Style Selector="TimePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPointerOver}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPointerOver}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundPointerOver}"/>
</Style>
<Style Selector="TimePicker /template/ Button:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundPressed}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushPressed}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundPressed}"/>
</Style>
<Style Selector="TimePicker /template/ Button:disabled /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource TimePickerButtonBackgroundDisabled}"/>
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerButtonBorderBrushDisabled}"/>
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TimePickerButtonForegroundDisabled}"/>
</Style>
<Style Selector="TimePicker:hasnotime /template/ Button#FlyoutButton TextBlock">
<Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/>
</Style>
<Style Selector="TimePickerPresenter">
<Setter Property="Width" Value="242" />
<Setter Property="MinWidth" Value="242" />
<Setter Property="MaxHeight" Value="398" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="Background" Value="{DynamicResource TimePickerFlyoutPresenterBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource TimePickerFlyoutPresenterBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="Background"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource OverlayCornerRadius}"
Padding="{DynamicResource DateTimeFlyoutBorderPadding}"
MaxHeight="398">
<Grid Name="ContentPanel" RowDefinitions="*,Auto">
<Grid Name="PickerContainer">
<!--Ignore col defs here, set in code-->
<Panel Name="HourHost" Grid.Column="0">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="HourSelector"
PanelType="Hour"
ShouldLoop="True"
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/>
</ScrollViewer>
<RepeatButton Name="HourUpButton"
Classes="DateTimeFlyoutButtonStyle UpButton"/>
<RepeatButton Name="HourDownButton"
Classes="DateTimeFlyoutButtonStyle DownButton"/>
</Panel>
<Panel Name="MinuteHost" Grid.Column="2">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="MinuteSelector"
PanelType="Minute"
ShouldLoop="True"
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/>
</ScrollViewer>
<RepeatButton Name="MinuteUpButton"
Classes="DateTimeFlyoutButtonStyle UpButton"/>
<RepeatButton Name="MinuteDownButton"
Classes="DateTimeFlyoutButtonStyle DownButton"/>
</Panel>
<Panel Name="PeriodHost" Grid.Column="4">
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Hidden">
<DateTimePickerPanel Name="PeriodSelector"
PanelType="TimePeriod"
ShouldLoop="False"
ItemHeight="{DynamicResource TimePickerFlyoutPresenterItemHeight}"/>
</ScrollViewer>
<RepeatButton Name="PeriodUpButton"
Classes="DateTimeFlyoutButtonStyle UpButton"/>
<RepeatButton Name="PeriodDownButton"
Classes="DateTimeFlyoutButtonStyle DownButton"/>
</Panel>
<Rectangle x:Name="HighlightRect" ZIndex="-1"
Fill="{DynamicResource TimePickerFlyoutPresenterHighlightFill}"
Grid.Column="0"
Grid.ColumnSpan="5"
VerticalAlignment="Center"
Height="{DynamicResource TimePickerFlyoutPresenterHighlightHeight}" />
<Rectangle Name="FirstSpacer"
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}"
HorizontalAlignment="Center"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
Grid.Column="1" />
<Rectangle Name="SecondSpacer"
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}"
HorizontalAlignment="Center"
Width="{DynamicResource TimePickerSpacerThemeWidth}"
Grid.Column="3" />
</Grid>
<Grid Grid.Row="1" Height="{DynamicResource TimePickerFlyoutPresenterAcceptDismissHostGridHeight}"
Name="AcceptDismissHostGrid" ColumnDefinitions="*,*">
<Rectangle Height="{DynamicResource TimePickerSpacerThemeWidth}"
VerticalAlignment="Top"
Fill="{DynamicResource TimePickerFlyoutPresenterSpacerFill}"
Grid.ColumnSpan="2" />
<Button Name="AcceptButton" Grid.Column="0" Classes="DateTimeFlyoutButtonStyle"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round"
StrokeThickness="0.75" Data="M0.5,8.5 5,13.5 15.5,3" />
</Button>
<Button Name="DismissButton" Grid.Column="1" Classes="DateTimeFlyoutButtonStyle"
FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round"
StrokeThickness="0.75" Data="M2,2 14,14 M2,14 14 2" />
</Button>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TimePickerPresenter /template/ Panel RepeatButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="TimePickerPresenter /template/ Panel:pointerover RepeatButton">
<Setter Property="IsVisible" Value="True" />
</Style>
</Styles>

53
src/Avalonia.Themes.Fluent/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>

2
src/Avalonia.Themes.Fluent/Window.xaml

@ -1,5 +1,5 @@
<Style xmlns="https://github.com/avaloniaui" Selector="Window">
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumBrush}"/>
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
<Setter Property="Template">

24
src/Avalonia.X11/X11Window.cs

@ -312,7 +312,15 @@ namespace Avalonia.X11
{
get => _transparencyHelper.TransparencyLevelChanged;
set => _transparencyHelper.TransparencyLevelChanged = value;
}
}
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
public Thickness ExtendedMargins { get; } = new Thickness();
public Thickness OffScreenMargin { get; } = new Thickness();
public bool IsClientAreaExtendedToDecorations { get; }
public Action Closed { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
@ -1039,6 +1047,18 @@ namespace Avalonia.X11
_disabled = !enable;
}
public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
{
}
public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
{
}
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
{
}
public Action GotInputWhenDisabled { get; set; }
public void SetIcon(IWindowIconImpl icon)
@ -1107,5 +1127,7 @@ namespace Avalonia.X11
}
public WindowTransparencyLevel TransparencyLevel => _transparencyHelper.CurrentLevel;
public bool NeedsManagedDecorations => false;
}
}

6
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DefineConstants>PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLIL_INTERNAL</DefineConstants>
<DefineConstants>PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLX_INTERNAL</DefineConstants>
<UseCecil>false</UseCecil>
<DefineConstants Condition="$(UseCecil) == true">$(DefineConstants);RUNTIME_XAML_CECIL</DefineConstants>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
@ -58,8 +58,8 @@
<Compile Include="XamlIl\Runtime\IAvaloniaXamlIlXmlNamespaceInfoProviderV1.cs" />
<Compile Include="XamlIl\Runtime\XamlIlRuntimeHelpers.cs" />
<Compile Include="XamlLoadException.cs" />
<Compile Include="XamlIl\xamlil.github\src\XamlIl\**\*.cs" />
<Compile Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlIl.Cecil\**\*.cs" />
<Compile Include="XamlIl\xamlil.github\src\XamlX\**\*.cs" />
<Compile Condition="$(UseCecil) == true" Include="XamlIl\xamlil.github\src\XamlX.Il.Cecil\**\*.cs" />
<Compile Remove="XamlIl\xamlil.github\**\obj\**\*.cs" />
<Compile Include="..\Avalonia.Markup\Markup\Parsers\SelectorGrammar.cs" />
<Compile Include="XamlTypes.cs" />

36
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs

@ -10,12 +10,15 @@ using System.Runtime.InteropServices;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Platform;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Transform;
using XamlX.TypeSystem;
using XamlX.IL;
using XamlX.Emit;
#if RUNTIME_XAML_CECIL
using TypeAttributes = Mono.Cecil.TypeAttributes;
using Mono.Cecil;
using XamlIl.Ast;
using XamlX.Ast;
using XamlX.IL.Cecil;
#endif
namespace Avalonia.Markup.Xaml.XamlIl
{
@ -24,9 +27,10 @@ namespace Avalonia.Markup.Xaml.XamlIl
#if !RUNTIME_XAML_CECIL
private static SreTypeSystem _sreTypeSystem;
private static ModuleBuilder _sreBuilder;
private static IXamlIlType _sreContextType;
private static XamlIlLanguageTypeMappings _sreMappings;
private static XamlIlXmlnsMappings _sreXmlns;
private static IXamlType _sreContextType;
private static XamlLanguageTypeMappings _sreMappings;
private static XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> _sreEmitMappings;
private static XamlXmlnsMappings _sreXmlns;
private static AssemblyBuilder _sreAsm;
private static bool _sreCanSave;
@ -82,13 +86,14 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
if (_sreMappings == null)
_sreMappings = AvaloniaXamlIlLanguage.Configure(_sreTypeSystem);
(_sreMappings, _sreEmitMappings) = AvaloniaXamlIlLanguage.Configure(_sreTypeSystem);
if (_sreXmlns == null)
_sreXmlns = XamlIlXmlnsMappings.Resolve(_sreTypeSystem, _sreMappings);
_sreXmlns = XamlXmlnsMappings.Resolve(_sreTypeSystem, _sreMappings);
if (_sreContextType == null)
_sreContextType = XamlIlContextDefinition.GenerateContextClass(
_sreContextType = XamlILContextDefinition.GenerateContextClass(
_sreTypeSystem.CreateTypeBuilder(
_sreBuilder.DefineType("XamlIlContext")), _sreTypeSystem, _sreMappings);
_sreBuilder.DefineType("XamlIlContext")), _sreTypeSystem, _sreMappings,
_sreEmitMappings);
}
@ -115,12 +120,13 @@ namespace Avalonia.Markup.Xaml.XamlIl
InitializeSre();
var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly);
var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_sreTypeSystem, asm,
var compiler = new AvaloniaXamlIlCompiler(new TransformerConfiguration(_sreTypeSystem, asm,
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter),
_sreEmitMappings,
_sreContextType) { EnableIlVerification = true };
var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
IXamlIlType overrideType = null;
IXamlType overrideType = null;
if (rootInstance != null)
{
overrideType = _sreTypeSystem.GetType(rootInstance.GetType());
@ -203,6 +209,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
private static string _cecilEmitDir;
private static CecilTypeSystem _cecilTypeSystem;
private static XamlIlLanguageTypeMappings _cecilMappings;
private static XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> _cecilEmitMappings;
private static XamlIlXmlnsMappings _cecilXmlns;
private static bool _cecilInitialized;
@ -215,7 +222,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
Directory.CreateDirectory(_cecilEmitDir);
var refs = new[] {path}.Concat(File.ReadAllLines(path + ".refs"));
_cecilTypeSystem = new CecilTypeSystem(refs);
_cecilMappings = AvaloniaXamlIlLanguage.Configure(_cecilTypeSystem);
(_cecilMappings, _cecilEmitMappings) = AvaloniaXamlIlLanguage.Configure(_cecilTypeSystem);
_cecilXmlns = XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings);
_cecilInitialized = true;
}
@ -226,7 +233,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
if (uri == null)
throw new InvalidOperationException("Please, go away");
InitializeCecil();
IXamlIlType overrideType = null;
IXamlType overrideType = null;
if (rootInstance != null)
{
overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName);
@ -261,6 +268,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
localAssembly == null ? null : _cecilTypeSystem.FindAssembly(localAssembly.GetName().Name),
_cecilMappings, XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings),
AvaloniaXamlIlLanguage.CustomValueConverter),
_cecilEmitMappings,
_cecilTypeSystem.CreateTypeBuilder(contextDef));
compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType);
var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll");

75
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -1,29 +1,32 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Parsers;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Parsers;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class AvaloniaXamlIlCompiler : XamlIlCompiler
class AvaloniaXamlIlCompiler : XamlILCompiler
{
private readonly XamlIlTransformerConfiguration _configuration;
private readonly IXamlIlType _contextType;
private readonly TransformerConfiguration _configuration;
private readonly IXamlType _contextType;
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
private AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration) : base(configuration, true)
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
: base(configuration, emitMappings, true)
{
_configuration = configuration;
void InsertAfter<T>(params IXamlIlAstTransformer[] t)
void InsertAfter<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t);
void InsertBefore<T>(params IXamlIlAstTransformer[] t)
void InsertBefore<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T), t);
@ -37,12 +40,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
// Targeted
InsertBefore<XamlIlPropertyReferenceResolver>(new AvaloniaXamlIlTransformInstanceAttachedProperties());
InsertAfter<XamlIlPropertyReferenceResolver>(new AvaloniaXamlIlAvaloniaPropertyResolver());
InsertBefore<PropertyReferenceResolver>(new AvaloniaXamlIlTransformInstanceAttachedProperties());
InsertAfter<PropertyReferenceResolver>(new AvaloniaXamlIlAvaloniaPropertyResolver());
InsertBefore<XamlIlContentConvertTransformer>(
InsertBefore<ContentConvertTransformer>(
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
@ -55,17 +58,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transformers.Add(new AddNameScopeRegistration());
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
}
public AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration,
IXamlIlTypeBuilder contextTypeBuilder) : this(configuration)
public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings,
IXamlTypeBuilder<IXamlILEmitter> contextTypeBuilder)
: this(configuration, emitMappings)
{
_contextType = CreateContextType(contextTypeBuilder);
}
public AvaloniaXamlIlCompiler(XamlIlTransformerConfiguration configuration,
IXamlIlType contextType) : this(configuration)
public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings,
IXamlType contextType) : this(configuration, emitMappings)
{
_contextType = contextType;
}
@ -79,37 +86,35 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
set => _designTransformer.IsDesignMode = value;
}
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlIlTypeBuilder tb, IXamlIlType overrideRootType)
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder<IXamlILEmitter> tb, IXamlType overrideRootType)
{
var parsed = XDocumentXamlIlParser.Parse(xaml, new Dictionary<string, string>
var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
{
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
});
var rootObject = (XamlIlAstObjectNode)parsed.Root;
var rootObject = (XamlAstObjectNode)parsed.Root;
var classDirective = rootObject.Children
.OfType<XamlIlAstXmlDirective>().FirstOrDefault(x =>
.OfType<XamlAstXmlDirective>().FirstOrDefault(x =>
x.Namespace == XamlNamespaces.Xaml2006
&& x.Name == "Class");
var rootType =
classDirective != null ?
new XamlIlAstClrTypeReference(classDirective,
_configuration.TypeSystem.GetType(((XamlIlAstTextNode)classDirective.Values[0]).Text),
new XamlAstClrTypeReference(classDirective,
_configuration.TypeSystem.GetType(((XamlAstTextNode)classDirective.Values[0]).Text),
false) :
XamlIlTypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
(XamlIlAstXmlTypeReference)rootObject.Type, true);
TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
(XamlAstXmlTypeReference)rootObject.Type, true);
if (overrideRootType != null)
{
if (!rootType.Type.IsAssignableFrom(overrideRootType))
throw new XamlIlLoadException(
throw new XamlX.XamlLoadException(
$"Unable to substitute {rootType.Type.GetFqn()} with {overrideRootType.GetFqn()}", rootObject);
rootType = new XamlIlAstClrTypeReference(rootObject, overrideRootType, false);
rootType = new XamlAstClrTypeReference(rootObject, overrideRootType, false);
}
OverrideRootType(parsed, rootType);
@ -119,17 +124,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
public void OverrideRootType(XamlIlDocument doc, IXamlIlAstTypeReference newType)
public void OverrideRootType(XamlDocument doc, IXamlAstTypeReference newType)
{
var root = (XamlIlAstObjectNode)doc.Root;
var root = (XamlAstObjectNode)doc.Root;
var oldType = root.Type;
if (oldType.Equals(newType))
return;
root.Type = newType;
foreach (var child in root.Children.OfType<XamlIlAstXamlPropertyValueNode>())
foreach (var child in root.Children.OfType<XamlAstXamlPropertyValueNode>())
{
if (child.Property is XamlIlAstNamePropertyReference prop)
if (child.Property is XamlAstNamePropertyReference prop)
{
if (prop.DeclaringType.Equals(oldType))
prop.DeclaringType = newType;

78
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -2,10 +2,12 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
@ -17,12 +19,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class AvaloniaXamlIlLanguage
{
public static XamlIlLanguageTypeMappings Configure(IXamlIlTypeSystem typeSystem)
public static (XamlLanguageTypeMappings language, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emit) Configure(IXamlTypeSystem typeSystem)
{
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var assignBindingAttribute = typeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
var bindingType = typeSystem.GetType("Avalonia.Data.IBinding");
var rv = new XamlIlLanguageTypeMappings(typeSystem)
var rv = new XamlLanguageTypeMappings(typeSystem)
{
SupportInitialize = typeSystem.GetType("System.ComponentModel.ISupportInitialize"),
XmlnsAttributes =
@ -51,18 +53,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
},
InnerServiceProviderFactoryMethod =
runtimeHelpers.FindMethod(m => m.Name == "CreateInnerServiceProviderV1"),
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit,
};
rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv);
rv.ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c);
return rv;
var emit = new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>
{
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit,
ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c)
};
return (rv, emit);
}
public const string ContextNameScopeFieldName = "AvaloniaNameScope";
private static void EmitNameScopeField(XamlIlLanguageTypeMappings mappings,
IXamlIlTypeSystem typeSystem,
IXamlIlTypeBuilder typebuilder, IXamlIlEmitter constructor)
private static void EmitNameScopeField(XamlLanguageTypeMappings mappings,
IXamlTypeSystem typeSystem,
IXamlTypeBuilder<IXamlILEmitter> typebuilder, IXamlILEmitter constructor)
{
var nameScopeType = typeSystem.FindType("Avalonia.Controls.INameScope");
@ -78,23 +84,23 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
class AttributeResolver : IXamlIlCustomAttributeResolver
class AttributeResolver : IXamlCustomAttributeResolver
{
private readonly IXamlIlType _typeConverterAttribute;
private readonly IXamlType _typeConverterAttribute;
private readonly List<KeyValuePair<IXamlIlType, IXamlIlType>> _converters =
new List<KeyValuePair<IXamlIlType, IXamlIlType>>();
private readonly List<KeyValuePair<IXamlType, IXamlType>> _converters =
new List<KeyValuePair<IXamlType, IXamlType>>();
private readonly IXamlIlType _avaloniaList;
private readonly IXamlIlType _avaloniaListConverter;
private readonly IXamlType _avaloniaList;
private readonly IXamlType _avaloniaListConverter;
public AttributeResolver(IXamlIlTypeSystem typeSystem, XamlIlLanguageTypeMappings mappings)
public AttributeResolver(IXamlTypeSystem typeSystem, XamlLanguageTypeMappings mappings)
{
_typeConverterAttribute = mappings.TypeConverterAttributes.First();
void AddType(IXamlIlType type, IXamlIlType conv)
=> _converters.Add(new KeyValuePair<IXamlIlType, IXamlIlType>(type, conv));
void AddType(IXamlType type, IXamlType conv)
=> _converters.Add(new KeyValuePair<IXamlType, IXamlType>(type, conv));
void Add(string type, string conv)
=> AddType(typeSystem.GetType(type), typeSystem.GetType(conv));
@ -113,7 +119,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
_avaloniaListConverter = typeSystem.GetType("Avalonia.Collections.AvaloniaListConverter`1");
}
IXamlIlType LookupConverter(IXamlIlType type)
IXamlType LookupConverter(IXamlType type)
{
foreach(var p in _converters)
if (p.Key.Equals(type))
@ -123,15 +129,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return null;
}
class ConstructedAttribute : IXamlIlCustomAttribute
class ConstructedAttribute : IXamlCustomAttribute
{
public bool Equals(IXamlIlCustomAttribute other) => false;
public bool Equals(IXamlCustomAttribute other) => false;
public IXamlIlType Type { get; }
public IXamlType Type { get; }
public List<object> Parameters { get; }
public Dictionary<string, object> Properties { get; }
public ConstructedAttribute(IXamlIlType type, List<object> parameters, Dictionary<string, object> properties)
public ConstructedAttribute(IXamlType type, List<object> parameters, Dictionary<string, object> properties)
{
Type = type;
Parameters = parameters ?? new List<object>();
@ -139,7 +145,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlType type, IXamlIlType attributeType)
public IXamlCustomAttribute GetCustomAttribute(IXamlType type, IXamlType attributeType)
{
if (attributeType.Equals(_typeConverterAttribute))
{
@ -151,25 +157,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return null;
}
public IXamlIlCustomAttribute GetCustomAttribute(IXamlIlProperty property, IXamlIlType attributeType)
public IXamlCustomAttribute GetCustomAttribute(IXamlProperty property, IXamlType attributeType)
{
return null;
}
}
public static bool CustomValueConverter(XamlIlAstTransformationContext context,
IXamlIlAstValueNode node, IXamlIlType type, out IXamlIlAstValueNode result)
public static bool CustomValueConverter(AstTransformationContext context,
IXamlAstValueNode node, IXamlType type, out IXamlAstValueNode result)
{
if (type.FullName == "System.TimeSpan"
&& node is XamlIlAstTextNode tn
&& node is XamlAstTextNode tn
&& !tn.Text.Contains(":"))
{
var seconds = double.Parse(tn.Text, CultureInfo.InvariantCulture);
result = new XamlIlStaticOrTargetedReturnMethodCallNode(tn,
result = new XamlStaticOrTargetedReturnMethodCallNode(tn,
type.FindMethod("FromSeconds", type, false, context.Configuration.WellKnownTypes.Double),
new[]
{
new XamlIlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds)
new XamlConstantNode(tn, context.Configuration.WellKnownTypes.Double, seconds)
});
return true;
}
@ -178,9 +184,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
var scope = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().FirstOrDefault();
if (scope == null)
throw new XamlIlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node);
if (!(node is XamlIlAstTextNode text))
throw new XamlIlLoadException("Property should be a text node", node);
throw new XamlX.XamlLoadException("Unable to find the parent scope for AvaloniaProperty lookup", node);
if (!(node is XamlAstTextNode text))
throw new XamlX.XamlLoadException("Property should be a text node", node);
result = XamlIlAvaloniaPropertyHelper.CreateNode(context, text.Text, scope.TargetType, text);
return true;
}

98
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs

@ -1,33 +1,35 @@
using System;
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AddNameScopeRegistration : IXamlIlAstTransformer
class AddNameScopeRegistration : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlPropertyAssignmentNode pa
if (node is XamlPropertyAssignmentNode pa
&& pa.Property.Name == "Name"
&& pa.Property.DeclaringType.FullName == "Avalonia.StyledElement")
{
if (context.ParentNodes().FirstOrDefault() is XamlIlManipulationGroupNode mg
if (context.ParentNodes().FirstOrDefault() is XamlManipulationGroupNode mg
&& mg.Children.OfType<AvaloniaNameScopeRegistrationXamlIlNode>().Any())
return node;
IXamlIlAstValueNode value = null;
IXamlAstValueNode value = null;
for (var c = 0; c < pa.Values.Count; c++)
if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String))
{
value = pa.Values[c];
if (!(value is XamlIlAstTextNode))
if (!(value is XamlAstTextNode))
{
var local = new XamlIlAstCompilerLocalNode(value);
var local = new XamlAstCompilerLocalNode(value);
// Wrap original in local initialization
pa.Values[c] = new XamlIlAstLocalInitializationNodeEmitter(value, value, local);
pa.Values[c] = new XamlAstLocalInitializationNodeEmitter(value, value, local);
// Use local
value = local;
}
@ -36,20 +38,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
if (value != null)
return new XamlIlManipulationGroupNode(pa)
return new XamlManipulationGroupNode(pa)
{
Children =
{
pa,
new AvaloniaNameScopeRegistrationXamlIlNode(value, context.GetAvaloniaTypes())
new AvaloniaNameScopeRegistrationXamlIlNode(value)
}
};
}
if (!context.ParentNodes().Any()
&& node is XamlIlValueWithManipulationNode mnode)
&& node is XamlValueWithManipulationNode mnode)
{
mnode.Manipulation = new XamlIlManipulationGroupNode(mnode,
mnode.Manipulation = new XamlManipulationGroupNode(mnode,
new[]
{
mnode.Manipulation,
@ -59,17 +61,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
class HandleRootObjectScopeNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
class HandleRootObjectScopeNode : XamlAstNode, IXamlAstManipulationNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
private readonly AvaloniaXamlIlWellKnownTypes _types;
public HandleRootObjectScopeNode(IXamlIlLineInfo lineInfo,
public HandleRootObjectScopeNode(IXamlLineInfo lineInfo,
AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
{
_types = types;
}
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var next = codeGen.DefineLabel();
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
@ -91,49 +93,55 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.EmitCall(_types.INameScopeComplete, true);
}
return XamlIlNodeEmitResult.Void(1);
return XamlILNodeEmitResult.Void(1);
}
}
}
class AvaloniaNameScopeRegistrationXamlIlNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
class AvaloniaNameScopeRegistrationXamlIlNode : XamlAstNode, IXamlAstManipulationNode
{
private readonly AvaloniaXamlIlWellKnownTypes _types;
public IXamlIlAstValueNode Name { get; set; }
public IXamlAstValueNode Name { get; set; }
public AvaloniaNameScopeRegistrationXamlIlNode(IXamlIlAstValueNode name, AvaloniaXamlIlWellKnownTypes types) : base(name)
public AvaloniaNameScopeRegistrationXamlIlNode(IXamlAstValueNode name) : base(name)
{
_types = types;
Name = name;
}
public override void VisitChildren(IXamlIlAstVisitor visitor)
=> Name = (IXamlIlAstValueNode)Name.Visit(visitor);
public override void VisitChildren(IXamlAstVisitor visitor)
=> Name = (IXamlAstValueNode)Name.Visit(visitor);
}
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
class AvaloniaNameScopeRegistrationXamlIlNodeEmitter : IXamlAstLocalsNodeEmitter<IXamlILEmitter, XamlILNodeEmitResult>
{
public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var targetLoc = context.GetLocal(context.Configuration.WellKnownTypes.Object))
if (node is AvaloniaNameScopeRegistrationXamlIlNode registration)
{
codeGen
// var target = {pop}
.Stloc(targetLoc.Local)
// _context.NameScope.Register(Name, target)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField);
context.Emit(Name, codeGen, Name.Type.GetClrType());
codeGen
.Ldloc(targetLoc.Local)
.EmitCall(_types.INameScopeRegister, true);
}
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var targetLoc = context.GetLocalOfType(context.Configuration.WellKnownTypes.Object))
{
return XamlIlNodeEmitResult.Void(1);
codeGen
// var target = {pop}
.Stloc(targetLoc.Local)
// _context.NameScope.Register(Name, target)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField);
context.Emit(registration.Name, codeGen, registration.Name.Type.GetClrType());
codeGen
.Ldloc(targetLoc.Local)
.EmitCall(context.GetAvaloniaTypes().INameScopeRegister, true);
}
return XamlILNodeEmitResult.Void(1);
}
return default;
}
}
}

10
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs

@ -1,16 +1,16 @@
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaBindingExtensionHackTransformer : IXamlIlAstTransformer
class AvaloniaBindingExtensionHackTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
// Our code base expects XAML parser to prefer `FooExtension` to `Foo` even with `<Foo>` syntax
// This is the legacy of Portable.Xaml, so we emulate that behavior here
if (node is XamlIlAstXmlTypeReference tref
if (node is XamlAstXmlTypeReference tref
&& tref.Name == "Binding"
&& tref.XmlNamespace == "https://github.com/avaloniaui")
tref.IsMarkupExtension = true;

10
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlAvaloniaPropertyResolver.cs

@ -1,14 +1,14 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlAvaloniaPropertyResolver : IXamlIlAstTransformer
class AvaloniaXamlIlAvaloniaPropertyResolver : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstClrProperty prop)
if (node is XamlAstClrProperty prop)
{
var n = prop.Name + "Property";
var field =

28
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs

@ -1,15 +1,17 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlConstructorServiceProviderTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlConstructorServiceProviderTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode on && on.Arguments.Count == 0)
if (node is XamlAstObjectNode on && on.Arguments.Count == 0)
{
var ctors = on.Type.GetClrType().Constructors;
if (!ctors.Any(c => c.IsPublic && !c.IsStatic && c.Parameters.Count == 0))
@ -27,20 +29,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
class InjectServiceProviderNode : XamlIlAstNode, IXamlIlAstValueNode,IXamlIlAstNodeNeedsParentStack,
IXamlIlAstEmitableNode
class InjectServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
public InjectServiceProviderNode(IXamlIlType type, IXamlIlLineInfo lineInfo) : base(lineInfo)
public InjectServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo)
{
Type = new XamlIlAstClrTypeReference(lineInfo, type, false);
Type = new XamlAstClrTypeReference(lineInfo, type, false);
}
public IXamlIlAstTypeReference Type { get; }
public IXamlAstTypeReference Type { get; }
public bool NeedsParentStack => true;
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldloc(context.ContextLocal);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
}
}

28
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer.cs

@ -1,29 +1,29 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlIlAstObjectNode on
if (!(node is XamlAstObjectNode on
&& on.Type.GetClrType().FullName == "Avalonia.Markup.Xaml.Templates.ControlTemplate"))
return node;
var tt = on.Children.OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(ch =>
var tt = on.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(ch =>
ch.Property.GetClrProperty().Name == "TargetType");
if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode)
// Deja vu. I've just been in this place before
return node;
IXamlIlAstTypeReference targetType;
IXamlAstTypeReference targetType;
var templatableBaseType = context.Configuration.TypeSystem.GetType("Avalonia.Controls.Control");
if ((tt?.Values.FirstOrDefault() is XamlIlTypeExtensionNode tn))
if ((tt?.Values.FirstOrDefault() is XamlTypeExtensionNode tn))
{
targetType = tn.Value;
}
@ -33,11 +33,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.FirstOrDefault();
if (parentScope?.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style)
targetType = parentScope.TargetType;
else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlIlAstObjectNode directParentNode
else if (context.ParentNodes().Skip(1).FirstOrDefault() is XamlAstObjectNode directParentNode
&& templatableBaseType.IsAssignableFrom(directParentNode.Type.GetClrType()))
targetType = directParentNode.Type;
else
targetType = new XamlIlAstClrTypeReference(node,
targetType = new XamlAstClrTypeReference(node,
templatableBaseType, false);
}
@ -48,9 +48,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
class AvaloniaXamlIlTargetTypeMetadataNode : XamlIlValueWithSideEffectNodeBase
class AvaloniaXamlIlTargetTypeMetadataNode : XamlValueWithSideEffectNodeBase
{
public IXamlIlAstTypeReference TargetType { get; set; }
public IXamlAstTypeReference TargetType { get; set; }
public ScopeTypes ScopeType { get; }
public enum ScopeTypes
@ -60,7 +60,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Transitions
}
public AvaloniaXamlIlTargetTypeMetadataNode(IXamlIlAstValueNode value, IXamlIlAstTypeReference targetType,
public AvaloniaXamlIlTargetTypeMetadataNode(IXamlAstValueNode value, IXamlAstTypeReference targetType,
ScopeTypes type)
: base(value, value)
{

26
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDesignPropertiesTransformer.cs

@ -1,12 +1,12 @@
using System.Collections.Generic;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlDesignPropertiesTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlDesignPropertiesTransformer : IXamlAstTransformer
{
public bool IsDesignMode { get; set; }
@ -17,14 +17,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
};
private const string AvaloniaNs = "https://github.com/avaloniaui";
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode on)
if (node is XamlAstObjectNode on)
{
for (var c=0; c<on.Children.Count;)
{
var ch = on.Children[c];
if (ch is XamlIlAstXmlDirective directive
if (ch is XamlAstXmlDirective directive
&& directive.Namespace == XamlNamespaces.Blend2008
&& DesignDirectives.TryGetValue(directive.Name, out var mapTo))
{
@ -34,9 +34,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
else
{
// Map to an actual property in `Design` class
on.Children[c] = new XamlIlAstXamlPropertyValueNode(ch,
new XamlIlAstNamePropertyReference(ch,
new XamlIlAstXmlTypeReference(ch, AvaloniaNs, "Design"),
on.Children[c] = new XamlAstXamlPropertyValueNode(ch,
new XamlAstNamePropertyReference(ch,
new XamlAstXmlTypeReference(ch, AvaloniaNs, "Design"),
mapTo, on.Type), directive.Values);
c++;
}
@ -44,9 +44,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
// Remove all "Design" attached properties in non-design mode
else if (
!IsDesignMode
&& ch is XamlIlAstXamlPropertyValueNode pv
&& pv.Property is XamlIlAstNamePropertyReference pref
&& pref.DeclaringType is XamlIlAstXmlTypeReference dref
&& ch is XamlAstXamlPropertyValueNode pv
&& pv.Property is XamlAstNamePropertyReference pref
&& pref.DeclaringType is XamlAstXmlTypeReference dref
&& dref.XmlNamespace == AvaloniaNs && dref.Name == "Design"
)
{

8
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs

@ -1,12 +1,12 @@
using System.Linq;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlMetadataRemover : IXamlIlAstTransformer
class AvaloniaXamlIlMetadataRemover : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is AvaloniaXamlIlTargetTypeMetadataNode md)
return md.Value;

118
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -3,41 +3,45 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Markup.Parsers;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlSelectorTransformer : IXamlIlAstTransformer
using XamlParseException = XamlX.XamlParseException;
using XamlLoadException = XamlX.XamlLoadException;
class AvaloniaXamlIlSelectorTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlIlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style"))
if (!(node is XamlAstObjectNode on && on.Type.GetClrType().FullName == "Avalonia.Styling.Style"))
return node;
var pn = on.Children.OfType<XamlIlAstXamlPropertyValueNode>()
var pn = on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (pn == null)
return node;
if (pn.Values.Count != 1)
throw new XamlIlParseException("Selector property should should have exactly one value", node);
throw new XamlParseException("Selector property should should have exactly one value", node);
if (pn.Values[0] is XamlIlSelectorNode)
//Deja vu. I've just been in this place before
return node;
if (!(pn.Values[0] is XamlIlAstTextNode tn))
throw new XamlIlParseException("Selector property should be a text node", node);
if (!(pn.Values[0] is XamlAstTextNode tn))
throw new XamlParseException("Selector property should be a text node", node);
var selectorType = pn.Property.GetClrProperty().Getter.ReturnType;
var initialNode = new XamlIlSelectorInitialNode(node, selectorType);
XamlIlSelectorNode Create(IEnumerable<SelectorGrammar.ISyntax> syntax,
Func<string, string, XamlIlAstClrTypeReference> typeResolver)
Func<string, string, XamlAstClrTypeReference> typeResolver)
{
XamlIlSelectorNode result = initialNode;
XamlIlOrSelectorNode results = null;
@ -63,18 +67,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var type = result?.TargetType;
if (type == null)
throw new XamlIlParseException("Property selectors must be applied to a type.", node);
throw new XamlParseException("Property selectors must be applied to a type.", node);
var targetProperty =
type.GetAllProperties().FirstOrDefault(p => p.Name == property.Property);
if (targetProperty == null)
throw new XamlIlParseException($"Cannot find '{property.Property}' on '{type}", node);
throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node);
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlIlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String),
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String),
targetProperty.PropertyType, out var typedValue))
throw new XamlIlParseException(
throw new XamlParseException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
node);
@ -100,7 +104,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
result = initialNode;
break;
default:
throw new XamlIlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
throw new XamlParseException($"Unsupported selector grammar '{i.GetType()}'.", node);
}
}
@ -119,15 +123,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
catch (Exception e)
{
throw new XamlIlParseException("Unable to parse selector: " + e.Message, node);
throw new XamlParseException("Unable to parse selector: " + e.Message, node);
}
var selector = Create(parsed, (p, n)
=> XamlIlTypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
=> TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
pn.Values[0] = selector;
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlIlAstClrTypeReference(selector, selector.TargetType, false),
new XamlAstClrTypeReference(selector, selector.TargetType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
}
@ -135,32 +139,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
abstract class XamlIlSelectorNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode
abstract class XamlIlSelectorNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
protected XamlIlSelectorNode Previous { get; }
public abstract IXamlIlType TargetType { get; }
public abstract IXamlType TargetType { get; }
public XamlIlSelectorNode(XamlIlSelectorNode previous,
IXamlIlLineInfo info = null,
IXamlIlType selectorType = null) : base(info ?? previous)
IXamlLineInfo info = null,
IXamlType selectorType = null) : base(info ?? previous)
{
Previous = previous;
Type = selectorType == null ? previous.Type : new XamlIlAstClrTypeReference(this, selectorType, false);
Type = selectorType == null ? previous.Type : new XamlAstClrTypeReference(this, selectorType, false);
}
public IXamlIlAstTypeReference Type { get; }
public IXamlAstTypeReference Type { get; }
public virtual XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public virtual XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (Previous != null)
context.Emit(Previous, codeGen, Type.GetClrType());
DoEmit(context, codeGen);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
protected abstract void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen);
protected abstract void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen);
protected void EmitCall(XamlIlEmitContext context, IXamlIlEmitter codeGen, Func<IXamlIlMethod, bool> method)
protected void EmitCall(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen, Func<IXamlMethod, bool> method)
{
var selectors = context.Configuration.TypeSystem.GetType("Avalonia.Styling.Selectors");
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 && method(m));
@ -170,27 +174,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
class XamlIlSelectorInitialNode : XamlIlSelectorNode
{
public XamlIlSelectorInitialNode(IXamlIlLineInfo info,
IXamlIlType selectorType) : base(null, info, selectorType)
public XamlIlSelectorInitialNode(IXamlLineInfo info,
IXamlType selectorType) : base(null, info, selectorType)
{
}
public override IXamlIlType TargetType => null;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen) => codeGen.Ldnull();
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen) => codeGen.Ldnull();
}
class XamlIlTypeSelector : XamlIlSelectorNode
{
public bool Concrete { get; }
public XamlIlTypeSelector(XamlIlSelectorNode previous, IXamlIlType type, bool concrete) : base(previous)
public XamlIlTypeSelector(XamlIlSelectorNode previous, IXamlType type, bool concrete) : base(previous)
{
TargetType = type;
Concrete = concrete;
}
public override IXamlIlType TargetType { get; }
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType { get; }
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var name = Concrete ? "OfType" : "Is";
codeGen.Ldtype(TargetType);
@ -217,8 +221,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
public override IXamlIlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
codeGen.Ldstr(String);
var name = _type.ToString();
@ -242,8 +246,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
_type = type;
}
public override IXamlIlType TargetType => null;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var name = _type.ToString();
EmitCall(context, codeGen,
@ -260,8 +264,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Argument = argument;
}
public override IXamlIlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
context.Emit(Argument, codeGen, Type.GetClrType());
EmitCall(context, codeGen,
@ -272,22 +276,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
class XamlIlPropertyEqualsSelector : XamlIlSelectorNode
{
public XamlIlPropertyEqualsSelector(XamlIlSelectorNode previous,
IXamlIlProperty property,
IXamlIlAstValueNode value)
IXamlProperty property,
IXamlAstValueNode value)
: base(previous)
{
Property = property;
Value = value;
}
public IXamlIlProperty Property { get; set; }
public IXamlIlAstValueNode Value { get; set; }
public IXamlProperty Property { get; set; }
public IXamlAstValueNode Value { get; set; }
public override IXamlIlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public override IXamlType TargetType => Previous?.TargetType;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
throw new XamlIlLoadException(
throw new XamlLoadException(
$"{Property.Name} of {(Property.Setter ?? Property.Getter).DeclaringType.GetFqn()} doesn't seem to be an AvaloniaProperty",
this);
context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object);
@ -302,7 +306,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
class XamlIlOrSelectorNode : XamlIlSelectorNode
{
List<XamlIlSelectorNode> _selectors = new List<XamlIlSelectorNode>();
public XamlIlOrSelectorNode(IXamlIlLineInfo info, IXamlIlType selectorType) : base(null, info, selectorType)
public XamlIlOrSelectorNode(IXamlLineInfo info, IXamlType selectorType) : base(null, info, selectorType)
{
}
@ -311,11 +315,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
_selectors.Add(node);
}
public override IXamlIlType TargetType
public override IXamlType TargetType
{
get
{
IXamlIlType result = null;
IXamlType result = null;
foreach (var selector in _selectors)
{
@ -340,10 +344,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
if (_selectors.Count == 0)
throw new XamlIlLoadException("Invalid selector count", this);
throw new XamlLoadException("Invalid selector count", this);
if (_selectors.Count == 1)
{
_selectors[0].Emit(context, codeGen);

68
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs

@ -1,64 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlSetterTransformer : IXamlIlAstTransformer
using XamlParseException = XamlX.XamlParseException;
using XamlLoadException = XamlX.XamlLoadException;
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (!(node is XamlIlAstObjectNode on
if (!(node is XamlAstObjectNode on
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node;
var parent = context.ParentNodes().OfType<XamlIlAstObjectNode>()
var parent = context.ParentNodes().OfType<XamlAstObjectNode>()
.FirstOrDefault(p => p.Type.GetClrType().FullName == "Avalonia.Styling.Style");
if (parent == null)
throw new XamlIlParseException(
throw new XamlParseException(
"Avalonia.Styling.Setter is only valid inside Avalonia.Styling.Style", node);
var selectorProperty = parent.Children.OfType<XamlIlAstXamlPropertyValueNode>()
var selectorProperty = parent.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector");
if (selectorProperty == null)
throw new XamlIlParseException(
throw new XamlParseException(
"Can not find parent Style Selector", node);
var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode;
if (selector?.TargetType == null)
throw new XamlIlParseException(
throw new XamlParseException(
"Can not resolve parent Style Selector type", node);
var property = @on.Children.OfType<XamlIlAstXamlPropertyValueNode>()
var property = @on.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property");
if (property == null)
throw new XamlIlParseException("Setter without a property is not valid", node);
throw new XamlParseException("Setter without a property is not valid", node);
var propertyName = property.Values.OfType<XamlIlAstTextNode>().FirstOrDefault()?.Text;
var propertyName = property.Values.OfType<XamlAstTextNode>().FirstOrDefault()?.Text;
if (propertyName == null)
throw new XamlIlParseException("Setter.Property must be a string", node);
throw new XamlParseException("Setter.Property must be a string", node);
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
new XamlIlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
property.Values = new List<IXamlIlAstValueNode>
new XamlAstClrTypeReference(selector, selector.TargetType, false), property.Values[0]);
property.Values = new List<IXamlAstValueNode>
{
avaloniaPropertyNode
};
var valueProperty = on.Children
.OfType<XamlIlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value");
if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlIlAstTextNode)
.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(p => p.Property.GetClrProperty().Name == "Value");
if (valueProperty?.Values?.Count == 1 && valueProperty.Values[0] is XamlAstTextNode)
{
var propType = avaloniaPropertyNode.AvaloniaPropertyType;
if (!XamlIlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, valueProperty.Values[0],
propType, out var converted))
throw new XamlIlParseException(
throw new XamlParseException(
$"Unable to convert property value to {propType.GetFqn()}",
valueProperty.Values[0]);
@ -69,9 +73,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
class SetterValueProperty : XamlIlAstClrProperty
class SetterValueProperty : XamlAstClrProperty
{
public SetterValueProperty(IXamlIlLineInfo line, IXamlIlType setterType, IXamlIlType targetType,
public SetterValueProperty(IXamlLineInfo line, IXamlType setterType, IXamlType targetType,
AvaloniaXamlIlWellKnownTypes types)
: base(line, "Value", setterType, null)
{
@ -82,21 +86,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType));
}
class XamlIlDirectCallPropertySetter : IXamlIlPropertySetter
class XamlIlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter<IXamlILEmitter>
{
private readonly IXamlIlMethod _method;
private readonly IXamlIlType _type;
public IXamlIlType TargetType { get; }
private readonly IXamlMethod _method;
private readonly IXamlType _type;
public IXamlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
public IReadOnlyList<IXamlIlType> Parameters { get; }
public void Emit(IXamlIlEmitter codegen)
public IReadOnlyList<IXamlType> Parameters { get; }
public void Emit(IXamlILEmitter codegen)
{
if (_type.IsValueType)
codegen.Box(_type);
codegen.EmitCall(_method, true);
}
public XamlIlDirectCallPropertySetter(IXamlIlMethod method, IXamlIlType type)
public XamlIlDirectCallPropertySetter(IXamlMethod method, IXamlType type)
{
_method = method;
_type = type;

78
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransformInstanceAttachedProperties.cs

@ -1,20 +1,22 @@
using System.Collections.Generic;
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlTransformInstanceAttachedProperties : IXamlIlAstTransformer
class AvaloniaXamlIlTransformInstanceAttachedProperties : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstNamePropertyReference prop
&& prop.TargetType is XamlIlAstClrTypeReference targetRef
&& prop.DeclaringType is XamlIlAstClrTypeReference declaringRef)
if (node is XamlAstNamePropertyReference prop
&& prop.TargetType is XamlAstClrTypeReference targetRef
&& prop.DeclaringType is XamlAstClrTypeReference declaringRef)
{
// Target and declared type aren't assignable but both inherit from AvaloniaObject
var avaloniaObject = context.Configuration.TypeSystem.FindType("Avalonia.AvaloniaObject");
@ -70,21 +72,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return node;
}
class AvaloniaAttachedInstanceProperty : XamlIlAstClrProperty, IXamlIlAvaloniaProperty
class AvaloniaAttachedInstanceProperty : XamlAstClrProperty, IXamlIlAvaloniaProperty
{
private readonly XamlIlTransformerConfiguration _config;
private readonly IXamlIlType _declaringType;
private readonly IXamlIlType _avaloniaPropertyType;
private readonly IXamlIlType _avaloniaObject;
private readonly IXamlIlField _field;
public AvaloniaAttachedInstanceProperty(XamlIlAstNamePropertyReference prop,
XamlIlTransformerConfiguration config,
IXamlIlType declaringType,
IXamlIlType type,
IXamlIlType avaloniaPropertyType,
IXamlIlType avaloniaObject,
IXamlIlField field) : base(prop, prop.Name,
private readonly TransformerConfiguration _config;
private readonly IXamlType _declaringType;
private readonly IXamlType _avaloniaPropertyType;
private readonly IXamlType _avaloniaObject;
private readonly IXamlField _field;
public AvaloniaAttachedInstanceProperty(XamlAstNamePropertyReference prop,
TransformerConfiguration config,
IXamlType declaringType,
IXamlType type,
IXamlType avaloniaPropertyType,
IXamlType avaloniaObject,
IXamlField field) : base(prop, prop.Name,
declaringType, null)
@ -104,11 +106,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Getter = new GetterMethod(this);
}
public IXamlIlType PropertyType { get; }
public IXamlType PropertyType { get; }
public IXamlIlField AvaloniaProperty => _field;
public IXamlField AvaloniaProperty => _field;
class SetterMethod : IXamlIlPropertySetter
class SetterMethod : IXamlPropertySetter, IXamlEmitablePropertySetter<IXamlILEmitter>
{
private readonly AvaloniaAttachedInstanceProperty _parent;
@ -118,10 +120,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Parameters = new[] {_parent._avaloniaObject, _parent.PropertyType};
}
public IXamlIlType TargetType => _parent.DeclaringType;
public IXamlType TargetType => _parent.DeclaringType;
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
public IReadOnlyList<IXamlIlType> Parameters { get; }
public void Emit(IXamlIlEmitter emitter)
public IReadOnlyList<IXamlType> Parameters { get; }
public void Emit(IXamlILEmitter emitter)
{
var so = _parent._config.WellKnownTypes.Object;
var method = _parent._avaloniaObject
@ -133,7 +135,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
&& m.Parameters[2].IsEnum
);
if (method == null)
throw new XamlIlTypeSystemException(
throw new XamlTypeSystemException(
"Unable to find SetValue(AvaloniaProperty, object, BindingPriority) on AvaloniaObject");
using (var loc = emitter.LocalsPool.GetLocal(_parent.PropertyType))
emitter
@ -150,7 +152,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
class GetterMethod : IXamlIlCustomEmitMethod
class GetterMethod : IXamlCustomEmitMethod<IXamlILEmitter>
{
public GetterMethod(AvaloniaAttachedInstanceProperty parent)
{
@ -163,16 +165,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public bool IsPublic => true;
public bool IsStatic => true;
public string Name { get; protected set; }
public IXamlIlType DeclaringType { get; }
public IXamlIlMethod MakeGenericMethod(IReadOnlyList<IXamlIlType> typeArguments)
public IXamlType DeclaringType { get; }
public IXamlMethod MakeGenericMethod(IReadOnlyList<IXamlType> typeArguments)
=> throw new System.NotSupportedException();
public bool Equals(IXamlIlMethod other) =>
public bool Equals(IXamlMethod other) =>
other is GetterMethod m && m.Name == Name && m.DeclaringType.Equals(DeclaringType);
public IXamlIlType ReturnType => Parent.PropertyType;
public IReadOnlyList<IXamlIlType> Parameters { get; }
public void EmitCall(IXamlIlEmitter emitter)
public IXamlType ReturnType => Parent.PropertyType;
public IReadOnlyList<IXamlType> Parameters { get; }
public void EmitCall(IXamlILEmitter emitter)
{
var method = Parent._avaloniaObject
.FindMethod(m => m.IsPublic && !m.IsStatic && m.Name == "GetValue"
@ -180,7 +182,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
m.Parameters.Count == 1
&& m.Parameters[0].Equals(Parent._avaloniaPropertyType));
if (method == null)
throw new XamlIlTypeSystemException(
throw new XamlTypeSystemException(
"Unable to find T GetValue<T>(AvaloniaProperty<T>) on AvaloniaObject");
emitter
.Ldsfld(Parent._field)

12
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlTransitionsTypeMetadataTransformer.cs

@ -1,17 +1,17 @@
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlTransitionsTypeMetadataTransformer : IXamlIlAstTransformer
class AvaloniaXamlIlTransitionsTypeMetadataTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode on)
if (node is XamlAstObjectNode on)
{
foreach (var ch in on.Children)
{
if (ch is XamlIlAstXamlPropertyValueNode pn
if (ch is XamlAstXamlPropertyValueNode pn
&& pn.Property.GetClrProperty().Getter?.ReturnType.Equals(context.GetAvaloniaTypes().Transitions) == true)
{
for (var c = 0; c < pn.Values.Count; c++)

96
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -1,52 +1,54 @@
using XamlIl.Transform;
using XamlIl.TypeSystem;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlWellKnownTypes
{
public IXamlIlType AvaloniaObject { get; }
public IXamlIlType IAvaloniaObject { get; }
public IXamlIlType BindingPriority { get; }
public IXamlIlType AvaloniaObjectExtensions { get; }
public IXamlIlType AvaloniaProperty { get; }
public IXamlIlType AvaloniaPropertyT { get; }
public IXamlIlType IBinding { get; }
public IXamlIlMethod AvaloniaObjectBindMethod { get; }
public IXamlIlMethod AvaloniaObjectSetValueMethod { get; }
public IXamlIlType IDisposable { get; }
public XamlIlTypeWellKnownTypes XamlIlTypes { get; }
public XamlIlLanguageTypeMappings XamlIlMappings { get; }
public IXamlIlType Transitions { get; }
public IXamlIlType AssignBindingAttribute { get; }
public IXamlIlType UnsetValueType { get; }
public IXamlIlType StyledElement { get; }
public IXamlIlType NameScope { get; }
public IXamlIlMethod NameScopeSetNameScope { get; }
public IXamlIlType INameScope { get; }
public IXamlIlMethod INameScopeRegister { get; }
public IXamlIlMethod INameScopeComplete { get; }
public IXamlType AvaloniaObject { get; }
public IXamlType IAvaloniaObject { get; }
public IXamlType BindingPriority { get; }
public IXamlType AvaloniaObjectExtensions { get; }
public IXamlType AvaloniaProperty { get; }
public IXamlType AvaloniaPropertyT { get; }
public IXamlType IBinding { get; }
public IXamlMethod AvaloniaObjectBindMethod { get; }
public IXamlMethod AvaloniaObjectSetValueMethod { get; }
public IXamlType IDisposable { get; }
public XamlTypeWellKnownTypes XamlIlTypes { get; }
public XamlLanguageTypeMappings XamlIlMappings { get; }
public IXamlType Transitions { get; }
public IXamlType AssignBindingAttribute { get; }
public IXamlType UnsetValueType { get; }
public IXamlType StyledElement { get; }
public IXamlType NameScope { get; }
public IXamlMethod NameScopeSetNameScope { get; }
public IXamlType INameScope { get; }
public IXamlMethod INameScopeRegister { get; }
public IXamlMethod INameScopeComplete { get; }
public AvaloniaXamlIlWellKnownTypes(XamlIlAstTransformationContext ctx)
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration config)
{
XamlIlTypes = ctx.Configuration.WellKnownTypes;
XamlIlMappings = ctx.Configuration.TypeMappings;
AvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObject");
IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject");
AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
AvaloniaProperty = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty");
AvaloniaPropertyT = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty`1");
BindingPriority = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.BindingPriority");
IBinding = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.IBinding");
IDisposable = ctx.Configuration.TypeSystem.GetType("System.IDisposable");
Transitions = ctx.Configuration.TypeSystem.GetType("Avalonia.Animation.Transitions");
AssignBindingAttribute = ctx.Configuration.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
XamlIlTypes = config.WellKnownTypes;
XamlIlMappings = config.TypeMappings;
AvaloniaObject = config.TypeSystem.GetType("Avalonia.AvaloniaObject");
IAvaloniaObject = config.TypeSystem.GetType("Avalonia.IAvaloniaObject");
AvaloniaObjectExtensions = config.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
AvaloniaProperty = config.TypeSystem.GetType("Avalonia.AvaloniaProperty");
AvaloniaPropertyT = config.TypeSystem.GetType("Avalonia.AvaloniaProperty`1");
BindingPriority = config.TypeSystem.GetType("Avalonia.Data.BindingPriority");
IBinding = config.TypeSystem.GetType("Avalonia.Data.IBinding");
IDisposable = config.TypeSystem.GetType("System.IDisposable");
Transitions = config.TypeSystem.GetType("Avalonia.Animation.Transitions");
AssignBindingAttribute = config.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject,
AvaloniaProperty,
IBinding, ctx.Configuration.WellKnownTypes.Object);
UnsetValueType = ctx.Configuration.TypeSystem.GetType("Avalonia.UnsetValueType");
StyledElement = ctx.Configuration.TypeSystem.GetType("Avalonia.StyledElement");
INameScope = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.INameScope");
IBinding, config.WellKnownTypes.Object);
UnsetValueType = config.TypeSystem.GetType("Avalonia.UnsetValueType");
StyledElement = config.TypeSystem.GetType("Avalonia.StyledElement");
INameScope = config.TypeSystem.GetType("Avalonia.Controls.INameScope");
INameScopeRegister = INameScope.GetMethod(
new FindMethodMethodSignature("Register", XamlIlTypes.Void,
XamlIlTypes.String, XamlIlTypes.Object)
@ -58,7 +60,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
IsStatic = false, DeclaringOnly = true, IsExactMatch = true
});
NameScope = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScope");
NameScope = config.TypeSystem.GetType("Avalonia.Controls.NameScope");
NameScopeSetNameScope = NameScope.GetMethod(new FindMethodMethodSignature("SetNameScope",
XamlIlTypes.Void, StyledElement, INameScope) {IsStatic = true});
@ -70,11 +72,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
static class AvaloniaXamlIlWellKnownTypesExtensions
{
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlIlAstTransformationContext ctx)
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this AstTransformationContext ctx)
{
if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))
return rv;
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx));
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
return rv;
}
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> ctx)
{
if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))
return rv;
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
return rv;
}
}

14
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/IgnoredDirectivesTransformer.cs

@ -1,17 +1,17 @@
using System.Linq;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class IgnoredDirectivesTransformer : IXamlIlAstTransformer
class IgnoredDirectivesTransformer : IXamlAstTransformer
{
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode no)
if (node is XamlAstObjectNode no)
{
foreach (var d in no.Children.OfType<XamlIlAstXmlDirective>().ToList())
foreach (var d in no.Children.OfType<XamlAstXmlDirective>().ToList())
{
if (d.Namespace == XamlNamespaces.Xaml2006)
{

19
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/XNameTransformer.cs

@ -1,30 +1,29 @@
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class XNameTransformer : IXamlIlAstTransformer
class XNameTransformer : IXamlAstTransformer
{
/// <summary>
/// Converts x:Name directives to regular Name assignments
/// </summary>
/// <returns></returns>
public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlIlAstObjectNode on)
if (node is XamlAstObjectNode on)
{
for (var c =0; c< on.Children.Count;c++)
{
var ch = on.Children[c];
if (ch is XamlIlAstXmlDirective d
if (ch is XamlAstXmlDirective d
&& d.Namespace == XamlNamespaces.Xaml2006
&& d.Name == "Name")
on.Children[c] = new XamlIlAstXamlPropertyValueNode(d,
new XamlIlAstNamePropertyReference(d, on.Type, "Name", on.Type),
on.Children[c] = new XamlAstXamlPropertyValueNode(d,
new XamlAstNamePropertyReference(d, on.Type, "Name", on.Type),
d.Values);
}
}

103
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs

@ -5,17 +5,22 @@ using System.Xml;
using Avalonia.Markup.Xaml.Parsers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Utilities;
using XamlIl;
using XamlIl.Ast;
using XamlIl.Transform;
using XamlIl.Transform.Transformers;
using XamlIl.TypeSystem;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
using XamlX.Emit;
using XamlX.IL;
using XamlIlEmitContext = XamlX.Emit.XamlEmitContext<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
using IXamlIlAstEmitableNode = XamlX.Emit.IXamlAstEmitableNode<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
class XamlIlAvaloniaPropertyHelper
{
public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, XamlIlAstClrProperty property)
public static bool Emit(XamlIlEmitContext context, IXamlILEmitter emitter, XamlAstClrProperty property)
{
if (property is IXamlIlAvaloniaProperty ap)
{
@ -32,7 +37,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return true;
}
public static bool Emit(XamlIlEmitContext context, IXamlIlEmitter emitter, IXamlIlProperty property)
public static bool Emit(XamlIlEmitContext context, IXamlILEmitter emitter, IXamlProperty property)
{
var type = (property.Getter ?? property.Setter).DeclaringType;
var name = property.Name + "Property";
@ -44,16 +49,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return true;
}
public static IXamlIlAvaloniaPropertyNode CreateNode(XamlIlAstTransformationContext context,
string propertyName, IXamlIlAstTypeReference selectorTypeReference, IXamlIlLineInfo lineInfo)
public static IXamlIlAvaloniaPropertyNode CreateNode(AstTransformationContext context,
string propertyName, IXamlAstTypeReference selectorTypeReference, IXamlLineInfo lineInfo)
{
XamlIlAstNamePropertyReference forgedReference;
XamlAstNamePropertyReference forgedReference;
var parser = new PropertyParser();
var parsedPropertyName = parser.Parse(new CharacterReader(propertyName.AsSpan()));
if(parsedPropertyName.owner == null)
forgedReference = new XamlIlAstNamePropertyReference(lineInfo, selectorTypeReference,
forgedReference = new XamlAstNamePropertyReference(lineInfo, selectorTypeReference,
propertyName, selectorTypeReference);
else
{
@ -62,19 +67,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
xmlOwner += ":";
xmlOwner += parsedPropertyName.owner;
var tref = XamlIlTypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true);
var tref = TypeReferenceResolver.ResolveType(context, xmlOwner, false, lineInfo, true);
var propertyFieldName = parsedPropertyName.name + "Property";
var found = tref.Type.GetAllFields()
.FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == propertyFieldName);
if (found == null)
throw new XamlIlParseException(
throw new XamlX.XamlParseException(
$"Unable to find {propertyFieldName} field on type {tref.Type.GetFullName()}", lineInfo);
return new XamlIlAvaloniaPropertyFieldNode(context.GetAvaloniaTypes(), lineInfo, found);
}
var clrProperty =
((XamlIlAstClrProperty)new XamlIlPropertyReferenceResolver().Transform(context,
((XamlAstClrProperty)new PropertyReferenceResolver().Transform(context,
forgedReference));
return new XamlIlAvaloniaPropertyNode(lineInfo,
context.Configuration.TypeSystem.GetType("Avalonia.AvaloniaProperty"),
@ -82,40 +87,40 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
interface IXamlIlAvaloniaPropertyNode : IXamlIlAstValueNode
interface IXamlIlAvaloniaPropertyNode : IXamlAstValueNode
{
IXamlIlType AvaloniaPropertyType { get; }
IXamlType AvaloniaPropertyType { get; }
}
class XamlIlAvaloniaPropertyNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
class XamlIlAvaloniaPropertyNode : XamlAstNode, IXamlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
{
public XamlIlAvaloniaPropertyNode(IXamlIlLineInfo lineInfo, IXamlIlType type, XamlIlAstClrProperty property) : base(lineInfo)
public XamlIlAvaloniaPropertyNode(IXamlLineInfo lineInfo, IXamlType type, XamlAstClrProperty property) : base(lineInfo)
{
Type = new XamlIlAstClrTypeReference(this, type, false);
Type = new XamlAstClrTypeReference(this, type, false);
Property = property;
AvaloniaPropertyType = Property.Getter?.ReturnType
?? Property.Setters.First().Parameters[0];
}
public XamlIlAstClrProperty Property { get; }
public XamlAstClrProperty Property { get; }
public IXamlIlAstTypeReference Type { get; }
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public IXamlAstTypeReference Type { get; }
public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
if (!XamlIlAvaloniaPropertyHelper.Emit(context, codeGen, Property))
throw new XamlIlLoadException(Property.Name + " is not an AvaloniaProperty", this);
return XamlIlNodeEmitResult.Type(0, Type.GetClrType());
throw new XamlX.XamlLoadException(Property.Name + " is not an AvaloniaProperty", this);
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
public IXamlIlType AvaloniaPropertyType { get; }
public IXamlType AvaloniaPropertyType { get; }
}
class XamlIlAvaloniaPropertyFieldNode : XamlIlAstNode, IXamlIlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
class XamlIlAvaloniaPropertyFieldNode : XamlAstNode, IXamlAstValueNode, IXamlIlAstEmitableNode, IXamlIlAvaloniaPropertyNode
{
private readonly IXamlIlField _field;
private readonly IXamlField _field;
public XamlIlAvaloniaPropertyFieldNode(AvaloniaXamlIlWellKnownTypes types,
IXamlIlLineInfo lineInfo, IXamlIlField field) : base(lineInfo)
IXamlLineInfo lineInfo, IXamlField field) : base(lineInfo)
{
_field = field;
var avaloniaPropertyType = field.FieldType;
@ -130,7 +135,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
avaloniaPropertyType = avaloniaPropertyType.BaseType;
}
throw new XamlIlParseException(
throw new XamlX.XamlParseException(
$"{field.Name}'s type {field.FieldType} doesn't inherit from AvaloniaProperty<T>, make sure to use typed properties",
lineInfo);
@ -138,25 +143,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public IXamlIlAstTypeReference Type => new XamlIlAstClrTypeReference(this, _field.FieldType, false);
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, _field.FieldType, false);
public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.Ldsfld(_field);
return XamlIlNodeEmitResult.Type(0, _field.FieldType);
return XamlILNodeEmitResult.Type(0, _field.FieldType);
}
public IXamlIlType AvaloniaPropertyType { get; }
public IXamlType AvaloniaPropertyType { get; }
}
interface IXamlIlAvaloniaProperty
{
IXamlIlField AvaloniaProperty { get; }
IXamlField AvaloniaProperty { get; }
}
class XamlIlAvaloniaProperty : XamlIlAstClrProperty, IXamlIlAvaloniaProperty
class XamlIlAvaloniaProperty : XamlAstClrProperty, IXamlIlAvaloniaProperty
{
public IXamlIlField AvaloniaProperty { get; }
public XamlIlAvaloniaProperty(XamlIlAstClrProperty original, IXamlIlField field,
public IXamlField AvaloniaProperty { get; }
public XamlIlAvaloniaProperty(XamlAstClrProperty original, IXamlField field,
AvaloniaXamlIlWellKnownTypes types)
:base(original, original.Name, original.DeclaringType, original.Getter, original.Setters)
{
@ -168,41 +173,41 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Setters.Insert(0, new UnsetValueSetter(types, original.DeclaringType, field));
}
abstract class AvaloniaPropertyCustomSetter : IXamlIlPropertySetter
abstract class AvaloniaPropertyCustomSetter : IXamlPropertySetter, IXamlEmitablePropertySetter<IXamlILEmitter>
{
protected AvaloniaXamlIlWellKnownTypes Types;
protected IXamlIlField AvaloniaProperty;
protected IXamlField AvaloniaProperty;
public AvaloniaPropertyCustomSetter(AvaloniaXamlIlWellKnownTypes types,
IXamlIlType declaringType,
IXamlIlField avaloniaProperty)
IXamlType declaringType,
IXamlField avaloniaProperty)
{
Types = types;
AvaloniaProperty = avaloniaProperty;
TargetType = declaringType;
}
public IXamlIlType TargetType { get; }
public IXamlType TargetType { get; }
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters
{
AllowXNull = false
};
public IReadOnlyList<IXamlIlType> Parameters { get; set; }
public abstract void Emit(IXamlIlEmitter codegen);
public IReadOnlyList<IXamlType> Parameters { get; set; }
public abstract void Emit(IXamlILEmitter codegen);
}
class BindingSetter : AvaloniaPropertyCustomSetter
{
public BindingSetter(AvaloniaXamlIlWellKnownTypes types,
IXamlIlType declaringType,
IXamlIlField avaloniaProperty) : base(types, declaringType, avaloniaProperty)
IXamlType declaringType,
IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty)
{
Parameters = new[] {types.IBinding};
}
public override void Emit(IXamlIlEmitter emitter)
public override void Emit(IXamlILEmitter emitter)
{
using (var bloc = emitter.LocalsPool.GetLocal(Types.IBinding))
emitter
@ -217,13 +222,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
class UnsetValueSetter : AvaloniaPropertyCustomSetter
{
public UnsetValueSetter(AvaloniaXamlIlWellKnownTypes types, IXamlIlType declaringType, IXamlIlField avaloniaProperty)
public UnsetValueSetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, IXamlField avaloniaProperty)
: base(types, declaringType, avaloniaProperty)
{
Parameters = new[] {types.UnsetValueType};
}
public override void Emit(IXamlIlEmitter codegen)
public override void Emit(IXamlILEmitter codegen)
{
var unsetValue = Types.AvaloniaProperty.Fields.First(f => f.Name == "UnsetValue");
codegen

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@ -1 +1 @@
Subproject commit 068162245473ec39ee36da12150e928072b96403
Subproject commit 0028377ce7c7dc21f9fe71b45f62a95991b1ab58

4
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@ -154,7 +154,7 @@ namespace Avalonia.Markup.Parsers
private static (State, ISyntax) ParseColon(ref CharacterReader r)
{
var identifier = r.ParseIdentifier();
var identifier = r.ParseStyleClass();
if (identifier.IsEmpty)
{
@ -214,7 +214,7 @@ namespace Avalonia.Markup.Parsers
private static (State, ISyntax) ParseClass(ref CharacterReader r)
{
var @class = r.ParseIdentifier();
var @class = r.ParseStyleClass();
if (@class.IsEmpty)
{
throw new ExpressionParseException(r.Position, $"Expected a class name after '.'.");

9
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -989,6 +989,12 @@ namespace Avalonia.Win32.Interop
}
}
[DllImport("user32.dll")]
public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll")]
public static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
@ -1317,6 +1323,9 @@ namespace Avalonia.Win32.Interop
[DllImport("dwmapi.dll")]
public static extern int DwmIsCompositionEnabled(out bool enabled);
[DllImport("dwmapi.dll")]
public static extern bool DwmDefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref IntPtr plResult);
[DllImport("dwmapi.dll")]
public static extern void DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind);

539
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -0,0 +1,539 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Win32.Input;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32
{
public partial class WindowImpl
{
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Using Win32 naming for consistency.")]
protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
const double wheelDelta = 120.0;
uint timestamp = unchecked((uint)GetMessageTime());
RawInputEventArgs e = null;
var shouldTakeFocus = false;
switch ((WindowsMessage)msg)
{
case WindowsMessage.WM_ACTIVATE:
{
var wa = (WindowActivate)(ToInt32(wParam) & 0xffff);
switch (wa)
{
case WindowActivate.WA_ACTIVE:
case WindowActivate.WA_CLICKACTIVE:
{
Activated?.Invoke();
break;
}
case WindowActivate.WA_INACTIVE:
{
Deactivated?.Invoke();
break;
}
}
return IntPtr.Zero;
}
case WindowsMessage.WM_NCCALCSIZE:
{
if (ToInt32(wParam) == 1 && !HasFullDecorations || _isClientAreaExtended)
{
return IntPtr.Zero;
}
break;
}
case WindowsMessage.WM_CLOSE:
{
bool? preventClosing = Closing?.Invoke();
if (preventClosing == true)
{
return IntPtr.Zero;
}
break;
}
case WindowsMessage.WM_DESTROY:
{
//Window doesn't exist anymore
_hwnd = IntPtr.Zero;
//Remove root reference to this class, so unmanaged delegate can be collected
s_instances.Remove(this);
Closed?.Invoke();
_mouseDevice.Dispose();
_touchDevice?.Dispose();
//Free other resources
Dispose();
return IntPtr.Zero;
}
case WindowsMessage.WM_DPICHANGED:
{
var dpi = ToInt32(wParam) & 0xffff;
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam);
_scaling = dpi / 96.0;
ScalingChanged?.Invoke(_scaling);
SetWindowPos(hWnd,
IntPtr.Zero,
newDisplayRect.left,
newDisplayRect.top,
newDisplayRect.right - newDisplayRect.left,
newDisplayRect.bottom - newDisplayRect.top,
SetWindowPosFlags.SWP_NOZORDER |
SetWindowPosFlags.SWP_NOACTIVATE);
return IntPtr.Zero;
}
case WindowsMessage.WM_KEYDOWN:
case WindowsMessage.WM_SYSKEYDOWN:
{
e = new RawKeyEventArgs(
WindowsKeyboardDevice.Instance,
timestamp,
_owner,
RawKeyEventType.KeyDown,
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)),
WindowsKeyboardDevice.Instance.Modifiers);
break;
}
case WindowsMessage.WM_MENUCHAR:
{
// mute the system beep
return (IntPtr)((int)MenuCharParam.MNC_CLOSE << 16);
}
case WindowsMessage.WM_KEYUP:
case WindowsMessage.WM_SYSKEYUP:
{
e = new RawKeyEventArgs(
WindowsKeyboardDevice.Instance,
timestamp,
_owner,
RawKeyEventType.KeyUp,
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)),
WindowsKeyboardDevice.Instance.Modifiers);
break;
}
case WindowsMessage.WM_CHAR:
{
// Ignore control chars
if (ToInt32(wParam) >= 32)
{
e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner,
new string((char)ToInt32(wParam), 1));
}
break;
}
case WindowsMessage.WM_LBUTTONDOWN:
case WindowsMessage.WM_RBUTTONDOWN:
case WindowsMessage.WM_MBUTTONDOWN:
case WindowsMessage.WM_XBUTTONDOWN:
{
shouldTakeFocus = ShouldTakeFocusOnClick;
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
}
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
(WindowsMessage)msg switch
{
WindowsMessage.WM_LBUTTONDOWN => RawPointerEventType.LeftButtonDown,
WindowsMessage.WM_RBUTTONDOWN => RawPointerEventType.RightButtonDown,
WindowsMessage.WM_MBUTTONDOWN => RawPointerEventType.MiddleButtonDown,
WindowsMessage.WM_XBUTTONDOWN =>
HighWord(ToInt32(wParam)) == 1 ?
RawPointerEventType.XButton1Down :
RawPointerEventType.XButton2Down
},
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_LBUTTONUP:
case WindowsMessage.WM_RBUTTONUP:
case WindowsMessage.WM_MBUTTONUP:
case WindowsMessage.WM_XBUTTONUP:
{
shouldTakeFocus = ShouldTakeFocusOnClick;
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
}
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
(WindowsMessage)msg switch
{
WindowsMessage.WM_LBUTTONUP => RawPointerEventType.LeftButtonUp,
WindowsMessage.WM_RBUTTONUP => RawPointerEventType.RightButtonUp,
WindowsMessage.WM_MBUTTONUP => RawPointerEventType.MiddleButtonUp,
WindowsMessage.WM_XBUTTONUP =>
HighWord(ToInt32(wParam)) == 1 ?
RawPointerEventType.XButton1Up :
RawPointerEventType.XButton2Up,
},
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_MOUSEMOVE:
{
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
}
if (!_trackingMouse)
{
var tm = new TRACKMOUSEEVENT
{
cbSize = Marshal.SizeOf<TRACKMOUSEEVENT>(),
dwFlags = 2,
hwndTrack = _hwnd,
dwHoverTime = 0,
};
TrackMouseEvent(ref tm);
}
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
RawPointerEventType.Move,
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_MOUSEWHEEL:
{
e = new RawMouseWheelEventArgs(
_mouseDevice,
timestamp,
_owner,
PointToClient(PointFromLParam(lParam)),
new Vector(0, (ToInt32(wParam) >> 16) / wheelDelta), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_MOUSEHWHEEL:
{
e = new RawMouseWheelEventArgs(
_mouseDevice,
timestamp,
_owner,
PointToClient(PointFromLParam(lParam)),
new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_MOUSELEAVE:
{
_trackingMouse = false;
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
RawPointerEventType.LeaveWindow,
new Point(-1, -1), WindowsKeyboardDevice.Instance.Modifiers);
break;
}
case WindowsMessage.WM_NCLBUTTONDOWN:
case WindowsMessage.WM_NCRBUTTONDOWN:
case WindowsMessage.WM_NCMBUTTONDOWN:
case WindowsMessage.WM_NCXBUTTONDOWN:
{
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
(WindowsMessage)msg switch
{
WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType
.NonClientLeftButtonDown,
WindowsMessage.WM_NCRBUTTONDOWN => RawPointerEventType.RightButtonDown,
WindowsMessage.WM_NCMBUTTONDOWN => RawPointerEventType.MiddleButtonDown,
WindowsMessage.WM_NCXBUTTONDOWN =>
HighWord(ToInt32(wParam)) == 1 ?
RawPointerEventType.XButton1Down :
RawPointerEventType.XButton2Down,
},
PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_TOUCH:
{
var touchInputCount = wParam.ToInt32();
var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount];
var touchInputs = new Span<TOUCHINPUT>(pTouchInputs, touchInputCount);
if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf<TOUCHINPUT>()))
{
foreach (var touchInput in touchInputs)
{
Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time,
_owner,
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ?
RawPointerEventType.TouchEnd :
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ?
RawPointerEventType.TouchBegin :
RawPointerEventType.TouchUpdate,
PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)),
WindowsKeyboardDevice.Instance.Modifiers,
touchInput.Id));
}
CloseTouchInputHandle(lParam);
return IntPtr.Zero;
}
break;
}
case WindowsMessage.WM_NCPAINT:
{
if (!HasFullDecorations)
{
return IntPtr.Zero;
}
break;
}
case WindowsMessage.WM_NCACTIVATE:
{
if (!HasFullDecorations)
{
return new IntPtr(1);
}
break;
}
case WindowsMessage.WM_PAINT:
{
using (_rendererLock.Lock())
{
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero)
{
var f = Scaling;
var r = ps.rcPaint;
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f,
(r.bottom - r.top) / f));
EndPaint(_hwnd, ref ps);
}
}
return IntPtr.Zero;
}
case WindowsMessage.WM_SIZE:
{
using (_rendererLock.Lock())
{
// Do nothing here, just block until the pending frame render is completed on the render thread
}
var size = (SizeCommand)wParam;
if (Resized != null &&
(size == SizeCommand.Restored ||
size == SizeCommand.Maximized))
{
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
Resized(clientSize / Scaling);
}
var windowState = size == SizeCommand.Maximized ?
WindowState.Maximized :
(size == SizeCommand.Minimized ? WindowState.Minimized : WindowState.Normal);
if (windowState != _lastWindowState)
{
_lastWindowState = windowState;
WindowStateChanged?.Invoke(windowState);
if (_isClientAreaExtended)
{
UpdateExtendMargins();
ExtendClientAreaToDecorationsChanged?.Invoke(true);
}
}
return IntPtr.Zero;
}
case WindowsMessage.WM_MOVE:
{
PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff),
(short)(ToInt32(lParam) >> 16)));
return IntPtr.Zero;
}
case WindowsMessage.WM_GETMINMAXINFO:
{
MINMAXINFO mmi = Marshal.PtrToStructure<MINMAXINFO>(lParam);
if (_minSize.Width > 0)
{
mmi.ptMinTrackSize.X =
(int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
}
if (_minSize.Height > 0)
{
mmi.ptMinTrackSize.Y =
(int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
}
if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0)
{
mmi.ptMaxTrackSize.X =
(int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
}
if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0)
{
mmi.ptMaxTrackSize.Y =
(int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
}
Marshal.StructureToPtr(mmi, lParam, true);
return IntPtr.Zero;
}
case WindowsMessage.WM_DISPLAYCHANGE:
{
(Screen as ScreenImpl)?.InvalidateScreensCache();
return IntPtr.Zero;
}
case WindowsMessage.WM_KILLFOCUS:
LostFocus?.Invoke();
break;
}
#if USE_MANAGED_DRAG
if (_managedDrag.PreprocessInputEvent(ref e))
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
#endif
if(shouldTakeFocus)
{
SetFocus(_hwnd);
}
if (e != null && Input != null)
{
Input(e);
if (e.Handled)
{
return IntPtr.Zero;
}
}
using (_rendererLock.Lock())
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
private static int ToInt32(IntPtr ptr)
{
if (IntPtr.Size == 4)
return ptr.ToInt32();
return (int)(ptr.ToInt64() & 0xffffffff);
}
private static int HighWord(int param) => param >> 16;
private Point DipFromLParam(IntPtr lParam)
{
return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling;
}
private PixelPoint PointFromLParam(IntPtr lParam)
{
return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16));
}
private bool ShouldIgnoreTouchEmulatedMessage()
{
if (!_multitouch)
{
return false;
}
// MI_WP_SIGNATURE
// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages
const long marker = 0xFF515700L;
var info = GetMessageExtraInfo().ToInt64();
return (info & marker) == marker;
}
private static RawInputModifiers GetMouseModifiers(IntPtr wParam)
{
var keys = (ModifierKeys)ToInt32(wParam);
var modifiers = WindowsKeyboardDevice.Instance.Modifiers;
if (keys.HasFlagCustom(ModifierKeys.MK_LBUTTON))
{
modifiers |= RawInputModifiers.LeftMouseButton;
}
if (keys.HasFlagCustom(ModifierKeys.MK_RBUTTON))
{
modifiers |= RawInputModifiers.RightMouseButton;
}
if (keys.HasFlagCustom(ModifierKeys.MK_MBUTTON))
{
modifiers |= RawInputModifiers.MiddleMouseButton;
}
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON1))
{
modifiers |= RawInputModifiers.XButton1MouseButton;
}
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON2))
{
modifiers |= RawInputModifiers.XButton2MouseButton;
}
return modifiers;
}
}
}

140
src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs

@ -0,0 +1,140 @@
using System;
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Input;
using static Avalonia.Win32.Interop.UnmanagedMethods;
#nullable enable
namespace Avalonia.Win32
{
public partial class WindowImpl
{
// Hit test the frame for resizing and moving.
HitTestValues HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam)
{
// Get the point coordinates for the hit test.
var ptMouse = PointFromLParam(lParam);
// Get the window rectangle.
GetWindowRect(hWnd, out var rcWindow);
// Get the frame rectangle, adjusted for the style without a caption.
RECT rcFrame = new RECT();
AdjustWindowRectEx(ref rcFrame, (uint)(WindowStyles.WS_OVERLAPPEDWINDOW & ~WindowStyles.WS_CAPTION), false, 0);
RECT border_thickness = new RECT();
if (GetStyle().HasFlag(WindowStyles.WS_THICKFRAME))
{
AdjustWindowRectEx(ref border_thickness, (uint)(GetStyle()), false, 0);
border_thickness.left *= -1;
border_thickness.top *= -1;
}
else if (GetStyle().HasFlag(WindowStyles.WS_BORDER))
{
border_thickness = new RECT { bottom = 1, left = 1, right = 1, top = 1 };
}
if (_extendTitleBarHint >= 0)
{
border_thickness.top = (int)(_extendedMargins.Top * Scaling);
}
// Determine if the hit test is for resizing. Default middle (1,1).
ushort uRow = 1;
ushort uCol = 1;
bool fOnResizeBorder = false;
// Determine if the point is at the top or bottom of the window.
if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + border_thickness.top)
{
fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top));
uRow = 0;
}
else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - border_thickness.bottom)
{
uRow = 2;
}
// Determine if the point is at the left or right of the window.
if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + border_thickness.left)
{
uCol = 0; // left side
}
else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - border_thickness.right)
{
uCol = 2; // right side
}
// Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
HitTestValues[][] hitTests = new[]
{
new []{ HitTestValues.HTTOPLEFT, fOnResizeBorder ? HitTestValues.HTTOP : HitTestValues.HTCAPTION, HitTestValues.HTTOPRIGHT },
new []{ HitTestValues.HTLEFT, HitTestValues.HTNOWHERE, HitTestValues.HTRIGHT },
new []{ HitTestValues.HTBOTTOMLEFT, HitTestValues.HTBOTTOM, HitTestValues.HTBOTTOMRIGHT },
};
return hitTests[uRow][uCol];
}
protected virtual IntPtr CustomCaptionProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool callDwp)
{
IntPtr lRet = IntPtr.Zero;
callDwp = !DwmDefWindowProc(hWnd, msg, wParam, lParam, ref lRet);
switch ((WindowsMessage)msg)
{
case WindowsMessage.WM_DWMCOMPOSITIONCHANGED:
// TODO handle composition changed.
break;
case WindowsMessage.WM_NCHITTEST:
if (lRet == IntPtr.Zero)
{
if(WindowState == WindowState.FullScreen)
{
return (IntPtr)HitTestValues.HTCLIENT;
}
var hittestResult = HitTestNCA(hWnd, wParam, lParam);
lRet = (IntPtr)hittestResult;
uint timestamp = unchecked((uint)GetMessageTime());
if (hittestResult == HitTestValues.HTCAPTION)
{
var position = PointToClient(PointFromLParam(lParam));
if (_owner is Window window)
{
var visual = window.Renderer.HitTestFirst(position, _owner as Window, x =>
{
if (x is IInputElement ie && !ie.IsHitTestVisible)
{
return false;
}
return true;
});
if (visual != null)
{
hittestResult = HitTestValues.HTCLIENT;
lRet = (IntPtr)hittestResult;
}
}
}
if (hittestResult != HitTestValues.HTNOWHERE)
{
callDwp = false;
}
}
break;
}
return lRet;
}
}
}

511
src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs

@ -15,519 +15,22 @@ namespace Avalonia.Win32
{
public partial class WindowImpl
{
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation",
Justification = "Using Win32 naming for consistency.")]
protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
const double wheelDelta = 120.0;
uint timestamp = unchecked((uint)GetMessageTime());
IntPtr lRet = IntPtr.Zero;
bool callDwp = true;
RawInputEventArgs e = null;
var shouldTakeFocus = false;
switch ((WindowsMessage)msg)
if (_isClientAreaExtended)
{
case WindowsMessage.WM_ACTIVATE:
{
var wa = (WindowActivate)(ToInt32(wParam) & 0xffff);
switch (wa)
{
case WindowActivate.WA_ACTIVE:
case WindowActivate.WA_CLICKACTIVE:
{
Activated?.Invoke();
break;
}
case WindowActivate.WA_INACTIVE:
{
Deactivated?.Invoke();
break;
}
}
return IntPtr.Zero;
}
case WindowsMessage.WM_NCCALCSIZE:
{
if (ToInt32(wParam) == 1 && !HasFullDecorations)
{
return IntPtr.Zero;
}
break;
}
case WindowsMessage.WM_CLOSE:
{
bool? preventClosing = Closing?.Invoke();
if (preventClosing == true)
{
return IntPtr.Zero;
}
break;
}
case WindowsMessage.WM_DESTROY:
{
//Window doesn't exist anymore
_hwnd = IntPtr.Zero;
//Remove root reference to this class, so unmanaged delegate can be collected
s_instances.Remove(this);
Closed?.Invoke();
_mouseDevice.Dispose();
_touchDevice?.Dispose();
//Free other resources
Dispose();
return IntPtr.Zero;
}
case WindowsMessage.WM_DPICHANGED:
{
var dpi = ToInt32(wParam) & 0xffff;
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam);
_scaling = dpi / 96.0;
ScalingChanged?.Invoke(_scaling);
SetWindowPos(hWnd,
IntPtr.Zero,
newDisplayRect.left,
newDisplayRect.top,
newDisplayRect.right - newDisplayRect.left,
newDisplayRect.bottom - newDisplayRect.top,
SetWindowPosFlags.SWP_NOZORDER |
SetWindowPosFlags.SWP_NOACTIVATE);
return IntPtr.Zero;
}
case WindowsMessage.WM_KEYDOWN:
case WindowsMessage.WM_SYSKEYDOWN:
{
e = new RawKeyEventArgs(
WindowsKeyboardDevice.Instance,
timestamp,
_owner,
RawKeyEventType.KeyDown,
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)),
WindowsKeyboardDevice.Instance.Modifiers);
break;
}
case WindowsMessage.WM_MENUCHAR:
{
// mute the system beep
return (IntPtr)((int)MenuCharParam.MNC_CLOSE << 16);
}
case WindowsMessage.WM_KEYUP:
case WindowsMessage.WM_SYSKEYUP:
{
e = new RawKeyEventArgs(
WindowsKeyboardDevice.Instance,
timestamp,
_owner,
RawKeyEventType.KeyUp,
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)),
WindowsKeyboardDevice.Instance.Modifiers);
break;
}
case WindowsMessage.WM_CHAR:
{
// Ignore control chars
if (ToInt32(wParam) >= 32)
{
e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner,
new string((char)ToInt32(wParam), 1));
}
break;
}
case WindowsMessage.WM_LBUTTONDOWN:
case WindowsMessage.WM_RBUTTONDOWN:
case WindowsMessage.WM_MBUTTONDOWN:
case WindowsMessage.WM_XBUTTONDOWN:
{
shouldTakeFocus = ShouldTakeFocusOnClick;
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
}
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
(WindowsMessage)msg switch
{
WindowsMessage.WM_LBUTTONDOWN => RawPointerEventType.LeftButtonDown,
WindowsMessage.WM_RBUTTONDOWN => RawPointerEventType.RightButtonDown,
WindowsMessage.WM_MBUTTONDOWN => RawPointerEventType.MiddleButtonDown,
WindowsMessage.WM_XBUTTONDOWN =>
HighWord(ToInt32(wParam)) == 1 ?
RawPointerEventType.XButton1Down :
RawPointerEventType.XButton2Down
},
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_LBUTTONUP:
case WindowsMessage.WM_RBUTTONUP:
case WindowsMessage.WM_MBUTTONUP:
case WindowsMessage.WM_XBUTTONUP:
{
shouldTakeFocus = ShouldTakeFocusOnClick;
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
}
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
(WindowsMessage)msg switch
{
WindowsMessage.WM_LBUTTONUP => RawPointerEventType.LeftButtonUp,
WindowsMessage.WM_RBUTTONUP => RawPointerEventType.RightButtonUp,
WindowsMessage.WM_MBUTTONUP => RawPointerEventType.MiddleButtonUp,
WindowsMessage.WM_XBUTTONUP =>
HighWord(ToInt32(wParam)) == 1 ?
RawPointerEventType.XButton1Up :
RawPointerEventType.XButton2Up,
},
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_MOUSEMOVE:
{
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
}
if (!_trackingMouse)
{
var tm = new TRACKMOUSEEVENT
{
cbSize = Marshal.SizeOf<TRACKMOUSEEVENT>(),
dwFlags = 2,
hwndTrack = _hwnd,
dwHoverTime = 0,
};
TrackMouseEvent(ref tm);
}
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
RawPointerEventType.Move,
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_MOUSEWHEEL:
{
e = new RawMouseWheelEventArgs(
_mouseDevice,
timestamp,
_owner,
PointToClient(PointFromLParam(lParam)),
new Vector(0, (ToInt32(wParam) >> 16) / wheelDelta), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_MOUSEHWHEEL:
{
e = new RawMouseWheelEventArgs(
_mouseDevice,
timestamp,
_owner,
PointToClient(PointFromLParam(lParam)),
new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_MOUSELEAVE:
{
_trackingMouse = false;
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
RawPointerEventType.LeaveWindow,
new Point(-1, -1), WindowsKeyboardDevice.Instance.Modifiers);
break;
}
case WindowsMessage.WM_NCLBUTTONDOWN:
case WindowsMessage.WM_NCRBUTTONDOWN:
case WindowsMessage.WM_NCMBUTTONDOWN:
case WindowsMessage.WM_NCXBUTTONDOWN:
{
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
(WindowsMessage)msg switch
{
WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType
.NonClientLeftButtonDown,
WindowsMessage.WM_NCRBUTTONDOWN => RawPointerEventType.RightButtonDown,
WindowsMessage.WM_NCMBUTTONDOWN => RawPointerEventType.MiddleButtonDown,
WindowsMessage.WM_NCXBUTTONDOWN =>
HighWord(ToInt32(wParam)) == 1 ?
RawPointerEventType.XButton1Down :
RawPointerEventType.XButton2Down,
},
PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam));
break;
}
case WindowsMessage.WM_TOUCH:
{
var touchInputCount = wParam.ToInt32();
var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount];
var touchInputs = new Span<TOUCHINPUT>(pTouchInputs, touchInputCount);
if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf<TOUCHINPUT>()))
{
foreach (var touchInput in touchInputs)
{
Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time,
_owner,
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ?
RawPointerEventType.TouchEnd :
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ?
RawPointerEventType.TouchBegin :
RawPointerEventType.TouchUpdate,
PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)),
WindowsKeyboardDevice.Instance.Modifiers,
touchInput.Id));
}
CloseTouchInputHandle(lParam);
return IntPtr.Zero;
}
break;
}
case WindowsMessage.WM_NCPAINT:
{
if (!HasFullDecorations)
{
return IntPtr.Zero;
}
break;
}
case WindowsMessage.WM_NCACTIVATE:
{
if (!HasFullDecorations)
{
return new IntPtr(1);
}
break;
}
case WindowsMessage.WM_PAINT:
{
using (_rendererLock.Lock())
{
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero)
{
var f = Scaling;
var r = ps.rcPaint;
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f,
(r.bottom - r.top) / f));
EndPaint(_hwnd, ref ps);
}
}
return IntPtr.Zero;
}
case WindowsMessage.WM_SIZE:
{
using (_rendererLock.Lock())
{
// Do nothing here, just block until the pending frame render is completed on the render thread
}
var size = (SizeCommand)wParam;
if (Resized != null &&
(size == SizeCommand.Restored ||
size == SizeCommand.Maximized))
{
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16);
Resized(clientSize / Scaling);
}
var windowState = size == SizeCommand.Maximized ?
WindowState.Maximized :
(size == SizeCommand.Minimized ? WindowState.Minimized : WindowState.Normal);
if (windowState != _lastWindowState)
{
_lastWindowState = windowState;
WindowStateChanged?.Invoke(windowState);
}
return IntPtr.Zero;
}
case WindowsMessage.WM_MOVE:
{
PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff),
(short)(ToInt32(lParam) >> 16)));
return IntPtr.Zero;
}
case WindowsMessage.WM_GETMINMAXINFO:
{
MINMAXINFO mmi = Marshal.PtrToStructure<MINMAXINFO>(lParam);
_maxTrackSize = mmi.ptMaxTrackSize;
if (_minSize.Width > 0)
{
mmi.ptMinTrackSize.X =
(int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
}
if (_minSize.Height > 0)
{
mmi.ptMinTrackSize.Y =
(int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
}
if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0)
{
mmi.ptMaxTrackSize.X =
(int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right);
}
if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0)
{
mmi.ptMaxTrackSize.Y =
(int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom);
}
Marshal.StructureToPtr(mmi, lParam, true);
return IntPtr.Zero;
}
case WindowsMessage.WM_DISPLAYCHANGE:
{
(Screen as ScreenImpl)?.InvalidateScreensCache();
return IntPtr.Zero;
}
case WindowsMessage.WM_KILLFOCUS:
LostFocus?.Invoke();
break;
}
#if USE_MANAGED_DRAG
if (_managedDrag.PreprocessInputEvent(ref e))
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
#endif
if (shouldTakeFocus)
SetFocus(_hwnd);
if (e != null && Input != null)
{
Input(e);
if (e.Handled)
{
return IntPtr.Zero;
}
}
using (_rendererLock.Lock())
{
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
private static int ToInt32(IntPtr ptr)
{
if (IntPtr.Size == 4)
return ptr.ToInt32();
return (int)(ptr.ToInt64() & 0xffffffff);
}
private static int HighWord(int param) => param >> 16;
private Point DipFromLParam(IntPtr lParam)
{
return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling;
}
private PixelPoint PointFromLParam(IntPtr lParam)
{
return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16));
}
private bool ShouldIgnoreTouchEmulatedMessage()
{
if (!_multitouch)
{
return false;
}
// MI_WP_SIGNATURE
// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages
const long marker = 0xFF515700L;
var info = GetMessageExtraInfo().ToInt64();
return (info & marker) == marker;
}
private static RawInputModifiers GetMouseModifiers(IntPtr wParam)
{
var keys = (ModifierKeys)ToInt32(wParam);
var modifiers = WindowsKeyboardDevice.Instance.Modifiers;
if (keys.HasFlagCustom(ModifierKeys.MK_LBUTTON))
{
modifiers |= RawInputModifiers.LeftMouseButton;
}
if (keys.HasFlagCustom(ModifierKeys.MK_RBUTTON))
{
modifiers |= RawInputModifiers.RightMouseButton;
}
if (keys.HasFlagCustom(ModifierKeys.MK_MBUTTON))
{
modifiers |= RawInputModifiers.MiddleMouseButton;
}
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON1))
{
modifiers |= RawInputModifiers.XButton1MouseButton;
lRet = CustomCaptionProc(hWnd, msg, wParam, lParam, ref callDwp);
}
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON2))
if (callDwp)
{
modifiers |= RawInputModifiers.XButton2MouseButton;
lRet = AppWndProc(hWnd, msg, wParam, lParam);
}
return modifiers;
return lRet;
}
public INativeControlHostImpl NativeControlHost => _nativeControlHost;

167
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -42,6 +42,10 @@ namespace Avalonia.Win32
private SavedWindowInfo _savedWindowInfo;
private bool _isFullScreenActive;
private bool _isClientAreaExtended;
private Thickness _extendedMargins;
private Thickness _offScreenMargin;
private double _extendTitleBarHint = -1;
#if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag;
@ -70,7 +74,8 @@ namespace Avalonia.Win32
private Size _minSize;
private Size _maxSize;
private POINT _maxTrackSize;
private WindowImpl _parent;
private WindowImpl _parent;
private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default;
public WindowImpl()
{
@ -183,6 +188,11 @@ namespace Avalonia.Win32
{
get
{
if(_isFullScreenActive)
{
return WindowState.FullScreen;
}
var placement = default(WINDOWPLACEMENT);
GetWindowPlacement(_hwnd, ref placement);
@ -668,6 +678,98 @@ namespace Avalonia.Win32
}
TaskBarList.MarkFullscreen(_hwnd, fullscreen);
ExtendClientArea();
}
private MARGINS UpdateExtendMargins()
{
RECT borderThickness = new RECT();
RECT borderCaptionThickness = new RECT();
AdjustWindowRectEx(ref borderCaptionThickness, (uint)(GetStyle()), false, 0);
AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle() & ~WindowStyles.WS_CAPTION), false, 0);
borderThickness.left *= -1;
borderThickness.top *= -1;
borderCaptionThickness.left *= -1;
borderCaptionThickness.top *= -1;
bool wantsTitleBar = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) || _extendTitleBarHint == -1;
if (!wantsTitleBar)
{
borderCaptionThickness.top = 1;
}
MARGINS margins = new MARGINS();
margins.cxLeftWidth = 1;
margins.cxRightWidth = 1;
margins.cyBottomHeight = 1;
if (_extendTitleBarHint != -1)
{
borderCaptionThickness.top = (int)(_extendTitleBarHint * Scaling);
}
margins.cyTopHeight = _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1;
if (WindowState == WindowState.Maximized)
{
_extendedMargins = new Thickness(0, (borderCaptionThickness.top - borderThickness.top) / Scaling, 0, 0);
_offScreenMargin = new Thickness(borderThickness.left / Scaling, borderThickness.top / Scaling, borderThickness.right / Scaling, borderThickness.bottom / Scaling);
}
else
{
_extendedMargins = new Thickness(0, (borderCaptionThickness.top) / Scaling, 0, 0);
_offScreenMargin = new Thickness();
}
return margins;
}
private void ExtendClientArea()
{
if (DwmIsCompositionEnabled(out bool compositionEnabled) < 0 || !compositionEnabled)
{
_isClientAreaExtended = false;
return;
}
GetWindowRect(_hwnd, out var rcClient);
// Inform the application of the frame change.
SetWindowPos(_hwnd,
IntPtr.Zero,
rcClient.left, rcClient.top,
rcClient.Width, rcClient.Height,
SetWindowPosFlags.SWP_FRAMECHANGED);
if (_isClientAreaExtended && WindowState != WindowState.FullScreen)
{
var margins = UpdateExtendMargins();
DwmExtendFrameIntoClientArea(_hwnd, ref margins);
}
else
{
var margins = new MARGINS();
DwmExtendFrameIntoClientArea(_hwnd, ref margins);
_offScreenMargin = new Thickness();
_extendedMargins = new Thickness();
}
if(!_isClientAreaExtended || (_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.SystemChrome) &&
!_extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome)))
{
EnableCloseButton(_hwnd);
}
else
{
DisableCloseButton(_hwnd);
}
ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended);
}
private void ShowWindow(WindowState state)
@ -818,9 +920,10 @@ namespace Avalonia.Win32
// Otherwise it will still show in the taskbar.
}
WindowStyles style;
if ((oldProperties.IsResizable != newProperties.IsResizable) || forceChanges)
{
var style = GetStyle();
style = GetStyle();
if (newProperties.IsResizable)
{
@ -841,7 +944,7 @@ namespace Avalonia.Win32
if ((oldProperties.Decorations != newProperties.Decorations) || forceChanges)
{
var style = GetStyle();
style = GetStyle();
const WindowStyles fullDecorationFlags = WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU;
@ -886,7 +989,26 @@ namespace Avalonia.Win32
SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE |
SetWindowPosFlags.SWP_FRAMECHANGED);
}
}
}
}
private const int MF_BYCOMMAND = 0x0;
private const int MF_BYPOSITION = 0x400;
private const int MF_REMOVE = 0x1000;
private const int MF_ENABLED = 0x0;
private const int MF_GRAYED = 0x1;
private const int MF_DISABLED = 0x2;
private const int SC_CLOSE = 0xF060;
void DisableCloseButton(IntPtr hwnd)
{
EnableMenuItem(GetSystemMenu(hwnd, false), SC_CLOSE,
MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
}
void EnableCloseButton(IntPtr hwnd)
{
EnableMenuItem(GetSystemMenu(hwnd, false), SC_CLOSE,
MF_BYCOMMAND | MF_ENABLED);
}
#if USE_MANAGED_DRAG
@ -912,6 +1034,43 @@ namespace Avalonia.Win32
IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle;
public void SetExtendClientAreaToDecorationsHint(bool hint)
{
_isClientAreaExtended = hint;
ExtendClientArea();
}
public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
{
_extendChromeHints = hints;
ExtendClientArea();
}
/// <inheritdoc/>
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
{
_extendTitleBarHint = titleBarHeight;
ExtendClientArea();
}
/// <inheritdoc/>
public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended;
/// <inheritdoc/>
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
/// <inheritdoc/>
public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasFlag(ExtendClientAreaChromeHints.PreferSystemChrome);
/// <inheritdoc/>
public Thickness ExtendedMargins => _extendedMargins;
/// <inheritdoc/>
public Thickness OffScreenMargin => _offScreenMargin;
private struct SavedWindowInfo
{
public WindowStyles Style { get; set; }

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

Loading…
Cancel
Save