Browse Source

Merge branch 'master' into feature/target-net6

pull/7122/head
Steven Kirk 4 years ago
committed by GitHub
parent
commit
c1995749ee
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      Avalonia.sln
  2. 27
      native/Avalonia.Native/src/OSX/app.mm
  3. 15
      native/Avalonia.Native/src/OSX/common.h
  4. 44
      native/Avalonia.Native/src/OSX/main.mm
  5. 53
      native/Avalonia.Native/src/OSX/menu.mm
  6. 1
      samples/ControlCatalog.Web/ControlCatalog.Web.csproj
  7. 5
      samples/ControlCatalog/App.xaml
  8. 1
      samples/ControlCatalog/ControlCatalog.csproj
  9. 267
      samples/ControlCatalog/MainView.xaml
  10. 54
      samples/ControlCatalog/MainView.xaml.cs
  11. 12
      samples/ControlCatalog/MainWindow.xaml
  12. 26
      samples/ControlCatalog/MainWindow.xaml.cs
  13. 14
      samples/ControlCatalog/Models/CatalogTheme.cs
  14. 264
      samples/ControlCatalog/Pages/AcrylicPage.xaml
  15. 125
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  16. 5
      samples/ControlCatalog/Pages/BorderPage.xaml
  17. 1
      samples/ControlCatalog/Pages/ButtonPage.xaml
  18. 1
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
  19. 1
      samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
  20. 55
      samples/ControlCatalog/Pages/CalendarPage.xaml
  21. 1
      samples/ControlCatalog/Pages/CanvasPage.xaml
  22. 13
      samples/ControlCatalog/Pages/CarouselPage.xaml
  23. 1
      samples/ControlCatalog/Pages/CheckBoxPage.xaml
  24. 3
      samples/ControlCatalog/Pages/ComboBoxPage.xaml
  25. 1
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
  26. 1
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  27. 1
      samples/ControlCatalog/Pages/CursorPage.xaml
  28. 4
      samples/ControlCatalog/Pages/DataGridPage.xaml
  29. 1
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  30. 3
      samples/ControlCatalog/Pages/DialogsPage.xaml
  31. 71
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  32. 1
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  33. 1
      samples/ControlCatalog/Pages/ImagePage.xaml
  34. 1
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  35. 1
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  36. 8
      samples/ControlCatalog/Pages/MenuPage.xaml
  37. 1
      samples/ControlCatalog/Pages/NotificationsPage.xaml
  38. 11
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  39. 1
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  40. 1
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  41. 3
      samples/ControlCatalog/Pages/RelativePanelPage.axaml
  42. 1
      samples/ControlCatalog/Pages/ScrollViewerPage.xaml
  43. 1
      samples/ControlCatalog/Pages/SliderPage.xaml
  44. 6
      samples/ControlCatalog/Pages/TabControlPage.xaml
  45. 1
      samples/ControlCatalog/Pages/TabStripPage.xaml
  46. 137
      samples/ControlCatalog/Pages/TextBlockPage.xaml
  47. 33
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  48. 3
      samples/ControlCatalog/Pages/ToggleSwitchPage.xaml
  49. 1
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  50. 1
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  51. 1
      samples/ControlCatalog/Pages/ViewboxPage.xaml
  52. 84
      samples/ControlCatalog/SideBar.xaml
  53. 15
      samples/RenderDemo/App.xaml
  54. 121
      samples/RenderDemo/MainWindow.xaml
  55. 1
      samples/RenderDemo/RenderDemo.csproj
  56. 67
      samples/RenderDemo/SideBar.xaml
  57. 23
      samples/SampleControls/ControlSamples.csproj
  58. 77
      samples/SampleControls/HamburgerMenu/HamburgerMenu.cs
  59. 242
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  60. 63
      src/Avalonia.Controls/MenuItem.cs
  61. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  62. 12
      src/Avalonia.Controls/Platform/INativeApplicationCommands.cs
  63. 2
      src/Avalonia.Controls/TextBox.cs
  64. 26
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  65. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
  66. 6
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
  67. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  68. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
  69. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  70. 32
      src/Avalonia.Input/InputMethod.cs
  71. 35
      src/Avalonia.Input/TextInput/InputMethodManager.cs
  72. 73
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  73. 5
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  74. 5
      src/Avalonia.Native/IAvnMenuItem.cs
  75. 35
      src/Avalonia.Native/MacOSNativeMenuCommands.cs
  76. 11
      src/Avalonia.Native/avn.idl
  77. 33
      src/Avalonia.Themes.Default/SplitView.xaml
  78. 34
      src/Avalonia.Themes.Fluent/Controls/SplitView.xaml
  79. 2
      src/Avalonia.Themes.Fluent/Controls/TabItem.xaml
  80. 2
      src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml
  81. 18
      src/Avalonia.Visuals/Media/FontFallback.cs
  82. 32
      src/Avalonia.Visuals/Media/FontManager.cs
  83. 11
      src/Avalonia.Visuals/Media/FontManagerOptions.cs
  84. 199
      src/Avalonia.Visuals/Media/UnicodeRange.cs
  85. BIN
      src/Web/Avalonia.Web.Blazor/Assets/NotoMono-Regular.ttf
  86. BIN
      src/Web/Avalonia.Web.Blazor/Assets/NotoSans-Italic.ttf
  87. 7
      src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props
  88. 4
      src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj
  89. 4
      src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props
  90. 4
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  91. 2
      src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs
  92. 78
      src/Web/Avalonia.Web.Blazor/CustomFontManagerImpl.cs
  93. 38
      src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs
  94. 231
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  95. 106
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  96. 45
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  97. 9
      src/Windows/Avalonia.Win32/WindowImpl.cs
  98. 161
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  99. 2
      tests/Avalonia.UnitTests/MockGlyphTypeface.cs
  100. 30
      tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs

29
Avalonia.sln

@ -234,7 +234,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@ -2170,6 +2172,30 @@ Global
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|iPhone.Build.0 = Release|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.AppStore|iPhone.Build.0 = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Debug|iPhone.Build.0 = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|Any CPU.Build.0 = Release|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhone.ActiveCfg = Release|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhone.Build.0 = Release|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2233,6 +2259,7 @@ Global
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

27
native/Avalonia.Native/src/OSX/app.mm

@ -97,3 +97,30 @@ extern void InitializeAvnApp(IAvnApplicationEvents* events)
id delegate = [[AvnAppDelegate alloc] initWithEvents:events];
[app setDelegate:delegate];
}
HRESULT AvnApplicationCommands::HideApp()
{
START_COM_CALL;
[[NSApplication sharedApplication] hide:[NSApp delegate]];
return S_OK;
}
HRESULT AvnApplicationCommands::ShowAll()
{
START_COM_CALL;
[[NSApplication sharedApplication] unhideAllApplications:[NSApp delegate]];
return S_OK;
}
HRESULT AvnApplicationCommands::HideOthers()
{
START_COM_CALL;
[[NSApplication sharedApplication] hideOtherApplications:[NSApp delegate]];
return S_OK;
}
extern IAvnApplicationCommands* CreateApplicationCommands()
{
return new AvnApplicationCommands();
}

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

@ -25,12 +25,12 @@ extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
extern IAvnTrayIcon* CreateTrayIcon();
extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnApplicationCommands* CreateApplicationCommands();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu);
extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
extern void SetAutoGenerateDefaultAppMenuItems (bool enabled);
extern bool GetAutoGenerateDefaultAppMenuItems ();
extern void InitializeAvnApp(IAvnApplicationEvents* events);
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
@ -67,4 +67,15 @@ public:
~AvnInsidePotentialDeadlock();
};
class AvnApplicationCommands : public ComSingleObject<IAvnApplicationCommands, &IID_IAvnApplicationCommands>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT HideApp() override;
virtual HRESULT ShowAll() override;
virtual HRESULT HideOthers() override;
};
#endif

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

@ -2,7 +2,6 @@
#define COM_GUIDS_MATERIALIZE
#include "common.h"
static bool s_generateDefaultAppMenuItems = true;
static NSString* s_appTitle = @"Avalonia";
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
@ -134,16 +133,6 @@ public:
}
}
virtual HRESULT SetDisableDefaultApplicationMenuItems (bool enabled) override
{
START_COM_CALL;
@autoreleasepool
{
SetAutoGenerateDefaultAppMenuItems(!enabled);
return S_OK;
}
}
};
/// See "Using POSIX Threads in a Cocoa Application" section here:
@ -357,6 +346,29 @@ public:
return S_OK;
}
}
virtual HRESULT SetServicesMenu (IAvnMenu* servicesMenu) override
{
START_COM_CALL;
@autoreleasepool
{
::SetServicesMenu(servicesMenu);
return S_OK;
}
}
virtual HRESULT CreateApplicationCommands (IAvnApplicationCommands** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
*ppv = ::CreateApplicationCommands();
return S_OK;
}
}
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
@ -410,13 +422,3 @@ CGFloat PrimaryDisplayHeight()
{
return NSMaxY([[[NSScreen screens] firstObject] frame]);
}
void SetAutoGenerateDefaultAppMenuItems (bool enabled)
{
s_generateDefaultAppMenuItems = enabled;
}
bool GetAutoGenerateDefaultAppMenuItems ()
{
return s_generateDefaultAppMenuItems;
}

53
native/Avalonia.Native/src/OSX/menu.mm

