Browse Source

Merge branch 'master' into misc-cleanup

pull/4182/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
ac7e6550d5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      Avalonia.sln
  2. 5
      native/Avalonia.Native/inc/avalonia-native.h
  3. 21
      native/Avalonia.Native/src/OSX/controlhost.mm
  4. 33
      native/Avalonia.Native/src/OSX/window.mm
  5. 4
      samples/ControlCatalog/MainView.xaml
  6. 123
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  7. 30
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
  8. 26
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  9. 21
      samples/ControlCatalog/Pages/SliderPage.xaml
  10. 97
      samples/ControlCatalog/Pages/SplitViewPage.xaml
  11. 21
      samples/ControlCatalog/Pages/SplitViewPage.xaml.cs
  12. 46
      samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs
  13. 9
      samples/interop/NativeEmbedSample/MainWindow.xaml
  14. 6
      src/Avalonia.Animation/Easing/Easing.cs
  15. 85
      src/Avalonia.Animation/Easing/SplineEasing.cs
  16. 35
      src/Avalonia.Animation/KeySpline.cs
  17. 25
      src/Avalonia.Animation/KeySplineTypeConverter.cs
  18. 1
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  19. 36
      src/Avalonia.Base/Data/Core/LogicalNotNode.cs
  20. 45
      src/Avalonia.Base/Utilities/StyleClassParser.cs
  21. 5
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  22. 412
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  23. 531
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  24. 19
      src/Avalonia.Controls/DateTimePickers/DatePickerSelectedValueChangedEventArgs.cs
  25. 566
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  26. 25
      src/Avalonia.Controls/DateTimePickers/PickerPresenterBase.cs
  27. 292
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  28. 262
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  29. 15
      src/Avalonia.Controls/DateTimePickers/TimePickerSelectedValueChangedEventArgs.cs
  30. 89
      src/Avalonia.Controls/NativeControlHost.cs
  31. 4
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  32. 164
      src/Avalonia.Controls/ProgressBar.cs
  33. 2
      src/Avalonia.Controls/SelectionModel.cs
  34. 66
      src/Avalonia.Controls/Slider.cs
  35. 487
      src/Avalonia.Controls/SplitView.cs
  36. 14
      src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs
  37. 12
      src/Avalonia.Controls/TickBar.cs
  38. 6
      src/Avalonia.Controls/TopLevel.cs
  39. 9
      src/Avalonia.Controls/Utils/IEnumerableUtils.cs
  40. 10
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  41. 29
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
  42. 23
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  43. 14
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  44. 78
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
  45. 10
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  46. 37
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  47. 22
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  48. 40
      src/Avalonia.Layout/LayoutManager.cs
  49. 9
      src/Avalonia.Native/NativeControlHostImpl.cs
  50. 12
      src/Avalonia.Native/PopupImpl.cs
  51. 2
      src/Avalonia.Native/WindowImplBase.cs
  52. 12
      src/Avalonia.Themes.Default/ProgressBar.xaml
  53. 9
      src/Avalonia.Themes.Default/Slider.xaml
  54. 43
      src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
  55. 45
      src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
  56. 72
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  57. 71
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  58. 338
      src/Avalonia.Themes.Fluent/DatePicker.xaml
  59. 3
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  60. 44
      src/Avalonia.Themes.Fluent/FocusAdorner.xaml
  61. 191
      src/Avalonia.Themes.Fluent/ProgressBar.xaml
  62. 1
      src/Avalonia.Themes.Fluent/Slider.xaml
  63. 219
      src/Avalonia.Themes.Fluent/SplitView.xaml
  64. 284
      src/Avalonia.Themes.Fluent/TimePicker.xaml
  65. 16
      src/Avalonia.Themes.Fluent/ToolTip.xaml
  66. BIN
      src/Avalonia.Visuals/Assets/GraphemeBreak.trie
  67. BIN
      src/Avalonia.Visuals/Assets/UnicodeData.trie
  68. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs
  69. 65
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
  70. 34
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs
  71. 4
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs
  72. 4
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  73. 178
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs
  74. 10
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs
  75. 15
      src/Avalonia.Visuals/Rect.cs
  76. 22
      src/Avalonia.X11/X11NativeControlHost.cs
  77. 4
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  78. 12
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
  79. 95
      tests/Avalonia.Animation.UnitTests/KeySplineTests.cs
  80. 255
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  81. 66
      tests/Avalonia.Controls.UnitTests/SplitViewTests.cs
  82. 173
      tests/Avalonia.Controls.UnitTests/TimePickerTests.cs
  83. 78
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs
  84. 54
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  85. 67
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt
  86. 61
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs
  87. 83
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
  88. 2
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs
  89. 85
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs
  90. 61
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs
  91. 25
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs
  92. 181
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs

8
Avalonia.sln

@ -201,9 +201,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject EndProject
@ -211,8 +211,8 @@ Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5 src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5 src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13

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

@ -278,6 +278,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
virtual HRESULT SetWindowState(AvnWindowState state) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0;
virtual HRESULT GetWindowState(AvnWindowState*ret) = 0; virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
virtual HRESULT TakeFocusFromChildren() = 0;
}; };
AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
@ -493,8 +494,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown
virtual void* GetParentHandle() = 0; virtual void* GetParentHandle() = 0;
virtual HRESULT InitializeWithChildHandle(void* child) = 0; virtual HRESULT InitializeWithChildHandle(void* child) = 0;
virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0; virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
virtual void MoveTo(float x, float y, float width, float height) = 0; virtual void ShowInBounds(float x, float y, float width, float height) = 0;
virtual void Hide() = 0; virtual void HideWithSize(float width, float height) = 0;
virtual void ReleaseChild() = 0; virtual void ReleaseChild() = 0;
}; };

21
native/Avalonia.Native/src/OSX/controlhost.mm

@ -97,7 +97,7 @@ public:
return S_OK; return S_OK;
}; };
virtual void MoveTo(float x, float y, float width, float height) override virtual void ShowInBounds(float x, float y, float width, float height) override
{ {
if(_child == nil) if(_child == nil)
return; return;
@ -106,7 +106,7 @@ public:
IAvnNativeControlHostTopLevelAttachment* slf = this; IAvnNativeControlHostTopLevelAttachment* slf = this;
slf->AddRef(); slf->AddRef();
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
slf->MoveTo(x, y, width, height); slf->ShowInBounds(x, y, width, height);
slf->Release(); slf->Release();
}); });
return; return;
@ -122,9 +122,24 @@ public:
[[_holder superview] setNeedsDisplay:true]; [[_holder superview] setNeedsDisplay:true];
} }
virtual void Hide() override virtual void HideWithSize(float width, float height) override
{ {
if(_child == nil)
return;
if(AvnInsidePotentialDeadlock::IsInside())
{
IAvnNativeControlHostTopLevelAttachment* slf = this;
slf->AddRef();
dispatch_async(dispatch_get_main_queue(), ^{
slf->HideWithSize(width, height);
slf->Release();
});
return;
}
NSRect frame = {0, 0, width, height};
[_holder setHidden: true]; [_holder setHidden: true];
[_child setFrame: frame];
} }
virtual void ReleaseChild() override virtual void ReleaseChild() override

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

@ -116,10 +116,15 @@ public:
{ {
SetPosition(lastPositionSet); SetPosition(lastPositionSet);
UpdateStyle(); UpdateStyle();
if(ShouldTakeFocusOnShow())
[Window makeKeyAndOrderFront:Window]; {
[NSApp activateIgnoringOtherApps:YES]; [Window makeKeyAndOrderFront:Window];
[NSApp activateIgnoringOtherApps:YES];
}
else
{
[Window orderFront: Window];
}
[Window setTitle:_lastTitle]; [Window setTitle:_lastTitle];
_shown = true; _shown = true;
@ -128,6 +133,11 @@ public:
} }
} }
virtual bool ShouldTakeFocusOnShow()
{
return true;
}
virtual HRESULT Hide () override virtual HRESULT Hide () override
{ {
@autoreleasepool @autoreleasepool
@ -774,6 +784,15 @@ private:
} }
} }
virtual HRESULT TakeFocusFromChildren () override
{
if(Window == nil)
return S_OK;
if([Window isKeyWindow])
[Window makeFirstResponder: View];
return S_OK;
}
void EnterFullScreenMode () void EnterFullScreenMode ()
{ {
_fullScreenActive = true; _fullScreenActive = true;
@ -1858,7 +1877,6 @@ private:
WindowEvents = events; WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel]; [Window setLevel:NSPopUpMenuWindowLevel];
} }
protected: protected:
virtual NSWindowStyleMask GetStyle() override virtual NSWindowStyleMask GetStyle() override
{ {
@ -1876,6 +1894,11 @@ protected:
return S_OK; return S_OK;
} }
} }
public:
virtual bool ShouldTakeFocusOnShow() override
{
return false;
}
}; };
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl) extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)

4
samples/ControlCatalog/MainView.xaml

@ -29,6 +29,9 @@
ScrollViewer.HorizontalScrollBarVisibility="Disabled"> ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:DataGridPage/> <pages:DataGridPage/>
</TabItem> </TabItem>
<TabItem Header="Date/Time Picker">
<pages:DateTimePickerPage/>
</TabItem>
<TabItem Header="CalendarDatePicker"> <TabItem Header="CalendarDatePicker">
<pages:CalendarDatePickerPage/></TabItem> <pages:CalendarDatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem> <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
@ -54,6 +57,7 @@
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem> <TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="ScrollViewer"><pages:ScrollViewerPage/></TabItem> <TabItem Header="ScrollViewer"><pages:ScrollViewerPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem> <TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="SplitView"><pages:SplitViewPage/></TabItem>
<TabItem Header="TabControl"><pages:TabControlPage/></TabItem> <TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
<TabItem Header="TabStrip"><pages:TabStripPage/></TabItem> <TabItem Header="TabStrip"><pages:TabStripPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem> <TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>

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

26
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@ -1,29 +1,19 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.ProgressBarPage">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ProgressBarPage">
<StackPanel Orientation="Vertical" Spacing="4"> <StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ProgressBar</TextBlock> <TextBlock Classes="h1">ProgressBar</TextBlock>
<TextBlock Classes="h2">A progress bar control</TextBlock> <TextBlock Classes="h2">A progress bar control</TextBlock>
<StackPanel> <StackPanel>
<CheckBox <CheckBox x:Name="showProgress" Margin="10,16,0,0" Content="Show Progress Text" />
x:Name="showProgress" <CheckBox x:Name="isIndeterminate" Margin="10,16,0,0" Content="Toggle Indeterminate" />
Margin="10,16,0,0" <StackPanel Orientation="Horizontal" Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16">
Content="Show Progress Text" />
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Spacing="16"> <StackPanel Spacing="16">
<ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" /> <ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" />
<ProgressBar IsIndeterminate="True"/>
</StackPanel> </StackPanel>
<ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" /> <ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" />
<ProgressBar Orientation="Vertical" IsIndeterminate="True" />
</StackPanel> </StackPanel>
<StackPanel Margin="16"> <StackPanel Margin="16">
<Slider Name="hprogress" Maximum="100" Value="40"/> <Slider Name="hprogress" Maximum="100" Value="40" />
<Slider Name="vprogress" Maximum="100" Value="60"/> <Slider Name="vprogress" Maximum="100" Value="60" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>

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> <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"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 16 0 0" Spacing="16">
<Slider Value="0" <StackPanel Orientation="Vertical"
Minimum="0" HorizontalAlignment="Center">
Maximum="100" <Slider Value="0"
TickFrequency="10" Minimum="0"
Width="300"/> 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" <Slider Value="0"
Minimum="0" Minimum="0"
Maximum="100" Maximum="100"

97
samples/ControlCatalog/Pages/SplitViewPage.xaml

@ -0,0 +1,97 @@
<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.SplitViewPage">
<Border>
<Grid ColumnDefinitions="*,225">
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="4" Margin="5">
<ToggleButton Name="PaneOpenButton"
Content="IsPaneOpen"
IsChecked="{Binding IsPaneOpen, ElementName=SplitView}" />
<ToggleButton Name="UseLightDismissOverlayModeButton"
Content="UseLightDismissOverlayMode"
IsChecked="{Binding UseLightDismissOverlayMode, ElementName=SplitView}" />
<ToggleSwitch OffContent="Left" OnContent="Right" Content="Placement" IsChecked="{Binding !IsLeft}" />
<TextBlock Text="DisplayMode" />
<ComboBox Name="DisplayModeSelector" Width="170" Margin="10" SelectedIndex="{Binding DisplayMode}">
<ComboBoxItem>Inline</ComboBoxItem>
<ComboBoxItem>CompactInline</ComboBoxItem>
<ComboBoxItem>Overlay</ComboBoxItem>
<ComboBoxItem>CompactOverlay</ComboBoxItem>
</ComboBox>
<TextBlock Text="PaneBackground" />
<ComboBox Name="PaneBackgroundSelector" SelectedIndex="0" Width="170" Margin="10">
<ComboBoxItem Tag="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}">SystemControlBackgroundChromeMediumLowBrush</ComboBoxItem>
<ComboBoxItem Tag="Red">Red</ComboBoxItem>
<ComboBoxItem Tag="Blue">Blue</ComboBoxItem>
<ComboBoxItem Tag="Green">Green</ComboBoxItem>
</ComboBox>
<TextBlock Text="{Binding Value, ElementName=OpenPaneLengthSlider, StringFormat='{}OpenPaneLength: {0}'}" />
<Slider Name="OpenPaneLengthSlider" Value="256" Minimum="128" Maximum="500"
Width="150" />
<TextBlock Text="{Binding Value, ElementName=CompactPaneLengthSlider, StringFormat='{}CompactPaneLength: {0}'}" />
<Slider Name="CompactPaneLengthSlider" Value="48" Minimum="24" Maximum="128"
Width="150" />
</StackPanel>
<Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
BorderThickness="1">
<!--{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}-->
<SplitView Name="SplitView"
PanePlacement="{Binding PanePlacement}"
PaneBackground="{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}"
OpenPaneLength="{Binding Value, ElementName=OpenPaneLengthSlider}"
CompactPaneLength="{Binding Value, ElementName=CompactPaneLengthSlider}"
DisplayMode="{Binding CurrentDisplayMode}">
<SplitView.Pane>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="PANE CONTENT" FontWeight="Bold" Name="PaneHeader" Margin="5,12,0,0" />
<ListBoxItem Grid.Row="1" VerticalAlignment="Top" Margin="0 10">
<StackPanel Orientation="Horizontal">
<!--Path glyph from materialdesignicons.com-->
<Border Width="48">
<Viewbox Width="24" Height="24" HorizontalAlignment="Left">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}" Data="M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z" />
</Canvas>
</Viewbox>
</Border>
<TextBlock Text="People" VerticalAlignment="Center" />
</StackPanel>
</ListBoxItem>
<TextBlock Grid.Row="2" Text="Item at bottom" Margin="60,12" />
</Grid>
</SplitView.Pane>
<Grid>
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" HorizontalAlignment="Right" TextAlignment="Left" Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
</Grid>
</SplitView>
</Border>
</Grid>
</Border>
</UserControl>

21
samples/ControlCatalog/Pages/SplitViewPage.xaml.cs

@ -0,0 +1,21 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
public class SplitViewPage : UserControl
{
public SplitViewPage()
{
this.InitializeComponent();
DataContext = new SplitViewPageViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

46
samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs

@ -0,0 +1,46 @@
using System;
using Avalonia.Controls;
using ReactiveUI;
namespace ControlCatalog.ViewModels
{
public class SplitViewPageViewModel : ReactiveObject
{
private bool _isLeft = true;
private int _displayMode = 3; //CompactOverlay
public bool IsLeft
{
get => _isLeft;
set
{
this.RaiseAndSetIfChanged(ref _isLeft, value);
this.RaisePropertyChanged(nameof(PanePlacement));
}
}
public int DisplayMode
{
get => _displayMode;
set
{
this.RaiseAndSetIfChanged(ref _displayMode, value);
this.RaisePropertyChanged(nameof(CurrentDisplayMode));
}
}
public SplitViewPanePlacement PanePlacement => _isLeft ? SplitViewPanePlacement.Left : SplitViewPanePlacement.Right;
public SplitViewDisplayMode CurrentDisplayMode
{
get
{
if (Enum.IsDefined(typeof(SplitViewDisplayMode), _displayMode))
{
return (SplitViewDisplayMode)_displayMode;
}
return SplitViewDisplayMode.CompactOverlay;
}
}
}
}

9
samples/interop/NativeEmbedSample/MainWindow.xaml

@ -20,7 +20,16 @@
<DockPanel DockPanel.Dock="Top"> <DockPanel DockPanel.Dock="Top">
<Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button> <Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
<Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button> <Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
<Border DockPanel.Dock="Right" Background="#c0c0c0">
<ToolTip.Tip>
<ToolTip>
<TextBlock>Text</TextBlock>
</ToolTip>
</ToolTip.Tip>
<TextBlock>Tooltip</TextBlock>
</Border>
<TextBox Text="Lorem ipsum dolor sit amet"/> <TextBox Text="Lorem ipsum dolor sit amet"/>
</DockPanel> </DockPanel>
<Grid ColumnDefinitions="*,5,*"> <Grid ColumnDefinitions="*,5,*">
<DockPanel> <DockPanel>

6
src/Avalonia.Animation/Easing/Easing.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization;
using System.Linq; using System.Linq;
namespace Avalonia.Animation.Easings namespace Avalonia.Animation.Easings
@ -25,6 +26,11 @@ namespace Avalonia.Animation.Easings
/// <returns>Returns the instance of the parsed type.</returns> /// <returns>Returns the instance of the parsed type.</returns>
public static Easing Parse(string e) public static Easing Parse(string e)
{ {
if (e.Contains(','))
{
return new SplineEasing(KeySpline.Parse(e, CultureInfo.InvariantCulture));
}
if (_easingTypes == null) if (_easingTypes == null)
{ {
_easingTypes = new Dictionary<string, Type>(); _easingTypes = new Dictionary<string, Type>();

85
src/Avalonia.Animation/Easing/SplineEasing.cs

@ -0,0 +1,85 @@
namespace Avalonia.Animation.Easings
{
/// <summary>
/// Eases a <see cref="double"/> value
/// using a user-defined cubic bezier curve.
/// Good for custom easing functions that doesn't quite
/// fit with the built-in ones.
/// </summary>
public class SplineEasing : Easing
{
/// <summary>
/// X coordinate of the first control point
/// </summary>
public double X1
{
get => _internalKeySpline.ControlPointX1;
set
{
_internalKeySpline.ControlPointX1 = value;
}
}
/// <summary>
/// Y coordinate of the first control point
/// </summary>
public double Y1
{
get => _internalKeySpline.ControlPointY1;
set
{
_internalKeySpline.ControlPointY1 = value;
}
}
/// <summary>
/// X coordinate of the second control point
/// </summary>
public double X2
{
get => _internalKeySpline.ControlPointX2;
set
{
_internalKeySpline.ControlPointX2 = value;
}
}
/// <summary>
/// Y coordinate of the second control point
/// </summary>
public double Y2
{
get => _internalKeySpline.ControlPointY2;
set
{
_internalKeySpline.ControlPointY2 = value;
}
}
private readonly KeySpline _internalKeySpline;
public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d)
{
_internalKeySpline = new KeySpline();
this.X1 = x1;
this.Y1 = y1;
this.X2 = x2;
this.Y1 = y2;
}
public SplineEasing(KeySpline keySpline)
{
_internalKeySpline = keySpline;
}
public SplineEasing()
{
_internalKeySpline = new KeySpline();
}
/// <inheritdoc/>
public override double Ease(double progress) =>
_internalKeySpline.GetSplineProgress(progress);
}
}

35
src/Avalonia.Animation/KeySpline.cs

@ -81,7 +81,10 @@ namespace Avalonia.Animation
/// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns> /// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns>
public static KeySpline Parse(string value, CultureInfo culture) public static KeySpline Parse(string value, CultureInfo culture)
{ {
using (var tokenizer = new StringTokenizer((string)value, CultureInfo.InvariantCulture, exceptionMessage: "Invalid KeySpline.")) if (culture is null)
culture = CultureInfo.InvariantCulture;
using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\"."))
{ {
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble()); return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
} }
@ -98,6 +101,7 @@ namespace Avalonia.Animation
if (IsValidXValue(value)) if (IsValidXValue(value))
{ {
_controlPointX1 = value; _controlPointX1 = value;
_isDirty = true;
} }
else else
{ {
@ -112,7 +116,11 @@ namespace Avalonia.Animation
public double ControlPointY1 public double ControlPointY1
{ {
get => _controlPointY1; get => _controlPointY1;
set => _controlPointY1 = value; set
{
_controlPointY1 = value;
_isDirty = true;
}
} }
/// <summary> /// <summary>
@ -126,6 +134,7 @@ namespace Avalonia.Animation
if (IsValidXValue(value)) if (IsValidXValue(value))
{ {
_controlPointX2 = value; _controlPointX2 = value;
_isDirty = true;
} }
else else
{ {
@ -140,7 +149,11 @@ namespace Avalonia.Animation
public double ControlPointY2 public double ControlPointY2
{ {
get => _controlPointY2; get => _controlPointY2;
set => _controlPointY2 = value; set
{
_controlPointY2 = value;
_isDirty = true;
}
} }
/// <summary> /// <summary>
@ -330,20 +343,4 @@ namespace Avalonia.Animation
} }
} }
} }
/// <summary>
/// Converts string values to <see cref="KeySpline"/> values
/// </summary>
public class KeySplineTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return KeySpline.Parse((string)value, culture);
}
}
} }

25
src/Avalonia.Animation/KeySplineTypeConverter.cs

@ -0,0 +1,25 @@
using System;
using System.ComponentModel;
using System.Globalization;
// Ported from WPF open-source code.
// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs
namespace Avalonia.Animation
{
/// <summary>
/// Converts string values to <see cref="KeySpline"/> values
/// </summary>
public class KeySplineTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return KeySpline.Parse((string)value, culture);
}
}
}

1
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@ -140,6 +140,7 @@ namespace Avalonia.Collections
} }
} }
[Obsolete("Causes memory leaks. Use DynamicData or similar instead.")]
public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>( public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
this IAvaloniaReadOnlyList<TSource> collection, this IAvaloniaReadOnlyList<TSource> collection,
Func<TSource, TDerived> select) Func<TSource, TDerived> select)

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

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

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

5
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -42,7 +42,10 @@
<Compile Include="../Avalonia.Base/Utilities/IdentifierParser.cs"> <Compile Include="../Avalonia.Base/Utilities/IdentifierParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </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\**\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\XamlIl\TypeSystem\SreTypeSystem.cs" />
<PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" /> <PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" />

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

89
src/Avalonia.Controls/NativeControlHost.cs

@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.LogicalTree;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -12,14 +14,18 @@ namespace Avalonia.Controls
private INativeControlHostControlTopLevelAttachment _attachment; private INativeControlHostControlTopLevelAttachment _attachment;
private IPlatformHandle _nativeControlHandle; private IPlatformHandle _nativeControlHandle;
private bool _queuedForDestruction; private bool _queuedForDestruction;
private bool _queuedForMoveResize;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
static NativeControlHost() static NativeControlHost()
{ {
IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged); IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
TransformedBoundsProperty.Changed.AddClassHandler<NativeControlHost>(OnBoundsChanged);
} }
private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) public NativeControlHost()
=> host.UpdateHost(); {
_propertyChangedHandler = PropertyChangedHandler;
}
private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
=> host.UpdateHost(); => host.UpdateHost();
@ -27,21 +33,46 @@ namespace Avalonia.Controls
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_currentRoot = e.Root as TopLevel; _currentRoot = e.Root as TopLevel;
var visual = (IVisual)this;
while (visual != _currentRoot)
{
if (visual is Visual v)
{
v.PropertyChanged += _propertyChangedHandler;
_propertyChangedSubscriptions.Add(v);
}
visual = visual.GetVisualParent();
}
UpdateHost(); UpdateHost();
} }
private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.IsEffectiveValueChange && e.Property == BoundsProperty)
EnqueueForMoveResize();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{ {
_currentRoot = null; _currentRoot = null;
if (_propertyChangedSubscriptions != null)
{
foreach (var v in _propertyChangedSubscriptions)
v.PropertyChanged -= _propertyChangedHandler;
_propertyChangedSubscriptions.Clear();
}
UpdateHost(); UpdateHost();
} }
void UpdateHost() private void UpdateHost()
{ {
_queuedForMoveResize = false;
_currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost; _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
var needsAttachment = _currentHost != null; var needsAttachment = _currentHost != null;
var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue;
if (needsAttachment) if (needsAttachment)
{ {
@ -93,22 +124,46 @@ namespace Avalonia.Controls
} }
} }
if (needsShow) if (_attachment?.AttachedTo != _currentHost)
_attachment?.ShowInBounds(TransformedBounds.Value); return;
else if (needsAttachment)
_attachment?.Hide(); TryUpdateNativeControlPosition();
}
private Rect? GetAbsoluteBounds()
{
var bounds = Bounds;
var position = this.TranslatePoint(bounds.Position, _currentRoot);
if (position == null)
return null;
return new Rect(position.Value, bounds.Size);
}
void EnqueueForMoveResize()
{
if(_queuedForMoveResize)
return;
_queuedForMoveResize = true;
Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render);
} }
public bool TryUpdateNativeControlPosition() public bool TryUpdateNativeControlPosition()
{ {
var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue; if (_currentHost == null)
return false;
var bounds = GetAbsoluteBounds();
var needsShow = IsEffectivelyVisible && bounds.HasValue;
if(needsShow) if (needsShow)
_attachment?.ShowInBounds(TransformedBounds.Value); _attachment?.ShowInBounds(bounds.Value);
return needsShow; else
_attachment?.HideWithSize(Bounds.Size);
return false;
} }
void CheckDestruction() private void CheckDestruction()
{ {
_queuedForDestruction = false; _queuedForDestruction = false;
if (_currentRoot == null) if (_currentRoot == null)
@ -117,10 +172,12 @@ namespace Avalonia.Controls
protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{ {
if (_currentHost == null)
throw new InvalidOperationException();
return _currentHost.CreateDefaultChild(parent); return _currentHost.CreateDefaultChild(parent);
} }
void DestroyNativeControl() private void DestroyNativeControl()
{ {
if (_nativeControlHandle != null) if (_nativeControlHandle != null)
{ {

4
src/Avalonia.Controls/Platform/INativeControlHostImpl.cs

@ -21,8 +21,8 @@ namespace Avalonia.Controls.Platform
{ {
INativeControlHostImpl AttachedTo { get; set; } INativeControlHostImpl AttachedTo { get; set; }
bool IsCompatibleWith(INativeControlHostImpl host); bool IsCompatibleWith(INativeControlHostImpl host);
void Hide(); void HideWithSize(Size size);
void ShowInBounds(TransformedBounds transformedBounds); void ShowInBounds(Rect rect);
} }
public interface ITopLevelImplWithNativeControlHost public interface ITopLevelImplWithNativeControlHost

164
src/Avalonia.Controls/ProgressBar.cs

@ -1,8 +1,7 @@
using System; using System;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -11,6 +10,92 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class ProgressBar : RangeBase public class ProgressBar : RangeBase
{ {
public class ProgressBarTemplateProperties : AvaloniaObject
{
private double _container2Width;
private double _containerWidth;
private double _containerAnimationStartPosition;
private double _containerAnimationEndPosition;
private double _container2AnimationStartPosition;
private double _container2AnimationEndPosition;
public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(ContainerAnimationStartPosition),
p => p.ContainerAnimationStartPosition,
(p, o) => p.ContainerAnimationStartPosition = o, 0d);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(ContainerAnimationEndPosition),
p => p.ContainerAnimationEndPosition,
(p, o) => p.ContainerAnimationEndPosition = o, 0d);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(Container2AnimationStartPosition),
p => p.Container2AnimationStartPosition,
(p, o) => p.Container2AnimationStartPosition = o, 0d);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(Container2AnimationEndPosition),
p => p.Container2AnimationEndPosition,
(p, o) => p.Container2AnimationEndPosition = o);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2WidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(Container2Width),
p => p.Container2Width,
(p, o) => p.Container2Width = o);
public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerWidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
nameof(ContainerWidth),
p => p.ContainerWidth,
(p, o) => p.ContainerWidth = o);
public double ContainerAnimationStartPosition
{
get => _containerAnimationStartPosition;
set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, value);
}
public double ContainerAnimationEndPosition
{
get => _containerAnimationEndPosition;
set => SetAndRaise(ContainerAnimationEndPositionProperty, ref _containerAnimationEndPosition, value);
}
public double Container2AnimationStartPosition
{
get => _container2AnimationStartPosition;
set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, value);
}
public double Container2Width
{
get => _container2Width;
set => SetAndRaise(Container2WidthProperty, ref _container2Width, value);
}
public double ContainerWidth
{
get => _containerWidth;
set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value);
}
public double Container2AnimationEndPosition
{
get => _container2AnimationEndPosition;
set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value);
}
}
private double _indeterminateStartingOffset;
private double _indeterminateEndingOffset;
private Border _indicator;
public static readonly StyledProperty<bool> IsIndeterminateProperty = public static readonly StyledProperty<bool> IsIndeterminateProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate)); AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
@ -20,19 +105,33 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Orientation> OrientationProperty = public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal); AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
private static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty = [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressBar, double>( AvaloniaProperty.RegisterDirect<ProgressBar, double>(
nameof(IndeterminateStartingOffset), nameof(IndeterminateStartingOffset),
p => p.IndeterminateStartingOffset, p => p.IndeterminateStartingOffset,
(p, o) => p.IndeterminateStartingOffset = o); (p, o) => p.IndeterminateStartingOffset = o);
private static readonly DirectProperty<ProgressBar, double> IndeterminateEndingOffsetProperty = [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public static readonly DirectProperty<ProgressBar, double> IndeterminateEndingOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressBar, double>( AvaloniaProperty.RegisterDirect<ProgressBar, double>(
nameof(IndeterminateEndingOffset), nameof(IndeterminateEndingOffset),
p => p.IndeterminateEndingOffset, p => p.IndeterminateEndingOffset,
(p, o) => p.IndeterminateEndingOffset = o); (p, o) => p.IndeterminateEndingOffset = o);
private Border _indicator; [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public double IndeterminateStartingOffset
{
get => _indeterminateStartingOffset;
set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
}
[Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public double IndeterminateEndingOffset
{
get => _indeterminateEndingOffset;
set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
}
static ProgressBar() static ProgressBar()
{ {
@ -45,6 +144,8 @@ namespace Avalonia.Controls
UpdatePseudoClasses(IsIndeterminate, Orientation); UpdatePseudoClasses(IsIndeterminate, Orientation);
} }
public ProgressBarTemplateProperties TemplateProperties { get; } = new ProgressBarTemplateProperties();
public bool IsIndeterminate public bool IsIndeterminate
{ {
get => GetValue(IsIndeterminateProperty); get => GetValue(IsIndeterminateProperty);
@ -62,19 +163,6 @@ namespace Avalonia.Controls
get => GetValue(OrientationProperty); get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value); set => SetValue(OrientationProperty, value);
} }
private double _indeterminateStartingOffset;
private double IndeterminateStartingOffset
{
get => _indeterminateStartingOffset;
set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
}
private double _indeterminateEndingOffset;
private double IndeterminateEndingOffset
{
get => _indeterminateEndingOffset;
set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
@ -111,21 +199,33 @@ namespace Avalonia.Controls
{ {
if (IsIndeterminate) if (IsIndeterminate)
{ {
if (Orientation == Orientation.Horizontal) // Pulled from ModernWPF.
{
var width = bounds.Width / 5.0;
IndeterminateStartingOffset = -width;
_indicator.Width = width;
IndeterminateEndingOffset = bounds.Width;
} var dim = Orientation == Orientation.Horizontal ? bounds.Width : bounds.Height;
else var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar
{ var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar
var height = bounds.Height / 5.0;
IndeterminateStartingOffset = -bounds.Height; TemplateProperties.ContainerWidth = barIndicatorWidth;
_indicator.Height = height; TemplateProperties.Container2Width = barIndicatorWidth2;
IndeterminateEndingOffset = height;
} TemplateProperties.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180%
TemplateProperties.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300%
TemplateProperties.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150%
TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
// Remove these properties when we switch to fluent as default and removed the old one.
IndeterminateStartingOffset = -(dim / 5d);
IndeterminateEndingOffset = dim;
var padding = Padding;
var rectangle = new RectangleGeometry(
new Rect(
padding.Left,
padding.Top,
bounds.Width - (padding.Right + padding.Left),
bounds.Height - (padding.Bottom + padding.Top)
));
} }
else else
{ {

2
src/Avalonia.Controls/SelectionModel.cs

@ -189,8 +189,6 @@ namespace Avalonia.Controls
} }
set set
{ {
var isSelected = IsSelectedWithPartialAt(value);
if (!IsSelectedAt(value) || SelectedItems.Count > 1) if (!IsSelectedAt(value) || SelectedItems.Count > 1)
{ {
using var operation = new Operation(this); using var operation = new Operation(this);

66
src/Avalonia.Controls/Slider.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Collections;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
@ -64,6 +65,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TickPlacement> TickPlacementProperty = public static readonly StyledProperty<TickPlacement> TickPlacementProperty =
AvaloniaProperty.Register<TickBar, TickPlacement>(nameof(TickPlacement), 0d); 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 // Slider required parts
private bool _isDragging = false; private bool _isDragging = false;
private Track _track; private Track _track;
@ -83,7 +90,8 @@ namespace Avalonia.Controls
PressedMixin.Attach<Slider>(); PressedMixin.Attach<Slider>();
OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); 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> /// <summary>
@ -94,6 +102,15 @@ namespace Avalonia.Controls
UpdatePseudoClasses(Orientation); 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> /// <summary>
/// Gets or sets the orientation of a <see cref="Slider"/>. /// Gets or sets the orientation of a <see cref="Slider"/>.
/// </summary> /// </summary>
@ -240,19 +257,50 @@ namespace Avalonia.Controls
/// <param name="value">Value that want to snap to closest Tick.</param> /// <param name="value">Value that want to snap to closest Tick.</param>
private double SnapToTick(double value) private double SnapToTick(double value)
{ {
var previous = Minimum; if (IsSnapToTickEnabled)
var next = Maximum;
if (TickFrequency > 0.0)
{ {
previous = Minimum + (Math.Round((value - Minimum) / TickFrequency) * TickFrequency); double previous = Minimum;
next = Math.Min(Maximum, previous + TickFrequency); 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 value;
return MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
} }
private void UpdatePseudoClasses(Orientation o) private void UpdatePseudoClasses(Orientation o)
{ {
PseudoClasses.Set(":vertical", o == Orientation.Vertical); PseudoClasses.Set(":vertical", o == Orientation.Vertical);

487
src/Avalonia.Controls/SplitView.cs

@ -0,0 +1,487 @@
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.VisualTree;
using System;
using System.Reactive.Disposables;
namespace Avalonia.Controls
{
/// <summary>
/// Defines constants for how the SplitView Pane should display
/// </summary>
public enum SplitViewDisplayMode
{
/// <summary>
/// Pane is displayed next to content, and does not auto collapse
/// when tapped outside
/// </summary>
Inline,
/// <summary>
/// Pane is displayed next to content. When collapsed, pane is still
/// visible according to CompactPaneLength. Pane does not auto collapse
/// when tapped outside
/// </summary>
CompactInline,
/// <summary>
/// Pane is displayed above content. Pane collapses when tapped outside
/// </summary>
Overlay,
/// <summary>
/// Pane is displayed above content. When collapsed, pane is still
/// visible according to CompactPaneLength. Pane collapses when tapped outside
/// </summary>
CompactOverlay
}
/// <summary>
/// Defines constants for where the Pane should appear
/// </summary>
public enum SplitViewPanePlacement
{
Left,
Right
}
public class SplitViewTemplateSettings : AvaloniaObject
{
internal SplitViewTemplateSettings() { }
public static readonly StyledProperty<double> ClosedPaneWidthProperty =
AvaloniaProperty.Register<SplitViewTemplateSettings, double>(nameof(ClosedPaneWidth), 0d);
public static readonly StyledProperty<GridLength> PaneColumnGridLengthProperty =
AvaloniaProperty.Register<SplitViewTemplateSettings, GridLength>(nameof(PaneColumnGridLength));
public double ClosedPaneWidth
{
get => GetValue(ClosedPaneWidthProperty);
internal set => SetValue(ClosedPaneWidthProperty, value);
}
public GridLength PaneColumnGridLength
{
get => GetValue(PaneColumnGridLengthProperty);
internal set => SetValue(PaneColumnGridLengthProperty, value);
}
}
/// <summary>
/// A control with two views: A collapsible pane and an area for content
/// </summary>
public class SplitView : TemplatedControl
{
/*
Pseudo classes & combos
:open / :closed
:compactoverlay :compactinline :overlay :inline
:left :right
*/
/// <summary>
/// Defines the <see cref="Content"/> property
/// </summary>
public static readonly StyledProperty<IControl> ContentProperty =
AvaloniaProperty.Register<SplitView, IControl>(nameof(Content));
/// <summary>
/// Defines the <see cref="CompactPaneLength"/> property
/// </summary>
public static readonly StyledProperty<double> CompactPaneLengthProperty =
AvaloniaProperty.Register<SplitView, double>(nameof(CompactPaneLength), defaultValue: 48);
/// <summary>
/// Defines the <see cref="DisplayMode"/> property
/// </summary>
public static readonly StyledProperty<SplitViewDisplayMode> DisplayModeProperty =
AvaloniaProperty.Register<SplitView, SplitViewDisplayMode>(nameof(DisplayMode), defaultValue: SplitViewDisplayMode.Overlay);
/// <summary>
/// Defines the <see cref="IsPaneOpen"/> property
/// </summary>
public static readonly DirectProperty<SplitView, bool> IsPaneOpenProperty =
AvaloniaProperty.RegisterDirect<SplitView, bool>(nameof(IsPaneOpen),
x => x.IsPaneOpen, (x, v) => x.IsPaneOpen = v);
/// <summary>
/// Defines the <see cref="OpenPaneLength"/> property
/// </summary>
public static readonly StyledProperty<double> OpenPaneLengthProperty =
AvaloniaProperty.Register<SplitView, double>(nameof(OpenPaneLength), defaultValue: 320);
/// <summary>
/// Defines the <see cref="PaneBackground"/> property
/// </summary>
public static readonly StyledProperty<IBrush> PaneBackgroundProperty =
AvaloniaProperty.Register<SplitView, IBrush>(nameof(PaneBackground));
/// <summary>
/// Defines the <see cref="PanePlacement"/> property
/// </summary>
public static readonly StyledProperty<SplitViewPanePlacement> PanePlacementProperty =
AvaloniaProperty.Register<SplitView, SplitViewPanePlacement>(nameof(PanePlacement));
/// <summary>
/// Defines the <see cref="Pane"/> property
/// </summary>
public static readonly StyledProperty<IControl> PaneProperty =
AvaloniaProperty.Register<SplitView, IControl>(nameof(Pane));
/// <summary>
/// Defines the <see cref="UseLightDismissOverlayMode"/> property
/// </summary>
public static readonly StyledProperty<bool> UseLightDismissOverlayModeProperty =
AvaloniaProperty.Register<SplitView, bool>(nameof(UseLightDismissOverlayMode));
/// <summary>
/// Defines the <see cref="TemplateSettings"/> property
/// </summary>
public static readonly StyledProperty<SplitViewTemplateSettings> TemplateSettingsProperty =
AvaloniaProperty.Register<SplitView, SplitViewTemplateSettings>(nameof(TemplateSettings));
private bool _isPaneOpen;
private Panel _pane;
private CompositeDisposable _pointerDisposables;
public SplitView()
{
PseudoClasses.Add(":overlay");
PseudoClasses.Add(":left");
TemplateSettings = new SplitViewTemplateSettings();
}
static SplitView()
{
UseLightDismissOverlayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnUseLightDismissChanged(v));
CompactPaneLengthProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnCompactPaneLengthChanged(v));
PanePlacementProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnPanePlacementChanged(v));
DisplayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnDisplayModeChanged(v));
}
/// <summary>
/// Gets or sets the content of the SplitView
/// </summary>
[Content]
public IControl Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
/// <summary>
/// Gets or sets the length of the pane when in <see cref="SplitViewDisplayMode.CompactOverlay"/>
/// or <see cref="SplitViewDisplayMode.CompactInline"/> mode
/// </summary>
public double CompactPaneLength
{
get => GetValue(CompactPaneLengthProperty);
set => SetValue(CompactPaneLengthProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="SplitViewDisplayMode"/> for the SplitView
/// </summary>
public SplitViewDisplayMode DisplayMode
{
get => GetValue(DisplayModeProperty);
set => SetValue(DisplayModeProperty, value);
}
/// <summary>
/// Gets or sets whether the pane is open or closed
/// </summary>
public bool IsPaneOpen
{
get => _isPaneOpen;
set
{
if (value == _isPaneOpen)
{
return;
}
if (value)
{
OnPaneOpening(this, null);
SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value);
PseudoClasses.Add(":open");
PseudoClasses.Remove(":closed");
OnPaneOpened(this, null);
}
else
{
SplitViewPaneClosingEventArgs args = new SplitViewPaneClosingEventArgs(false);
OnPaneClosing(this, args);
if (!args.Cancel)
{
SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value);
PseudoClasses.Add(":closed");
PseudoClasses.Remove(":open");
OnPaneClosed(this, null);
}
}
}
}
/// <summary>
/// Gets or sets the length of the pane when open
/// </summary>
public double OpenPaneLength
{
get => GetValue(OpenPaneLengthProperty);
set => SetValue(OpenPaneLengthProperty, value);
}
/// <summary>
/// Gets or sets the background of the pane
/// </summary>
public IBrush PaneBackground
{
get => GetValue(PaneBackgroundProperty);
set => SetValue(PaneBackgroundProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="SplitViewPanePlacement"/> for the SplitView
/// </summary>
public SplitViewPanePlacement PanePlacement
{
get => GetValue(PanePlacementProperty);
set => SetValue(PanePlacementProperty, value);
}
/// <summary>
/// Gets or sets the Pane for the SplitView
/// </summary>
public IControl Pane
{
get => GetValue(PaneProperty);
set => SetValue(PaneProperty, value);
}
/// <summary>
/// Gets or sets whether WinUI equivalent LightDismissOverlayMode is enabled
/// <para>When enabled, and the pane is open in Overlay or CompactOverlay mode,
/// the contents of the splitview are darkened to visually separate the open pane
/// and the rest of the SplitView</para>
/// </summary>
public bool UseLightDismissOverlayMode
{
get => GetValue(UseLightDismissOverlayModeProperty);
set => SetValue(UseLightDismissOverlayModeProperty, value);
}
/// <summary>
/// Gets or sets the TemplateSettings for the SplitView
/// </summary>
public SplitViewTemplateSettings TemplateSettings
{
get => GetValue(TemplateSettingsProperty);
set => SetValue(TemplateSettingsProperty, value);
}
/// <summary>
/// Fired when the pane is closed
/// </summary>
public event EventHandler<EventArgs> PaneClosed;
/// <summary>
/// Fired when the pane is closing
/// </summary>
public event EventHandler<SplitViewPaneClosingEventArgs> PaneClosing;
/// <summary>
/// Fired when the pane is opened
/// </summary>
public event EventHandler<EventArgs> PaneOpened;
/// <summary>
/// Fired when the pane is opening
/// </summary>
public event EventHandler<EventArgs> PaneOpening;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_pane = e.NameScope.Find<Panel>("PART_PaneRoot");
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
var topLevel = this.VisualRoot;
if (topLevel is Window window)
{
//Logic adapted from Popup
//Basically if we're using an overlay DisplayMode, close the pane if we don't click on the pane
IDisposable subscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler,
Action<T, TEventHandler> subscribe, Action<T, TEventHandler> unsubscribe)
{
subscribe(target, handler);
return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler));
}
_pointerDisposables = new CompositeDisposable(
window.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel),
InputManager.Instance?.Process.Subscribe(OnNonClientClick),
subscribeToEventHandler<Window, EventHandler>(window, Window_Deactivated,
(x, handler) => x.Deactivated += handler, (x, handler) => x.Deactivated -= handler),
subscribeToEventHandler<IWindowImpl, Action>(window.PlatformImpl, OnWindowLostFocus,
(x, handler) => x.LostFocus += handler, (x, handler) => x.LostFocus -= handler));
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_pointerDisposables?.Dispose();
}
private void OnWindowLostFocus()
{
if (IsPaneOpen && ShouldClosePane())
{
IsPaneOpen = false;
}
}
private void PointerPressedOutside(object sender, PointerPressedEventArgs e)
{
if (!IsPaneOpen)
{
return;
}
//If we click within the Pane, don't do anything
//Otherwise, ClosePane if open & using an overlay display mode
bool closePane = ShouldClosePane();
if (!closePane)
{
return;
}
var src = e.Source as IVisual;
while (src != null)
{
if (src == _pane)
{
closePane = false;
break;
}
src = src.VisualParent;
}
if (closePane)
{
IsPaneOpen = false;
e.Handled = true;
}
}
private void OnNonClientClick(RawInputEventArgs obj)
{
if (!IsPaneOpen)
{
return;
}
var mouse = obj as RawPointerEventArgs;
if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
{
if (ShouldClosePane())
IsPaneOpen = false;
}
}
private void Window_Deactivated(object sender, EventArgs e)
{
if (IsPaneOpen && ShouldClosePane())
{
IsPaneOpen = false;
}
}
private bool ShouldClosePane()
{
return (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay);
}
protected virtual void OnPaneOpening(SplitView sender, EventArgs args)
{
PaneOpening?.Invoke(sender, args);
}
protected virtual void OnPaneOpened(SplitView sender, EventArgs args)
{
PaneOpened?.Invoke(sender, args);
}
protected virtual void OnPaneClosing(SplitView sender, SplitViewPaneClosingEventArgs args)
{
PaneClosing?.Invoke(sender, args);
}
protected virtual void OnPaneClosed(SplitView sender, EventArgs args)
{
PaneClosed?.Invoke(sender, args);
}
private void OnCompactPaneLengthChanged(AvaloniaPropertyChangedEventArgs e)
{
var newLen = (double)e.NewValue;
var displayMode = DisplayMode;
if (displayMode == SplitViewDisplayMode.CompactInline)
{
TemplateSettings.ClosedPaneWidth = newLen;
}
else if (displayMode == SplitViewDisplayMode.CompactOverlay)
{
TemplateSettings.ClosedPaneWidth = newLen;
TemplateSettings.PaneColumnGridLength = new GridLength(newLen, GridUnitType.Pixel);
}
}
private void OnPanePlacementChanged(AvaloniaPropertyChangedEventArgs e)
{
var oldState = e.OldValue.ToString().ToLower();
var newState = e.NewValue.ToString().ToLower();
PseudoClasses.Remove($":{oldState}");
PseudoClasses.Add($":{newState}");
}
private void OnDisplayModeChanged(AvaloniaPropertyChangedEventArgs e)
{
var oldState = e.OldValue.ToString().ToLower();
var newState = e.NewValue.ToString().ToLower();
PseudoClasses.Remove($":{oldState}");
PseudoClasses.Add($":{newState}");
var (closedPaneWidth, paneColumnGridLength) = (SplitViewDisplayMode)e.NewValue switch
{
SplitViewDisplayMode.Overlay => (0, new GridLength(0, GridUnitType.Pixel)),
SplitViewDisplayMode.CompactOverlay => (CompactPaneLength, new GridLength(CompactPaneLength, GridUnitType.Pixel)),
SplitViewDisplayMode.Inline => (0, new GridLength(0, GridUnitType.Auto)),
SplitViewDisplayMode.CompactInline => (CompactPaneLength, new GridLength(0, GridUnitType.Auto)),
_ => throw new NotImplementedException(),
};
TemplateSettings.ClosedPaneWidth = closedPaneWidth;
TemplateSettings.PaneColumnGridLength = paneColumnGridLength;
}
private void OnUseLightDismissChanged(AvaloniaPropertyChangedEventArgs e)
{
var mode = (bool)e.NewValue;
PseudoClasses.Set(":lightdismiss", mode);
}
}
}

14
src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs

@ -0,0 +1,14 @@
using System;
namespace Avalonia.Controls
{
public class SplitViewPaneClosingEventArgs : EventArgs
{
public bool Cancel { get; set; }
public SplitViewPaneClosingEventArgs(bool cancel)
{
Cancel = cancel;
}
}
}

12
src/Avalonia.Controls/TickBar.cs

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

6
src/Avalonia.Controls/TopLevel.cs

@ -340,6 +340,9 @@ namespace Avalonia.Controls
_globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved; _globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved;
} }
Renderer?.Dispose();
Renderer = null;
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
@ -349,8 +352,7 @@ namespace Avalonia.Controls
(this as IInputRoot).MouseDevice?.TopLevelClosed(this); (this as IInputRoot).MouseDevice?.TopLevelClosed(this);
PlatformImpl = null; PlatformImpl = null;
OnClosed(EventArgs.Empty); OnClosed(EventArgs.Empty);
Renderer?.Dispose();
Renderer = null;
LayoutManager?.Dispose(); LayoutManager?.Dispose();
} }

9
src/Avalonia.Controls/Utils/IEnumerableUtils.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Avalonia.Controls.Utils namespace Avalonia.Controls.Utils
@ -15,12 +16,14 @@ namespace Avalonia.Controls.Utils
{ {
if (items != null) if (items != null)
{ {
var collection = items as ICollection; if (items is ICollection collection)
if (collection != null)
{ {
return collection.Count; return collection.Count;
} }
else if (items is IReadOnlyCollection<object> readOnly)
{
return readOnly.Count;
}
else else
{ {
return Enumerable.Count(items.Cast<object>()); return Enumerable.Count(items.Cast<object>());

10
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -45,7 +45,15 @@ namespace Avalonia.Diagnostics
window.Closed += DevToolsClosed; window.Closed += DevToolsClosed;
s_open.Add(root, window); s_open.Add(root, window);
window.Show();
if (root is Window inspectedWindow)
{
window.Show(inspectedWindow);
}
else
{
window.Show();
}
} }
return Disposable.Create(() => window?.Close()); return Disposable.Create(() => window?.Close());

29
src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
@ -9,7 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
public LogicalTreeNode(ILogical logical, TreeNode parent) public LogicalTreeNode(ILogical logical, TreeNode parent)
: base((Control)logical, parent) : base((Control)logical, parent)
{ {
Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this)); Children = new LogicalTreeNodeCollection(this, logical);
} }
public static LogicalTreeNode[] Create(object control) public static LogicalTreeNode[] Create(object control)
@ -17,5 +18,31 @@ namespace Avalonia.Diagnostics.ViewModels
var logical = control as ILogical; var logical = control as ILogical;
return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null; return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null;
} }
internal class LogicalTreeNodeCollection : TreeNodeCollection
{
private readonly ILogical _control;
private IDisposable _subscription;
public LogicalTreeNodeCollection(TreeNode owner, ILogical control)
: base(owner)
{
_control = control;
}
public override void Dispose()
{
base.Dispose();
_subscription?.Dispose();
}
protected override void Initialize(AvaloniaList<TreeNode> nodes)
{
_subscription = _control.LogicalChildren.ForEachItem(
(i, item) => nodes.Insert(i, new LogicalTreeNode(item, Owner)),
(i, item) => nodes.RemoveAt(i),
() => nodes.Clear());
}
}
} }
} }