@ -490,53 +490,6 @@ extern void SetAppMenu (NSString* appName, IAvnMenu* menu)
{
[s_appMenuItem setSubmenu:[NSMenu new]];
}
auto appMenu = [s_appMenuItem submenu];
if(GetAutoGenerateDefaultAppMenuItems())
{
[appMenu addItem:[NSMenuItem separatorItem]];
// Services item and menu
auto servicesItem = [[NSMenuItem alloc] init];
servicesItem.title = @"Services";
NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"];
servicesItem.submenu = servicesMenu;
[NSApplication sharedApplication].servicesMenu = servicesMenu;
[appMenu addItem:servicesItem];
[appMenu addItem:[NSMenuItem separatorItem]];
// Hide Application
auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"];
[appMenu addItem:hideItem];
// Hide Others
auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"];
hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption;
[appMenu addItem:hideAllOthersItem];
// Show All
auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
[appMenu addItem:showAllItem];
[appMenu addItem:[NSMenuItem separatorItem]];
// Quit Application
auto quitItem = [[NSMenuItem alloc] init];
quitItem.title = [@"Quit " stringByAppendingString:appName];
quitItem.keyEquivalent = @"q";
quitItem.target = [AvnWindow class];
quitItem.action = @selector(closeAll);
[appMenu addItem:quitItem];
}
}
else
{
@ -544,6 +497,12 @@ extern void SetAppMenu (NSString* appName, IAvnMenu* menu)
}
}
extern void SetServicesMenu (IAvnMenu* menu)
{
auto nativeMenu = dynamic_cast<AvnAppMenu*>(menu);
[NSApplication sharedApplication].servicesMenu = nativeMenu->GetNative();
}
extern IAvnMenu* GetAppMenu ()
{
return s_appMenu;

1
samples/ControlCatalog.Web/ControlCatalog.Web.csproj

@ -29,6 +29,7 @@
</ItemGroup>
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.targets" />
<Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.CompilationTuning.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj"/>

5
samples/ControlCatalog/App.xaml

@ -5,6 +5,9 @@
x:CompileBindings="True"
x:Class="ControlCatalog.App">
<Application.Styles>
<Style Selector="TextBlock.h1, TextBlock.h2, TextBlock.h3">
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Medium" />
@ -25,7 +28,7 @@
<Style Selector="Label.h3">
<Setter Property="FontSize" Value="12" />
</Style>
<StyleInclude Source="/SideBar.xaml" />
<StyleInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</Application.Styles>
<TrayIcon.Icons>
<TrayIcons>

1
samples/ControlCatalog/ControlCatalog.csproj

@ -26,6 +26,7 @@
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />

267
samples/ControlCatalog/MainView.xaml

@ -1,108 +1,195 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:pages="clr-namespace:ControlCatalog.Pages"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.MainView">
<UserControl x:Class="ControlCatalog.MainView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:ControlSamples;assembly=ControlSamples"
xmlns:pages="clr-namespace:ControlCatalog.Pages"
xmlns:models="clr-namespace:ControlCatalog.Models">
<Grid>
<Grid.Styles>
<Style Selector="TextBlock.h2">
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="MaxWidth" Value="400"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</Grid.Styles>
<TabControl Classes="sidebar" Name="Sidebar">
<TabItem Header="Acrylic"><pages:AcrylicPage/></TabItem>
<TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
<TabItem Header="Border"><pages:BorderPage/></TabItem>
<TabItem Header="Button"><pages:ButtonPage/></TabItem>
<TabItem Header="ButtonSpinner"><pages:ButtonSpinnerPage/></TabItem>
<TabItem Header="Calendar"><pages:CalendarPage/></TabItem>
<TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
<TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
<Style Selector="TextBlock.h2">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="MaxWidth" Value="400" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</Grid.Styles>
<controls:HamburgerMenu Name="Sidebar">
<TabItem Header="Acrylic">
<pages:AcrylicPage />
</TabItem>
<TabItem Header="AutoCompleteBox">
<pages:AutoCompleteBoxPage />
</TabItem>
<TabItem Header="Border">
<pages:BorderPage />
</TabItem>
<TabItem Header="Button">
<pages:ButtonPage />
</TabItem>
<TabItem Header="ButtonSpinner">
<pages:ButtonSpinnerPage />
</TabItem>
<TabItem Header="Calendar">
<pages:CalendarPage />
</TabItem>
<TabItem Header="Canvas">
<pages:CanvasPage />
</TabItem>
<TabItem Header="Carousel">
<pages:CarouselPage />
</TabItem>
<TabItem Header="CheckBox">
<pages:CheckBoxPage />
</TabItem>
<TabItem Header="ComboBox">
<pages:ComboBoxPage />
</TabItem>
<TabItem Header="ContextFlyout">
<pages:ContextFlyoutPage/>
<pages:ContextFlyoutPage />
</TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="Cursor"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:CursorPage/>
<TabItem Header="ContextMenu">
<pages:ContextMenuPage />
</TabItem>
<TabItem Header="DataGrid"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:DataGridPage/>
<TabItem Header="Cursor" ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:CursorPage />
</TabItem>
<TabItem Header="DataGrid"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:DataGridPage />
</TabItem>
<TabItem Header="Date/Time Picker">
<pages:DateTimePickerPage/>
<pages:DateTimePickerPage />
</TabItem>
<TabItem Header="CalendarDatePicker">
<pages:CalendarDatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<pages:CalendarDatePickerPage />
</TabItem>
<TabItem Header="Drag+Drop">
<pages:DragAndDropPage />
</TabItem>
<TabItem Header="Expander">
<pages:ExpanderPage />
</TabItem>
<TabItem Header="Flyouts">
<pages:FlyoutsPage />
</TabItem>
<TabItem Header="Image"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:ImagePage/>
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:ImagePage />
</TabItem>
<TabItem Header="ItemsRepeater"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:ItemsRepeaterPage/>
</TabItem>
<TabItem Header="Label"><pages:LabelsPage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="ListBox"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:ListBoxPage/>
</TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="Notifications"><pages:NotificationsPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="OpenGL"><pages:OpenGlPage/></TabItem>
<TabItem Header="Pointers (Touch)"><pages:PointersPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="RelativePanel"><pages:RelativePanelPage/></TabItem>
<TabItem Header="ScrollViewer"><pages:ScrollViewerPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="SplitView"><pages:SplitViewPage/></TabItem>
<TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
<TabItem Header="TabStrip"><pages:TabStripPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>
<TabItem Header="TextBlock"><pages:TextBlockPage/></TabItem>
<TabItem Header="ToggleSwitch"><pages:ToggleSwitchPage/></TabItem>
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
<TabItem Header="Window Customizations"><pages:WindowCustomizationsPage/></TabItem>
<TabControl.Tag>
<StackPanel Width="115" Spacing="4" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="8">
<ComboBox x:Name="Decorations" SelectedIndex="0">
<ComboBoxItem>No Decorations</ComboBoxItem>
<ComboBoxItem>Border Only</ComboBoxItem>
<ComboBoxItem>Full Decorations</ComboBoxItem>
</ComboBox>
<ComboBox x:Name="Themes" SelectedIndex="0">
<ComboBoxItem>Fluent - Light</ComboBoxItem>
<ComboBoxItem>Fluent - Dark</ComboBoxItem>
<ComboBoxItem>Simple - Light</ComboBoxItem>
<ComboBoxItem>Simple - Dark</ComboBoxItem>
</ComboBox>
<ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Transparent</ComboBoxItem>
<ComboBoxItem>Blur</ComboBoxItem>
<ComboBoxItem>AcrylicBlur</ComboBoxItem>
<ComboBoxItem>Mica</ComboBoxItem>
</ComboBox>
<ComboBox Items="{Binding WindowStates}" SelectedItem="{Binding WindowState}" />
</StackPanel>
</TabControl.Tag>
</TabControl>
<pages:ItemsRepeaterPage />
</TabItem>
<TabItem Header="Label">
<pages:LabelsPage />
</TabItem>
<TabItem Header="LayoutTransformControl">
<pages:LayoutTransformControlPage />
</TabItem>
<TabItem Header="ListBox" ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:ListBoxPage />
</TabItem>
<TabItem Header="Menu">
<pages:MenuPage />
</TabItem>
<TabItem Header="Notifications">
<pages:NotificationsPage />
</TabItem>
<TabItem Header="NumericUpDown">
<pages:NumericUpDownPage />
</TabItem>
<TabItem Header="OpenGL">
<pages:OpenGlPage />
</TabItem>
<TabItem Header="Pointers (Touch)">
<pages:PointersPage />
</TabItem>
<TabItem Header="ProgressBar">
<pages:ProgressBarPage />
</TabItem>
<TabItem Header="RadioButton">
<pages:RadioButtonPage />
</TabItem>
<TabItem Header="RelativePanel">
<pages:RelativePanelPage />
</TabItem>
<TabItem Header="ScrollViewer">
<pages:ScrollViewerPage />
</TabItem>
<TabItem Header="Slider">
<pages:SliderPage />
</TabItem>
<TabItem Header="SplitView">
<pages:SplitViewPage />
</TabItem>
<TabItem Header="TabControl">
<pages:TabControlPage />
</TabItem>
<TabItem Header="TabStrip">
<pages:TabStripPage />
</TabItem>
<TabItem Header="TextBox">
<pages:TextBoxPage />
</TabItem>
<TabItem Header="TextBlock">
<pages:TextBlockPage />
</TabItem>
<TabItem Header="ToggleSwitch">
<pages:ToggleSwitchPage />
</TabItem>
<TabItem Header="ToolTip">
<pages:ToolTipPage />
</TabItem>
<TabItem Header="TreeView">
<pages:TreeViewPage />
</TabItem>
<TabItem Header="Viewbox">
<pages:ViewboxPage />
</TabItem>
<TabItem Header="Window Customizations">
<pages:WindowCustomizationsPage />
</TabItem>
<FlyoutBase.AttachedFlyout>
<Flyout>
<StackPanel Width="152" Spacing="8">
<ComboBox x:Name="Decorations"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<ComboBox.Items>
<SystemDecorations>None</SystemDecorations>
<SystemDecorations>BorderOnly</SystemDecorations>
<SystemDecorations>Full</SystemDecorations>
</ComboBox.Items>
</ComboBox>
<ComboBox x:Name="Themes"
HorizontalAlignment="Stretch"
SelectedIndex="0">
<ComboBox.Items>
<models:CatalogTheme>FluentLight</models:CatalogTheme>
<models:CatalogTheme>FluentDark</models:CatalogTheme>
<models:CatalogTheme>DefaultLight</models:CatalogTheme>
<models:CatalogTheme>DefaultDark</models:CatalogTheme>
</ComboBox.Items>
</ComboBox>
<ComboBox x:Name="TransparencyLevels"
HorizontalAlignment="Stretch"
SelectedIndex="{Binding TransparencyLevel}">
<ComboBox.Items>
<WindowTransparencyLevel>None</WindowTransparencyLevel>
<WindowTransparencyLevel>Transparent</WindowTransparencyLevel>
<WindowTransparencyLevel>Blur</WindowTransparencyLevel>
<WindowTransparencyLevel>AcrylicBlur</WindowTransparencyLevel>
<WindowTransparencyLevel>Mica</WindowTransparencyLevel>
</ComboBox.Items>
</ComboBox>
<ComboBox HorizontalAlignment="Stretch"
Items="{Binding WindowStates}"
SelectedItem="{Binding WindowState}" />
</StackPanel>
</Flyout>
</FlyoutBase.AttachedFlyout>
</controls:HamburgerMenu>
</Grid>
</UserControl>

54
samples/ControlCatalog/MainView.xaml.cs

@ -6,8 +6,11 @@ using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Markup.Xaml.XamlIl;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using ControlCatalog.Pages;
using ControlCatalog.Models;
namespace ControlCatalog
{
@ -16,9 +19,12 @@ namespace ControlCatalog
public MainView()
{
AvaloniaXamlLoader.Load(this);
var sideBar = this.FindControl<TabControl>("Sidebar");
if (AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().IsDesktop)
{
IList tabItems = ((IList)this.FindControl<TabControl>("Sidebar").Items);
IList tabItems = ((IList)sideBar.Items);
tabItems.Add(new TabItem()
{
Header = "Dialogs",
@ -35,28 +41,42 @@ namespace ControlCatalog
var themes = this.Find<ComboBox>("Themes");
themes.SelectionChanged += (sender, e) =>
{
switch (themes.SelectedIndex)
if (themes.SelectedItem is CatalogTheme theme)
{
case 0:
Application.Current.Styles[0] = App.FluentLight;
break;
case 1:
Application.Current.Styles[0] = App.FluentDark;
break;
case 2:
Application.Current.Styles[0] = App.DefaultLight;
break;
case 3:
Application.Current.Styles[0] = App.DefaultDark;
break;
Application.Current.Styles[0] = theme switch
{
CatalogTheme.FluentLight => App.FluentLight,
CatalogTheme.FluentDark => App.FluentDark,
CatalogTheme.DefaultLight => App.DefaultLight,
CatalogTheme.DefaultDark => App.DefaultDark,
_ => Application.Current.Styles[0]
};
}
};
};
var decorations = this.Find<ComboBox>("Decorations");
decorations.SelectionChanged += (sender, e) =>
{
if (VisualRoot is Window window)
window.SystemDecorations = (SystemDecorations)decorations.SelectedIndex;
if (VisualRoot is Window window
&& decorations.SelectedItem is SystemDecorations systemDecorations)
{
window.SystemDecorations = systemDecorations;
}
};
var transparencyLevels = this.Find<ComboBox>("TransparencyLevels");
IDisposable backgroundSetter = null, paneBackgroundSetter = null;
transparencyLevels.SelectionChanged += (sender, e) =>
{
backgroundSetter?.Dispose();
paneBackgroundSetter?.Dispose();
if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected
&& selected != WindowTransparencyLevel.None)
{
var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.5);
backgroundSetter = sideBar.SetValue(BackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style);
paneBackgroundSetter = sideBar.SetValue(SplitView.PaneBackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style);
}
};
}

12
samples/ControlCatalog/MainWindow.xaml

@ -61,17 +61,7 @@
</Window.DataTemplates>
<Panel>
<Panel Margin="{Binding #MainWindow.OffScreenMargin}">
<DockPanel LastChildFill="True" Margin="{Binding #MainWindow.WindowDecorationMargin}">
<Menu Name="MainMenu" DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="E_xit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="_Help">
<MenuItem Header="_About" Command="{Binding AboutCommand}" />
</MenuItem>
</Menu>
<local:MainView />
</DockPanel>
<local:MainView Margin="{Binding #MainWindow.WindowDecorationMargin}" />
</Panel>
<Border IsVisible="{Binding ExtendClientAreaEnabled}" BorderThickness="1 1 1 0" CornerRadius="4 4 0 0" BorderBrush="#55000000" Height="22" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="250 8 0 0">
<Border.Background>

26
samples/ControlCatalog/MainWindow.xaml.cs

@ -33,9 +33,6 @@ namespace ControlCatalog
DataContext = new MainWindowViewModel(_notificationArea);
_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
var mainMenu = this.FindControl<Menu>("MainMenu");
mainMenu.AttachedToVisualTree += MenuAttached;
ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.OSXThickTitleBar;
}
@ -45,14 +42,6 @@ namespace ControlCatalog
new KeyGesture(Key.Q, KeyModifiers.Meta) :
new KeyGesture(Key.F4, KeyModifiers.Alt);
public void MenuAttached(object sender, VisualTreeAttachmentEventArgs e)
{
if (NativeMenu.GetIsNativeMenuExported(this) && sender is Menu mainMenu)
{
mainMenu.IsVisible = false;
}
}
public void OnOpenClicked(object sender, EventArgs args)
{
_recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
@ -65,21 +54,6 @@ namespace ControlCatalog
private void InitializeComponent()
{
// TODO: iOS does not support dynamically loading assemblies
// so we must refer to this resource DLL statically. For
// now I am doing that here. But we need a better solution!!
// Note, theme swiching probably will not work in runtime for iOS.
if (Application.Current.Styles.Contains(App.FluentDark)
|| Application.Current.Styles.Contains(App.FluentLight))
{
var theme = new Avalonia.Themes.Fluent.Controls.FluentControls();
theme.TryGetResource("Button", out _);
}
else
{
var theme = new Avalonia.Themes.Default.DefaultTheme();
theme.TryGetResource("Button", out _);
}
AvaloniaXamlLoader.Load(this);
}
}

14
samples/ControlCatalog/Models/CatalogTheme.cs

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace ControlCatalog.Models
{
public enum CatalogTheme
{
FluentLight,
FluentDark,
DefaultLight,
DefaultDark
}
}

264
samples/ControlCatalog/Pages/AcrylicPage.xaml

@ -1,139 +1,165 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl x:Class="ControlCatalog.Pages.AcrylicPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.AcrylicPage">
<Border Padding="20" HorizontalAlignment="Center">
<StackPanel Spacing="20">
d:DesignHeight="800"
d:DesignWidth="400"
mc:Ignorable="d">
<UserControl.Styles>
<Style Selector="ExperimentalAcrylicBorder">
<Setter Property="CornerRadius" Value="5" />
<Setter Property="MaxWidth" Value="660" />
</Style>
<Style Selector="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="Black" />
</Style>
<Style Selector="Slider">
<Setter Property="Margin" Value="8,0" />
<Setter Property="Minimum" Value="0" />
<Setter Property="Maximum" Value="1" />
<Setter Property="LargeChange" Value="0.2" />
<Setter Property="SmallChange" Value="0.1" />
</Style>
</UserControl.Styles>
<StackPanel Spacing="20">
<ExperimentalAcrylicBorder Width="660" CornerRadius="5">
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger" TintColor="White" />
</ExperimentalAcrylicBorder.Material>
<Grid Margin="20,10"
ColumnDefinitions="Auto,*,Auto"
RowDefinitions="Auto,Auto">
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="TintOpacity" />
<Slider Name="TintOpacitySlider"
Grid.Row="0"
Grid.Column="1"
Value="0.9" />
<TextBlock Grid.Row="0"
Grid.Column="2"
Text="{Binding #TintOpacitySlider.Value, StringFormat=\{0:0.#\}}" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Text="MaterialOpacity" />
<Slider Name="MaterialOpacitySlider"
Grid.Row="1"
Grid.Column="1"
Value="0.8" />
<TextBlock Grid.Row="1"
Grid.Column="2"
Text="{Binding #MaterialOpacitySlider.Value, StringFormat=\{0:0.#\}}" />
</Grid>
</ExperimentalAcrylicBorder>
<UniformGrid x:Name="BordersGrid"
HorizontalAlignment="Stretch"
MaxWidth="660"
Columns="3">
<UniformGrid.Styles>
<Style Selector="ExperimentalAcrylicBorder">
<Setter Property="Height" Value="{Binding $self.Bounds.Width}" />
<Setter Property="Margin" Value="10" />
<Setter Property="MaxWidth" Value="200" />
</Style>
</UniformGrid.Styles>
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="White"
BackgroundSource="Digger" />
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="#FF0000"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
<StackPanel Spacing="5" Margin="40 10">
<StackPanel Orientation="Horizontal">
<TextBlock Text="TintOpacity" Foreground="Black" />
<Slider Name="TintOpacitySlider" Minimum="0" Maximum="1" Value="0.9" SmallChange="0.1" LargeChange="0.2" Width="400" />
<TextBlock Text="{Binding #TintOpacitySlider.Value, StringFormat=\{0:0.#\}}" Foreground="Black" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="MaterialOpacity" Foreground="Black" />
<Slider Name="MaterialOpacitySlider" Minimum="0" Maximum="1" Value="0.8" SmallChange="0.1" LargeChange="0.2" Width="400" />
<TextBlock Text="{Binding #MaterialOpacitySlider.Value, StringFormat=\{0:0.#\}}" Foreground="Black" />
</StackPanel>
</StackPanel>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="#00FF00"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<StackPanel Orientation="Horizontal" Spacing="20">
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#FF0000"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#00FF00"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#000000"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
</StackPanel>
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="#000000"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<StackPanel Orientation="Horizontal" Spacing="20">
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#0000FF"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="#0000FF"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#FFFF00"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="#FFFF00"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#000000"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
</StackPanel>
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="#000000"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<StackPanel Orientation="Horizontal" Spacing="20">
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="White"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="#3c3c3c"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="White"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="200" CornerRadius="5">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="White"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
</StackPanel>
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="#3c3c3c"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder Height="200" Width="660" CornerRadius="5">
<ExperimentalAcrylicBorder>
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial
TintColor="Red"
TintOpacity="{Binding #TintOpacitySlider.Value}"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
BackgroundSource="Digger" />
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="White"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
</StackPanel>
</Border>
</UniformGrid>
<ExperimentalAcrylicBorder Width="{Binding #BordersGrid.Bounds.Width}"
Height="160">
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger"
MaterialOpacity="{Binding #MaterialOpacitySlider.Value}"
TintColor="Red"
TintOpacity="{Binding #TintOpacitySlider.Value}" />
</ExperimentalAcrylicBorder.Material>
</ExperimentalAcrylicBorder>
</StackPanel>
</UserControl>

125
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@ -1,73 +1,72 @@
<UserControl xmlns="https://github.com/avaloniaui"
<UserControl x:Class="ControlCatalog.Pages.AutoCompleteBoxPage"
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"
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="ControlCatalog.Pages.AutoCompleteBoxPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">AutoCompleteBox</TextBlock>
d:DesignHeight="600"
d:DesignWidth="400">
<StackPanel Orientation="Vertical"
Spacing="4"
MaxWidth="660">
<TextBlock Classes="h2">A control into which the user can input text</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="8">
<StackPanel Orientation="Vertical">
<TextBlock Text="MinimumPrefixLength: 1"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
MinimumPrefixLength="1"/>
<TextBlock Text="MinimumPrefixLength: 3"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
MinimumPrefixLength="3"/>
<TextBlock Text="MinimumPopulateDelay: 1 Second"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
MinimumPopulateDelay="1"/>
<TextBlock Text="MaxDropDownHeight: 60"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
MaxDropDownHeight="60"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
Watermark="Watermark"/>
<TextBlock Text="Disabled"/>
<AutoCompleteBox Width="200"
IsEnabled="False"/>
<UniformGrid Margin="-8,0"
Columns="2">
<UniformGrid.Styles>
<Style Selector="StackPanel">
<Setter Property="Margin" Value="8" />
</Style>
</UniformGrid.Styles>
<StackPanel>
<TextBlock Text="MinimumPrefixLength: 1" />
<AutoCompleteBox MinimumPrefixLength="1" />
</StackPanel>
<StackPanel>
<TextBlock Text="MinimumPrefixLength: 3" />
<AutoCompleteBox MinimumPrefixLength="3" />
</StackPanel>
<StackPanel>
<TextBlock Text="MinimumPopulateDelay: 1s" />
<AutoCompleteBox MinimumPopulateDelay="1" />
</StackPanel>
<StackPanel>
<TextBlock Text="MaxDropDownHeight: 60" />
<AutoCompleteBox MaxDropDownHeight="60" />
</StackPanel>
<StackPanel>
<TextBlock Text="Watermark" />
<AutoCompleteBox Watermark="Hello World" />
</StackPanel>
<StackPanel>
<TextBlock Text="Disabled" />
<AutoCompleteBox IsEnabled="False" />
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="ValueMemberBinding"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
ValueMemberBinding="{Binding Capital}"/>
<TextBlock Text="Multi-Binding"/>
<AutoCompleteBox Name="MultiBindingBox"
Width="200"
Margin="0,0,0,8"
FilterMode="Contains"/>
<TextBlock Text="Async Populate"/>
<AutoCompleteBox Name="AsyncBox"
Width="200"
Margin="0,0,0,8"
FilterMode="None"/>
<TextBlock Text="Custom Autocomplete"/>
<AutoCompleteBox Name="CustomAutocompleteBox"
Width="200"
Margin="0,0,0,8"
FilterMode="None"/>
<TextBlock Text="With Validation Errors"/>
<AutoCompleteBox Name="ValidationErrors"
Width="200"
Margin="0,0,0,8"
FilterMode="None">
<DataValidationErrors.Error>
<sys:Exception />
</DataValidationErrors.Error>
</AutoCompleteBox>
<StackPanel>
<TextBlock Text="ValueMemberBinding" />
<AutoCompleteBox ValueMemberBinding="{Binding Capital}" />
</StackPanel>
<StackPanel>
<TextBlock Text="Multi-Binding" />
<AutoCompleteBox Name="MultiBindingBox" FilterMode="Contains" />
</StackPanel>
<StackPanel>
<TextBlock Text="Async Populate" />
<AutoCompleteBox Name="AsyncBox" FilterMode="None" />
</StackPanel>
<StackPanel>
<TextBlock Text="Custom Autocomplete" />
<AutoCompleteBox Name="CustomAutocompleteBox" FilterMode="None" />
</StackPanel>
</UniformGrid>
<StackPanel>
<TextBlock Text="With Validation Errors" />
<AutoCompleteBox Name="ValidationErrors" FilterMode="None">
<DataValidationErrors.Error>
<sys:Exception />
</DataValidationErrors.Error>
</AutoCompleteBox>
</StackPanel>
</StackPanel>
</UserControl>

5
samples/ControlCatalog/Pages/BorderPage.xaml

@ -1,8 +1,11 @@
<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"
d:DesignHeight="800"
d:DesignWidth="400"
x:Class="ControlCatalog.Pages.BorderPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Border</TextBlock>
<TextBlock Classes="h2">A control which decorates a child with a border and background</TextBlock>
<StackPanel Orientation="Vertical"

1
samples/ControlCatalog/Pages/ButtonPage.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ButtonPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Button</TextBlock>
<TextBlock Classes="h2">A button control</TextBlock>
<StackPanel Orientation="Horizontal"

1
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml

@ -4,7 +4,6 @@
xmlns:sys="clr-namespace:System;assembly=netstandard">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ButtonSpinner</TextBlock>
<TextBlock Classes="h2">The ButtonSpinner control allows you to add button spinners to any element and then respond to the Spin event to manipulate that element.</TextBlock>
<StackPanel Orientation="Vertical" Spacing="8" Width="200" Margin="0,20,0,0">

1
samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CalendarDatePickerPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">CalendarDatePicker</TextBlock>
<TextBlock Classes="h2">A control for selecting dates with a calendar drop-down</TextBlock>
<StackPanel Orientation="Horizontal"

55
samples/ControlCatalog/Pages/CalendarPage.xaml

@ -2,47 +2,46 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CalendarPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Calendar</TextBlock>
<TextBlock Classes="h2">A calendar control for selecting dates</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical">
<TextBlock Text="SelectionMode: None"/>
<Calendar SelectionMode="None"
Margin="0,0,0,8"/>
<WrapPanel MaxWidth="660"
HorizontalAlignment="Center"
Margin="-8, 0">
<WrapPanel.Styles>
<Style Selector="StackPanel">
<Setter Property="Margin" Value="8" />
</Style>
</WrapPanel.Styles>
<StackPanel>
<TextBlock Text="SelectionMode: None"/>
<Calendar SelectionMode="None" />
</StackPanel>
<StackPanel>
<TextBlock Text="SelectionMode: SingleDate"/>
<Calendar SelectionMode="SingleDate"
Margin="0,0,0,8"/>
<Calendar SelectionMode="SingleDate" />
</StackPanel>
<StackPanel>
<TextBlock Text="Disabled"/>
<Calendar IsEnabled="False"
SelectionMode="SingleDate"/>
<Calendar IsEnabled="False" />
</StackPanel>
<StackPanel Orientation="Vertical">
<StackPanel>
<TextBlock Text="SelectionMode: SingleRange"/>
<Calendar SelectionMode="SingleRange"
Margin="0,0,0,8"/>
<Calendar SelectionMode="SingleRange" />
</StackPanel>
<StackPanel>
<TextBlock Text="SelectionMode: MultipleRange"/>
<Calendar SelectionMode="MultipleRange"/>
<Calendar SelectionMode="MultipleRange" />
</StackPanel>
<StackPanel Orientation="Vertical">
<StackPanel>
<TextBlock Text="DisplayDates"/>
<Calendar Name="DisplayDatesCalendar"
SelectionMode="SingleDate"
Margin="0,0,0,8"/>
SelectionMode="SingleDate" />
</StackPanel>
<StackPanel>
<TextBlock Text="BlackoutDates"/>
<Calendar Name="BlackoutDatesCalendar"
SelectionMode="SingleDate" />
</StackPanel>
</StackPanel>
</WrapPanel>
</StackPanel>
</UserControl>

1
samples/ControlCatalog/Pages/CanvasPage.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CanvasPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Canvas</TextBlock>
<TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock>
<Canvas Background="Yellow" Width="300" Height="400">
<Rectangle Fill="Blue" Width="63" Height="41" Canvas.Left="40" Canvas.Top="31">

13
samples/ControlCatalog/Pages/CarouselPage.xaml

@ -2,14 +2,15 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CarouselPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Carousel</TextBlock>
<TextBlock Classes="h2">An items control that displays its items as pages that fill the control.</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 16 0 0" Spacing="8">
<Button Name="left" VerticalAlignment="Center" Padding="20">
<Grid ColumnDefinitions="Auto,*,Auto"
MaxWidth="660"
HorizontalAlignment="Stretch" Margin="0 16 0 0">
<Button Name="left" Grid.Column="0" VerticalAlignment="Center" Padding="10,20" Margin="4">
<Path Data="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" Fill="Black"/>
</Button>
<Carousel Name="carousel">
<Carousel Name="carousel" Grid.Column="1">
<Carousel.PageTransition>
<PageSlide Duration="0.25" Orientation="Vertical" />
</Carousel.PageTransition>
@ -17,10 +18,10 @@
<Image Source="/Assets/hirsch-899118_640.jpg"/>
<Image Source="/Assets/maple-leaf-888807_640.jpg"/>
</Carousel>
<Button Name="right" VerticalAlignment="Center" Padding="20">
<Button Name="right" Grid.Column="2" VerticalAlignment="Center" Padding="10,20" Margin="4">
<Path Data="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" Fill="Black"/>
</Button>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock VerticalAlignment="Center">Transition</TextBlock>

1
samples/ControlCatalog/Pages/CheckBoxPage.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CheckBoxPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">CheckBox</TextBlock>
<TextBlock Classes="h2">A check box control</TextBlock>
<StackPanel Orientation="Horizontal"

3
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@ -4,11 +4,10 @@
xmlns:sys="using:System"
xmlns:col="using:System.Collections">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ComboBox</TextBlock>
<TextBlock Classes="h2">A drop-down list.</TextBlock>
<WrapPanel HorizontalAlignment="Center" Margin="0 16 0 0"
MaxWidth="750">
MaxWidth="660">
<WrapPanel.Styles>
<Style Selector="ComboBox">
<Setter Property="Width" Value="250" />

1
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml

@ -24,7 +24,6 @@
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</StackPanel.Styles>
<TextBlock Classes="h1">Context Flyout</TextBlock>
<TextBlock Classes="h2">A right click Flyout that can be applied to any control.</TextBlock>
<UniformGrid HorizontalAlignment="Center" Rows="2">

1
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@ -2,7 +2,6 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Context Menu</TextBlock>
<TextBlock Classes="h2">A right click menu that can be applied to any control.</TextBlock>
<UniformGrid HorizontalAlignment="Center" Rows="2">

1
samples/ControlCatalog/Pages/CursorPage.xaml

@ -3,7 +3,6 @@
x:Class="ControlCatalog.Pages.CursorPage">
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*">
<StackPanel Grid.ColumnSpan="2" Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Cursor</TextBlock>
<TextBlock Classes="h2">Defines a cursor (mouse pointer)</TextBlock>
</StackPanel>

4
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -19,7 +19,6 @@
</UserControl.Styles>
<Grid RowDefinitions="Auto,Auto,*">
<StackPanel Orientation="Vertical" Spacing="4" Grid.Row="0">
<TextBlock Classes="h1">DataGrid</TextBlock>
<TextBlock Classes="h2">A control for displaying and interacting with a data source.</TextBlock>
</StackPanel>
<StackPanel Grid.Row="1" Spacing="4" Orientation="Horizontal" IsVisible="{Binding #EditableTab.IsSelected}">
@ -35,12 +34,13 @@
<DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All">
<DataGrid.Columns>
<!-- Using HeaderTemplate -->
<DataGridTextColumn Header="Country" HeaderTemplate="{StaticResource Demo.DataTemplates.CountryHeader}" Binding="{Binding Name}" Width="6*" MinWidth="400" />
<DataGridTextColumn Header="Country" HeaderTemplate="{StaticResource Demo.DataTemplates.CountryHeader}" Binding="{Binding Name}" Width="6*" />
<!-- CompiledBinding example of usage. -->
<DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" />
<DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" />
<DataGridTextColumn Header="Area" Binding="{Binding Area}" Width="3*" />
<DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*" CellStyleClasses="gdp"
MinWidth="200"
IsVisible="{Binding #ShowGDP.IsChecked}"/>
</DataGrid.Columns>
</DataGrid>

1
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@ -6,7 +6,6 @@
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"/>

3
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -2,9 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.DialogsPage">
<StackPanel Orientation="Vertical" Spacing="4" Margin="4">
<TextBlock Classes="h1"
Text="Picker dialogs" />
<CheckBox Name="UseFilters">Use filters</CheckBox>
<Button Name="OpenFile">_Open File</Button>
<Button Name="OpenMultipleFiles">Open _Multiple File</Button>

71
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@ -1,32 +1,45 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.DragAndDropPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Drag+Drop</TextBlock>
<TextBlock Classes="h2">Example of Drag+Drop capabilities</TextBlock>
<UserControl x:Class="ControlCatalog.Pages.DragAndDropPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">Example of Drag+Drop capabilities</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel>
<Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="2" Padding="16" Name="DragMeText">
<TextBlock Name="DragStateText">Drag Me</TextBlock>
</Border>
<Border BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="2" Padding="16" Name="DragMeCustom">
<TextBlock Name="DragStateCustom">Drag Me (custom)</TextBlock>
</Border>
<TextBlock Name="DropState"></TextBlock>
</StackPanel>
<WrapPanel HorizontalAlignment="Center">
<StackPanel Margin="8"
MaxWidth="160">
<Border Name="DragMeText"
Padding="16"
BorderBrush="{DynamicResource SystemAccentColor}"
BorderThickness="2">
<TextBlock Name="DragStateText" TextWrapping="Wrap">Drag Me</TextBlock>
</Border>
<Border Name="DragMeCustom"
Padding="16"
BorderBrush="{DynamicResource SystemAccentColor}"
BorderThickness="2">
<TextBlock Name="DragStateCustom" TextWrapping="Wrap">Drag Me (custom)</TextBlock>
</Border>
<TextBlock Name="DropState" TextWrapping="Wrap" />
</StackPanel>
<Border Background="{DynamicResource SystemAccentColorDark1}" Padding="16"
DragDrop.AllowDrop="True" Name="CopyTarget">
<TextBlock>Drop some text or files here (Copy)</TextBlock>
</Border>
<Border Background="{DynamicResource SystemAccentColorDark1}" Padding="16"
DragDrop.AllowDrop="True" Name="MoveTarget">
<TextBlock>Drop some text or files here (Move)</TextBlock>
</Border>
</StackPanel>
</StackPanel>
<StackPanel Margin="8"
Orientation="Horizontal"
Spacing="16">
<Border Name="CopyTarget"
Padding="16"
MaxWidth="260"
Background="{DynamicResource SystemAccentColorDark1}"
DragDrop.AllowDrop="True">
<TextBlock TextWrapping="Wrap">Drop some text or files here (Copy)</TextBlock>
</Border>
<Border Name="MoveTarget"
Padding="16"
MaxWidth="260"
Background="{DynamicResource SystemAccentColorDark1}"
DragDrop.AllowDrop="True">
<TextBlock TextWrapping="Wrap">Drop some text or files here (Move)</TextBlock>
</Border>
</StackPanel>
</WrapPanel>
</StackPanel>
</UserControl>

1
samples/ControlCatalog/Pages/ExpanderPage.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ExpanderPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Expander</TextBlock>
<TextBlock Classes="h2">Expands to show nested content</TextBlock>
<StackPanel Orientation="Vertical"

1
samples/ControlCatalog/Pages/ImagePage.xaml

@ -3,7 +3,6 @@
x:Class="ControlCatalog.Pages.ImagePage">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Image</TextBlock>
<TextBlock Classes="h2">Displays an image</TextBlock>
</StackPanel>

1
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -43,7 +43,6 @@
<DockPanel>
<StackPanel DockPanel.Dock="Top" Spacing="4" Margin="0 0 0 16">
<TextBlock Classes="h1">ItemsRepeater</TextBlock>
<TextBlock Classes="h2">A data-driven collection control that incorporates a flexible layout system, custom views, and virtualization.</TextBlock>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Margin="8 0" Spacing="4">

1
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -13,7 +13,6 @@
</Style>
</DockPanel.Styles>
<StackPanel DockPanel.Dock="Top" Margin="4">
<TextBlock Classes="h1">ListBox</TextBlock>
<TextBlock Classes="h2">Hosts a collection of ListBoxItem.</TextBlock>
<TextBlock Classes="h2">Each 5th item is highlighted with nth-child(5n+3) and nth-last-child(5n+4) rules.</TextBlock>
</StackPanel>

8
samples/ControlCatalog/Pages/MenuPage.xaml

@ -2,16 +2,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.MenuPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Menu</TextBlock>
<TextBlock Classes="h2">Exported menu fallback</TextBlock>
<TextBlock>(Should be only visible on platforms without desktop-global menu bar)</TextBlock>
<NativeMenuBar/>
<TextBlock Classes="h2">A window menu</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<WrapPanel HorizontalAlignment="Center">
<StackPanel>
<TextBlock Classes="h3" Margin="4 8">Defined in XAML</TextBlock>
<Menu>
@ -85,6 +81,6 @@
</MenuItem>
</Menu>
</StackPanel>
</StackPanel>
</WrapPanel>
</StackPanel>
</UserControl>

1
samples/ControlCatalog/Pages/NotificationsPage.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.NotificationsPage">
<StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Left">
<TextBlock Classes="h1">Notifications</TextBlock>
<Button Content="Show Standard Managed Notification" Command="{Binding ShowManagedNotificationCommand}" />
<Button Content="Show Custom Managed Notification" Command="{Binding ShowCustomManagedNotificationCommand}" />
<Button Content="Show Native Notification" Command="{Binding ShowNativeNotificationCommand}" />

11
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -4,12 +4,11 @@
x:Class="ControlCatalog.Pages.NumericUpDownPage">
<StackPanel Orientation="Vertical" Spacing="4"
MaxWidth="800">
<TextBlock Margin="2" Classes="h1">Numeric up-down control</TextBlock>
<TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock>
<TextBlock Margin="2,5,2,2" FontSize="14" FontWeight="Bold">Features:</TextBlock>
<Grid Margin="2" ColumnDefinitions="Auto,Auto,Auto,Auto" RowDefinitions="Auto,Auto">
<Grid Grid.Row="0" Grid.Column="0" ColumnDefinitions="Auto, Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<WrapPanel Margin="-8,0">
<Grid Grid.Row="0" Grid.Column="0" Margin="8" ColumnDefinitions="Auto, Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">ShowButtonSpinner:</TextBlock>
<CheckBox Grid.Row="0" Grid.Column="1" IsChecked="{Binding #upDown.ShowButtonSpinner}" VerticalAlignment="Center" Margin="2"/>
@ -23,7 +22,7 @@
<CheckBox Grid.Row="3" Grid.Column="1" IsChecked="{Binding #upDown.ClipValueToMinMax}" VerticalAlignment="Center" Margin="2"/>
</Grid>
<Grid Grid.Row="0" Grid.Column="1" Margin="10,2,2,2" ColumnDefinitions="Auto, 120" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<Grid Grid.Row="0" Grid.Column="1" Margin="8" ColumnDefinitions="Auto, 120" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock>
<ComboBox Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
VerticalAlignment="Center" Margin="2">
@ -52,7 +51,7 @@
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">Text:</TextBlock>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding #upDown.Text}" VerticalAlignment="Center" Margin="2" />
</Grid>
<Grid Grid.Row="0" Grid.Column="2" Margin="10,2,2,2" RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto, Auto">
<Grid Grid.Row="0" Grid.Column="2" Margin="8" RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="Auto, Auto">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Minimum:</TextBlock>
<NumericUpDown Grid.Row="0" Grid.Column="1" Value="{Binding #upDown.Minimum}"
CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Margin="2" HorizontalAlignment="Center"/>
@ -71,7 +70,7 @@
</Grid>
</Grid>
</WrapPanel>
<WrapPanel Margin="2,10,2,2">
<StackPanel Orientation="Vertical" Margin="10">

1
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@ -1,6 +1,5 @@
<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.ProgressBarPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ProgressBar</TextBlock>
<TextBlock Classes="h2">A progress bar control</TextBlock>
<StackPanel>
<CheckBox x:Name="showProgress" Margin="10,16,0,0" Content="Show Progress Text" />

1
samples/ControlCatalog/Pages/RadioButtonPage.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.RadioButtonPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">RadioButton</TextBlock>
<TextBlock Classes="h2">Allows the selection of a single option of many</TextBlock>
<StackPanel Orientation="Horizontal"

3
samples/ControlCatalog/Pages/RelativePanelPage.axaml

@ -4,6 +4,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.RelativePanelPage">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<RelativePanel Width="620" Height="700" Margin="32">
<Border Name="Rect1" Background="Red" Height="50" Width="50">
<TextBlock Text="Rect1" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
@ -57,4 +59,5 @@
<TextBlock Text="Rect15 (LeftOf Rect2, AlignTopWith Rect9)" Padding="10,0" Foreground="White" FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</RelativePanel>
</ScrollViewer>
</UserControl>

1
samples/ControlCatalog/Pages/ScrollViewerPage.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ScrollViewerPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">ScrollViewer</TextBlock>
<TextBlock Classes="h2">Allows for horizontal and vertical content scrolling.</TextBlock>
<Grid ColumnDefinitions="Auto, *">

1
samples/ControlCatalog/Pages/SliderPage.xaml

@ -3,7 +3,6 @@
xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="ControlCatalog.Pages.SliderPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Slider</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">

6
samples/ControlCatalog/Pages/TabControlPage.xaml

@ -3,12 +3,6 @@
x:Class="ControlCatalog.Pages.TabControlPage"
xmlns="https://github.com/avaloniaui">
<DockPanel>
<TextBlock
DockPanel.Dock="Top"
Classes="h1"
Text="TabControl"
Margin="4">
</TextBlock>
<TextBlock
DockPanel.Dock="Top"
Classes="h2"

1
samples/ControlCatalog/Pages/TabStripPage.xaml

@ -2,7 +2,6 @@
x:Class="ControlCatalog.Pages.TabStripPage"
xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">TabStrip</TextBlock>
<TextBlock Classes="h2">A control which displays a selectable strip of tabs</TextBlock>
<Separator Margin="0 16"/>

137
samples/ControlCatalog/Pages/TextBlockPage.xaml

@ -1,102 +1,99 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.TextBlockPage">
<UserControl x:Class="ControlCatalog.Pages.TextBlockPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock Classes="h1">TextBlock</TextBlock>
<TextBlock Classes="h2">A control that can display text</TextBlock>
<StackPanel
Orientation="Horizontal"
Spacing="16"
HorizontalAlignment="Center"
Margin="0,16,0,0">
<StackPanel.Styles>
<WrapPanel MaxWidth="680"
Margin="-10,0"
HorizontalAlignment="Center">
<WrapPanel.Styles>
<Style Selector="Border">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumLowBrush}"/>
<Setter Property="Padding" Value="2"/>
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumLowBrush}" />
<Setter Property="Padding" Value="2" />
<Setter Property="Margin" Value="10" />
<Setter Property="Width" Value="200" />
</Style>
</StackPanel.Styles>
</WrapPanel.Styles>
<Border>
<StackPanel Width="200" Spacing="8">
<TextBlock Margin="0 0 10 0" TextTrimming="CharacterEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<TextBlock Margin="0 0 10 0" TextTrimming="WordEllipsis" Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
<StackPanel Spacing="8">
<TextBlock Margin="0,0,10,0"
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
TextTrimming="CharacterEllipsis" />
<TextBlock Margin="0,0,10,0"
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
TextTrimming="WordEllipsis" />
<TextBlock Text="Left aligned text" TextAlignment="Left" />
<TextBlock Text="Center aligned text" TextAlignment="Center" />
<TextBlock Text="Right aligned text" TextAlignment="Right" />
</StackPanel>
</Border>
<Border>
<StackPanel Width="200" Spacing="8">
<TextBlock
TextWrapping="Wrap"
Text="Multiline TextBlock with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<StackPanel Spacing="8">
<TextBlock Text="Multiline TextBlock with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." TextWrapping="Wrap" />
</StackPanel>
</Border>
<Border>
<StackPanel Width="200" Spacing="8">
<TextBlock Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBlock Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBlock Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/>
<TextBlock Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
<StackPanel Spacing="8">
<TextBlock FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"
FontStyle="Normal"
FontWeight="Normal"
Text="Custom font regular" />
<TextBlock FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"
FontStyle="Normal"
FontWeight="Bold"
Text="Custom font bold" />
<TextBlock FontFamily="/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"
FontStyle="Italic"
FontWeight="Normal"
Text="Custom font italic" />
<TextBlock FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"
FontStyle="Italic"
FontWeight="Bold"
Text="Custom font italic bold" />
</StackPanel>
</Border>
</StackPanel>
<StackPanel
Orientation="Horizontal"
Spacing="16"
HorizontalAlignment="Center"
Margin="0,16,0,0">
<StackPanel.Styles>
<Style Selector="Border">
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumLowBrush}"/>
<Setter Property="Padding" Value="2"/>
</Style>
</StackPanel.Styles>
<Border>
<StackPanel Width="200" Spacing="8">
<TextBlock TextDecorations="Underline" Text="Underline"/>
<TextBlock TextDecorations="Strikethrough" Text="Strikethrough"/>
<TextBlock TextDecorations="Overline" Text="Overline" />
<TextBlock TextDecorations="Baseline" Text="Baseline"/>
<StackPanel Spacing="8">
<TextBlock Text="Underline" TextDecorations="Underline" />
<TextBlock Text="Strikethrough" TextDecorations="Strikethrough" />
<TextBlock Text="Overline" TextDecorations="Overline" />
<TextBlock Text="Baseline" TextDecorations="Baseline" />
<TextBlock Text="Custom TextDecorations">
<TextBlock.TextDecorations>
<TextDecorationCollection>
<TextDecoration
Location="Overline"
StrokeThicknessUnit="Pixel"
StrokeThickness="2">
<TextDecoration Location="Overline"
StrokeThickness="2"
StrokeThicknessUnit="Pixel">
<TextDecoration.Stroke>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Green"/>
<GradientStop Offset="0" Color="Red" />
<GradientStop Offset="1" Color="Green" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextDecoration.Stroke>
</TextDecoration>
<TextDecoration
Location="Strikethrough"
StrokeThicknessUnit="Pixel"
StrokeThickness="1">
<TextDecoration Location="Strikethrough"
StrokeThickness="1"
StrokeThicknessUnit="Pixel">
<TextDecoration.Stroke>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Blue"/>
<GradientStop Offset="0" Color="Green" />
<GradientStop Offset="1" Color="Blue" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextDecoration.Stroke>
</TextDecoration>
<TextDecoration
Location="Underline"
StrokeThicknessUnit="Pixel"
StrokeThickness="2">
<TextDecoration Location="Underline"
StrokeThickness="2"
StrokeThicknessUnit="Pixel">
<TextDecoration.Stroke>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="1" Color="Red"/>
<GradientStop Offset="0" Color="Blue" />
<GradientStop Offset="1" Color="Red" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</TextDecoration.Stroke>
@ -107,19 +104,19 @@
</StackPanel>
</Border>
<Border>
<StackPanel Width="200" Spacing="8">
<TextBlock Text="🏻 👌🏻"/>
<StackPanel Spacing="8">
<TextBlock Text="🏻 👌🏻" />
<TextBlock Text="🏼 👌🏼" />
<TextBlock Text="🏽 👌🏽"/>
<TextBlock Text="🏾 👌🏾"/>
<TextBlock Text="🏿 👌🏿"/>
<TextBlock Text="🏽 👌🏽" />
<TextBlock Text="🏾 👌🏾" />
<TextBlock Text="🏿 👌🏿" />
</StackPanel>
</Border>
<Border>
<StackPanel Width="200" Spacing="8">
<TextBlock Text="👪 👨‍👩‍👧 👨‍👩‍👧‍👦"/>
<StackPanel Spacing="8">
<TextBlock Text="👪 👨‍👩‍👧 👨‍👩‍👧‍👦" />
</StackPanel>
</Border>
</StackPanel>
</WrapPanel>
</StackPanel>
</UserControl>