23
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Diagnostics.Models; using Avalonia.Diagnostics.Models;
using Avalonia.Input; using Avalonia.Input;
@ -12,6 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
private readonly TreePageViewModel _logicalTree; private readonly TreePageViewModel _logicalTree;
private readonly TreePageViewModel _visualTree; private readonly TreePageViewModel _visualTree;
private readonly EventsPageViewModel _events; private readonly EventsPageViewModel _events;
private readonly IDisposable _pointerOverSubscription;
private ViewModelBase _content; private ViewModelBase _content;
private int _selectedTab; private int _selectedTab;
private string _focusedControl; private string _focusedControl;
@ -25,16 +27,9 @@ namespace Avalonia.Diagnostics.ViewModels
_events = new EventsPageViewModel(root); _events = new EventsPageViewModel(root);
UpdateFocusedControl(); UpdateFocusedControl();
KeyboardDevice.Instance.PropertyChanged += (s, e) => KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged;
{
if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
{
UpdateFocusedControl();
}
};
SelectedTab = 0; SelectedTab = 0;
root.GetObservable(TopLevel.PointerOverElementProperty) _pointerOverSubscription = root.GetObservable(TopLevel.PointerOverElementProperty)
.Subscribe(x => PointerOverElement = x?.GetType().Name); .Subscribe(x => PointerOverElement = x?.GetType().Name);
Console = new ConsoleViewModel(UpdateConsoleContext); Console = new ConsoleViewModel(UpdateConsoleContext);
} }
@ -129,6 +124,8 @@ namespace Avalonia.Diagnostics.ViewModels
public void Dispose() public void Dispose()
{ {
KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged;
_pointerOverSubscription.Dispose();
_logicalTree.Dispose(); _logicalTree.Dispose();
_visualTree.Dispose(); _visualTree.Dispose();
} }
@ -137,5 +134,13 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name; FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
} }
private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
{
UpdateFocusedControl();
}
}
} }
} }

14
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@ -3,15 +3,15 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class TreeNode : ViewModelBase internal class TreeNode : ViewModelBase, IDisposable
{ {
private IDisposable _classesSubscription;
private string _classes; private string _classes;
private bool _isExpanded; private bool _isExpanded;
@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels
x => control.Classes.CollectionChanged -= x) x => control.Classes.CollectionChanged -= x)
.TakeUntil(removed); .TakeUntil(removed);
classesChanged.Select(_ => Unit.Default) _classesSubscription = classesChanged.Select(_ => Unit.Default)
.StartWith(Unit.Default) .StartWith(Unit.Default)
.Subscribe(_ => .Subscribe(_ =>
{ {
@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public IAvaloniaReadOnlyList<TreeNode> Children public TreeNodeCollection Children
{ {
get; get;
protected set; protected set;
@ -104,6 +104,12 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public void Dispose()
{
_classesSubscription.Dispose();
Children.Dispose();
}
private static int IndexOf(IReadOnlyList<TreeNode> collection, TreeNode item) private static int IndexOf(IReadOnlyList<TreeNode> collection, TreeNode item)
{ {
var count = collection.Count; var count = collection.Count;

78
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs

@ -0,0 +1,78 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using Avalonia.Collections;
namespace Avalonia.Diagnostics.ViewModels
{
internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable
{
private AvaloniaList<TreeNode> _inner;
public TreeNodeCollection(TreeNode owner) => Owner = owner;
public TreeNode this[int index]
{
get
{
EnsureInitialized();
return _inner[index];
}
}
public int Count
{
get
{
EnsureInitialized();
return _inner.Count;
}
}
protected TreeNode Owner { get; }
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => _inner.CollectionChanged += value;
remove => _inner.CollectionChanged -= value;
}
public event PropertyChangedEventHandler PropertyChanged
{
add => _inner.PropertyChanged += value;
remove => _inner.PropertyChanged -= value;
}
public virtual void Dispose()
{
if (_inner is object)
{
foreach (var node in _inner)
{
node.Dispose();
}
}
}
public IEnumerator<TreeNode> GetEnumerator()
{
EnsureInitialized();
return _inner.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
protected abstract void Initialize(AvaloniaList<TreeNode> nodes);
private void EnsureInitialized()
{
if (_inner is null)
{
_inner = new AvaloniaList<TreeNode>();
Initialize(_inner);
}
}
}
}

10
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -62,7 +62,15 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public void Dispose() => _details?.Dispose(); public void Dispose()
{
foreach (var node in Nodes)
{
node.Dispose();
}
_details?.Dispose();
}
public TreeNode FindNode(IControl control) public TreeNode FindNode(IControl control)
{ {

37
src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs

@ -1,3 +1,4 @@
using System;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -9,16 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
public VisualTreeNode(IVisual visual, TreeNode parent) public VisualTreeNode(IVisual visual, TreeNode parent)
: base(visual, parent) : base(visual, parent)
{ {
var host = visual as IVisualTreeHost; Children = new VisualTreeNodeCollection(this, visual);
if (host?.Root == null)
{
Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
}
else
{
Children = new AvaloniaList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
}
if ((Visual is IStyleable styleable)) if ((Visual is IStyleable styleable))
{ {
@ -33,5 +25,30 @@ namespace Avalonia.Diagnostics.ViewModels
var visual = control as IVisual; var visual = control as IVisual;
return visual != null ? new[] { new VisualTreeNode(visual, null) } : null; return visual != null ? new[] { new VisualTreeNode(visual, null) } : null;
} }
internal class VisualTreeNodeCollection : TreeNodeCollection
{
private readonly IVisual _control;
private IDisposable _subscription;
public VisualTreeNodeCollection(TreeNode owner, IVisual control)
: base(owner)
{
_control = control;
}
public override void Dispose()
{
_subscription?.Dispose();
}
protected override void Initialize(AvaloniaList<TreeNode> nodes)
{
_subscription = _control.VisualChildren.ForEachItem(
(i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)),
(i, item) => nodes.RemoveAt(i),
() => nodes.Clear());
}
}
} }
} }

22
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@ -14,8 +14,8 @@ namespace Avalonia.Diagnostics.Views
{ {
internal class MainWindow : Window, IStyleHost internal class MainWindow : Window, IStyleHost
{ {
private readonly IDisposable _keySubscription;
private TopLevel _root; private TopLevel _root;
private IDisposable _keySubscription;
public MainWindow() public MainWindow()
{ {
@ -33,8 +33,22 @@ namespace Avalonia.Diagnostics.Views
{ {
if (_root != value) if (_root != value)
{ {
if (_root != null)
{
_root.Closed -= RootClosed;
}
_root = value; _root = value;
DataContext = new MainViewModel(value);
if (_root != null)
{
_root.Closed += RootClosed;
DataContext = new MainViewModel(value);
}
else
{
DataContext = null;
}
} }
} }
} }
@ -45,6 +59,8 @@ namespace Avalonia.Diagnostics.Views
{ {
base.OnClosed(e); base.OnClosed(e);
_keySubscription.Dispose(); _keySubscription.Dispose();
_root.Closed -= RootClosed;
_root = null;
((MainViewModel)DataContext)?.Dispose(); ((MainViewModel)DataContext)?.Dispose();
} }
@ -70,5 +86,7 @@ namespace Avalonia.Diagnostics.Views
} }
} }
} }
private void RootClosed(object sender, EventArgs e) => Close();
} }
} }

40
src/Avalonia.Layout/LayoutManager.cs

@ -106,8 +106,6 @@ namespace Avalonia.Layout
if (!_running) if (!_running)
{ {
_running = true;
Stopwatch? stopwatch = null; Stopwatch? stopwatch = null;
const LogEventLevel timingLogLevel = LogEventLevel.Information; const LogEventLevel timingLogLevel = LogEventLevel.Information;
@ -128,15 +126,24 @@ namespace Avalonia.Layout
_toMeasure.BeginLoop(MaxPasses); _toMeasure.BeginLoop(MaxPasses);
_toArrange.BeginLoop(MaxPasses); _toArrange.BeginLoop(MaxPasses);
for (var pass = 0; pass < MaxPasses; ++pass) try
{ {
InnerLayoutPass(); _running = true;
if (!RaiseEffectiveViewportChanged()) for (var pass = 0; pass < MaxPasses; ++pass)
{ {
break; InnerLayoutPass();
if (!RaiseEffectiveViewportChanged())
{
break;
}
} }
} }
finally
{
_running = false;
}
_toMeasure.EndLoop(); _toMeasure.EndLoop();
_toArrange.EndLoop(); _toArrange.EndLoop();
@ -221,23 +228,16 @@ namespace Avalonia.Layout
private void InnerLayoutPass() private void InnerLayoutPass()
{ {
try for (var pass = 0; pass < MaxPasses; ++pass)
{ {
for (var pass = 0; pass < MaxPasses; ++pass) ExecuteMeasurePass();
{ ExecuteArrangePass();
ExecuteMeasurePass();
ExecuteArrangePass();
if (_toMeasure.Count == 0) if (_toMeasure.Count == 0)
{ {
break; break;
}
} }
} }
finally
{
_running = false;
}
} }
private void ExecuteMeasurePass() private void ExecuteMeasurePass()
@ -362,7 +362,7 @@ namespace Avalonia.Layout
} }
} }
return startCount != _toMeasure.Count + _toMeasure.Count; return startCount != _toMeasure.Count + _toArrange.Count;
} }
private Rect CalculateEffectiveViewport(IVisual control) private Rect CalculateEffectiveViewport(IVisual control)

9
src/Avalonia.Native/NativeControlHostImpl.cs

@ -114,19 +114,18 @@ namespace Avalonia.Native
public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl; public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl;
public void Hide() public void HideWithSize(Size size)
{ {
_native?.Hide(); _native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height));
} }
public void ShowInBounds(TransformedBounds transformedBounds) public void ShowInBounds(Rect bounds)
{ {
if (_attachedTo == null) if (_attachedTo == null)
throw new InvalidOperationException("Native control isn't attached to a toplevel"); throw new InvalidOperationException("Native control isn't attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform);
bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width), bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width),
Math.Max(1, bounds.Height)); Math.Max(1, bounds.Height));
_native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height); _native.ShowInBounds((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
} }
public void InitWithChild(IPlatformHandle handle) public void InitWithChild(IPlatformHandle handle)

12
src/Avalonia.Native/PopupImpl.cs

@ -10,6 +10,7 @@ namespace Avalonia.Native
private readonly IAvaloniaNativeFactory _factory; private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts; private readonly AvaloniaNativePlatformOptions _opts;
private readonly GlPlatformFeature _glFeature; private readonly GlPlatformFeature _glFeature;
private readonly IWindowBaseImpl _parent;
public PopupImpl(IAvaloniaNativeFactory factory, public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOptions opts,
@ -19,6 +20,7 @@ namespace Avalonia.Native
_factory = factory; _factory = factory;
_opts = opts; _opts = opts;
_glFeature = glFeature; _glFeature = glFeature;
_parent = parent;
using (var e = new PopupEvents(this)) using (var e = new PopupEvents(this))
{ {
var context = _opts.UseGpu ? glFeature?.DeferredContext : null; var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
@ -58,6 +60,16 @@ namespace Avalonia.Native
} }
} }
public override void Show()
{
var parent = _parent;
while (parent is PopupImpl p)
parent = p._parent;
if (parent is WindowImpl w)
w.Native.TakeFocusFromChildren();
base.Show();
}
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this); public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);
public void SetWindowManagerAddShadowHint(bool enabled) public void SetWindowManagerAddShadowHint(bool enabled)

2
src/Avalonia.Native/WindowImplBase.cs

@ -319,7 +319,7 @@ namespace Avalonia.Native
} }
public void Show() public virtual void Show()
{ {
_native.Show(); _native.Show();
} }

12
src/Avalonia.Themes.Default/ProgressBar.xaml

@ -1,4 +1,12 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel>
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
<ProgressBar HorizontalAlignment="Left" IsIndeterminate="True" Orientation="Vertical" />
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="ProgressBar"> <Style Selector="ProgressBar">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/> <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/> <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
@ -69,11 +77,11 @@
Easing="LinearEasing"> Easing="LinearEasing">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y" <Setter Property="TranslateTransform.Y"
Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> Value="{Binding TemplateProperties.IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="TranslateTransform.Y" <Setter Property="TranslateTransform.Y"
Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> Value="{Binding TemplateProperties.IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>

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

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

43
src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml

@ -2,35 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"> xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources> <Style.Resources>
<Color x:Key="SystemAccentColor">#FF0078D7</Color> <Color x:Key="SystemRevealListLowColor">#18FFFFFF</Color>
<Color x:Key="SystemAltHighColor">#FF000000</Color> <Color x:Key="SystemRevealListMediumColor">#30FFFFFF</Color>
<Color x:Key="SystemAltLowColor">#FF000000</Color>
<Color x:Key="SystemAltMediumColor">#FF000000</Color>
<Color x:Key="SystemAltMediumHighColor">#FF000000</Color>
<Color x:Key="SystemAltMediumLowColor">#FF000000</Color>
<Color x:Key="SystemBaseHighColor">#FFFFFFFF</Color>
<Color x:Key="SystemBaseLowColor">#FF333333</Color>
<Color x:Key="SystemBaseMediumColor">#FF9A9A9A</Color>
<Color x:Key="SystemBaseMediumHighColor">#FFB4B4B4</Color>
<Color x:Key="SystemBaseMediumLowColor">#FF676767</Color>
<Color x:Key="SystemChromeAltLowColor">#FFB4B4B4</Color>
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackLowColor">#FFB4B4B4</Color>
<Color x:Key="SystemChromeBlackMediumColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackMediumLowColor">#FF000000</Color>
<Color x:Key="SystemChromeDisabledHighColor">#FF333333</Color>
<Color x:Key="SystemChromeGrayColor">#FF808080</Color>
<Color x:Key="SystemChromeHighColor">#FF808080</Color>
<Color x:Key="SystemChromeLowColor">#FF151515</Color>
<Color x:Key="SystemChromeMediumColor">#FF1D1D1D</Color>
<Color x:Key="SystemChromeMediumLowColor">#FF2C2C2C</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="SystemListLowColor">#FF1D1D1D</Color>
<Color x:Key="SystemListMediumColor">#FF333333</Color>
<Color x:Key="SystemChromeAltMediumHighColor">#CC000000</Color>
<Color x:Key="SystemChromeAltHighColor">#FF333333</Color>
<Color x:Key="SystemRevealListLowColor">#FF1D1D1D</Color>
<Color x:Key="SystemRevealListMediumColor">#FF333333</Color>
<!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />--> <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
<!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />--> <!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" /> <SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@ -67,13 +40,6 @@
<Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness> <Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
<x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double> <x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double>
<!-- Override system generated accent colors -->
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
<Color x:Key="SystemAccentColorDark2">#FF004275</Color>
<Color x:Key="SystemAccentColorDark3">#FF002642</Color>
<Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
<Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
<Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
<Color x:Key="RegionColor">#FF000000</Color> <Color x:Key="RegionColor">#FF000000</Color>
<SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" /> <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
@ -218,6 +184,11 @@
<SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99000000" /> <SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99000000" />
<SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" /> <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
<SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" /> <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
<!-- BaseResources for ProgressBar.xaml -->
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#30000000" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF4617B4" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF4617B4" />
<!-- BaseResources for TextBox.xaml --> <!-- BaseResources for TextBox.xaml -->
<StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />

45
src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml

@ -2,36 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard"> xmlns:sys="clr-namespace:System;assembly=netstandard">
<Style.Resources> <Style.Resources>
<Color x:Key="SystemAccentColor">#FF0078D7</Color> <Color x:Key="SystemRevealListLowColor">#17000000</Color>
<Color x:Key="SystemAltHighColor">#FFFFFFFF</Color> <Color x:Key="SystemRevealListMediumColor">#2E000000</Color>
<Color x:Key="SystemAltLowColor">#FFFFFFFF</Color>
<Color x:Key="SystemAltMediumColor">#FFFFFFFF</Color>
<Color x:Key="SystemAltMediumHighColor">#FFFFFFFF</Color>
<Color x:Key="SystemAltMediumLowColor">#FFFFFFFF</Color>
<Color x:Key="SystemBaseHighColor">#FF000000</Color>
<Color x:Key="SystemBaseLowColor">#FFCCCCCC</Color>
<Color x:Key="SystemBaseMediumColor">#FF898989</Color>
<Color x:Key="SystemBaseMediumHighColor">#FF5D5D5D</Color>
<Color x:Key="SystemBaseMediumLowColor">#FF737373</Color>
<Color x:Key="SystemChromeAltLowColor">#FF5D5D5D</Color>
<Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
<Color x:Key="SystemChromeBlackLowColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeBlackMediumColor">#FF5D5D5D</Color>
<Color x:Key="SystemChromeBlackMediumLowColor">#FF898989</Color>
<Color x:Key="SystemChromeDisabledHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeDisabledLowColor">#FF898989</Color>
<Color x:Key="SystemChromeGrayColor">#FF737373</Color>
<Color x:Key="SystemChromeHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeLowColor">#FFECECEC</Color>
<Color x:Key="SystemChromeMediumColor">#FFE6E6E6</Color>
<Color x:Key="SystemChromeMediumLowColor">#FFECECEC</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="SystemListLowColor">#FFE6E6E6</Color>
<Color x:Key="SystemListMediumColor">#FFCCCCCC</Color>
<Color x:Key="SystemChromeAltMediumHighColor">#CCFFFFFF</Color>
<Color x:Key="SystemChromeAltHighColor">#FFCCCCCC</Color>
<Color x:Key="SystemRevealListLowColor">#FFE6E6E6</Color>
<Color x:Key="SystemRevealListMediumColor">#FFCCCCCC</Color>
<!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />--> <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
<!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />--> <!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
<SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" /> <SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@ -68,13 +40,6 @@
<Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness> <Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
<x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double> <x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double>
<!-- Override system generated accent colors -->
<Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
<Color x:Key="SystemAccentColorDark2">#FF004275</Color>
<Color x:Key="SystemAccentColorDark3">#FF002642</Color>
<Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
<Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
<Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
<!--<RevealBackgroundBrush x:Key="SystemControlHighlightListLowRevealBackgroundBrush" TargetTheme="Light" Color="{ThemeResource SystemRevealListMediumColor}" FallbackColor="{ StaticResource SystemListMediumColor}" />--> <!--<RevealBackgroundBrush x:Key="SystemControlHighlightListLowRevealBackgroundBrush" TargetTheme="Light" Color="{ThemeResource SystemRevealListMediumColor}" FallbackColor="{ StaticResource SystemListMediumColor}" />-->
<Color x:Key="RegionColor">#FFFFFFFF</Color> <Color x:Key="RegionColor">#FFFFFFFF</Color>
<SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" /> <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
@ -222,6 +187,12 @@
<SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" /> <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
<SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" /> <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
<!-- BaseResources for ProgressBar.xaml -->
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#59FFFFFF" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF5B2EC5" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF8A57FF" />
<!-- BaseResources for TextBox.xaml --> <!-- BaseResources for TextBox.xaml -->
<StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="TextControlForegroundPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" /> <StaticResource x:Key="TextControlForegroundPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" />

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

@ -336,6 +336,15 @@
<StaticResource x:Key="MenuFlyoutSubItemRevealBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />--> <StaticResource x:Key="MenuFlyoutSubItemRevealBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />-->
<!--<Thickness x:Key="LanguageSwitcherMenuFlyoutItemPlaceholderThemeThickness">44,0,0,0</Thickness>--> <!--<Thickness x:Key="LanguageSwitcherMenuFlyoutItemPlaceholderThemeThickness">44,0,0,0</Thickness>-->
<!-- Resources for ProgressBar.xaml -->
<x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
<x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#59FFFFFF" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF5B2EC5" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF8A57FF" />
<!-- Resources for TextBox.xaml --> <!-- Resources for TextBox.xaml -->
<SolidColorBrush x:Key="TextBoxForegroundHeaderThemeBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="TextBoxForegroundHeaderThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#AB000000" /> <SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#AB000000" />
@ -658,6 +667,67 @@
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" /> <SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness> <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 --> <!-- Resources for Pivot.xaml -->
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily> <FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily> <FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
@ -819,5 +889,7 @@
<StaticResource x:Key="TreeViewItemCheckGlyphSelected" ResourceKey="SystemControlForegroundBaseMediumHighBrush" /> <StaticResource x:Key="TreeViewItemCheckGlyphSelected" ResourceKey="SystemControlForegroundBaseMediumHighBrush" />
<Thickness x:Key="TreeViewItemBorderThemeThickness">1</Thickness> <Thickness x:Key="TreeViewItemBorderThemeThickness">1</Thickness>
<x:Double x:Key="TreeViewItemMinHeight">32</x:Double> <x:Double x:Key="TreeViewItemMinHeight">32</x:Double>
<!-- Resources for SplitView.xaml -->
<StaticResource x:Key="SplitViewLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
</Style.Resources> </Style.Resources>
</Style> </Style>

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

@ -248,6 +248,14 @@
<SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99FFFFFF" /> <SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99FFFFFF" />
<SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" /> <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
<SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" /> <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
<!-- Resources for ProgressBar.xaml -->
<x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
<x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#30000000" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF4617B4" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF4617B4" />
<!-- Resources for MenuFlyout.xaml (Menu, ContextMenu, etc) --> <!-- Resources for MenuFlyout.xaml (Menu, ContextMenu, etc) -->
<x:Double x:Key="MenuFlyoutSeparatorThemeHeight">1</x:Double> <x:Double x:Key="MenuFlyoutSeparatorThemeHeight">1</x:Double>
@ -656,6 +664,67 @@
<SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" /> <SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
<Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness> <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 --> <!-- Resources for Pivot.xaml -->
<FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily> <FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
<FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily> <FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
@ -817,5 +886,7 @@
<StaticResource x:Key="TreeViewItemCheckGlyphSelected" ResourceKey="SystemControlForegroundBaseMediumHighBrush" /> <StaticResource x:Key="TreeViewItemCheckGlyphSelected" ResourceKey="SystemControlForegroundBaseMediumHighBrush" />
<Thickness x:Key="TreeViewItemBorderThemeThickness">1</Thickness> <Thickness x:Key="TreeViewItemBorderThemeThickness">1</Thickness>
<x:Double x:Key="TreeViewItemMinHeight">32</x:Double> <x:Double x:Key="TreeViewItemMinHeight">32</x:Double>
<!-- Resources for SplitView.xaml -->
<StaticResource x:Key="SplitViewLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
</Style.Resources> </Style.Resources>
</Style> </Style>

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>

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

@ -53,4 +53,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Fluent.NotificationCard.xaml?assembly=Avalonia.Themes.Fluent"/> <StyleInclude Source="resm:Avalonia.Themes.Fluent.NotificationCard.xaml?assembly=Avalonia.Themes.Fluent"/>
<StyleInclude Source="resm:Avalonia.Themes.Fluent.NativeMenuBar.xaml?assembly=Avalonia.Themes.Fluent"/> <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.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> </Styles>

44
src/Avalonia.Themes.Fluent/FocusAdorner.xaml

@ -1,10 +1,34 @@
<Style xmlns="https://github.com/avaloniaui" Selector=":is(Control)"> <Styles xmlns="https://github.com/avaloniaui"
<Setter Property="FocusAdorner"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<FocusAdornerTemplate> <Styles.Resources>
<Rectangle Stroke="Black" <Thickness x:Key="SystemControlFocusVisualMargin">0</Thickness>
StrokeThickness="1" <Thickness x:Key="SystemControlFocusVisualPrimaryThickness">2</Thickness>
StrokeDashArray="1,2" <Thickness x:Key="SystemControlFocusVisualSecondaryThickness">1</Thickness>
Margin="1"/> </Styles.Resources>
</FocusAdornerTemplate>
</Setter> <!-- HighVisibility FocusAdorner -->
</Style> <Style Selector=":is(Control)">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Border BorderThickness="{StaticResource SystemControlFocusVisualPrimaryThickness}"
BorderBrush="{DynamicResource SystemControlFocusVisualPrimaryBrush}"
Margin="{StaticResource SystemControlFocusVisualMargin}">
<Border BorderThickness="{StaticResource SystemControlFocusVisualSecondaryThickness}"
BorderBrush="{DynamicResource SystemControlFocusVisualSecondaryBrush}" />
</Border>
</FocusAdornerTemplate>
</Setter>
</Style>
<!-- DottedLine FocusAdorner -->
<Style Selector="Window.DottedLineFocusAdorner :is(Control)">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Rectangle Stroke="{StaticResource SystemControlFocusVisualPrimaryBrush}"
StrokeThickness="1"
StrokeDashArray="1,2"
Margin="1" />
</FocusAdornerTemplate>
</Setter>
</Style>
</Styles>

191
src/Avalonia.Themes.Fluent/ProgressBar.xaml