33
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -3,14 +3,11 @@
x:Class="ControlCatalog.Pages.TextBoxPage"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<StackPanel Orientation="Vertical" Spacing="4">
<Label Classes="h1">TextBox</Label>
<Label Classes="h2">A control into which the user can input text</Label>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<WrapPanel Margin="-8,0"
HorizontalAlignment="Center">
<StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<TextBox Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit." Width="200"
FontFamily="Comic Sans MS"
Foreground="Blue">
@ -18,10 +15,10 @@
<Flyout>
<TextBlock>Custom context flyout</TextBlock>
</Flyout>
</TextBox.ContextFlyout>
</TextBox.ContextFlyout>
</TextBox>
<TextBox Width="200" Watermark="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" Watermark="Numeric Watermark" x:Name="numericWatermark"/>
<TextBox Width="200" Watermark="Numeric with watermark" x:Name="numericWatermark"/>
<TextBox Width="200"
Watermark="Floating Watermark"
UseFloatingWatermark="True"
@ -49,15 +46,27 @@
<TextBox Width="200" Text="Custom caret brush" CaretBrush="DarkOrange"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8">
<StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Width="200" Height="125"
Text="Multiline TextBox with TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox AcceptsReturn="True" Width="200" Height="125"
Text="Multiline TextBox with no TextWrapping.&#xD;&#xD;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." />
<TextBox Classes="clearButton" Text="Clear Content" Width="200" FontWeight="Normal" FontStyle="Normal" Watermark="Watermark" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Text="IME small font" Width="200"
FontFamily="Comic Sans MS"
FontSize="10"
Foreground="Red"/>
<TextBox Text="IME large font" Width="200"
FontFamily="Comic Sans MS"
FontSize="22"
Foreground="Red"/>
<TextBox Text="IME disabled" Width="200"
FontFamily="Comic Sans MS"
InputMethod.IsInputMethodEnabled="False"
Foreground="Red"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8">
<StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<Label Classes="h2" Target="{Binding #firstResMFont}">res_m fonts</Label>
<TextBox Width="200" x:Name="firstResMFont" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="resm:ControlCatalog.Assets.Fonts?assembly=ControlCatalog#Source Sans Pro"/>
@ -65,14 +74,14 @@
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="resm:ControlCatalog.Assets.Fonts.SourceSansPro-*.ttf?assembly=ControlCatalog#Source Sans Pro"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8">
<StackPanel Orientation="Vertical" Spacing="8" Margin="8">
<Label Classes="h2" Target="{Binding #firstResFont}">_res fonts</Label>
<TextBox Width="200" x:Name="firstResFont" Text="Custom font regular" FontWeight="Normal" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font bold" FontWeight="Bold" FontStyle="Normal" FontFamily="avares://ControlCatalog/Assets/Fonts#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic" FontWeight="Normal" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-Italic.ttf#Source Sans Pro"/>
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
</StackPanel>
</StackPanel>
</WrapPanel>
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Height="200" MaxWidth="400"
FontFamily="avares://ControlCatalog/Assets/Fonts#WenQuanYi Micro Hei"
Text="计算机科学(是系统性研究信息与计算的理论基础以及它们在计算机系统中如何实现与应用的实用技术的学科。它通常被形容为对那些创造、描述以及转换信息的算法处理的系统研究。计算机科学包含很多分支领域;有些强调特定结果的计算,比如计算机图形学;而有些是探討计算问题的性质,比如计算复杂性理论;还有一些领域專注于怎样实现计算,比如程式語言理論是研究描述计算的方法,而程式设计是应用特定的程式語言解决特定的计算问题,人机交互则是專注于怎样使计算机和计算变得有用、好用,以及随时随地为人所用。&#xD;&#xD;有时公众会误以为计算机科学就是解决计算机问题的事业(比如信息技术),或者只是与使用计算机的经验有关,如玩游戏、上网或者文字处理。其实计算机科学所关注的,不仅仅是去理解实现类似游戏、浏览器这些软件的程序的性质,更要通过现有的知识创造新的程序或者改进已有的程序。" />