@ -1,81 +1,176 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel>
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
<ProgressBar HorizontalAlignment="Left" IsIndeterminate="True" Orientation="Vertical" />
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="ProgressBar"> <Style Selector="ProgressBar">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/> <Setter Property="Foreground" Value="{DynamicResource SystemControlHighlightAccentBrush}" />
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/> <Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ProgressBarBorderThemeThickness}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightTransparentBrush}" />
<Setter Property="Maximum" Value="100" />
<Setter Property="MinHeight" Value="{DynamicResource ProgressBarThemeMinHeight}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid> <Border x:Name="ProgressBarRoot" ClipToBounds="True" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{DynamicResource ControlCornerRadius}">
<Border Background="{TemplateBinding Background}" <Panel>
BorderBrush="{TemplateBinding BorderBrush}" <Panel x:Name="DeterminateRoot">
BorderThickness="{TemplateBinding BorderThickness}"> <Border CornerRadius="{DynamicResource ControlCornerRadius}" x:Name="PART_Indicator" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
<Border Name="PART_Indicator" Background="{TemplateBinding Foreground}"/> </Panel>
</Border> <Panel x:Name="IndeterminateRoot">
<LayoutTransformControl <Border x:Name="IndeterminateProgressBarIndicator" CornerRadius="{DynamicResource ControlCornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
HorizontalAlignment="Center" <Border x:Name="IndeterminateProgressBarIndicator2" CornerRadius="{DynamicResource ControlCornerRadius}" Margin="{TemplateBinding Padding}" Background="{TemplateBinding Foreground}" />
VerticalAlignment="Center" </Panel>
IsVisible="{Binding ShowProgressText, RelativeSource={RelativeSource TemplatedParent}}" </Panel>
Name="PART_LayoutTransformControl"> </Border>
<TextBlock
Foreground="{DynamicResource ThemeForegroundBrush}"
Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
</LayoutTransformControl>
</Grid>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="ProgressBar:horizontal /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:horizontal /template/ Border#PART_Indicator">
<Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Stretch"/> <Setter Property="VerticalAlignment" Value="Stretch" />
</Style> </Style>
<Style Selector="ProgressBar:vertical /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:vertical /template/ Border#PART_Indicator">
<Setter Property="HorizontalAlignment" Value="Stretch"/> <Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Bottom"/> <Setter Property="VerticalAlignment" Value="Bottom" />
</Style> </Style>
<Style Selector="ProgressBar:horizontal"> <Style Selector="ProgressBar:horizontal">
<Setter Property="MinWidth" Value="200"/> <Setter Property="MinWidth" Value="200" />
<Setter Property="MinHeight" Value="16"/> <Setter Property="MinHeight" Value="4" />
</Style> </Style>
<Style Selector="ProgressBar:vertical"> <Style Selector="ProgressBar:vertical">
<Setter Property="MinWidth" Value="16"/> <Setter Property="MinWidth" Value="4" />
<Setter Property="MinHeight" Value="200"/> <Setter Property="MinHeight" Value="200" />
</Style> </Style>
<Style Selector="ProgressBar:vertical /template/ LayoutTransformControl#PART_LayoutTransformControl"> <Style Selector="ProgressBar:vertical /template/ LayoutTransformControl#PART_LayoutTransformControl">
<Setter Property="LayoutTransform"> <Setter Property="LayoutTransform">
<Setter.Value> <Setter.Value>
<RotateTransform Angle="90"/> <RotateTransform Angle="90" />
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator"> <!-- FadeInAnimation mockup-->
<Style Selector="ProgressBar /template/ Panel#DeterminateRoot">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.197" />
</Transitions>
</Setter>
</Style>
<Style Selector="ProgressBar /template/ Panel#IndeterminateRoot">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.197" />
</Transitions>
</Setter>
</Style>
<Style Selector="ProgressBar /template/ Panel#DeterminateRoot">
<Setter Property="Opacity" Value="1" />
</Style>
<Style Selector="ProgressBar /template/ Panel#IndeterminateRoot">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="ProgressBar:indeterminate /template/ Panel#DeterminateRoot">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="ProgressBar:indeterminate /template/ Panel#IndeterminateRoot">
<Setter Property="Opacity" Value="1" />
</Style>
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#IndeterminateProgressBarIndicator">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeyTime="0:0:1.5" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
</KeyFrame>
<KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#IndeterminateProgressBarIndicator2">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeyTime="0:0:0.75" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
</KeyFrame>
<KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationEndPosition}" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="ProgressBar:vertical:indeterminate /template/ Border#IndeterminateProgressBarIndicator">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:3" <Animation Duration="0:0:2" IterationCount="Infinite">
IterationCount="Infinite" <KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
Easing="LinearEasing"> <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
<KeyFrame Cue="0%"> </KeyFrame>
<Setter Property="TranslateTransform.X" <KeyFrame KeyTime="0:0:1.5" KeySpline="0.4,0,0.6,1">
Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.X" <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationEndPosition}" />
Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:vertical:indeterminate /template/ Border#IndeterminateProgressBarIndicator2">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:3" <Animation Duration="0:0:2" IterationCount="Infinite">
IterationCount="Infinite" <KeyFrame KeyTime="0:0:0" KeySpline="0.4,0,0.6,1">
Easing="LinearEasing"> <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y"
Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame KeyTime="0:0:0.75" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.Y" <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> </KeyFrame>
<KeyFrame KeyTime="0:0:2" KeySpline="0.4,0,0.6,1">
<Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationEndPosition}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="ProgressBar:horizontal /template/ Border#IndeterminateProgressBarIndicator">
<Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="ProgressBar:horizontal /template/ Border#IndeterminateProgressBarIndicator2">
<Setter Property="Width" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="ProgressBar:vertical /template/ Border#IndeterminateProgressBarIndicator">
<Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.ContainerWidth}" />
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform Y="{Binding $parent[ProgressBar].TemplateProperties.ContainerAnimationStartPosition}" />
</Setter.Value>
</Setter>
</Style>
<Style Selector="ProgressBar:vertical /template/ Border#IndeterminateProgressBarIndicator2">
<Setter Property="Height" Value="{Binding $parent[ProgressBar].TemplateProperties.Container2Width}" />
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform Y="{Binding $parent[ProgressBar].TemplateProperties.Container2AnimationStartPosition}" />
</Setter.Value>
</Setter>
</Style>
</Styles> </Styles>

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

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

219
src/Avalonia.Themes.Fluent/SplitView.xaml

@ -0,0 +1,219 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<x:Double x:Key="SplitViewOpenPaneThemeLength">320</x:Double>
<x:Double x:Key="SplitViewCompactPaneThemeLength">48</x:Double>
<!-- Not used here (directly) since they're strings, but preserving for reference
<x:String x:Key="SplitViewPaneAnimationOpenDuration">00:00:00.2</x:String>
<x:String x:Key="SplitViewPaneAnimationOpenPreDuration">00:00:00.19999</x:String>
<x:String x:Key="SplitViewPaneAnimationCloseDuration">00:00:00.1</x:String>-->
</Styles.Resources>
<Style Selector="SplitView">
<Setter Property="OpenPaneLength" Value="{DynamicResource SplitViewOpenPaneThemeLength}" />
<Setter Property="CompactPaneLength" Value="{DynamicResource SplitViewCompactPaneThemeLength}" />
<Setter Property="PaneBackground" Value="{DynamicResource SystemControlPageBackgroundChromeLowBrush}" />
</Style>
<!-- Left -->
<Style Selector="SplitView:left">
<Setter Property="Template">
<ControlTemplate>
<Grid Name="Container" Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<!-- why is this throwing a binding error? -->
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PaneColumnGridLength}"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Panel Name="PART_PaneRoot" Background="{TemplateBinding PaneBackground}"
ClipToBounds="True"
HorizontalAlignment="Left"
ZIndex="100">
<Border Child="{TemplateBinding Pane}"/>
<Rectangle Name="HCPaneBorder" Fill="{DynamicResource SystemControlForegroundTransparentBrush}" Width="1" HorizontalAlignment="Right" />
</Panel>
<Panel Name="ContentRoot">
<Border Child="{TemplateBinding Content}" />
<Rectangle Name="LightDismissLayer"/>
</Panel>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<!-- Overlay -->
<Style Selector="SplitView:overlay:left /template/ Panel#PART_PaneRoot">
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
<!-- ColumnSpan should be 2 -->
<Setter Property="Grid.ColumnSpan" Value="1"/>
<Setter Property="Grid.Column" Value="0"/>
</Style>
<Style Selector="SplitView:overlay:left /template/ Panel#ContentRoot">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Grid.ColumnSpan" Value="2"/>
</Style>
<!-- CompactInline -->
<Style Selector="SplitView:compactinline:left /template/ Panel#PART_PaneRoot">
<Setter Property="Grid.ColumnSpan" Value="1"/>
<Setter Property="Grid.Column" Value="0"/>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
</Style>
<Style Selector="SplitView:compactinline:left /template/ Panel#ContentRoot">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Grid.ColumnSpan" Value="1"/>
</Style>
<!-- CompactOverlay -->
<Style Selector="SplitView:compactoverlay:left /template/ Panel#PART_PaneRoot">
<!-- ColumnSpan should be 2 -->
<Setter Property="Grid.ColumnSpan" Value="1"/>
<Setter Property="Grid.Column" Value="0"/>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
</Style>
<Style Selector="SplitView:compactoverlay:left /template/ Panel#ContentRoot">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Grid.ColumnSpan" Value="1"/>
</Style>
<!-- Inline -->
<Style Selector="SplitView:inline:left /template/ Panel#PART_PaneRoot">
<Setter Property="Grid.ColumnSpan" Value="1"/>
<Setter Property="Grid.Column" Value="0"/>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
</Style>
<Style Selector="SplitView:inline:left /template/ Panel#ContentRoot">
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Grid.ColumnSpan" Value="1"/>
</Style>
<!-- Right -->
<Style Selector="SplitView:right">
<Setter Property="Template">
<ControlTemplate>
<Grid Name="Container" Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PaneColumnGridLength}"/>
</Grid.ColumnDefinitions>
<Panel Name="PART_PaneRoot" Background="{TemplateBinding PaneBackground}"
ClipToBounds="True"
HorizontalAlignment="Right"
ZIndex="100">
<Border Child="{TemplateBinding Pane}"/>
<Rectangle Name="HCPaneBorder"
Fill="{DynamicResource SystemControlForegroundTransparentBrush}"
Width="1" HorizontalAlignment="Left" />
</Panel>
<Panel Name="ContentRoot">
<Border Child="{TemplateBinding Content}" />
<Rectangle Name="LightDismissLayer"/>
</Panel>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<!-- Overlay -->
<Style Selector="SplitView:overlay:right /template/ Panel#PART_PaneRoot">
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
<Setter Property="Grid.ColumnSpan" Value="2"/>
<Setter Property="Grid.Column" Value="1"/>
</Style>
<Style Selector="SplitView:overlay:right /template/ Panel#ContentRoot">
<Setter Property="Grid.Column" Value="0"/>
<Setter Property="Grid.ColumnSpan" Value="2"/>
</Style>
<!-- CompactInline -->
<Style Selector="SplitView:compactinline:right /template/ Panel#PART_PaneRoot">
<Setter Property="Grid.ColumnSpan" Value="1"/>
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
</Style>
<Style Selector="SplitView:compactinline:right /template/ Panel#ContentRoot">
<Setter Property="Grid.Column" Value="0"/>
<Setter Property="Grid.ColumnSpan" Value="1"/>
</Style>
<!-- CompactOverlay -->
<Style Selector="SplitView:compactoverlay:right /template/ Panel#PART_PaneRoot">
<Setter Property="Grid.ColumnSpan" Value="2"/>
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
</Style>
<Style Selector="SplitView:compactoverlay:right /template/ Panel#ContentRoot">
<Setter Property="Grid.Column" Value="0"/>
<Setter Property="Grid.ColumnSpan" Value="1"/>
</Style>
<!-- Inline -->
<Style Selector="SplitView:inline:right /template/ Panel#PART_PaneRoot">
<Setter Property="Grid.ColumnSpan" Value="1"/>
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
</Style>
<Style Selector="SplitView:inline:right /template/ Panel#ContentRoot">
<Setter Property="Grid.Column" Value="0"/>
<Setter Property="Grid.ColumnSpan" Value="1"/>
</Style>
<!-- Open/Close Pane animation -->
<Style Selector="SplitView:open /template/ Panel#PART_PaneRoot">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Width" Duration="00:00:00.2" Easing="0.1,0.9,0.2,1.0" />
</Transitions>
</Setter>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=OpenPaneLength}" />
</Style>
<Style Selector="SplitView:open /template/ Rectangle#LightDismissLayer">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="00:00:00.2" Easing="0.1,0.9,0.2,1.0" />
</Transitions>
</Setter>
<Setter Property="Opacity" Value="1.0"/>
</Style>
<Style Selector="SplitView:closed /template/ Panel#PART_PaneRoot">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Width" Duration="00:00:00.1" Easing="0.1,0.9,0.2,1.0" />
</Transitions>
</Setter>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
</Style>
<Style Selector="SplitView:closed /template/ Rectangle#LightDismissLayer">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="00:00:00.2" Easing="0.1,0.9,0.2,1.0" />
</Transitions>
</Setter>
<Setter Property="Opacity" Value="0.0"/>
</Style>
<Style Selector="SplitView /template/ Rectangle#LightDismissLayer">
<Setter Property="IsVisible" Value="False"/>
<Setter Property="Fill" Value="Transparent" />
</Style>
<Style Selector="SplitView:lightdismiss /template/ Rectangle#LightDismissLayer">
<Setter Property="Fill" Value="{DynamicResource SplitViewLightDismissOverlayBackground}" />
</Style>
<Style Selector="SplitView:overlay:open /template/ Rectangle#LightDismissLayer">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="SplitView:compactoverlay:open /template/ Rectangle#LightDismissLayer">
<Setter Property="IsVisible" Value="True"/>
</Style>
</Styles>

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>

16
src/Avalonia.Themes.Fluent/ToolTip.xaml

@ -1,11 +1,14 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Design.PreviewWith> <Design.PreviewWith>
<Grid RowDefinitions="Auto,Auto" <Grid RowDefinitions="Auto,Auto"
ColumnDefinitions="Auto,Auto" ColumnDefinitions="Auto,Auto"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<Border Grid.Column="0" <Border Grid.Column="0"
Grid.Row="1" Grid.Row="1"
Background="{DynamicResource ThemeAccentBrush}" Background="{DynamicResource SystemControlBackgroundAccentBrush}"
Margin="5" Margin="5"
Padding="50" Padding="50"
ToolTip.Tip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."> ToolTip.Tip="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.">
@ -19,7 +22,7 @@
<Border Name="Border" <Border Name="Border"
Grid.Column="1" Grid.Column="1"
Grid.Row="1" Grid.Row="1"
Background="{DynamicResource ThemeAccentBrush}" Background="{DynamicResource SystemControlBackgroundAccentBrush}"
Margin="5" Margin="5"
Padding="50" Padding="50"
ToolTip.Placement="Bottom"> ToolTip.Placement="Bottom">
@ -34,6 +37,10 @@
</Grid> </Grid>
</Design.PreviewWith> </Design.PreviewWith>
<Styles.Resources>
<sys:Double x:Key="ToolTipContentMaxWidth">320</sys:Double>
</Styles.Resources>
<Style Selector="ToolTip"> <Style Selector="ToolTip">
<Setter Property="Foreground" Value="{DynamicResource ToolTipForeground}" /> <Setter Property="Foreground" Value="{DynamicResource ToolTipForeground}" />
<Setter Property="Background" Value="{DynamicResource ToolTipBackground}" /> <Setter Property="Background" Value="{DynamicResource ToolTipBackground}" />
@ -42,6 +49,7 @@
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource ToolTipContentThemeFontSize}" /> <Setter Property="FontSize" Value="{DynamicResource ToolTipContentThemeFontSize}" />
<Setter Property="Padding" Value="{DynamicResource ToolTipBorderThemePadding}" /> <Setter Property="Padding" Value="{DynamicResource ToolTipBorderThemePadding}" />
<Setter Property="MaxWidth" Value="{DynamicResource ToolTipContentMaxWidth}" />
<Setter Property="Transitions"> <Setter Property="Transitions">
<Transitions> <Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.15" /> <DoubleTransition Property="Opacity" Duration="0:0:0.15" />
@ -56,7 +64,7 @@
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
CornerRadius="{DynamicResource OverlayCornerRadius}"> CornerRadius="{DynamicResource OverlayCornerRadius}">
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
MaxWidth="320" MaxWidth="{TemplateBinding MaxWidth}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" /> ContentTemplate="{TemplateBinding ContentTemplate}" />
</Border> </Border>

BIN
src/Avalonia.Visuals/Assets/GraphemeBreak.trie

Binary file not shown.

BIN
src/Avalonia.Visuals/Assets/UnicodeData.trie

Binary file not shown.

2
src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs

@ -2,6 +2,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
public enum BiDiClass public enum BiDiClass
{ {
LeftToRight, //L
ArabicLetter, //AL ArabicLetter, //AL
ArabicNumber, //AN ArabicNumber, //AN
ParagraphSeparator, //B ParagraphSeparator, //B
@ -11,7 +12,6 @@ namespace Avalonia.Media.TextFormatting.Unicode
EuropeanSeparator, //ES EuropeanSeparator, //ES
EuropeanTerminator, //ET EuropeanTerminator, //ET
FirstStrongIsolate, //FSI FirstStrongIsolate, //FSI
LeftToRight, //L
LeftToRightEmbedding, //LRE LeftToRightEmbedding, //LRE
LeftToRightIsolate, //LRI LeftToRightIsolate, //LRI
LeftToRightOverride, //LRO LeftToRightOverride, //LRO

65
src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs

@ -4,38 +4,39 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
private static readonly byte[][] s_breakPairTable = private static readonly byte[][] s_breakPairTable =
{ {
new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4}, new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,4},
new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0}, new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,1,0,1,1,0,0,4,2,4,0,0,0,0,0,0,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,0,4,4,4,0,0,0,0,0,0,0,0,0,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
}; };
public static PairBreakType Map(LineBreakClass first, LineBreakClass second) public static PairBreakType Map(LineBreakClass first, LineBreakClass second)

34
src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs

@ -2,24 +2,20 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
public enum GraphemeBreakClass public enum GraphemeBreakClass
{ {
Control, //CN Other,
CR, //CR CR,
EBase, //EB LF,
EBaseGAZ, //EBG Control,
EModifier, //EM Extend,
Extend, //EX ZWJ,
GlueAfterZwj, //GAZ RegionalIndicator,
L, //L Prepend,
LF, //LF SpacingMark,
LV, //LV L,
LVT, //LVT V,
Prepend, //PP T,
RegionalIndicator, //RI LV,
SpacingMark, //SM LVT,
T, //T ExtendedPictographic,
V, //V
Other, //XX
ZWJ, //ZWJ
ExtendedPictographic
} }
} }

4
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs

@ -34,10 +34,11 @@ namespace Avalonia.Media.TextFormatting.Unicode
EBase, //EB EBase, //EB
EModifier, //EM EModifier, //EM
ZWJ, //ZWJ ZWJ, //ZWJ
ContingentBreak, //CB
Unknown, //XX
Ambiguous, //AI Ambiguous, //AI
MandatoryBreak, //BK MandatoryBreak, //BK
ContingentBreak, //CB
ConditionalJapaneseStarter, //CJ ConditionalJapaneseStarter, //CJ
CarriageReturn, //CR CarriageReturn, //CR
LineFeed, //LF LineFeed, //LF
@ -45,6 +46,5 @@ namespace Avalonia.Media.TextFormatting.Unicode
ComplexContext, //SA ComplexContext, //SA
Surrogate, //SG Surrogate, //SG
Space, //SP Space, //SP
Unknown, //XX
} }
} }

4
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs

@ -95,7 +95,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
if (_nextClass.Value == LineBreakClass.MandatoryBreak) if (_nextClass.Value == LineBreakClass.MandatoryBreak)
{ {
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos); _lastPos = _pos;
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true);
return true; return true;
} }
@ -108,6 +109,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
case PairBreakType.DI: // Direct break case PairBreakType.DI: // Direct break
shouldBreak = true; shouldBreak = true;
_lastPos = _pos;
break; break;
case PairBreakType.IN: // possible indirect break case PairBreakType.IN: // possible indirect break

178
src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs

@ -0,0 +1,178 @@
using System.Collections.Generic;
namespace Avalonia.Media.TextFormatting.Unicode
{
internal static class PropertyValueAliasHelper
{
private static readonly Dictionary<Script, string> s_scriptToTag =
new Dictionary<Script, string>{
{ Script.Unknown, "Zzzz"},
{ Script.Common, "Zyyy"},
{ Script.Inherited, "Zinh"},
{ Script.Adlam, "Adlm"},
{ Script.CaucasianAlbanian, "Aghb"},
{ Script.Ahom, "Ahom"},
{ Script.Arabic, "Arab"},
{ Script.ImperialAramaic, "Armi"},
{ Script.Armenian, "Armn"},
{ Script.Avestan, "Avst"},
{ Script.Balinese, "Bali"},
{ Script.Bamum, "Bamu"},
{ Script.BassaVah, "Bass"},
{ Script.Batak, "Batk"},
{ Script.Bengali, "Beng"},
{ Script.Bhaiksuki, "Bhks"},
{ Script.Bopomofo, "Bopo"},
{ Script.Brahmi, "Brah"},
{ Script.Braille, "Brai"},
{ Script.Buginese, "Bugi"},
{ Script.Buhid, "Buhd"},
{ Script.Chakma, "Cakm"},
{ Script.CanadianAboriginal, "Cans"},
{ Script.Carian, "Cari"},
{ Script.Cham, "Cham"},
{ Script.Cherokee, "Cher"},
{ Script.Chorasmian, "Chrs"},
{ Script.Coptic, "Copt"},
{ Script.Cypriot, "Cprt"},
{ Script.Cyrillic, "Cyrl"},
{ Script.Devanagari, "Deva"},
{ Script.DivesAkuru, "Diak"},
{ Script.Dogra, "Dogr"},
{ Script.Deseret, "Dsrt"},
{ Script.Duployan, "Dupl"},
{ Script.EgyptianHieroglyphs, "Egyp"},
{ Script.Elbasan, "Elba"},
{ Script.Elymaic, "Elym"},
{ Script.Ethiopic, "Ethi"},
{ Script.Georgian, "Geor"},
{ Script.Glagolitic, "Glag"},
{ Script.GunjalaGondi, "Gong"},
{ Script.MasaramGondi, "Gonm"},
{ Script.Gothic, "Goth"},
{ Script.Grantha, "Gran"},
{ Script.Greek, "Grek"},
{ Script.Gujarati, "Gujr"},
{ Script.Gurmukhi, "Guru"},
{ Script.Hangul, "Hang"},
{ Script.Han, "Hani"},
{ Script.Hanunoo, "Hano"},
{ Script.Hatran, "Hatr"},
{ Script.Hebrew, "Hebr"},
{ Script.Hiragana, "Hira"},
{ Script.AnatolianHieroglyphs, "Hluw"},
{ Script.PahawhHmong, "Hmng"},
{ Script.NyiakengPuachueHmong, "Hmnp"},
{ Script.KatakanaOrHiragana, "Hrkt"},
{ Script.OldHungarian, "Hung"},
{ Script.OldItalic, "Ital"},
{ Script.Javanese, "Java"},
{ Script.KayahLi, "Kali"},
{ Script.Katakana, "Kana"},
{ Script.Kharoshthi, "Khar"},
{ Script.Khmer, "Khmr"},
{ Script.Khojki, "Khoj"},
{ Script.KhitanSmallScript, "Kits"},
{ Script.Kannada, "Knda"},
{ Script.Kaithi, "Kthi"},
{ Script.TaiTham, "Lana"},
{ Script.Lao, "Laoo"},
{ Script.Latin, "Latn"},
{ Script.Lepcha, "Lepc"},
{ Script.Limbu, "Limb"},
{ Script.LinearA, "Lina"},
{ Script.LinearB, "Linb"},
{ Script.Lisu, "Lisu"},
{ Script.Lycian, "Lyci"},
{ Script.Lydian, "Lydi"},
{ Script.Mahajani, "Mahj"},
{ Script.Makasar, "Maka"},
{ Script.Mandaic, "Mand"},
{ Script.Manichaean, "Mani"},
{ Script.Marchen, "Marc"},
{ Script.Medefaidrin, "Medf"},
{ Script.MendeKikakui, "Mend"},
{ Script.MeroiticCursive, "Merc"},
{ Script.MeroiticHieroglyphs, "Mero"},
{ Script.Malayalam, "Mlym"},
{ Script.Modi, "Modi"},
{ Script.Mongolian, "Mong"},
{ Script.Mro, "Mroo"},
{ Script.MeeteiMayek, "Mtei"},
{ Script.Multani, "Mult"},
{ Script.Myanmar, "Mymr"},
{ Script.Nandinagari, "Nand"},
{ Script.OldNorthArabian, "Narb"},
{ Script.Nabataean, "Nbat"},
{ Script.Newa, "Newa"},
{ Script.Nko, "Nkoo"},
{ Script.Nushu, "Nshu"},
{ Script.Ogham, "Ogam"},
{ Script.OlChiki, "Olck"},
{ Script.OldTurkic, "Orkh"},
{ Script.Oriya, "Orya"},
{ Script.Osage, "Osge"},
{ Script.Osmanya, "Osma"},
{ Script.Palmyrene, "Palm"},
{ Script.PauCinHau, "Pauc"},
{ Script.OldPermic, "Perm"},
{ Script.PhagsPa, "Phag"},
{ Script.InscriptionalPahlavi, "Phli"},
{ Script.PsalterPahlavi, "Phlp"},
{ Script.Phoenician, "Phnx"},
{ Script.Miao, "Plrd"},
{ Script.InscriptionalParthian, "Prti"},
{ Script.Rejang, "Rjng"},
{ Script.HanifiRohingya, "Rohg"},
{ Script.Runic, "Runr"},
{ Script.Samaritan, "Samr"},
{ Script.OldSouthArabian, "Sarb"},
{ Script.Saurashtra, "Saur"},
{ Script.SignWriting, "Sgnw"},
{ Script.Shavian, "Shaw"},
{ Script.Sharada, "Shrd"},
{ Script.Siddham, "Sidd"},
{ Script.Khudawadi, "Sind"},
{ Script.Sinhala, "Sinh"},
{ Script.Sogdian, "Sogd"},
{ Script.OldSogdian, "Sogo"},
{ Script.SoraSompeng, "Sora"},
{ Script.Soyombo, "Soyo"},
{ Script.Sundanese, "Sund"},
{ Script.SylotiNagri, "Sylo"},
{ Script.Syriac, "Syrc"},
{ Script.Tagbanwa, "Tagb"},
{ Script.Takri, "Takr"},
{ Script.TaiLe, "Tale"},
{ Script.NewTaiLue, "Talu"},
{ Script.Tamil, "Taml"},
{ Script.Tangut, "Tang"},
{ Script.TaiViet, "Tavt"},
{ Script.Telugu, "Telu"},
{ Script.Tifinagh, "Tfng"},
{ Script.Tagalog, "Tglg"},
{ Script.Thaana, "Thaa"},
{ Script.Thai, "Thai"},
{ Script.Tibetan, "Tibt"},
{ Script.Tirhuta, "Tirh"},
{ Script.Ugaritic, "Ugar"},
{ Script.Vai, "Vaii"},
{ Script.WarangCiti, "Wara"},
{ Script.Wancho, "Wcho"},
{ Script.OldPersian, "Xpeo"},
{ Script.Cuneiform, "Xsux"},
{ Script.Yezidi, "Yezi"},
{ Script.Yi, "Yiii"},
{ Script.ZanabazarSquare, "Zanb"},
};
public static string GetTag(Script script)
{
if(!s_scriptToTag.ContainsKey(script))
{
return "Zzzz";
}
return s_scriptToTag[script];
}
}
}

10
src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs

@ -2,6 +2,9 @@ namespace Avalonia.Media.TextFormatting.Unicode
{ {
public enum Script public enum Script
{ {
Unknown, //Zzzz
Common, //Zyyy
Inherited, //Zinh
Adlam, //Adlm Adlam, //Adlm
CaucasianAlbanian, //Aghb CaucasianAlbanian, //Aghb
Ahom, //Ahom Ahom, //Ahom
@ -25,10 +28,12 @@ namespace Avalonia.Media.TextFormatting.Unicode
Carian, //Cari Carian, //Cari
Cham, //Cham Cham, //Cham
Cherokee, //Cher Cherokee, //Cher
Chorasmian, //Chrs
Coptic, //Copt Coptic, //Copt
Cypriot, //Cprt Cypriot, //Cprt
Cyrillic, //Cyrl Cyrillic, //Cyrl
Devanagari, //Deva Devanagari, //Deva
DivesAkuru, //Diak
Dogra, //Dogr Dogra, //Dogr
Deseret, //Dsrt Deseret, //Dsrt
Duployan, //Dupl Duployan, //Dupl
@ -63,6 +68,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
Kharoshthi, //Khar Kharoshthi, //Khar
Khmer, //Khmr Khmer, //Khmr
Khojki, //Khoj Khojki, //Khoj
KhitanSmallScript, //Kits
Kannada, //Knda Kannada, //Knda
Kaithi, //Kthi Kaithi, //Kthi
TaiTham, //Lana TaiTham, //Lana
@ -151,10 +157,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
Wancho, //Wcho Wancho, //Wcho
OldPersian, //Xpeo OldPersian, //Xpeo
Cuneiform, //Xsux Cuneiform, //Xsux
Yezidi, //Yezi
Yi, //Yiii Yi, //Yiii
ZanabazarSquare, //Zanb ZanabazarSquare, //Zanb
Inherited, //Zinh
Common, //Zyyy
Unknown, //Zzzz
} }
} }

15
src/Avalonia.Visuals/Rect.cs

@ -211,6 +211,21 @@ namespace Avalonia
rect.Width * scale.X, rect.Width * scale.X,
rect.Height * scale.Y); rect.Height * scale.Y);
} }
/// <summary>
/// Multiplies a rectangle by a scale.
/// </summary>
/// <param name="rect">The rectangle.</param>
/// <param name="scale">The scale.</param>
/// <returns>The scaled rectangle.</returns>
public static Rect operator *(Rect rect, double scale)
{
return new Rect(
rect.X * scale,
rect.Y * scale,
rect.Width * scale,
rect.Height * scale);
}
/// <summary> /// <summary>
/// Divides a rectangle by a vector. /// Divides a rectangle by a vector.

22
src/Avalonia.X11/X11NativeControlHost.cs

@ -157,21 +157,30 @@ namespace Avalonia.X11
public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost; public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost;
public void Hide() public void HideWithSize(Size size)
{ {
if(_attachedTo == null || _child == null) if(_attachedTo == null || _child == null)
return; return;
_mapped = false; if (_mapped)
XUnmapWindow(_display, _holder.Handle); {
_mapped = false;
XUnmapWindow(_display, _holder.Handle);
}
size *= _attachedTo.Window.Scaling;
XResizeWindow(_display, _child.Handle,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
} }
public void ShowInBounds(TransformedBounds transformedBounds)
public void ShowInBounds(Rect bounds)
{ {
CheckDisposed(); CheckDisposed();
if (_attachedTo == null) if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel"); throw new InvalidOperationException("The control isn't currently attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) * bounds *= _attachedTo.Window.Scaling;
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height)); Math.Max(1, (int)bounds.Height));
XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height); XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height);
@ -183,7 +192,6 @@ namespace Avalonia.X11
XRaiseWindow(_display, _holder.Handle); XRaiseWindow(_display, _holder.Handle);
_mapped = true; _mapped = true;
} }
Console.WriteLine($"Moved {_child.Handle} to {pixelRect}");
} }
} }
} }

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

12
src/Windows/Avalonia.Win32/Win32NativeControlHost.cs

@ -168,21 +168,25 @@ namespace Avalonia.Win32
public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost; public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost;
public void Hide() public void HideWithSize(Size size)
{ {
UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero, UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero,
-100, -100, 1, 1, -100, -100, 1, 1,
UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW | UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW |
UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
if (_attachedTo == null || _child == null)
return;
size *= _attachedTo.Window.Scaling;
UnmanagedMethods.MoveWindow(_child.Handle, 0, 0,
Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false);
} }
public unsafe void ShowInBounds(TransformedBounds transformedBounds) public unsafe void ShowInBounds(Rect bounds)
{ {
CheckDisposed(); CheckDisposed();
if (_attachedTo == null) if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel"); throw new InvalidOperationException("The control isn't currently attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) * bounds *= _attachedTo.Window.Scaling;
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height)); Math.Max(1, (int)bounds.Height));