3
samples/ControlCatalog/Pages/ToggleSwitchPage.xaml

@ -1,7 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ToggleSwitchPage" Margin="5">
<StackPanel Width="500" HorizontalAlignment="Center">
<StackPanel MaxWidth="500"
HorizontalAlignment="Stretch">
<TextBlock Text="Simple ToggleSwitch" Classes="header"/>
<Border Classes="Thin">
<StackPanel>

1
samples/ControlCatalog/Pages/ToolTipPage.xaml

@ -3,7 +3,6 @@
x:Class="ControlCatalog.Pages.ToolTipPage">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
<Grid RowDefinitions="Auto,Auto,Auto"

1
samples/ControlCatalog/Pages/TreeViewPage.xaml

@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.TreeViewPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">TreeView</TextBlock>
<TextBlock Classes="h2">Displays a hierachical tree of data.</TextBlock>
<StackPanel Orientation="Horizontal"

1
samples/ControlCatalog/Pages/ViewboxPage.xaml

@ -4,7 +4,6 @@
<Grid RowDefinitions="Auto,*,*">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Viewbox</TextBlock>
<TextBlock Classes="h2">A control used to scale single child.</TextBlock>
</StackPanel>

84
samples/ControlCatalog/SideBar.xaml

@ -1,84 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Design.PreviewWith>
<Border Padding="20">
<TabControl Classes="sidebar">
<TabItem Header="Item1"/>
<TabItem Header="Item2"/>
</TabControl>
</Border>
</Design.PreviewWith>
<Style Selector="TabControl.sidebar">
<Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="Padding" Value="8 0 0 0"/>
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}"/>
<Setter Property="Template">
<ControlTemplate>
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<ScrollViewer
Name="PART_ScrollViewer"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
Background="{TemplateBinding Background}"
DockPanel.Dock="Left">
<ItemsPresenter
Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}">
</ItemsPresenter>
</ScrollViewer>
<ContentControl Content="{TemplateBinding Tag}" HorizontalContentAlignment="Right" DockPanel.Dock="Bottom"/>
<ScrollViewer
HorizontalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.VerticalScrollBarVisibility)}">
<ContentPresenter
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TabControl.sidebar > TabItem">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.150"/>
</Transitions>
</Setter>
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Auto"/>
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Auto"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Background" Value="{DynamicResource SystemAccentColorLight2}"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected">
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected">
<Setter Property="Background" Value="{DynamicResource SystemAccentColorLight1}"/>
</Style>
</Styles>

15
samples/RenderDemo/App.xaml

@ -1,9 +1,8 @@
<Application
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.App">
<Application.Styles>
<FluentTheme/>
<StyleInclude Source="avares://RenderDemo/SideBar.xaml"/>
</Application.Styles>
<Application x:Class="RenderDemo.App"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</Application.Styles>
</Application>

121
samples/RenderDemo/MainWindow.xaml

@ -1,68 +1,67 @@
<Window xmlns="https://github.com/avaloniaui"
<Window x:Class="RenderDemo.MainWindow"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RenderDemo.MainWindow"
Title="AvaloniaUI Rendering Test"
xmlns:controls="clr-namespace:ControlSamples;assembly=ControlSamples"
xmlns:pages="clr-namespace:RenderDemo.Pages"
Title="AvaloniaUI Rendering Test"
Width="{Binding Width, Mode=TwoWay}"
Height="{Binding Height, Mode=TwoWay}">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Rendering">
<MenuItem Header="Draw Dirty Rects" Command="{Binding ToggleDrawDirtyRects}">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsHitTestVisible="False"
IsChecked="{Binding DrawDirtyRects}"/>
</MenuItem.Icon>
<controls:HamburgerMenu ExpandedModeThresholdWidth="760">
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuItem Header="Rendering">
<MenuItem Command="{Binding ToggleDrawDirtyRects}" Header="Draw Dirty Rects">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsChecked="{Binding DrawDirtyRects}"
IsHitTestVisible="False" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="{Binding ToggleDrawFps}" Header="Draw FPS">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsChecked="{Binding DrawFps}"
IsHitTestVisible="False" />
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Draw FPS"
Command="{Binding ToggleDrawFps}">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsHitTestVisible="False"
IsChecked="{Binding DrawFps}"/>
</MenuItem.Icon>
<MenuItem Header="Tests">
<MenuItem Command="{Binding ResizeWindow}" Header="Resize window" />
</MenuItem>
</MenuItem>
<MenuItem Header="Tests">
<MenuItem Header="Resize window"
Command="{Binding ResizeWindow}"/>
</MenuItem>
</Menu>
<TabControl Classes="sidebar">
<TabItem Header="Animations">
<pages:AnimationsPage/>
</TabItem>
<TabItem Header="Transitions">
<pages:TransitionsPage/>
</TabItem>
<TabItem Header="Custom Animator">
<pages:CustomAnimatorPage/>
</TabItem>
<TabItem Header="Clipping">
<pages:ClippingPage/>
</TabItem>
<TabItem Header="Drawing">
<pages:DrawingPage/>
</TabItem>
<TabItem Header="SkCanvas">
<pages:CustomSkiaPage/>
</TabItem>
<TabItem Header="RenderTargetBitmap">
<pages:RenderTargetBitmapPage/>
</TabItem>
<TabItem Header="WriteableBitmap">
<pages:WriteableBitmapPage/>
</TabItem>
<TabItem Header="GlyphRun">
<pages:GlyphRunPage/>
</TabItem>
<TabItem Header="LineBounds">
<pages:LineBoundsPage />
</TabItem>
<TabItem Header="Path Measurement">
<pages:PathMeasurementPage />
</TabItem>
</TabControl>
</DockPanel>
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<TabItem Header="Animations">
<pages:AnimationsPage />
</TabItem>
<TabItem Header="Transitions">
<pages:TransitionsPage />
</TabItem>
<TabItem Header="Custom Animator">
<pages:CustomAnimatorPage />
</TabItem>
<TabItem Header="Clipping">
<pages:ClippingPage />
</TabItem>
<TabItem Header="Drawing">
<pages:DrawingPage />
</TabItem>
<TabItem Header="SkCanvas">
<pages:CustomSkiaPage />
</TabItem>
<TabItem Header="RenderTargetBitmap">
<pages:RenderTargetBitmapPage />
</TabItem>
<TabItem Header="WriteableBitmap">
<pages:WriteableBitmapPage />
</TabItem>
<TabItem Header="GlyphRun">
<pages:GlyphRunPage />
</TabItem>
<TabItem Header="LineBounds">
<pages:LineBoundsPage />
</TabItem>
<TabItem Header="Path Measurement">
<pages:PathMeasurementPage />
</TabItem>
</controls:HamburgerMenu>
</Window>

1
samples/RenderDemo/RenderDemo.csproj

@ -10,6 +10,7 @@
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\MiniMvvm\MiniMvvm.csproj" />
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</ItemGroup>
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\EmbedXaml.props" />

67
samples/RenderDemo/SideBar.xaml

@ -1,67 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="TabControl.sidebar">
<Setter Property="TabStripPlacement" Value="Left"/>
<Setter Property="Padding" Value="8 0 0 0"/>
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}"/>
<Setter Property="Template">
<ControlTemplate>
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel>
<ScrollViewer
Name="PART_ScrollViewer"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
Background="{TemplateBinding Background}">
<ItemsPresenter
Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}">
</ItemsPresenter>
</ScrollViewer>
<ContentPresenter
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="TabControl.sidebar > TabItem">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.150"/>
</Transitions>
</Setter>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected /template/ Border#PART_SelectedPipe">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource SystemAccentColorLight2}"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected">
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:selected /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource SystemAccentColorLight1}"/>
</Style>
</Styles>

23
samples/SampleControls/ControlSamples.csproj

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

77
samples/SampleControls/HamburgerMenu/HamburgerMenu.cs

@ -0,0 +1,77 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
namespace ControlSamples
{
public class HamburgerMenu : TabControl
{
private SplitView? _splitView;
public static readonly StyledProperty<IBrush?> PaneBackgroundProperty =
SplitView.PaneBackgroundProperty.AddOwner<HamburgerMenu>();
public IBrush? PaneBackground
{
get => GetValue(PaneBackgroundProperty);
set => SetValue(PaneBackgroundProperty, value);
}
public static readonly StyledProperty<IBrush?> ContentBackgroundProperty =
AvaloniaProperty.Register<HamburgerMenu, IBrush?>(nameof(ContentBackground));
public IBrush? ContentBackground
{
get => GetValue(ContentBackgroundProperty);
set => SetValue(ContentBackgroundProperty, value);
}
public static readonly StyledProperty<int> ExpandedModeThresholdWidthProperty =
AvaloniaProperty.Register<HamburgerMenu, int>(nameof(ExpandedModeThresholdWidth), 1008);
public int ExpandedModeThresholdWidth
{
get => GetValue(ExpandedModeThresholdWidthProperty);
set => SetValue(ExpandedModeThresholdWidthProperty, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_splitView = e.NameScope.Find<SplitView>("PART_NavigationPane");
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == BoundsProperty && _splitView is not null)
{
var oldBounds = change.OldValue.GetValueOrDefault<Rect>();
var newBounds = change.NewValue.GetValueOrDefault<Rect>();
EnsureSplitViewMode(oldBounds, newBounds);
}
}
private void EnsureSplitViewMode(Rect oldBounds, Rect newBounds)
{
if (_splitView is not null)
{
var threshold = ExpandedModeThresholdWidth;
if (newBounds.Width >= threshold && oldBounds.Width < threshold)
{
_splitView.DisplayMode = SplitViewDisplayMode.Inline;
_splitView.IsPaneOpen = true;
}
else if (newBounds.Width < threshold && oldBounds.Width >= threshold)
{
_splitView.DisplayMode = SplitViewDisplayMode.Overlay;
_splitView.IsPaneOpen = false;
}
}
}
}
}

242
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@ -0,0 +1,242 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:catalog="using:ControlSamples">
<Design.PreviewWith>
<Border Width="400"
Height="150">
<catalog:HamburgerMenu>
<FlyoutBase.AttachedFlyout>
<Flyout>
<TextBox Text="Hello World" />
</Flyout>
</FlyoutBase.AttachedFlyout>
<TabItem Header="Item1" IsSelected="True">
<UserControl>
<Border Height="400" Background="Green" />
</UserControl>
</TabItem>
<TabItem Header="Item2" />
</catalog:HamburgerMenu>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<x:Double x:Key="PaneCompactWidth">40</x:Double>
<x:Double x:Key="PaneExpandWidth">200</x:Double>
<x:Double x:Key="HeaderHeight">36</x:Double>
<x:Double x:Key="NavigationItemHeight">36</x:Double>
<x:Double x:Key="HamburgerMenuButtonHeight">32</x:Double>
<Thickness x:Key="HeaderMarginCollapsedPane">12,0,0,0</Thickness>
<Thickness x:Key="HeaderMarginExpandedPane">52,0,0,0</Thickness>
<Thickness x:Key="HeaderMarginExpandedOverlayPane">212,0,0,0</Thickness>
<BoxShadows x:Key="NavigationItemShadow">1 1 1 1 #2000, 0 0 1 1 #2fff</BoxShadows>
<BoxShadows x:Key="NavigationContentShadow">0 0 1 1 #2000</BoxShadows>
</Styles.Resources>
<!-- HamburgerMenu -->
<Style Selector="catalog|HamburgerMenu">
<Setter Property="Padding" Value="12 8 4 0" />
<Setter Property="PaneBackground" Value="{DynamicResource SystemChromeMediumColor}" />
<Setter Property="Background" Value="{DynamicResource SystemChromeMediumColor}" />
<Setter Property="ContentBackground" Value="{DynamicResource SystemAltHighColor}" />
<Setter Property="Template">
<ControlTemplate>
<Panel Background="{TemplateBinding PaneBackground}">
<SplitView x:Name="PART_NavigationPane"
CompactPaneLength="{StaticResource PaneCompactWidth}"
DisplayMode="Inline"
IsPaneOpen="True"
OpenPaneLength="{StaticResource PaneExpandWidth}">
<SplitView.Pane>
<Grid Margin="0,0,1,5" RowDefinitions="Auto, *, Auto">
<Panel Height="{StaticResource HeaderHeight}" />
<ScrollViewer x:Name="PART_ScrollViewer"
Grid.Row="1"
HorizontalAlignment="Stretch"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<ItemsPresenter Name="PART_ItemsPresenter"
HorizontalAlignment="Stretch"
ItemTemplate="{TemplateBinding ItemTemplate}"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}">
<ItemsPresenter.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel x:Name="HamburgerItemsPanel"
Margin="0,2" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsPresenter.ItemsPanel>
</ItemsPresenter>
</ScrollViewer>
<Button x:Name="SettingsButton"
Grid.Row="2"
Classes="NavigationButton"
Content="Settings"
Flyout="{TemplateBinding (FlyoutBase.AttachedFlyout)}"
IsVisible="{Binding $parent[TabControl].(FlyoutBase.AttachedFlyout), Converter={x:Static ObjectConverters.IsNotNull}}" />
</Grid>
</SplitView.Pane>
<SplitView.Content>
<DockPanel>
<Border Height="{StaticResource HeaderHeight}" DockPanel.Dock="Top">
<TextBlock x:Name="HeaderHolder"
VerticalAlignment="Center"
Classes="h1"
Text="{Binding $parent[TabControl].SelectedItem.Header, FallbackValue=''}">
<TextBlock.Transitions>
<Transitions>
<ThicknessTransition Easing="{StaticResource SplitViewPaneAnimationEasing}"
Property="Margin"
Duration="{StaticResource SplitViewPaneAnimationCloseDuration}" />
</Transitions>
</TextBlock.Transitions>
</TextBlock>
</Border>
<Border x:Name="BackgroundBorder">
<Border.Transitions>
<Transitions>
<CornerRadiusTransition Property="CornerRadius" Duration="{StaticResource SplitViewPaneAnimationCloseDuration}" />
</Transitions>
</Border.Transitions>
<ScrollViewer x:Name="HamburgerContentScroller"
HorizontalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.VerticalScrollBarVisibility)}">
<ContentPresenter Name="PART_SelectedContentHost"
Background="Transparent"
Padding="{TemplateBinding Padding}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}" />
</ScrollViewer>
</Border>
</DockPanel>
</SplitView.Content>
</SplitView>
<ToggleButton x:Name="HamburgerMenuButton"
Width="{StaticResource PaneCompactWidth}"
Height="{StaticResource HamburgerMenuButtonHeight}"
Margin="4,2,0,0"
Padding="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
HorizontalContentAlignment="Center"
Classes="NavigationButton"
CornerRadius="4"
IsChecked="{Binding #PART_NavigationPane.IsPaneOpen, Mode=TwoWay}">
<PathIcon Data="M3 17h18a1 1 0 0 1 .117 1.993L21 19H3a1 1 0 0 1-.117-1.993L3 17h18H3Zm0-6 18-.002a1 1 0 0 1 .117 1.993l-.117.007L3 13a1 1 0 0 1-.117-1.993L3 11l18-.002L3 11Zm0-6h18a1 1 0 0 1 .117 1.993L21 7H3a1 1 0 0 1-.117-1.993L3 5h18H3Z" Foreground="{TemplateBinding Foreground}" />
</ToggleButton>
</Panel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginExpandedPane}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[IsPaneOpen=True] TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginCollapsedPane}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Overlay][IsPaneOpen=True] TextBlock#HeaderHolder">
<Setter Property="Margin" Value="{StaticResource HeaderMarginExpandedOverlayPane}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView">
<Setter Property="PaneBackground" Value="Transparent" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Overlay]">
<Setter Property="PaneBackground" Value="{TemplateBinding PaneBackground}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Overlay]">
<Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Inline] Border#BackgroundBorder">
<Setter Property="Background" Value="{Binding $parent[TabControl].ContentBackground}" />
<Setter Property="BoxShadow" Value="{StaticResource NavigationContentShadow}" />
</Style>
<Style Selector="catalog|HamburgerMenu /template/ SplitView[DisplayMode=Inline][IsPaneOpen=True] Border#BackgroundBorder">
<Setter Property="CornerRadius" Value="8 0 0 0" />
</Style>
<!-- HamburgerMenu TabItem -->
<Style Selector="catalog|HamburgerMenu > TabItem, :is(Button).NavigationButton">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="Height" Value="{StaticResource NavigationItemHeight}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="12,0,4,0" />
<Setter Property="Margin" Value="4,0,8,0" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="ClipToBounds" Value="False" />
</Style>
<Style Selector="catalog|HamburgerMenu > TabItem">
<Setter Property="Template">
<ControlTemplate>
<Border Name="PART_LayoutRoot"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<Panel>
<Border Name="PART_SelectedPipe"
Width="{DynamicResource TabItemPipeThickness}"
Height="{DynamicResource TabItemVerticalPipeHeight}"
Margin="6,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}" />
<ContentPresenter Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
TextBlock.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" />
</Panel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector=":is(Button).NavigationButton">
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{TemplateBinding CornerRadius}"
TextBlock.FontFamily="{TemplateBinding FontFamily}"
TextBlock.FontSize="{TemplateBinding FontSize}"
TextBlock.FontWeight="{TemplateBinding FontWeight}" />
</ControlTemplate>
</Setter>
</Style>
<Style Selector="catalog|HamburgerMenu > TabItem /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton /template/ ContentPresenter">
<Setter Property="Border.Background" Value="Transparent" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemBaseHighColor}" />
</Style>
<Style Selector="catalog|HamburgerMenu > TabItem:pointerover /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton:pointerover /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPointerOver}" />
</Style>
<Style Selector="catalog|HamburgerMenu > TabItem:pressed /template/ Border#PART_LayoutRoot, :is(Button).NavigationButton:pressed /template/ ContentPresenter">
<Setter Property="Border.Background" Value="{DynamicResource SystemChromeLowColor}" />
<Setter Property="Border.BoxShadow" Value="{StaticResource NavigationItemShadow}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundUnselectedPressed}" />
</Style>
<Style Selector=":is(Button).NavigationButton:pressed">
<Setter Property="RenderTransform" Value="none" />
</Style>
</Styles>

63
src/Avalonia.Controls/MenuItem.cs

@ -107,6 +107,7 @@ namespace Avalonia.Controls
private ICommand? _command;
private bool _commandCanExecute = true;
private bool _commandBindingError;
private Popup? _popup;
private KeyGesture? _hotkey;
private bool _isEmbeddedInMenu;
@ -379,6 +380,8 @@ namespace Avalonia.Controls
{
Command.CanExecuteChanged += CanExecuteChanged;
}
TryUpdateCanExecute();
var parent = Parent;
@ -498,13 +501,11 @@ namespace Avalonia.Controls
base.UpdateDataValidation(property, value);
if (property == CommandProperty)
{
if (value.Type == BindingValueType.BindingError)
_commandBindingError = value.Type == BindingValueType.BindingError;
if (_commandBindingError && _commandCanExecute)
{
if (_commandCanExecute)
{
_commandCanExecute = false;
UpdateIsEffectivelyEnabled();
}
_commandCanExecute = false;
UpdateIsEffectivelyEnabled();
}
}
}
@ -526,22 +527,20 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
private static void CommandChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is MenuItem menuItem)
if (e.Sender is MenuItem menuItem &&
((ILogical)menuItem).IsAttachedToLogicalTree)
{
if (((ILogical)menuItem).IsAttachedToLogicalTree)
if (e.OldValue is ICommand oldCommand)
{
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
}
oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
}
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);
menuItem.TryUpdateCanExecute();
}
}
@ -553,7 +552,7 @@ namespace Avalonia.Controls
{
if (e.Sender is MenuItem menuItem)
{
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);
menuItem.TryUpdateCanExecute();
}
}
@ -564,8 +563,29 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
private void CanExecuteChanged(object sender, EventArgs e)
{
var canExecute = Command == null || Command.CanExecute(CommandParameter);
TryUpdateCanExecute();
}
/// <summary>
/// Tries to evaluate CanExecute value of a Command if menu is opened
/// </summary>
private void TryUpdateCanExecute()
{
if (Command == null)
{
_commandCanExecute = !_commandBindingError;
UpdateIsEffectivelyEnabled();
return;
}
//Perf optimization - only raise CanExecute event if the menu is open
if (!((ILogical)this).IsAttachedToLogicalTree ||
Parent is MenuItem { IsSubMenuOpen: false })
{
return;
}
var canExecute = Command.CanExecute(CommandParameter);
if (canExecute != _commandCanExecute)
{
_commandCanExecute = canExecute;
@ -635,6 +655,11 @@ namespace Avalonia.Controls
if (value)
{
foreach (var item in Items.OfType<MenuItem>())
{
item.TryUpdateCanExecute();
}
RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent));
IsSelected = true;
PseudoClasses.Add(":open");

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

@ -116,7 +116,7 @@ namespace Avalonia.Controls.Platform
protected IMenu? Menu { get; private set; }
public static TimeSpan MenuShowDelay { get; set; } = TimeSpan.FromMilliseconds(400);
protected static TimeSpan MenuShowDelay { get; } = TimeSpan.FromMilliseconds(400);
protected internal virtual void GotFocus(object sender, GotFocusEventArgs e)
{

12
src/Avalonia.Controls/Platform/INativeApplicationCommands.cs

@ -0,0 +1,12 @@
namespace Avalonia.Controls.Platform
{
/// <summary>
/// Native Menu Default Application Commands
/// </summary>
public interface INativeApplicationCommands
{
void HideApp();
void ShowAll();
void HideOthers();
}
}

2
src/Avalonia.Controls/TextBox.cs

@ -533,7 +533,7 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
_imClient.SetPresenter(_presenter);
_imClient.SetPresenter(_presenter, this);
if (IsFocused)
{
_presenter?.ShowCaret();

26
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.TextInput;
using Avalonia.VisualTree;
@ -7,9 +8,26 @@ namespace Avalonia.Controls
{
internal class TextBoxTextInputMethodClient : ITextInputMethodClient
{
private InputElement _parent;
private TextPresenter _presenter;
private IDisposable _subscription;
public Rect CursorRectangle => _presenter?.GetCursorRectangle() ?? default;
public Rect CursorRectangle
{
get
{
if (_parent == null || _presenter == null)
{
return default;
}
var transform = _presenter.TransformToVisual(_parent);
if (transform == null)
{
return default;
}
return _presenter.GetCursorRectangle().TransformToAABB(transform.Value);
}
}
public event EventHandler CursorRectangleChanged;
public IVisual TextViewVisual => _presenter;
public event EventHandler TextViewVisualChanged;
@ -23,9 +41,11 @@ namespace Avalonia.Controls
public string TextAfterCursor => null;
private void OnCaretIndexChanged(int index) => CursorRectangleChanged?.Invoke(this, EventArgs.Empty);
public void SetPresenter(TextPresenter presenter)
public void SetPresenter(TextPresenter presenter, InputElement parent)
{
_parent = parent;
_subscription?.Dispose();
_subscription = null;
_presenter = presenter;

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs

@ -3,7 +3,7 @@ namespace Avalonia.Diagnostics.ViewModels
internal class AvaloniaPropertyViewModel : PropertyViewModel
{
private readonly AvaloniaObject _target;
private string _type;
private System.Type _type;
private object? _value;
private string _priority;
private string _group;
@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.ViewModels
public override string Priority =>
_priority;
public override string Type => _type;
public override System.Type Type => _type;
public override string Value
{
@ -58,7 +58,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (Property.IsDirect)
{
RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value));
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type));
RaiseAndSetIfChanged(ref _type, _value?.GetType() ?? Property.PropertyType, nameof(Type));
RaiseAndSetIfChanged(ref _priority, "Direct", nameof(Priority));
_group = "Properties";
@ -68,7 +68,7 @@ namespace Avalonia.Diagnostics.ViewModels
var val = _target.GetDiagnostic(Property);
RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value));
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type));
RaiseAndSetIfChanged(ref _type, _value?.GetType() ?? Property.PropertyType, nameof(Type));
if (val != null)
{

6
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs

@ -5,7 +5,7 @@ namespace Avalonia.Diagnostics.ViewModels
internal class ClrPropertyViewModel : PropertyViewModel
{
private readonly object _target;
private string _type;
private System.Type _type;
private object? _value;
#nullable disable
@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels
public override string Name { get; }
public override string Group => "CLR Properties";
public override string Type => _type;
public override System.Type Type => _type;
public override string Value
{
@ -62,7 +62,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
var val = Property.GetValue(_target);
RaiseAndSetIfChanged(ref _value, val, nameof(Value));
RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type));
RaiseAndSetIfChanged(ref _type, _value?.GetType() ?? Property.PropertyType, nameof(Type));
}
}
}

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -403,7 +403,11 @@ namespace Avalonia.Diagnostics.ViewModels
var selectedProperty = SelectedProperty;
var selectedEntity = SelectedEntity;
var selectedEntityName = SelectedEntityName;
if (selectedEntity == null || selectedProperty == null)
if (selectedEntity == null
|| selectedProperty == null
|| selectedProperty.Type == typeof(string)
|| selectedProperty.Type.IsValueType
)
return;
object? property;
@ -416,7 +420,7 @@ namespace Avalonia.Diagnostics.ViewModels
property = selectedEntity.GetType().GetProperties()
.FirstOrDefault(pi => pi.Name == selectedProperty.Name
&& pi.DeclaringType == selectedProperty.DeclaringType
&& pi.PropertyType.Name == selectedProperty.Type)
&& pi.PropertyType.Name == selectedProperty.Type.Name)
?.GetValue(selectedEntity);
}
if (property == null) return;

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs

@ -14,7 +14,7 @@ namespace Avalonia.Diagnostics.ViewModels
public abstract object Key { get; }
public abstract string Name { get; }
public abstract string Group { get; }
public abstract string Type { get; }
public abstract Type Type { get; }
public abstract Type? DeclaringType { get; }
public abstract string Value { get; set; }
public abstract string Priority { get; }

2
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -40,7 +40,7 @@
<DataGrid.Columns>
<DataGridTextColumn Header="Property" Binding="{Binding Name}" IsReadOnly="True" />
<DataGridTextColumn Header="Value" Binding="{Binding Value}" />
<DataGridTextColumn Header="Type" Binding="{Binding Type}" />
<DataGridTextColumn Header="Type" Binding="{Binding Type.Name}" />
<DataGridTextColumn Header="Priority" Binding="{Binding Priority}" IsReadOnly="True" />
</DataGrid.Columns>

32
src/Avalonia.Input/InputMethod.cs

@ -0,0 +1,32 @@
namespace Avalonia.Input
{
public class InputMethod
{
/// <summary>
/// A dependency property that enables alternative text inputs.
/// </summary>
public static readonly AvaloniaProperty<bool> IsInputMethodEnabledProperty =
AvaloniaProperty.RegisterAttached<InputMethod, InputElement, bool>("IsInputMethodEnabled", true);
/// <summary>
/// Setter for IsInputMethodEnabled AvaloniaProperty
/// </summary>
public static void SetIsInputMethodEnabled(InputElement target, bool value)
{
target.SetValue(IsInputMethodEnabledProperty, value);
}
/// <summary>
/// Getter for IsInputMethodEnabled AvaloniaProperty
/// </summary>
public static bool GetIsInputMethodEnabled(InputElement target)
{
return target.GetValue<bool>(IsInputMethodEnabledProperty);
}
private InputMethod()
{
}
}
}

35
src/Avalonia.Input/TextInput/InputMethodManager.cs

@ -8,9 +8,14 @@ namespace Avalonia.Input.TextInput
private ITextInputMethodImpl? _im;
private IInputElement? _focusedElement;
private ITextInputMethodClient? _client;
private IDisposable? _subscribeDisposable;
private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper();
public TextInputMethodManager() => _transformTracker.MatrixChanged += UpdateCursorRect;
public TextInputMethodManager()
{
_transformTracker.MatrixChanged += UpdateCursorRect;
InputMethod.IsInputMethodEnabledProperty.Changed.Subscribe(OnIsInputMethodEnabledChanged);
}
private ITextInputMethodClient? Client
{
@ -40,6 +45,7 @@ namespace Avalonia.Input.TextInput
_im?.SetOptions(optionsQuery);
_transformTracker?.SetVisual(_client?.TextViewVisual);
UpdateCursorRect();
_im?.SetActive(true);
}
else
@ -50,6 +56,14 @@ namespace Avalonia.Input.TextInput
}
}
private void OnIsInputMethodEnabledChanged(AvaloniaPropertyChangedEventArgs<bool> obj)
{
if (ReferenceEquals(obj.Sender, _focusedElement))
{
TryFindAndApplyClient();
}
}
private void OnTextViewVisualChanged(object sender, EventArgs e)
=> _transformTracker.SetVisual(_client?.TextViewVisual);
@ -57,6 +71,7 @@ namespace Avalonia.Input.TextInput
{
if (_im == null || _client == null || _focusedElement?.VisualRoot == null)
return;
var transform = _focusedElement.TransformToVisual(_focusedElement.VisualRoot);
if (transform == null)
_im.SetCursorRect(default);
@ -75,17 +90,23 @@ namespace Avalonia.Input.TextInput
if(_focusedElement == element)
return;
_focusedElement = element;
var inputMethod = (element?.VisualRoot as ITextInputMethodRoot)?.InputMethod;
if(_im != inputMethod)
if (_im != inputMethod)
_im?.SetActive(false);
_im = inputMethod;
if (_focusedElement == null || _im == null)
TryFindAndApplyClient();
}
private void TryFindAndApplyClient()
{
if (_focusedElement is not InputElement focused ||
_im == null ||
!InputMethod.GetIsInputMethodEnabled(focused))
{
Client = null;
_im?.SetActive(false);
return;
}
@ -93,7 +114,7 @@ namespace Avalonia.Input.TextInput
{
RoutedEvent = InputElement.TextInputMethodClientRequestedEvent
};
_focusedElement.RaiseEvent(clientQuery);
Client = clientQuery.Client;
}

73
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
using Avalonia.Dialogs;
using Avalonia.Input;
using Avalonia.Native.Interop;
using Avalonia.Native.Interop.Impl;
using Avalonia.Threading;
@ -18,11 +19,13 @@ namespace Avalonia.Native
private NativeMenu _menu;
private __MicroComIAvnMenuProxy _nativeMenu;
private readonly IAvnTrayIcon _trayIcon;
private readonly IAvnApplicationCommands _applicationCommands;
public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory)
{
_factory = factory;
_nativeWindow = nativeWindow;
_applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset();
}
@ -30,6 +33,7 @@ namespace Avalonia.Native
public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory)
{
_factory = factory;
_applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset();
}
@ -38,7 +42,8 @@ namespace Avalonia.Native
{
_factory = factory;
_trayIcon = trayIcon;
_applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset();
}
@ -60,15 +65,11 @@ namespace Avalonia.Native
}
}
private static NativeMenu CreateDefaultAppMenu()
private NativeMenu CreateDefaultAppMenu()
{
var result = new NativeMenu();
var aboutItem = new NativeMenuItem
{
Header = "About Avalonia",
};
var aboutItem = new NativeMenuItem("About Avalonia");
aboutItem.Click += async (sender, e) =>
{
var dialog = new AboutAvaloniaDialog();
@ -77,9 +78,65 @@ namespace Avalonia.Native
await dialog.ShowDialog(mainWindow);
};
result.Add(aboutItem);
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
if (macOpts == null || !macOpts.DisableDefaultApplicationMenuItems)
{
result.Add(new NativeMenuItemSeparator());
var servicesMenu = new NativeMenuItem("Services");
servicesMenu.Menu = new NativeMenu
{
[MacOSNativeMenuCommands.IsServicesSubmenuProperty] = true
};
result.Add(servicesMenu);
result.Add(new NativeMenuItemSeparator());
var hideItem = new NativeMenuItem("Hide " + Application.Current.Name)
{
Gesture = new KeyGesture(Key.H, KeyModifiers.Meta)
};
hideItem.Click += (sender, args) =>
{
_applicationCommands.HideApp();
};
result.Add(hideItem);
var hideOthersItem = new NativeMenuItem("Hide Others")
{
Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta | KeyModifiers.Alt)
};
hideOthersItem.Click += (sender, args) =>
{
_applicationCommands.HideOthers();
};
result.Add(hideOthersItem);
var showAllItem = new NativeMenuItem("Show All");
showAllItem.Click += (sender, args) =>
{
_applicationCommands.ShowAll();
};
result.Add(showAllItem);
result.Add(new NativeMenuItemSeparator());
var quitItem = new NativeMenuItem("Quit")
{
Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta)
};
quitItem.Click += (sender, args) =>
{
_applicationCommands.ShowAll();
};
result.Add(quitItem);
}
return result;
}

5
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -93,8 +93,6 @@ namespace Avalonia.Native
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
_factory.MacOptions.SetShowInDock(macOpts?.ShowInDock != false ? 1 : 0);
_factory.MacOptions.SetDisableDefaultApplicationMenuItems(
macOpts?.DisableDefaultApplicationMenuItems == true ? 1 : 0);
}
AvaloniaLocator.CurrentMutable
@ -112,7 +110,8 @@ namespace Avalonia.Native
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform);
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform)
.Bind<INativeApplicationCommands>().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands()));
var hotkeys = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers));

5
src/Avalonia.Native/IAvnMenuItem.cs

@ -150,6 +150,11 @@ namespace Avalonia.Native.Interop.Impl
{
_subMenu = __MicroComIAvnMenuProxy.Create(factory);
if (item.Menu.GetValue(MacOSNativeMenuCommands.IsServicesSubmenuProperty))
{
factory.SetServicesMenu(_subMenu);
}
_subMenu.Initialize(exporter, item.Menu, item.Header);
SetSubMenu(_subMenu);

35
src/Avalonia.Native/MacOSNativeMenuCommands.cs

@ -0,0 +1,35 @@
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Native.Interop;
namespace Avalonia.Native
{
internal class MacOSNativeMenuCommands : INativeApplicationCommands
{
private readonly IAvnApplicationCommands _commands;
public MacOSNativeMenuCommands(IAvnApplicationCommands commands)
{
_commands = commands;
}
public void HideApp()
{
_commands.HideApp();
}
public void ShowAll()
{
_commands.ShowAll();
}
public void HideOthers()
{
_commands.HideOthers();
}
public static readonly AttachedProperty<bool> IsServicesSubmenuProperty =
AvaloniaProperty.RegisterAttached<MacOSNativeMenuCommands, NativeMenu, bool>("IsServicesSubmenu", false);
}
}

11
src/Avalonia.Native/avn.idl

@ -424,10 +424,12 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreateCursorFactory(IAvnCursorFactory** ppv);
HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv);
HRESULT SetAppMenu(IAvnMenu* menu);
HRESULT SetServicesMenu(IAvnMenu* menu);
HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv);
HRESULT CreateMenuItem(IAvnMenuItem** ppv);
HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv);
HRESULT CreateTrayIcon(IAvnTrayIcon** ppv);
HRESULT CreateApplicationCommands(IAvnApplicationCommands** ppv);
}
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]
@ -539,7 +541,6 @@ interface IAvnMacOptions : IUnknown
{
HRESULT SetShowInDock(int show);
HRESULT SetApplicationTitle(char* utf8string);
HRESULT SetDisableDefaultApplicationMenuItems(bool enabled);
}
[uuid(04c1b049-1f43-418a-9159-cae627ec1367)]
@ -753,3 +754,11 @@ interface IAvnApplicationEvents : IUnknown
void FilesOpened (IAvnStringArray* urls);
bool TryShutdown();
}
[uuid(b4284791-055b-4313-8c2e-50f0a8c72ce9)]
interface IAvnApplicationCommands : IUnknown
{
HRESULT HideApp();
HRESULT ShowAll();
HRESULT HideOthers();
}

33
src/Avalonia.Themes.Default/SplitView.xaml

@ -1,14 +1,29 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System">
<Design.PreviewWith>
<Border Width="400" Height="400">
<SplitView DisplayMode="Inline"
IsPaneOpen="True">
<SplitView.Pane>
<Border Background="Green" />
</SplitView.Pane>
<SplitView.Content>
<Border Background="Blue" />
</SplitView.Content>
</SplitView>
</Border>
</Design.PreviewWith>
<Styles.Resources>
<x:Double x:Key="SplitViewOpenPaneThemeLength">320</x:Double>
<x:Double x:Key="SplitViewCompactPaneThemeLength">48</x:Double>
<SolidColorBrush x:Key="SplitViewLightDismissOverlayBackground" Color="{DynamicResource ThemeControlLowColor}" Opacity="0.6"/>
<!-- 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>-->
<sys:TimeSpan x:Key="SplitViewPaneAnimationOpenDuration">00:00:00.2</sys:TimeSpan>
<sys:TimeSpan x:Key="SplitViewPaneAnimationCloseDuration">00:00:00.1</sys:TimeSpan>
<Easing x:Key="SplitViewPaneAnimationEasing">0.1,0.9,0.2,1.0</Easing>
<!-- Not used here (directly), but preserving for reference
<x:String x:Key="SplitViewPaneAnimationOpenPreDuration">00:00:00.19999</x:String>-->
</Styles.Resources>
<Style Selector="SplitView">
@ -170,7 +185,7 @@
<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" />
<DoubleTransition Property="Width" Duration="{StaticResource SplitViewPaneAnimationOpenDuration}" Easing="{StaticResource SplitViewPaneAnimationEasing}" />
</Transitions>
</Setter>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=OpenPaneLength}" />
@ -178,7 +193,7 @@
<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" />
<DoubleTransition Property="Opacity" Duration="{StaticResource SplitViewPaneAnimationOpenDuration}" Easing="{StaticResource SplitViewPaneAnimationEasing}" />
</Transitions>
</Setter>
<Setter Property="Opacity" Value="1.0"/>
@ -187,7 +202,7 @@
<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" />
<DoubleTransition Property="Width" Duration="{StaticResource SplitViewPaneAnimationCloseDuration}" Easing="{StaticResource SplitViewPaneAnimationEasing}" />
</Transitions>
</Setter>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
@ -195,7 +210,7 @@
<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" />
<DoubleTransition Property="Opacity" Duration="{StaticResource SplitViewPaneAnimationCloseDuration}" Easing="{StaticResource SplitViewPaneAnimationEasing}" />
</Transitions>
</Setter>
<Setter Property="Opacity" Value="0.0"/>

34
src/Avalonia.Themes.Fluent/Controls/SplitView.xaml

@ -1,14 +1,28 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="using:System">
<Design.PreviewWith>
<Border Width="400" Height="400">
<SplitView DisplayMode="Inline"
IsPaneOpen="True">
<SplitView.Pane>
<Border Background="Green" />
</SplitView.Pane>
<SplitView.Content>
<Border Background="Blue" />
</SplitView.Content>
</SplitView>
</Border>
</Design.PreviewWith>
<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>-->
<sys:TimeSpan x:Key="SplitViewPaneAnimationOpenDuration">00:00:00.2</sys:TimeSpan>
<sys:TimeSpan x:Key="SplitViewPaneAnimationCloseDuration">00:00:00.1</sys:TimeSpan>
<Easing x:Key="SplitViewPaneAnimationEasing">0.1,0.9,0.2,1.0</Easing>
<!-- Not used here (directly), but preserving for reference
<x:String x:Key="SplitViewPaneAnimationOpenPreDuration">00:00:00.19999</x:String>-->
</Styles.Resources>
<Style Selector="SplitView">
@ -170,7 +184,7 @@
<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" />
<DoubleTransition Property="Width" Duration="{StaticResource SplitViewPaneAnimationOpenDuration}" Easing="{StaticResource SplitViewPaneAnimationEasing}" />
</Transitions>
</Setter>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=OpenPaneLength}" />
@ -178,7 +192,7 @@
<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" />
<DoubleTransition Property="Opacity" Duration="{StaticResource SplitViewPaneAnimationOpenDuration}" Easing="{StaticResource SplitViewPaneAnimationEasing}" />
</Transitions>
</Setter>
<Setter Property="Opacity" Value="1.0"/>
@ -187,7 +201,7 @@
<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" />
<DoubleTransition Property="Width" Duration="{StaticResource SplitViewPaneAnimationCloseDuration}" Easing="{StaticResource SplitViewPaneAnimationEasing}" />
</Transitions>
</Setter>
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ClosedPaneWidth}" />
@ -195,7 +209,7 @@
<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" />
<DoubleTransition Property="Opacity" Duration="{StaticResource SplitViewPaneAnimationCloseDuration}" Easing="{StaticResource SplitViewPaneAnimationEasing}" />
</Transitions>
</Setter>
<Setter Property="Opacity" Value="0.0"/>