95
tests/Avalonia.Animation.UnitTests/KeySplineTests.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Animation.Easings;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
@ -25,6 +26,16 @@ namespace Avalonia.Animation.UnitTests
Assert.Equal(4, keySpline.ControlPointY2); Assert.Equal(4, keySpline.ControlPointY2);
} }
[Theory]
[InlineData("1,2F,3,4")]
[InlineData("Foo,Bar,Fee,Buzz")]
public void Can_Handle_Invalid_String_KeySpline_Via_TypeConverter(string input)
{
var conv = new KeySplineTypeConverter();
Assert.ThrowsAny<Exception>(() => (KeySpline)conv.ConvertFrom(input));
}
[Theory] [Theory]
[InlineData(0.00)] [InlineData(0.00)]
[InlineData(0.50)] [InlineData(0.50)]
@ -46,6 +57,22 @@ namespace Avalonia.Animation.UnitTests
Assert.Throws<ArgumentException>(() => keySpline.ControlPointX2 = input); Assert.Throws<ArgumentException>(() => keySpline.ControlPointX2 = input);
} }
[Fact]
public void SplineEasing_Can_Be_Mutated()
{
var easing = new SplineEasing();
Assert.Equal(0, easing.Ease(0));
Assert.Equal(1, easing.Ease(1));
easing.X1 = 0.25;
easing.Y1 = 0.5;
easing.X2 = 0.75;
easing.Y2 = 1.0;
Assert.NotEqual(0.5, easing.Ease(0.5));
}
/* /*
To get the test values for the KeySpline test, you can: To get the test values for the KeySpline test, you can:
1) Grab the WPF sample for KeySpline animations from https://github.com/microsoft/WPF-Samples/tree/master/Animation/KeySplineAnimations 1) Grab the WPF sample for KeySpline animations from https://github.com/microsoft/WPF-Samples/tree/master/Animation/KeySplineAnimations
@ -141,5 +168,73 @@ namespace Avalonia.Animation.UnitTests
expected = 1.8016358493761722; expected = 1.8016358493761722;
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
} }
[Fact]
public void Check_KeySpline_Parsing_Is_Correct()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(RotateTransform.AngleProperty, -2.5d),
},
KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(RotateTransform.AngleProperty, 2.5d),
},
KeyTime = TimeSpan.FromSeconds(5),
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(5),
Children =
{
keyframe1,
keyframe2
},
IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate,
Easing = Easing.Parse("0.1123555056179775,0.657303370786517,0.8370786516853934,0.499999999999999999")
};
var rotateTransform = new RotateTransform(-2.5);
var rect = new Rectangle()
{
RenderTransform = rotateTransform
};
var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock);
// position is what you'd expect at end and beginning
clock.Step(TimeSpan.Zero);
Assert.Equal(rotateTransform.Angle, -2.5);
clock.Step(TimeSpan.FromSeconds(5));
Assert.Equal(rotateTransform.Angle, 2.5);
// test some points in between end and beginning
var tolerance = 0.01;
clock.Step(TimeSpan.Parse("00:00:10.0153932"));
var expected = -2.4122350198982545;
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
clock.Step(TimeSpan.Parse("00:00:11.2655407"));
expected = -0.37153223002125113;
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
clock.Step(TimeSpan.Parse("00:00:12.6158773"));
expected = 0.3967885416786294;
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
clock.Step(TimeSpan.Parse("00:00:14.6495256"));
expected = 1.8016358493761722;
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
}
} }
} }

255
tests/Avalonia.Controls.UnitTests/DatePickerTests.cs

@ -0,0 +1,255 @@
using System;
using System.Linq;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class DatePickerTests
{
[Fact]
public void SelectedDateChanged_Should_Fire_When_SelectedDate_Set()
{
using (UnitTestApplication.Start(Services))
{
bool handled = false;
DatePicker datePicker = new DatePicker();
datePicker.SelectedDateChanged += (s, e) =>
{
handled = true;
};
DateTimeOffset value = new DateTimeOffset(2000, 10, 10, 0, 0, 0, TimeSpan.Zero);
datePicker.SelectedDate = value;
Threading.Dispatcher.UIThread.RunJobs();
Assert.True(handled);
}
}
[Fact]
public void DayVisible_False_Should_Hide_Day()
{
using (UnitTestApplication.Start(Services))
{
DatePicker datePicker = new DatePicker
{
Template = CreateTemplate(),
DayVisible = false
};
datePicker.ApplyTemplate();
Threading.Dispatcher.UIThread.RunJobs();
var desc = datePicker.GetVisualDescendants();
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
TextBlock dayText = null;
Grid container = null;
Assert.True(desc.ElementAt(1) is Button);
container = (desc.ElementAt(1) as Button).Content as Grid;
Assert.True(container != null);
for(int i = 0; i < container.Children.Count; i++)
{
if(container.Children[i] is TextBlock tb && tb.Name == "DayText")
{
dayText = tb;
break;
}
}
Assert.True(dayText != null);
Assert.True(!dayText.IsVisible);
Assert.True(container.ColumnDefinitions.Count == 3);
}
}
[Fact]
public void MonthVisible_False_Should_Hide_Month()
{
using (UnitTestApplication.Start(Services))
{
DatePicker datePicker = new DatePicker
{
Template = CreateTemplate(),
MonthVisible = false
};
datePicker.ApplyTemplate();
Threading.Dispatcher.UIThread.RunJobs();
var desc = datePicker.GetVisualDescendants();
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
TextBlock monthText = null;
Grid container = null;
Assert.True(desc.ElementAt(1) is Button);
container = (desc.ElementAt(1) as Button).Content as Grid;
Assert.True(container != null);
for (int i = 0; i < container.Children.Count; i++)
{
if (container.Children[i] is TextBlock tb && tb.Name == "MonthText")
{
monthText = tb;
break;
}
}
Assert.True(monthText != null);
Assert.True(!monthText.IsVisible);
Assert.True(container.ColumnDefinitions.Count == 3);
}
}
[Fact]
public void YearVisible_False_Should_Hide_Year()
{
using (UnitTestApplication.Start(Services))
{
DatePicker datePicker = new DatePicker
{
Template = CreateTemplate(),
YearVisible = false
};
datePicker.ApplyTemplate();
Threading.Dispatcher.UIThread.RunJobs();
var desc = datePicker.GetVisualDescendants();
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
TextBlock yearText = null;
Grid container = null;
Assert.True(desc.ElementAt(1) is Button);
container = (desc.ElementAt(1) as Button).Content as Grid;
Assert.True(container != null);
for (int i = 0; i < container.Children.Count; i++)
{
if (container.Children[i] is TextBlock tb && tb.Name == "YearText")
{
yearText = tb;
break;
}
}
Assert.True(yearText != null);
Assert.True(!yearText.IsVisible);
Assert.True(container.ColumnDefinitions.Count == 3);
}
}
[Fact]
public void SelectedDate_null_Should_Use_Placeholders()
{
using (UnitTestApplication.Start(Services))
{
DatePicker datePicker = new DatePicker
{
Template = CreateTemplate(),
YearVisible = false
};
datePicker.ApplyTemplate();
Threading.Dispatcher.UIThread.RunJobs();
var desc = datePicker.GetVisualDescendants();
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
TextBlock yearText = null;
TextBlock monthText = null;
TextBlock dayText = null;
Grid container = null;
Assert.True(desc.ElementAt(1) is Button);
container = (desc.ElementAt(1) as Button).Content as Grid;
Assert.True(container != null);
for (int i = 0; i < container.Children.Count; i++)
{
if (container.Children[i] is TextBlock tb && tb.Name == "YearText")
{
yearText = tb;
}
else if (container.Children[i] is TextBlock tb1 && tb1.Name == "MonthText")
{
monthText = tb1;
}
else if (container.Children[i] is TextBlock tb2 && tb2.Name == "DayText")
{
dayText = tb2;
}
}
DateTimeOffset value = new DateTimeOffset(2000, 10, 10, 0, 0, 0, TimeSpan.Zero);
datePicker.SelectedDate = value;
Assert.False(dayText.Text == "day");
Assert.False(monthText.Text == "month");
Assert.False(yearText.Text == "year");
Assert.False(datePicker.Classes.Contains(":hasnodate"));
datePicker.SelectedDate = null;
Assert.True(dayText.Text == "day");
Assert.True(monthText.Text == "month");
Assert.True(yearText.Text == "year");
Assert.True(datePicker.Classes.Contains(":hasnodate"));
}
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new MockFontManagerImpl(),
standardCursorFactory: Mock.Of<IStandardCursorFactory>(),
textShaperImpl: new MockTextShaperImpl());
private IControlTemplate CreateTemplate()
{
return new FuncControlTemplate((control, scope) =>
{
var layoutRoot = new Grid
{
Name = "LayoutRoot"
}.RegisterInNameScope(scope);
//Skip contentpresenter
var flyoutButton = new Button
{
Name = "FlyoutButton"
}.RegisterInNameScope(scope);
var contentGrid = new Grid
{
Name = "ButtonContentGrid"
}.RegisterInNameScope(scope);
var dayText = new TextBlock
{
Name = "DayText"
}.RegisterInNameScope(scope);
var monthText = new TextBlock
{
Name = "MonthText"
}.RegisterInNameScope(scope);
var yearText = new TextBlock
{
Name = "YearText"
}.RegisterInNameScope(scope);
var firstSpacer = new Rectangle
{
Name = "FirstSpacer"
}.RegisterInNameScope(scope);
var secondSpacer = new Rectangle
{
Name = "SecondSpacer"
}.RegisterInNameScope(scope);
contentGrid.Children.AddRange(new IControl[] { dayText, monthText, yearText, firstSpacer, secondSpacer });
flyoutButton.Content = contentGrid;
layoutRoot.Children.Add(flyoutButton);
return layoutRoot;
});
}
}
}

66
tests/Avalonia.Controls.UnitTests/SplitViewTests.cs

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class SplitViewTests
{
[Fact]
public void SplitView_PaneOpening_Should_Fire_Before_PaneOpened()
{
var splitView = new SplitView();
bool handledOpening = false;
splitView.PaneOpening += (x, e) =>
{
handledOpening = true;
};
splitView.PaneOpened += (x, e) =>
{
Assert.True(handledOpening);
};
splitView.IsPaneOpen = true;
}
[Fact]
public void SplitView_PaneClosing_Should_Fire_Before_PaneClosed()
{
var splitView = new SplitView();
splitView.IsPaneOpen = true;
bool handledClosing = false;
splitView.PaneClosing += (x, e) =>
{
handledClosing = true;
};
splitView.PaneClosed += (x, e) =>
{
Assert.True(handledClosing);
};
splitView.IsPaneOpen = false;
}
[Fact]
public void SplitView_Cancel_Close_Should_Prevent_Pane_From_Closing()
{
var splitView = new SplitView();
splitView.IsPaneOpen = true;
splitView.PaneClosing += (x, e) =>
{
e.Cancel = true;
};
splitView.IsPaneOpen = false;
Assert.True(splitView.IsPaneOpen);
}
}
}

173
tests/Avalonia.Controls.UnitTests/TimePickerTests.cs

@ -0,0 +1,173 @@
using System;
using System.Linq;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class TimePickerTests
{
[Fact]
public void SelectedTimeChanged_Should_Fire_When_SelectedTime_Set()
{
using (UnitTestApplication.Start(Services))
{
bool handled = false;
TimePicker timePicker = new TimePicker();
timePicker.SelectedTimeChanged += (s, e) =>
{
handled = true;
};
TimeSpan value = TimeSpan.FromHours(10);
timePicker.SelectedTime = value;
Threading.Dispatcher.UIThread.RunJobs();
Assert.True(handled);
}
}
[Fact]
public void Using_24HourClock_Should_Hide_Period()
{
using (UnitTestApplication.Start(Services))
{
TimePicker timePicker = new TimePicker()
{
ClockIdentifier = "12HourClock",
Template = CreateTemplate()
};
timePicker.ApplyTemplate();
var desc = timePicker.GetVisualDescendants();
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
Grid container = null;
Assert.True(desc.ElementAt(1) is Button);
container = (desc.ElementAt(1) as Button).Content as Grid;
Assert.True(container != null);
var periodTextHost = container.Children[4] as Border;
Assert.True(periodTextHost != null);
Assert.True(periodTextHost.IsVisible);
timePicker.ClockIdentifier = "24HourClock";
Assert.False(periodTextHost.IsVisible);
}
}
[Fact]
public void SelectedTime_null_Should_Use_Placeholders()
{
using (UnitTestApplication.Start(Services))
{
TimePicker timePicker = new TimePicker()
{
Template = CreateTemplate()
};
timePicker.ApplyTemplate();
var desc = timePicker.GetVisualDescendants();
Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
Grid container = null;
Assert.True(desc.ElementAt(1) is Button);
container = (desc.ElementAt(1) as Button).Content as Grid;
Assert.True(container != null);
var hourTextHost = container.Children[0] as Border;
Assert.True(hourTextHost != null);
var hourText = hourTextHost.Child as TextBlock;
var minuteTextHost = container.Children[2] as Border;
Assert.True(minuteTextHost != null);
var minuteText = minuteTextHost.Child as TextBlock;
TimeSpan ts = TimeSpan.FromHours(10);
timePicker.SelectedTime = ts;
Assert.False(hourText.Text == "hour");
Assert.False(minuteText.Text == "minute");
timePicker.SelectedTime = null;
Assert.True(hourText.Text == "hour");
Assert.True(minuteText.Text == "minute");
}
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new MockFontManagerImpl(),
standardCursorFactory: Mock.Of<IStandardCursorFactory>(),
textShaperImpl: new MockTextShaperImpl());
private IControlTemplate CreateTemplate()
{
return new FuncControlTemplate((control, scope) =>
{
var layoutRoot = new Grid
{
Name = "LayoutRoot"
}.RegisterInNameScope(scope);
//Skip contentpresenter
var flyoutButton = new Button
{
Name = "FlyoutButton"
}.RegisterInNameScope(scope);
var contentGrid = new Grid
{
Name = "FlyoutButtonContentGrid"
}.RegisterInNameScope(scope);
var firstPickerHost = new Border
{
Name = "FirstPickerHost",
Child = new TextBlock
{
Name = "HourTextBlock"
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope);
Grid.SetColumn(firstPickerHost, 0);
var secondPickerHost = new Border
{
Name = "SecondPickerHost",
Child = new TextBlock
{
Name = "MinuteTextBlock"
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope);
Grid.SetColumn(secondPickerHost, 2);
var thirdPickerHost = new Border
{
Name = "ThirdPickerHost",
Child = new TextBlock
{
Name = "PeriodTextBlock"
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope);
Grid.SetColumn(thirdPickerHost, 4);
var firstSpacer = new Rectangle
{
Name = "FirstColumnDivider"
}.RegisterInNameScope(scope);
Grid.SetColumn(firstSpacer, 1);
var secondSpacer = new Rectangle
{
Name = "SecondColumnDivider"
}.RegisterInNameScope(scope);
Grid.SetColumn(secondSpacer, 3);
contentGrid.Children.AddRange(new IControl[] { firstPickerHost, firstSpacer, secondPickerHost, secondSpacer, thirdPickerHost });
flyoutButton.Content = contentGrid;
layoutRoot.Children.Add(flyoutButton);
return layoutRoot;
});
}
}
}

78
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs

@ -1,4 +1,6 @@
using System; using System;
using System.Collections;
using System.ComponentModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Data; using Avalonia.Data;
@ -89,6 +91,69 @@ namespace Avalonia.Markup.UnitTests.Parsers
GC.KeepAlive(data); GC.KeepAlive(data);
} }
[Fact]
public async Task Should_Negate_BindingNotification_Value()
{
var data = new { Foo = true };
var target = ExpressionObserverBuilder.Build(data, "!Foo", enableDataValidation: true);
var result = await target.Take(1);
Assert.Equal(new BindingNotification(false), result);
GC.KeepAlive(data);
}
[Fact]
public async Task Should_Pass_Through_BindingNotification_Error()
{
var data = new { };
var target = ExpressionObserverBuilder.Build(data, "!Foo", enableDataValidation: true);
var result = await target.Take(1);
Assert.Equal(
new BindingNotification(
new MissingMemberException("Could not find a matching property accessor for 'Foo' on '{ }'"),
BindingErrorType.Error),
result);
GC.KeepAlive(data);
}
[Fact]
public async Task Should_Negate_BindingNotification_Error_FallbackValue()
{
var data = new Test { DataValidationError = "Test error" };
var target = ExpressionObserverBuilder.Build(data, "!Foo", enableDataValidation: true);
var result = await target.Take(1);
Assert.Equal(
new BindingNotification(
new DataValidationException("Test error"),
BindingErrorType.DataValidationError,
true),
result);
GC.KeepAlive(data);
}
[Fact]
public async Task Should_Add_Error_To_BindingNotification_For_FallbackValue_Not_Convertible_To_Boolean()
{
var data = new Test { Bar = new object(), DataValidationError = "Test error" };
var target = ExpressionObserverBuilder.Build(data, "!Bar", enableDataValidation: true);
var result = await target.Take(1);
Assert.Equal(
new BindingNotification(
new AggregateException(
new DataValidationException("Test error"),
new InvalidCastException($"Unable to convert 'System.Object' to bool.")),
BindingErrorType.Error),
result);
GC.KeepAlive(data);
}
[Fact] [Fact]
public void SetValue_Should_Return_False_For_Invalid_Value() public void SetValue_Should_Return_False_For_Invalid_Value()
{ {
@ -101,9 +166,20 @@ namespace Avalonia.Markup.UnitTests.Parsers
GC.KeepAlive(data); GC.KeepAlive(data);
} }
private class Test private class Test : INotifyDataErrorInfo
{ {
public bool Foo { get; set; } public bool Foo { get; set; }
public object Bar { get; set; }
public string DataValidationError { get; set; }
public bool HasErrors => !string.IsNullOrWhiteSpace(DataValidationError);
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
return DataValidationError is object ? new[] { DataValidationError } : null;
}
} }
} }
} }

54
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -382,5 +382,59 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(Border.WidthProperty, border.Transitions[0].Property); Assert.Equal(Border.WidthProperty, border.Transitions[0].Property);
} }
} }
[Fact]
public void Style_Can_Use_Class_Selector_With_Dash()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Border.foo-bar'>
<Setter Property='Background' Value='Red'/>
</Style>
</Window.Styles>
<StackPanel>
<Border Name='foo' Classes='foo-bar'/>
</StackPanel>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var foo = window.FindControl<Border>("foo");
Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
}
}
[Fact]
public void Style_Can_Use_Pseudolass_Selector_With_Dash()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Border:foo-bar'>
<Setter Property='Background' Value='Red'/>
</Style>
</Window.Styles>
<StackPanel>
<Border Name='foo'/>
</StackPanel>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var foo = window.FindControl<Border>("foo");
Assert.Null(foo.Background);
((IPseudoClasses)foo.Classes).Add(":foo-bar");
Assert.Equal(Colors.Red, ((ISolidColorBrush)foo.Background).Color);
}
}
} }
} }

67
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt

@ -1,33 +1,34 @@
OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ CB
OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % %
GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % %
NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
EX _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % EX _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
SY _ ^ ^ % % % ^ ^ ^ _ _ % _ % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % SY _ ^ ^ % % % ^ ^ ^ _ _ % _ % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
IS _ ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % IS _ ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
PR % ^ ^ % % % ^ ^ ^ _ _ % % % % _ % % _ _ ^ # ^ % % % % % _ % % % PR % ^ ^ % % % ^ ^ ^ _ _ % % % % _ % % _ _ ^ # ^ % % % % % _ % % % _
PO % ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % PO % ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
NU % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % NU % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
AL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % AL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
HL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % HL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
ID _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % ID _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
IN _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % IN _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
HY _ ^ ^ % _ % ^ ^ ^ _ _ % _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % HY _ ^ ^ % _ % ^ ^ ^ _ _ % _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
BA _ ^ ^ % _ % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % BA _ ^ ^ % _ % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
BB % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % BB % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % _
B2 _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ ^ ^ # ^ _ _ _ _ _ _ _ _ % B2 _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ ^ ^ # ^ _ _ _ _ _ _ _ _ % _
ZW _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ^ _ _ _ _ _ _ _ _ _ _ _ ZW _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ^ _ _ _ _ _ _ _ _ _ _ _ _
CM % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % CM % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
WJ % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % WJ % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % %
H2 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % H2 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % _
H3 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % H3 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % _
JL _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ % % % % _ _ _ _ % JL _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ % % % % _ _ _ _ % _
JV _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % JV _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % _
JT _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % JT _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % _
RI _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ % _ _ % RI _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ % _ _ % _
EB _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ % % EB _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ % % _
EM _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % EM _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
ZWJ _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ % _ % % _ _ ^ # ^ _ _ _ _ _ _ % % % ZWJ % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _
CB _ ^ ^ % % _ ^ ^ ^ _ _ _ _ _ _ _ _ _ _ _ ^ # ^ _ _ _ _ _ _ _ _ % _

61
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
@ -12,6 +11,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
public static void Execute() public static void Execute()
{ {
if (!Directory.Exists("Generated"))
{
Directory.CreateDirectory("Generated");
}
using (var stream = File.Create("Generated\\GraphemeBreak.trie")) using (var stream = File.Create("Generated\\GraphemeBreak.trie"))
{ {
var trie = GenerateBreakTypeTrie(); var trie = GenerateBreakTypeTrie();
@ -22,48 +26,29 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
private static UnicodeTrie GenerateBreakTypeTrie() private static UnicodeTrie GenerateBreakTypeTrie()
{ {
var graphemeBreakClassValues = UnicodeEnumsGenerator.GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)");
var graphemeBreakClassMapping = graphemeBreakClassValues.Select(x => x.name).ToList();
var trieBuilder = new UnicodeTrieBuilder(); var trieBuilder = new UnicodeTrieBuilder();
var graphemeBreakData = ReadBreakData( var graphemeBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/GraphemeBreakProperty.txt"));
"https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt");
foreach (var (start, end, graphemeBreakType) in graphemeBreakData)
{
if (!graphemeBreakClassMapping.Contains(graphemeBreakType))
{
continue;
}
if (start == end)
{
trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
}
else
{
trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType));
}
}
var emojiBreakData = ReadBreakData("https://unicode.org/Public/emoji/12.0/emoji-data.txt"); var emojiBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "emoji/emoji-data.txt"));
foreach (var (start, end, graphemeBreakType) in emojiBreakData) foreach (var breakData in new [] { graphemeBreakData, emojiBreakData })
{ {
if (!graphemeBreakClassMapping.Contains(graphemeBreakType)) foreach (var (start, end, graphemeBreakType) in breakData)
{ {
continue; if (!Enum.TryParse<GraphemeBreakClass>(graphemeBreakType, out var value))
} {
continue;
}
if (start == end) if (start == end)
{ {
trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType)); trieBuilder.Set(start, (uint)value);
} }
else else
{ {
trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType)); trieBuilder.SetRange(start, end, (uint)value);
}
} }
} }
@ -113,7 +98,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
end = Convert.ToInt32(match.Groups[2].Value, 16); end = Convert.ToInt32(match.Groups[2].Value, 16);
} }
data.Add((start, end, match.Groups[3].Value)); var breakType = match.Groups[3].Value;
data.Add((start, end, breakType));
} }
} }
} }

83
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs

@ -1,9 +1,4 @@
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Media.TextFormatting.Unicode;
using Xunit; using Xunit;
@ -16,10 +11,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
public class GraphemeBreakClassTrieGeneratorTests public class GraphemeBreakClassTrieGeneratorTests
{ {
[Theory(Skip = "Only run when we update the trie.")] [Theory(Skip = "Only run when we update the trie.")]
[ClassData(typeof(GraphemeEnumeratorTestDataGenerator))] [ClassData(typeof(GraphemeBreakTestDataGenerator))]
public void Should_Enumerate(string text, int expectedLength) public void Should_Enumerate(string text, int expectedLength)
{ {
var enumerator = new GraphemeEnumerator(text.AsMemory()); var textMemory = text.AsMemory();
var enumerator = new GraphemeEnumerator(textMemory);
Assert.True(enumerator.MoveNext()); Assert.True(enumerator.MoveNext());
@ -31,7 +28,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
const string text = "ABCDEFGHIJ"; const string text = "ABCDEFGHIJ";
var enumerator = new GraphemeEnumerator(text.AsMemory()); var textMemory = text.AsMemory();
var enumerator = new GraphemeEnumerator(textMemory);
var count = 0; var count = 0;
@ -51,73 +50,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
GraphemeBreakClassTrieGenerator.Execute(); GraphemeBreakClassTrieGenerator.Execute();
} }
public class GraphemeEnumeratorTestDataGenerator : IEnumerable<object[]> private class GraphemeBreakTestDataGenerator : TestDataGenerator
{ {
private readonly List<object[]> _testData; public GraphemeBreakTestDataGenerator()
: base("auxiliary/GraphemeBreakTest.txt")
public GraphemeEnumeratorTestDataGenerator()
{
_testData = ReadTestData();
}
public IEnumerator<object[]> GetEnumerator()
{
return _testData.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private static List<object[]> ReadTestData()
{ {
var testData = new List<object[]>();
using (var client = new HttpClient())
{
using (var result = client.GetAsync("https://www.unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakTest.txt").GetAwaiter().GetResult())
{
if (!result.IsSuccessStatusCode)
return testData;
using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
using (var reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line == null)
{
break;
}
if (line.StartsWith("#") || string.IsNullOrEmpty(line))
{
continue;
}
var elements = line.Split('#')[0].Replace("÷\t", "÷").Trim('÷').Split('÷');
var chars = elements[0].Replace(" × ", " ").Split(' ');
var codepoints = chars.Where(x => x != "" && x != "×")
.Select(x => Convert.ToInt32(x, 16)).ToArray();
var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32));
var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum();
var data = new object[] { text, length };
testData.Add(data);
}
}
}
}
return testData;
} }
} }
} }

2
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs

@ -3,7 +3,7 @@ using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utility; using Avalonia.Utility;
using Xunit; using Xunit;
namespace Avalonia.Visuals.UnitTests.Media.Text namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
public class LineBreakerTests public class LineBreakerTests
{ {

85
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs

@ -0,0 +1,85 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{
public abstract class TestDataGenerator : IEnumerable<object[]>
{
private readonly string _fileName;
private readonly List<object[]> _testData;
protected TestDataGenerator(string fileName)
{
_fileName = fileName;
_testData = ReadTestData();
}
public IEnumerator<object[]> GetEnumerator()
{
return _testData.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private List<object[]> ReadTestData()
{
var testData = new List<object[]>();
using (var client = new HttpClient())
{
var url = Path.Combine(UnicodeDataGenerator.Ucd, _fileName);
using (var result = client.GetAsync(url).GetAwaiter().GetResult())
{
if (!result.IsSuccessStatusCode)
return testData;
using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
using (var reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line == null)
{
break;
}
if (line.StartsWith("#") || string.IsNullOrEmpty(line))
{
continue;
}
var elements = line.Split('#');
elements = elements[0].Replace("÷\t", "÷").Trim('÷').Split('÷');
var chars = elements[0].Replace(" × ", " ").Split(' ');
var codepoints = chars.Where(x => x != "" && x != "×")
.Select(x => Convert.ToInt32(x, 16)).ToArray();
var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32));
var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum();
var data = new object[] { text, length };
testData.Add(data);
}
}
}
}
return testData;
}
}
}

61
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs

@ -9,13 +9,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
internal static class UnicodeDataGenerator internal static class UnicodeDataGenerator
{ {
public const string Ucd = "https://www.unicode.org/Public/13.0.0/ucd/";
public static void Execute() public static void Execute()
{ {
var codepoints = new Dictionary<int, UnicodeDataItem>(); var codepoints = new Dictionary<int, UnicodeDataItem>();
var generalCategoryValues = UnicodeEnumsGenerator.CreateGeneralCategoryEnum(); var generalCategoryEntries =
UnicodeEnumsGenerator.CreateGeneralCategoryEnum();
var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryValues); var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryEntries);
var generalCategoryData = ReadGeneralCategoryData(); var generalCategoryData = ReadGeneralCategoryData();
@ -26,23 +29,23 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddGeneralCategoryRange(codepoints, range, generalCategory); AddGeneralCategoryRange(codepoints, range, generalCategory);
} }
var scriptValues = UnicodeEnumsGenerator.CreateScriptEnum(); var scriptEntries = UnicodeEnumsGenerator.CreateScriptEnum();
var scriptMappings = CreateNameToIndexMappings(scriptValues); var scriptMappings = CreateNameToIndexMappings(scriptEntries);
var scriptData = ReadScriptData(); var scriptData = ReadScriptData();
foreach (var (range, name) in scriptData) foreach (var (range, name) in scriptData)
{ {
var script = scriptMappings[name.Replace("_", "")]; var script = scriptMappings[name];
AddScriptRange(codepoints, range, script); AddScriptRange(codepoints, range, script);
} }
var biDiClassValues = UnicodeEnumsGenerator.CreateBiDiClassEnum(); var biDiClassEntries =
UnicodeEnumsGenerator.CreateBiDiClassEnum();
var biDiClassMappings = CreateTagToIndexMappings(biDiClassValues); var biDiClassMappings = CreateTagToIndexMappings(biDiClassEntries);
var biDiData = ReadBiDiData(); var biDiData = ReadBiDiData();
@ -53,9 +56,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddBiDiClassRange(codepoints, range, biDiClass); AddBiDiClassRange(codepoints, range, biDiClass);
} }
var lineBreakClassValues = UnicodeEnumsGenerator.CreateLineBreakClassEnum(); var lineBreakClassEntries =
UnicodeEnumsGenerator.CreateLineBreakClassEnum();
var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassValues); var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassEntries);
var lineBreakClassData = ReadLineBreakClassData(); var lineBreakClassData = ReadLineBreakClassData();
@ -66,11 +70,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
AddLineBreakClassRange(codepoints, range, lineBreakClass); AddLineBreakClassRange(codepoints, range, lineBreakClass);
} }
const int initialValue = ((int)LineBreakClass.Unknown << UnicodeData.LINEBREAK_SHIFT) | //const int initialValue = (0 << UnicodeData.LINEBREAK_SHIFT) |
((int)BiDiClass.LeftToRight << UnicodeData.BIDI_SHIFT) | // (0 << UnicodeData.BIDI_SHIFT) |
((int)Script.Unknown << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other; // (0 << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other;
var builder = new UnicodeTrieBuilder(initialValue); var builder = new UnicodeTrieBuilder(/*initialValue*/);
foreach (var properties in codepoints.Values) foreach (var properties in codepoints.Values)
{ {
@ -88,27 +92,30 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
trie.Save(stream); trie.Save(stream);
} }
UnicodeEnumsGenerator.CreatePropertyValueAliasHelper(scriptEntries, generalCategoryEntries,
biDiClassEntries, lineBreakClassEntries);
} }
private static Dictionary<string, int> CreateTagToIndexMappings(List<(string name, string tag, string comment)> values) private static Dictionary<string, int> CreateTagToIndexMappings(List<DataEntry> entries)
{ {
var mappings = new Dictionary<string, int>(); var mappings = new Dictionary<string, int>();
for (var i = 0; i < values.Count; i++) for (var i = 0; i < entries.Count; i++)
{ {
mappings.Add(values[i].tag, i); mappings.Add(entries[i].Tag, i);
} }
return mappings; return mappings;
} }
private static Dictionary<string, int> CreateNameToIndexMappings(List<(string name, string tag, string comment)> values) private static Dictionary<string, int> CreateNameToIndexMappings(List<DataEntry> entries)
{ {
var mappings = new Dictionary<string, int>(); var mappings = new Dictionary<string, int>();
for (var i = 0; i < values.Count; i++) for (var i = 0; i < entries.Count; i++)
{ {
mappings.Add(values[i].name, i); mappings.Add(entries[i].Name, i);
} }
return mappings; return mappings;
@ -180,24 +187,22 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
public static List<(CodepointRange, string)> ReadGeneralCategoryData() public static List<(CodepointRange, string)> ReadGeneralCategoryData()
{ {
return ReadUnicodeData( return ReadUnicodeData("extracted/DerivedGeneralCategory.txt");
"https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedGeneralCategory.txt");
} }
public static List<(CodepointRange, string)> ReadScriptData() public static List<(CodepointRange, string)> ReadScriptData()
{ {
return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/Scripts.txt"); return ReadUnicodeData("Scripts.txt");
} }
public static List<(CodepointRange, string)> ReadBiDiData() public static List<(CodepointRange, string)> ReadBiDiData()
{ {
return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt"); return ReadUnicodeData("extracted/DerivedBidiClass.txt");
} }
public static List<(CodepointRange, string)> ReadLineBreakClassData() public static List<(CodepointRange, string)> ReadLineBreakClassData()
{ {
return ReadUnicodeData( return ReadUnicodeData("extracted/DerivedLineBreak.txt");
"https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedLineBreak.txt");
} }
private static List<(CodepointRange, string)> ReadUnicodeData(string file) private static List<(CodepointRange, string)> ReadUnicodeData(string file)
@ -208,7 +213,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
using (var result = client.GetAsync(file).GetAwaiter().GetResult()) var url = Path.Combine(Ucd, file);
using (var result = client.GetAsync(url).GetAwaiter().GetResult())
{ {
if (!result.IsSuccessStatusCode) if (!result.IsSuccessStatusCode)
{ {

25
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs

@ -1,4 +1,6 @@
using Xunit; using System;
using Avalonia.Media.TextFormatting.Unicode;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media.TextFormatting namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
@ -13,5 +15,26 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
UnicodeDataGenerator.Execute(); UnicodeDataGenerator.Execute();
} }
[Theory(Skip = "Only run when we update the trie.")]
[ClassData(typeof(LineBreakTestDataGenerator))]
public void Should_Enumerate_LineBreaks(string text, int expectedLength)
{
var textMemory = text.AsMemory();
var enumerator = new LineBreakEnumerator(textMemory);
Assert.True(enumerator.MoveNext());
Assert.Equal(expectedLength, enumerator.Current.PositionWrap);
}
private class LineBreakTestDataGenerator : TestDataGenerator
{
public LineBreakTestDataGenerator()
: base("auxiliary/LineBreakTest.txt")
{
}
}
} }
} }