2
src/Avalonia.Themes.Fluent/Controls/TabItem.xaml

@ -85,7 +85,7 @@
</Style>
<!-- Selected Pressed state -->
<Style Selector="TabItem:pressed /template/ Border#PART_LayoutRoot">
<Style Selector="TabItem:selected:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPressed}" />
</Style>

2
src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml

@ -93,7 +93,7 @@
</Style>
<!-- Selected Pressed state -->
<Style Selector="TabStripItem:pressed /template/ Border#PART_LayoutRoot">
<Style Selector="TabStripItem:selected:pressed /template/ Border#PART_LayoutRoot">
<Setter Property="Background" Value="{DynamicResource TabItemHeaderBackgroundSelectedPressed}" />
<Setter Property="TextBlock.Foreground" Value="{DynamicResource TabItemHeaderForegroundSelectedPressed}" />
</Style>

18
src/Avalonia.Visuals/Media/FontFallback.cs

@ -0,0 +1,18 @@
namespace Avalonia.Media
{
/// <summary>
/// Font fallback definition that is used to override the default fallback lookup of the current <see cref="FontManager"/>
/// </summary>
public class FontFallback
{
/// <summary>
/// Get or set the fallback <see cref="FontFamily"/>
/// </summary>
public FontFamily FontFamily { get; set; }
/// <summary>
/// Get or set the <see cref="UnicodeRange"/> that is covered by the fallback.
/// </summary>
public UnicodeRange UnicodeRange { get; set; } = UnicodeRange.Default;
}
}

32
src/Avalonia.Visuals/Media/FontManager.cs

@ -16,12 +16,17 @@ namespace Avalonia.Media
private readonly ConcurrentDictionary<Typeface, GlyphTypeface> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, GlyphTypeface>();
private readonly FontFamily _defaultFontFamily;
private readonly IReadOnlyList<FontFallback> _fontFallbacks;
public FontManager(IFontManagerImpl platformImpl)
{
PlatformImpl = platformImpl;
DefaultFontFamilyName = PlatformImpl.GetDefaultFontFamilyName();
var options = AvaloniaLocator.Current.GetService<FontManagerOptions>();
_fontFallbacks = options?.FontFallbacks;
DefaultFontFamilyName = options?.DefaultFamilyName ?? PlatformImpl.GetDefaultFontFamilyName();
if (string.IsNullOrEmpty(DefaultFontFamilyName))
{
@ -121,7 +126,28 @@ namespace Avalonia.Media
/// </returns>
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle,
FontWeight fontWeight,
FontFamily fontFamily, CultureInfo culture, out Typeface typeface) =>
PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out typeface);
FontFamily fontFamily, CultureInfo culture, out Typeface typeface)
{
if(_fontFallbacks != null)
{
foreach (var fallback in _fontFallbacks)
{
if(fallback is null)
{
continue;
}
typeface = new Typeface(fallback.FontFamily, fontStyle, fontWeight);
var glyphTypeface = typeface.GlyphTypeface;
if(glyphTypeface.TryGetGlyph((uint)codepoint, out _)){
return true;
}
}
}
return PlatformImpl.TryMatchCharacter(codepoint, fontStyle, fontWeight, fontFamily, culture, out typeface);
}
}
}

11
src/Avalonia.Visuals/Media/FontManagerOptions.cs

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Avalonia.Media
{
public class FontManagerOptions
{
public string DefaultFamilyName { get; set; }
public IReadOnlyList<FontFallback> FontFallbacks { get; set; }
}
}

199
src/Avalonia.Visuals/Media/UnicodeRange.cs

@ -0,0 +1,199 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace Avalonia.Media
{
/// <summary>
/// The <see cref="UnicodeRange"/> descripes a set of Unicode characters.
/// </summary>
public readonly struct UnicodeRange
{
public static UnicodeRange Default = Parse("0-10FFFD");
private readonly UnicodeRangeSegment _single;
private readonly IReadOnlyList<UnicodeRangeSegment> _segments = null;
public UnicodeRange(int start, int end)
{
_single = new UnicodeRangeSegment(start, end);
}
public UnicodeRange(UnicodeRangeSegment single)
{
_single = single;
}
public UnicodeRange(IReadOnlyList<UnicodeRangeSegment> segments)
{
if(segments is null || segments.Count == 0)
{
throw new ArgumentException(nameof(segments));
}
_single = segments[0];
_segments = segments;
}
internal UnicodeRangeSegment Single => _single;
internal IReadOnlyList<UnicodeRangeSegment> Segments => _segments;
/// <summary>
/// Determines if given value is inside the range.
/// </summary>
/// <param name="value">The value to verify.</param>
/// <returns>
/// <c>true</c> If given value is inside the range, <c>false</c> otherwise.
/// </returns>
public bool IsInRange(int value)
{
if(_segments is null)
{
return _single.IsInRange(value);
}
foreach(var segment in _segments)
{
if (segment.IsInRange(value))
{
return true;
}
}
return false;
}
/// <summary>
/// Parses a <see cref="UnicodeRange"/>.
/// </summary>
/// <param name="s">The string to parse.</param>
/// <returns>The parsed <see cref="UnicodeRange"/>.</returns>
/// <exception cref="FormatException"></exception>
public static UnicodeRange Parse(string s)
{
if (string.IsNullOrEmpty(s))
{
throw new FormatException("Could not parse specified Unicode range.");
}
var parts = s.Split(',');
var length = parts.Length;
if(length == 0)
{
throw new FormatException("Could not parse specified Unicode range.");
}
if(length == 1)
{
return new UnicodeRange(UnicodeRangeSegment.Parse(parts[0]));
}
var segments = new UnicodeRangeSegment[length];
for (int i = 0; i < length; i++)
{
segments[i] = UnicodeRangeSegment.Parse(parts[i].Trim());
}
return new UnicodeRange(segments);
}
}
public readonly struct UnicodeRangeSegment
{
private static Regex s_regex = new Regex(@"^(?:[uU]\+)?(?:([0-9a-fA-F](?:[0-9a-fA-F?]{1,5})?))$");
public UnicodeRangeSegment(int start, int end)
{
Start = start;
End = end;
}
/// <summary>
/// Get the start of the segment.
/// </summary>
public int Start { get; }
/// <summary>
/// Get the end of the segment.
/// </summary>
public int End { get; }
/// <summary>
/// Determines if given value is inside the range segment.
/// </summary>
/// <param name="value">The value to verify.</param>
/// <returns>
/// <c>true</c> If given value is inside the range segment, <c>false</c> otherwise.
/// </returns>
public bool IsInRange(int value)
{
return value - Start <= End - Start;
}
/// <summary>
/// Parses a <see cref="UnicodeRangeSegment"/>.
/// </summary>
/// <param name="s">The string to parse.</param>
/// <returns>The parsed <see cref="UnicodeRangeSegment"/>.</returns>
/// <exception cref="FormatException"></exception>
public static UnicodeRangeSegment Parse(string s)
{
if (string.IsNullOrEmpty(s))
{
throw new FormatException("Could not parse specified Unicode range segment.");
}
var parts = s.Split('-');
int start, end;
switch (parts.Length)
{
case 1:
{
//e.g. U+20, U+3F U+30??
var single = s_regex.Match(parts[0]);
if (!single.Success)
{
throw new FormatException("Could not parse specified Unicode range segment.");
}
if (!single.Value.Contains("?"))
{
start = int.Parse(single.Groups[1].Value, System.Globalization.NumberStyles.HexNumber);
end = start;
}
else
{
start = int.Parse(single.Groups[1].Value.Replace('?', '0'), System.Globalization.NumberStyles.HexNumber);
end = int.Parse(single.Groups[1].Value.Replace('?', 'F'), System.Globalization.NumberStyles.HexNumber);
}
break;
}
case 2:
{
var first = s_regex.Match(parts[0]);
var second = s_regex.Match(parts[1]);
if (!first.Success || !second.Success)
{
throw new FormatException("Could not parse specified Unicode range segment.");
}
start = int.Parse(first.Groups[1].Value, System.Globalization.NumberStyles.HexNumber);
end = int.Parse(second.Groups[1].Value, System.Globalization.NumberStyles.HexNumber);
break;
}
default:
throw new FormatException("Could not parse specified Unicode range segment.");
}
return new UnicodeRangeSegment(start, end);
}
}
}

BIN
src/Web/Avalonia.Web.Blazor/Assets/NotoMono-Regular.ttf

Binary file not shown.

BIN
src/Web/Avalonia.Web.Blazor/Assets/NotoSans-Italic.ttf

Binary file not shown.

7
src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props

@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<EmccTotalMemory>16777216</EmccTotalMemory>
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
<BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
</PropertyGroup>
</Project>

4
src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj

@ -6,6 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>Avalonia.Web.Blazor</PackageId>
<LangVersion>preview</LangVersion>
<StaticWebAssetsDisableProjectBuildPropsFileGeneration>true</StaticWebAssetsDisableProjectBuildPropsFileGeneration>
</PropertyGroup>
<ItemGroup>
@ -33,10 +34,9 @@
<Import Project="..\..\..\build\HarfBuzzSharp.props" />
<ItemGroup>
<AvaloniaResource Include="Assets\*" />
<Content Include="*.props">
<Pack>true</Pack>
<PackagePath>build\;buildTransitive\</PackagePath>
<PackagePath>build\</PackagePath>
</Content>
<Content Include="*.targets">
<Pack>true</Pack>

4
src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props

@ -0,0 +1,4 @@
<Project>
<Import Project="Microsoft.AspNetCore.StaticWebAssets.props" />
<Import Project="Avalonia.Web.Blazor.CompilationTuning.props" />
</Project>

4
src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs

@ -278,9 +278,9 @@ namespace Avalonia.Web.Blazor
_glInterface = GRGlInterface.Create();
_context = GRContext.CreateGl(_glInterface);
var options = AvaloniaLocator.Current.GetService<SkiaOptions>();
// bump the default resource cache limit
_context.SetResourceCacheLimit(256 * 1024 * 1024);
_context.SetResourceCacheLimit(options.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
Console.WriteLine("glcontext created and resource limit set");
}

2
src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs

@ -25,8 +25,6 @@ namespace Avalonia.Web.Blazor
.UseSkia()
.With(new SkiaOptions { CustomGpuFactory = () => new BlazorSkiaGpu() });
AvaloniaLocator.CurrentMutable.Bind<FontManager>().ToConstant(new FontManager(new CustomFontManagerImpl()));
return builder;
}
}

78
src/Web/Avalonia.Web.Blazor/CustomFontManagerImpl.cs

@ -1,78 +0,0 @@
using System.Globalization;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Skia;
using SkiaSharp;
namespace Avalonia.Web.Blazor
{
public class CustomFontManagerImpl : IFontManagerImpl
{
private readonly Typeface[] _customTypefaces;
private readonly string _defaultFamilyName;
private readonly Typeface _defaultTypeface =
new Typeface("avares://Avalonia.Web.Blazor/Assets#Noto Mono");
private readonly Typeface _italicTypeface =
new Typeface("avares://Avalonia.Web.Blazor/Assets#Noto Sans");
public CustomFontManagerImpl()
{
_customTypefaces = new[] { _italicTypeface, _defaultTypeface };
_defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName;
}
public string GetDefaultFontFamilyName()
{
return _defaultFamilyName;
}
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false)
{
return _customTypefaces.Select(x => x.FontFamily.Name);
}
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out Typeface typeface)
{
foreach (var customTypeface in _customTypefaces)
{
if (customTypeface.GlyphTypeface.GetGlyph((uint)codepoint) == 0)
{
continue;
}
typeface = new Typeface(customTypeface.FontFamily, fontStyle, fontWeight);
return true;
}
typeface = _defaultTypeface;
return true;
}
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
{
SKTypeface skTypeface;
switch (typeface.FontFamily.Name)
{
case "Noto Sans":
{
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_italicTypeface.FontFamily);
skTypeface = typefaceCollection.Get(typeface);
break;
}
default:
{
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily);
skTypeface = typefaceCollection.Get(_defaultTypeface);
break;
}
}
return new GlyphTypefaceImpl(skTypeface);
}
}
}

38
src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs

@ -0,0 +1,38 @@
using System;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32.Input
{
internal struct Imm32CaretManager
{
private bool _isCaretCreated;
public void TryCreate(int _langId, IntPtr hwnd)
{
if (!_isCaretCreated)
{
if (_langId == LANG_ZH || _langId == LANG_JA)
{
_isCaretCreated = CreateCaret(hwnd, IntPtr.Zero, 2, 10);
}
}
}
public void TryMove(int x, int y)
{
if (_isCaretCreated)
{
SetCaretPos(x, y);
}
}
public void TryDestroy()
{
if (_isCaretCreated)
{
DestroyCaret();
_isCaretCreated = false;
}
}
}
}

231
src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs

@ -0,0 +1,231 @@
using System;
using Avalonia.Input.TextInput;
using Avalonia.Threading;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32.Input
{
/// <summary>
/// A Windows input method editor based on Windows Input Method Manager (IMM32).
/// </summary>
class Imm32InputMethod : ITextInputMethodImpl
{
public IntPtr HWND { get; private set; }
private IntPtr _defaultImc;
private WindowImpl _parent;
private bool _active;
private bool _showCompositionWindow;
private Imm32CaretManager _caretManager = new();
private bool _showCandidateList;
private ushort _langId;
private const int _caretMargin = 1;
public void SetLanguageAndWindow(WindowImpl parent, IntPtr hwnd, IntPtr HKL)
{
if (HWND != hwnd)
{
_defaultImc = IntPtr.Zero;
}
HWND = hwnd;
_parent = parent;
_active = false;
_langId = PRIMARYLANGID(LGID(HKL));
_showCompositionWindow = true;
_showCandidateList = true;
IsComposing = false;
}
//Dependant on CurrentThread. When Avalonia will support Multiple Dispatchers -
//every Dispatcher should have their own InputMethod.
public static Imm32InputMethod Current { get; } = new Imm32InputMethod();
private IntPtr DefaultImc
{
get
{
if (_defaultImc == IntPtr.Zero &&
HWND != IntPtr.Zero)
{
_defaultImc = ImmGetContext(HWND);
ImmReleaseContext(HWND, _defaultImc);
}
if (_defaultImc == IntPtr.Zero)
{
_defaultImc = ImmCreateContext();
}
return _defaultImc;
}
}
public void Reset()
{
if (IsComposing)
{
Dispatcher.UIThread.Post(() =>
{
ImmNotifyIME(DefaultImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
ImmReleaseContext(HWND, DefaultImc);
IsComposing = false;
});
}
}
public void SetActive(bool active)
{
_active = active;
Dispatcher.UIThread.Post(() =>
{
if (active)
{
if (DefaultImc != IntPtr.Zero)
{
_caretManager.TryCreate(_langId, HWND);
// Load the default IME context.
// NOTE(hbono)
// IMM ignores this call if the IME context is loaded. Therefore, we do
// not have to check whether or not the IME context is loaded.
ImmAssociateContext(HWND, _defaultImc);
}
}
else
{
// A renderer process have moved its input focus to a password input
// when there is an ongoing composition, e.g. a user has clicked a
// mouse button and selected a password input while composing a text.
// For this case, we have to complete the ongoing composition and
// clean up the resources attached to this object BEFORE DISABLING THE IME.
if (IsComposing)
{
ImmNotifyIME(DefaultImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
ImmReleaseContext(HWND, DefaultImc);
IsComposing = false;
}
ImmAssociateContext(HWND, IntPtr.Zero);
_caretManager.TryDestroy();
}
});
}
public void SetCursorRect(Rect rect)
{
var focused = GetActiveWindow() == HWND;
if (!focused)
{
return;
}
Dispatcher.UIThread.Post(() =>
{
IntPtr himc = DefaultImc;
if (himc == IntPtr.Zero)
{
return;
}
MoveImeWindow(rect, himc);
ImmReleaseContext(HWND, himc);
});
}
// see: https://chromium.googlesource.com/experimental/chromium/src/+/bf09a5036ccfb77d2277247c66dc55daf41df3fe/chrome/browser/ime_input.cc
// see: https://engine.chinmaygarde.com/window__win32_8cc_source.html
private void MoveImeWindow(Rect rect, IntPtr himc)
{
var p1 = rect.TopLeft;
var p2 = rect.BottomRight;
var s = _parent?.DesktopScaling ?? 1;
var (x1, y1, x2, y2) = ((int) (p1.X * s), (int) (p1.Y * s), (int) (p2.X * s), (int) (p2.Y * s));
if (!_showCompositionWindow &&
_langId == LANG_ZH)
{
// Chinese IMEs ignore function calls to ::ImmSetCandidateWindow()
// when a user disables TSF (Text Service Framework) and CUAS (Cicero
// Unaware Application Support).
// On the other hand, when a user enables TSF and CUAS, Chinese IMEs
// ignore the position of the current system caret and uses the
// parameters given to ::ImmSetCandidateWindow() with its 'dwStyle'
// parameter CFS_CANDIDATEPOS.
// Therefore, we do not only call ::ImmSetCandidateWindow() but also
// set the positions of the temporary system caret.
var candidateForm = new CANDIDATEFORM
{
dwIndex = 0,
dwStyle = CFS_CANDIDATEPOS,
ptCurrentPos = new POINT {X = x2, Y = y2}
};
ImmSetCandidateWindow(himc, ref candidateForm);
}
_caretManager.TryMove(x2, y2);
if (_showCompositionWindow)
{
ConfigureCompositionWindow(x1, y1, himc, y2 - y1);
// Don't need to set the position of candidate window.
return;
}
if (_langId == LANG_KO)
{
// Chinese IMEs and Japanese IMEs require the upper-left corner of
// the caret to move the position of their candidate windows.
// On the other hand, Korean IMEs require the lower-left corner of the
// caret to move their candidate windows.
y2 += _caretMargin;
}
// Need to return here since some Chinese IMEs would stuck if set
// candidate window position with CFS_EXCLUDE style.
if (_langId == LANG_ZH)
{
return;
}
// Japanese IMEs and Korean IMEs also use the rectangle given to
// ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE
// to move their candidate windows when a user disables TSF and CUAS.
// Therefore, we also set this parameter here.
var excludeRectangle = new CANDIDATEFORM
{
dwIndex = 0,
dwStyle = CFS_EXCLUDE,
ptCurrentPos = new POINT {X = x1, Y = y1},
rcArea = new RECT {left = x1, top = y1, right = x2, bottom = y2 + _caretMargin}
};
ImmSetCandidateWindow(himc, ref excludeRectangle);
}
private static void ConfigureCompositionWindow(int x1, int y1, IntPtr himc, int height)
{
var compForm = new COMPOSITIONFORM
{
dwStyle = CFS_POINT,
ptCurrentPos = new POINT {X = x1, Y = y1},
};
ImmSetCompositionWindow(himc, ref compForm);
var logFont = new LOGFONT()
{
lfHeight = height,
lfQuality = 5 //CLEARTYPE_QUALITY
};
ImmSetCompositionFont(himc, ref logFont);
}
public void SetOptions(TextInputOptionsQueryEventArgs options)
{
// we're skipping this. not usable on windows
}
public bool IsComposing { get; set; }
~Imm32InputMethod()
{
_caretManager.TryDestroy();
}
}
}

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

@ -1551,6 +1551,112 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
[DllImport("imm32.dll", SetLastError = true)]
public static extern IntPtr ImmGetContext(IntPtr hWnd);
[DllImport("imm32.dll", SetLastError = true)]
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll", SetLastError = true)]
public static extern IntPtr ImmCreateContext();
[DllImport("imm32.dll")]
public static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
[DllImport("imm32.dll")]
public static extern bool ImmSetOpenStatus(IntPtr hIMC, bool flag);
[DllImport("imm32.dll")]
public static extern bool ImmSetActiveContext(IntPtr hIMC, bool flag);
[DllImport("imm32.dll")]
public static extern bool ImmSetStatusWindowPos(IntPtr hIMC, ref POINT lpptPos);
[DllImport("imm32.dll")]
public static extern bool ImmIsIME(IntPtr HKL);
[DllImport("imm32.dll")]
public static extern bool ImmSetCandidateWindow(IntPtr hIMC, ref CANDIDATEFORM lpCandidate);
[DllImport("imm32.dll")]
public static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM lpComp);
[DllImport("imm32.dll")]
public static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT lf);
[DllImport("imm32.dll")]
public static extern bool ImmNotifyIME(IntPtr hIMC, int dwAction, int dwIndex, int dwValue);
[DllImport("user32.dll")]
public static extern bool CreateCaret(IntPtr hwnd, IntPtr hBitmap, int nWidth, int nHeight);
[DllImport("user32.dll")]
public static extern bool SetCaretPos(int X, int Y);
[DllImport("user32.dll")]
public static extern bool DestroyCaret();
[DllImport("user32.dll")]
public static extern IntPtr GetKeyboardLayout(int idThread);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int LCIDToLocaleName(uint Locale, StringBuilder lpName, int cchName, int dwFlags);
public static uint MAKELCID(uint lgid, uint srtid)
{
return (((uint)(ushort)srtid) << 16) |
((ushort)lgid);
}
public static ushort PRIMARYLANGID(uint lgid)
{
return (ushort)(lgid & 0x3ff);
}
public static uint LGID(IntPtr HKL)
{
return (uint)(HKL.ToInt32() & 0xffff);
}
public const int SORT_DEFAULT = 0;
public const int LANG_ZH = 0x0004;
public const int LANG_JA = 0x0011;
public const int LANG_KO = 0x0012;
public const int CFS_FORCE_POSITION = 0x0020;
public const int CFS_CANDIDATEPOS = 0x0040;
public const int CFS_EXCLUDE = 0x0080;
public const int CFS_POINT = 0x0002;
public const int CFS_RECT = 0x0001;
public const uint ISC_SHOWUICOMPOSITIONWINDOW = 0x80000000;
public const int NI_COMPOSITIONSTR = 21;
public const int CPS_COMPLETE = 1;
public const int CPS_CONVERT = 2;
public const int CPS_REVERT = 3;
public const int CPS_CANCEL = 4;
[StructLayout(LayoutKind.Sequential)]
internal struct CANDIDATEFORM
{
public int dwIndex;
public int dwStyle;
public POINT ptCurrentPos;
public RECT rcArea;
}
[StructLayout(LayoutKind.Sequential)]
internal struct COMPOSITIONFORM
{
public int dwStyle;
public POINT ptCurrentPos;
public RECT rcArea;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct LOGFONT
{
public int lfHeight;
public int lfWidth;
public int lfEscapement;
public int lfOrientation;
public int lfWeight;
public byte lfItalic;
public byte lfUnderline;
public byte lfStrikeOut;
public byte lfCharSet;
public byte lfOutPrecision;
public byte lfClipPrecision;
public byte lfQuality;
public byte lfPitchAndFamily;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string lfFaceName;
}
[StructLayout(LayoutKind.Sequential)]
internal struct WindowCompositionAttributeData
{

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

@ -1,6 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Controls;
using Avalonia.Controls.Remote;
using Avalonia.Input;
@ -34,6 +35,7 @@ namespace Avalonia.Win32
case WindowActivate.WA_CLICKACTIVE:
{
Activated?.Invoke();
UpdateInputMethod(GetKeyboardLayout(0));
break;
}
@ -472,6 +474,35 @@ namespace Avalonia.Win32
case WindowsMessage.WM_KILLFOCUS:
LostFocus?.Invoke();
break;
case WindowsMessage.WM_INPUTLANGCHANGE:
{
UpdateInputMethod(lParam);
// call DefWindowProc to pass to all children
break;
}
case WindowsMessage.WM_IME_SETCONTEXT:
{
// TODO if we implement preedit, disable the composition window:
// lParam = new IntPtr((int)(((uint)lParam.ToInt64()) & ~ISC_SHOWUICOMPOSITIONWINDOW));
UpdateInputMethod(GetKeyboardLayout(0));
break;
}
case WindowsMessage.WM_IME_CHAR:
case WindowsMessage.WM_IME_COMPOSITION:
case WindowsMessage.WM_IME_COMPOSITIONFULL:
case WindowsMessage.WM_IME_CONTROL:
case WindowsMessage.WM_IME_KEYDOWN:
case WindowsMessage.WM_IME_KEYUP:
case WindowsMessage.WM_IME_NOTIFY:
case WindowsMessage.WM_IME_SELECT:
break;
case WindowsMessage.WM_IME_STARTCOMPOSITION:
Imm32InputMethod.Current.IsComposing = true;
break;
case WindowsMessage.WM_IME_ENDCOMPOSITION:
Imm32InputMethod.Current.IsComposing = false;
break;
}
#if USE_MANAGED_DRAG
@ -500,6 +531,20 @@ namespace Avalonia.Win32
}
}
private void UpdateInputMethod(IntPtr hkl)
{
// note: for non-ime language, also create it so that emoji panel tracks cursor
var langid = LGID(hkl);
if (langid == _langid && Imm32InputMethod.Current.HWND == Hwnd)
{
return;
}
_langid = langid;
Imm32InputMethod.Current.SetLanguageAndWindow(this, Hwnd, hkl);
}
private static int ToInt32(IntPtr ptr)
{
if (IntPtr.Size == 4)

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

@ -6,6 +6,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
@ -25,7 +26,8 @@ namespace Avalonia.Win32
/// Window implementation for Win32 platform.
/// </summary>
public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithNativeControlHost
ITopLevelImplWithNativeControlHost,
ITopLevelImplWithTextInputMethod
{
private static readonly List<WindowImpl> s_instances = new List<WindowImpl>();
@ -87,6 +89,7 @@ namespace Avalonia.Win32
private bool _isCloseRequested;
private bool _shown;
private bool _hiddenWindowIsParent;
private uint _langid;
public WindowImpl()
{
@ -122,7 +125,7 @@ namespace Avalonia.Win32
CreateWindow();
_framebuffer = new FramebufferManager(_hwnd);
UpdateInputMethod(GetKeyboardLayout(0));
if (glPlatform != null)
{
if (_isUsingComposition)
@ -1353,5 +1356,7 @@ namespace Avalonia.Win32
public void Dispose() => _owner._resizeReason = _restore;
}
public ITextInputMethodImpl TextInputMethod => Imm32InputMethod.Current;
}
}

161
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -2,15 +2,21 @@
using System.Collections.Generic;
using System.Text;
using System.Windows.Input;
using Avalonia.Collections;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class MenuItemTests
{
private Mock<IPopupImpl> popupImpl;
[Fact]
public void Header_Of_Minus_Should_Apply_Separator_Pseudoclass()
{
@ -79,7 +85,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
public void MenuItem_Is_Enabled_When_Bound_Command_Is_Added()
public void MenuItem_Is_Enabled_When_Added_To_Logical_Tree_And_Bound_Command_Is_Added()
{
var viewModel = new
{
@ -91,7 +97,8 @@ namespace Avalonia.Controls.UnitTests
DataContext = new object(),
[!MenuItem.CommandProperty] = new Binding("Command"),
};
var root = new TestRoot { Child = target };
Assert.True(target.IsEnabled);
Assert.False(target.IsEffectivelyEnabled);
@ -158,10 +165,11 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
public void MenuItem_Invokes_CanExecute_When_CommandParameter_Changed()
public void MenuItem_Invokes_CanExecute_When_Added_To_Logical_Tree_And_CommandParameter_Changed()
{
var command = new TestCommand(p => p is bool value && value);
var target = new MenuItem { Command = command };
var root = new TestRoot { Child = target };
target.CommandParameter = true;
Assert.True(target.IsEffectivelyEnabled);
@ -169,6 +177,151 @@ namespace Avalonia.Controls.UnitTests
target.CommandParameter = false;
Assert.False(target.IsEffectivelyEnabled);
}
[Fact]
public void MenuItem_Does_Not_Invoke_CanExecute_When_ContextMenu_Closed()
{
using (Application())
{
var canExecuteCallCount = 0;
var command = new TestCommand(_ =>
{
canExecuteCallCount++;
return true;
});
var target = new MenuItem();
var contextMenu = new ContextMenu { Items = new AvaloniaList<MenuItem> { target } };
var window = new Window { Content = new Panel { ContextMenu = contextMenu } };
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.True(target.IsEffectivelyEnabled);
target.Command = command;
Assert.Equal(0, canExecuteCallCount);
target.CommandParameter = false;
Assert.Equal(0, canExecuteCallCount);
command.RaiseCanExecuteChanged();
Assert.Equal(0, canExecuteCallCount);
contextMenu.Open();
Assert.Equal(2, canExecuteCallCount);//2 because popup is changing logical child
command.RaiseCanExecuteChanged();
Assert.Equal(3, canExecuteCallCount);
target.CommandParameter = true;
Assert.Equal(4, canExecuteCallCount);
}
}
[Fact]
public void MenuItem_Does_Not_Invoke_CanExecute_When_MenuFlyout_Closed()
{
using (Application())
{
var canExecuteCallCount = 0;
var command = new TestCommand(_ =>
{
canExecuteCallCount++;
return true;
});
var target = new MenuItem();
var flyout = new MenuFlyout { Items = new AvaloniaList<MenuItem> { target } };
var button = new Button { Flyout = flyout };
var window = new Window { Content = button };
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.True(target.IsEffectivelyEnabled);
target.Command = command;
Assert.Equal(0, canExecuteCallCount);
target.CommandParameter = false;
Assert.Equal(0, canExecuteCallCount);
command.RaiseCanExecuteChanged();
Assert.Equal(0, canExecuteCallCount);
flyout.ShowAt(button);
Assert.Equal(2, canExecuteCallCount);
command.RaiseCanExecuteChanged();
Assert.Equal(3, canExecuteCallCount);
target.CommandParameter = true;
Assert.Equal(4, canExecuteCallCount);
}
}
[Fact]
public void MenuItem_Does_Not_Invoke_CanExecute_When_Parent_MenuItem_Closed()
{
using (Application())
{
var canExecuteCallCount = 0;
var command = new TestCommand(_ =>
{
canExecuteCallCount++;
return true;
});
var target = new MenuItem();
var parentMenuItem = new MenuItem { Items = new AvaloniaList<MenuItem> { target } };
var contextMenu = new ContextMenu { Items = new AvaloniaList<MenuItem> { parentMenuItem } };
var window = new Window { Content = new Panel { ContextMenu = contextMenu } };
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
contextMenu.Open();
Assert.True(target.IsEffectivelyEnabled);
target.Command = command;
Assert.Equal(0, canExecuteCallCount);
target.CommandParameter = false;
Assert.Equal(0, canExecuteCallCount);
command.RaiseCanExecuteChanged();
Assert.Equal(0, canExecuteCallCount);
try
{
parentMenuItem.IsSubMenuOpen = true;
}
catch (InvalidOperationException)
{
//popup host creation failed exception
}
Assert.Equal(1, canExecuteCallCount);
command.RaiseCanExecuteChanged();
Assert.Equal(2, canExecuteCallCount);
target.CommandParameter = true;
Assert.Equal(3, canExecuteCallCount);
}
}
private IDisposable Application()
{
var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100));
var screenImpl = new Mock<IScreenImpl>();
screenImpl.Setup(x => x.ScreenCount).Returns(1);
screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(1, screen, screen, true) });
var windowImpl = MockWindowingPlatform.CreateWindowMock();
popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object);
popupImpl.SetupGet(x => x.RenderScaling).Returns(1);
windowImpl.Setup(x => x.CreatePopup()).Returns(popupImpl.Object);
windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object);
var services = TestServices.StyledWindow.With(
inputManager: new InputManager(),
windowImpl: windowImpl.Object,
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object, x => popupImpl.Object));
return UnitTestApplication.Start(services);
}
private class TestCommand : ICommand
{
@ -198,6 +351,8 @@ namespace Avalonia.Controls.UnitTests
public bool CanExecute(object parameter) => _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public void RaiseCanExecuteChanged() => _canExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}

2
tests/Avalonia.UnitTests/MockGlyphTypeface.cs

@ -17,7 +17,7 @@ namespace Avalonia.UnitTests
public ushort GetGlyph(uint codepoint)
{
return 0;
return (ushort)codepoint;
}
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)

30
tests/Avalonia.Visuals.UnitTests/Media/FontManagerTests.cs

@ -30,5 +30,35 @@ namespace Avalonia.Visuals.UnitTests.Media
Assert.Throws<InvalidOperationException>(() => FontManager.Current);
}
}
[Fact]
public void Should_Use_FontManagerOptions_DefaultFamilyName()
{
var options = new FontManagerOptions { DefaultFamilyName = "MyFont" };
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
.With(fontManagerImpl: new MockFontManagerImpl())))
{
AvaloniaLocator.CurrentMutable.Bind<FontManagerOptions>().ToConstant(options);
Assert.Equal("MyFont", FontManager.Current.DefaultFontFamilyName);
}
}
[Fact]
public void Should_Use_FontManagerOptions_FontFallback()
{
var options = new FontManagerOptions { FontFallbacks = new[] { new FontFallback { FontFamily = new FontFamily("MyFont"), UnicodeRange = UnicodeRange.Default} } };
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface
.With(fontManagerImpl: new MockFontManagerImpl())))
{
AvaloniaLocator.CurrentMutable.Bind<FontManagerOptions>().ToConstant(options);
FontManager.Current.TryMatchCharacter(1, FontStyle.Normal, FontWeight.Normal, FontFamily.Default, null, out var typeface);
Assert.Equal("MyFont", typeface.FontFamily.Name);
}
}
}
}

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

Loading…
Cancel
Save