181
tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs

@ -8,9 +8,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
{ {
internal static class UnicodeEnumsGenerator internal static class UnicodeEnumsGenerator
{ {
public static List<(string name, string tag, string comment)> CreateScriptEnum() public static List<DataEntry> CreateScriptEnum()
{ {
var scriptValues = GetPropertyValueAliases("# Script (sc)"); var entries = new List<DataEntry>
{
new DataEntry("Unknown", "Zzzz", string.Empty),
new DataEntry("Common", "Zyyy", string.Empty),
new DataEntry("Inherited", "Zinh", string.Empty)
};
ParseDataEntries("# Script (sc)", entries);
using (var stream = File.Create("Generated\\Script.cs")) using (var stream = File.Create("Generated\\Script.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -20,22 +27,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum Script"); writer.WriteLine(" public enum Script");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in scriptValues) foreach (var entry in entries)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
return scriptValues; return entries;
} }
public static List<(string name, string tag, string comment)> CreateGeneralCategoryEnum() public static List<DataEntry> CreateGeneralCategoryEnum()
{ {
var generalCategoryValues = GetPropertyValueAliases("# General_Category (gc)"); var entries = new List<DataEntry> { new DataEntry("Other", "C", " Cc | Cf | Cn | Co | Cs") };
ParseDataEntries("# General_Category (gc)", entries);
using (var stream = File.Create("Generated\\GeneralCategory.cs")) using (var stream = File.Create("Generated\\GeneralCategory.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -45,22 +54,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum GeneralCategory"); writer.WriteLine(" public enum GeneralCategory");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in generalCategoryValues) foreach (var entry in entries)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
return generalCategoryValues; return entries;
} }
public static List<(string name, string tag, string comment)> CreateGraphemeBreakTypeEnum() public static List<DataEntry> CreateGraphemeBreakTypeEnum()
{ {
var graphemeClusterBreakValues = GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)"); var entries = new List<DataEntry> { new DataEntry("Other", "XX", string.Empty) };
ParseDataEntries("# Grapheme_Cluster_Break (GCB)", entries);
using (var stream = File.Create("Generated\\GraphemeBreakClass.cs")) using (var stream = File.Create("Generated\\GraphemeBreakClass.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -70,10 +81,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum GraphemeBreakClass"); writer.WriteLine(" public enum GraphemeBreakClass");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in graphemeClusterBreakValues) foreach (var entry in entries)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" ExtendedPictographic"); writer.WriteLine(" ExtendedPictographic");
@ -82,7 +93,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine("}"); writer.WriteLine("}");
} }
return graphemeClusterBreakValues; return entries;
} }
private static List<string> GenerateBreakPairTable() private static List<string> GenerateBreakPairTable()
@ -185,20 +196,32 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
} }
} }
public static List<(string name, string tag, string comment)> CreateLineBreakClassEnum() public static List<DataEntry> CreateLineBreakClassEnum()
{ {
var usedLineBreakClasses = GenerateBreakPairTable(); var usedLineBreakClasses = GenerateBreakPairTable();
var lineBreakValues = GetPropertyValueAliases("# Line_Break (lb)"); var entries = new List<DataEntry> { new DataEntry("Unknown", "XX", string.Empty) };
ParseDataEntries("# Line_Break (lb)", entries);
var lineBreakClassMappings = lineBreakValues.ToDictionary(x => x.tag, x => (x.name, x.tag, x.comment)); var orderedLineBreakEntries = new Dictionary<string, DataEntry>();
var orderedLineBreakValues = usedLineBreakClasses.Select(x => foreach (var tag in usedLineBreakClasses)
{ {
var value = lineBreakClassMappings[x]; var entry = entries.Single(x => x.Tag == tag);
lineBreakClassMappings.Remove(x);
return value; orderedLineBreakEntries.Add(tag, entry);
}).ToList(); }
foreach (var entry in entries)
{
if (orderedLineBreakEntries.ContainsKey(entry.Tag))
{
continue;
}
orderedLineBreakEntries.Add(entry.Tag, entry);
}
using (var stream = File.Create("Generated\\LineBreakClass.cs")) using (var stream = File.Create("Generated\\LineBreakClass.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -208,32 +231,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum LineBreakClass"); writer.WriteLine(" public enum LineBreakClass");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in orderedLineBreakValues) foreach (var entry in orderedLineBreakEntries.Values)
{
writer.WriteLine(" " + name + ", //" + tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment));
}
writer.WriteLine();
foreach (var (name, tag, comment) in lineBreakClassMappings.Values)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
orderedLineBreakValues.AddRange(lineBreakClassMappings.Values); return orderedLineBreakEntries.Values.ToList();
return orderedLineBreakValues;
} }
public static List<(string name, string tag, string comment)> CreateBiDiClassEnum() public static List<DataEntry> CreateBiDiClassEnum()
{ {
var biDiClassValues = GetPropertyValueAliases("# Bidi_Class (bc)"); var entries = new List<DataEntry> { new DataEntry("Left_To_Right", "L", string.Empty) };
ParseDataEntries("# Bidi_Class (bc)", entries);
using (var stream = File.Create("Generated\\BiDiClass.cs")) using (var stream = File.Create("Generated\\BiDiClass.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -243,23 +258,21 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(" public enum BiDiClass"); writer.WriteLine(" public enum BiDiClass");
writer.WriteLine(" {"); writer.WriteLine(" {");
foreach (var (name, tag, comment) in biDiClassValues) foreach (var entry in entries)
{ {
writer.WriteLine(" " + name + ", //" + tag + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag +
(string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment));
} }
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
return biDiClassValues; return entries;
} }
public static void CreatePropertyValueAliasHelper(List<(string name, string tag, string comment)> scriptValues, public static void CreatePropertyValueAliasHelper(List<DataEntry> scriptEntries, IEnumerable<DataEntry> generalCategoryEntries,
List<(string name, string tag, string comment)> generalCategoryValues, IEnumerable<DataEntry> biDiClassEntries, IEnumerable<DataEntry> lineBreakClassEntries)
List<(string name, string tag, string comment)> biDiClassValues,
List<(string name, string tag, string comment)> lineBreakValues)
{ {
using (var stream = File.Create("Generated\\PropertyValueAliasHelper.cs")) using (var stream = File.Create("Generated\\PropertyValueAliasHelper.cs"))
using (var writer = new StreamWriter(stream)) using (var writer = new StreamWriter(stream))
@ -269,35 +282,35 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode");
writer.WriteLine("{"); writer.WriteLine("{");
writer.WriteLine(" public static class PropertyValueAliasHelper"); writer.WriteLine(" internal static class PropertyValueAliasHelper");
writer.WriteLine(" {"); writer.WriteLine(" {");
WritePropertyValueAliasGetTag(writer, scriptValues, "Script", "Zzzz"); WritePropertyValueAliasGetTag(writer, scriptEntries, "Script", "Zzzz");
WritePropertyValueAlias(writer, scriptValues, "Script", "Unknown"); WritePropertyValueAlias(writer, scriptEntries, "Script", "Unknown");
WritePropertyValueAlias(writer, generalCategoryValues, "GeneralCategory", "Other"); WritePropertyValueAlias(writer, generalCategoryEntries, "GeneralCategory", "Other");
WritePropertyValueAlias(writer, biDiClassValues, "BiDiClass", "LeftToRight"); WritePropertyValueAlias(writer, biDiClassEntries, "BiDiClass", "LeftToRight");
WritePropertyValueAlias(writer, lineBreakValues, "LineBreakClass", "Unknown"); WritePropertyValueAlias(writer, lineBreakClassEntries, "LineBreakClass", "Unknown");
writer.WriteLine(" }"); writer.WriteLine(" }");
writer.WriteLine("}"); writer.WriteLine("}");
} }
} }
public static List<(string name, string tag, string comment)> GetPropertyValueAliases(string property) public static void ParseDataEntries(string property, List<DataEntry> entries)
{ {
var data = new List<(string name, string tag, string comment)>();
using (var client = new HttpClient()) using (var client = new HttpClient())
{ {
using (var result = client.GetAsync("https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt").GetAwaiter().GetResult()) var url = Path.Combine(UnicodeDataGenerator.Ucd, "PropertyValueAliases.txt");
using (var result = client.GetAsync(url).GetAwaiter().GetResult())
{ {
if (!result.IsSuccessStatusCode) if (!result.IsSuccessStatusCode)
{ {
return data; return;
} }
using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
@ -337,7 +350,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
elements = elements[2].Split('#'); elements = elements[2].Split('#');
var name = elements[0].Trim().Replace("_", string.Empty); var name = elements[0].Trim();
if (entries.Any(x => x.Name == name))
{
continue;
}
var comment = string.Empty; var comment = string.Empty;
@ -346,24 +364,25 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
comment = elements[1]; comment = elements[1];
} }
data.Add((name, tag, comment)); var entry = new DataEntry(name, tag, comment);
entries.Add(entry);
} }
} }
} }
} }
return data;
} }
private static void WritePropertyValueAliasGetTag(TextWriter writer, private static void WritePropertyValueAliasGetTag(TextWriter writer, IEnumerable<DataEntry> entries,
IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue) string typeName, string defaultValue)
{ {
writer.WriteLine($" private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = "); writer.WriteLine(
$" private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = ");
writer.WriteLine($" new Dictionary<{typeName}, string>{{"); writer.WriteLine($" new Dictionary<{typeName}, string>{{");
foreach (var (name, tag, comment) in values) foreach (var entry in entries)
{ {
writer.WriteLine($" {{ {typeName}.{name}, \"{tag}\"}},"); writer.WriteLine($" {{ {typeName}.{entry.Name.Replace("_", "")}, \"{entry.Tag}\"}},");
} }
writer.WriteLine(" };"); writer.WriteLine(" };");
@ -382,15 +401,15 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(); writer.WriteLine();
} }
private static void WritePropertyValueAlias(TextWriter writer, private static void WritePropertyValueAlias(TextWriter writer, IEnumerable<DataEntry> entries, string typeName,
IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue) string defaultValue)
{ {
writer.WriteLine($" private static readonly Dictionary<string, {typeName}> s_tagTo{typeName} = "); writer.WriteLine($" private static readonly Dictionary<string, {typeName}> s_tagTo{typeName} = ");
writer.WriteLine($" new Dictionary<string,{typeName}>{{"); writer.WriteLine($" new Dictionary<string,{typeName}>{{");
foreach (var (name, tag, comment) in values) foreach (var entry in entries)
{ {
writer.WriteLine($" {{ \"{tag}\", {typeName}.{name}}},"); writer.WriteLine($" {{ \"{entry.Tag}\", {typeName}.{entry.Name.Replace("_", "")}}},");
} }
writer.WriteLine(" };"); writer.WriteLine(" };");
@ -409,4 +428,18 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting
writer.WriteLine(); writer.WriteLine();
} }
} }
public readonly struct DataEntry
{
public DataEntry(string name, string tag, string comment)
{
Name = name;
Tag = tag;
Comment = comment;
}
public string Name { get; }
public string Tag { get; }
public string Comment { get; }
}
} }

Loading…
Cancel
Save