Browse Source

Merge branch 'master' into 'parent-ambient-provider'

pull/1513/head
Amadeusz Sadowski 8 years ago
parent
commit
a9ebd15616
  1. 2
      readme.md
  2. 1
      samples/ControlCatalog.Desktop/Program.cs
  3. 2
      samples/ControlCatalog.NetCore/Program.cs
  4. 200
      samples/ControlCatalog/ControlCatalog.csproj
  5. 7
      samples/ControlCatalog/MainView.xaml
  6. 59
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  7. 143
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  8. 19
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  9. 71
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  10. 80
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  11. 94
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  12. 36
      samples/ControlCatalog/Properties/AssemblyInfo.cs
  13. 4
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  14. 4
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  15. 205
      src/Avalonia.Base/Utilities/StringTokenizer.cs
  16. 9
      src/Avalonia.Controls/Application.cs
  17. 2726
      src/Avalonia.Controls/AutoCompleteBox.cs
  18. 51
      src/Avalonia.Controls/Border.cs
  19. 2
      src/Avalonia.Controls/Button.cs
  20. 5
      src/Avalonia.Controls/ButtonSpinner.cs
  21. 1
      src/Avalonia.Controls/DropDown.cs
  22. 9
      src/Avalonia.Controls/GridLength.cs
  23. 35
      src/Avalonia.Controls/MenuItem.cs
  24. 998
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  25. 16
      src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs
  26. 210
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  27. 153
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  28. 47
      src/Avalonia.Controls/Primitives/Popup.cs
  29. 4
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  30. 4
      src/Avalonia.Controls/TextBlock.cs
  31. 36
      src/Avalonia.Controls/TextBox.cs
  32. 279
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  33. 64
      src/Avalonia.Controls/Utils/ISelectionAdapter.cs
  34. 342
      src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
  35. 8
      src/Avalonia.Controls/Utils/UndoRedoHelper.cs
  36. 2
      src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
  37. 5
      src/Avalonia.Input/Cursors.cs
  38. 15
      src/Avalonia.Input/DataFormats.cs
  39. 43
      src/Avalonia.Input/DataObject.cs
  40. 54
      src/Avalonia.Input/DragDrop.cs
  41. 111
      src/Avalonia.Input/DragDropDevice.cs
  42. 13
      src/Avalonia.Input/DragDropEffects.cs
  43. 18
      src/Avalonia.Input/DragEventArgs.cs
  44. 39
      src/Avalonia.Input/IDataObject.cs
  45. 2
      src/Avalonia.Input/KeyboardDevice.cs
  46. 14
      src/Avalonia.Input/Platform/IPlatformDragSource.cs
  47. 8
      src/Avalonia.Input/Raw/IDragDropDevice.cs
  48. 26
      src/Avalonia.Input/Raw/RawDragEvent.cs
  49. 10
      src/Avalonia.Input/Raw/RawDragEventType.cs
  50. 2
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  51. 43
      src/Avalonia.Themes.Default/AutoCompleteBox.xaml
  52. 4
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  53. 4
      src/Avalonia.Themes.Default/MenuItem.xaml
  54. 41
      src/Avalonia.Themes.Default/NumericUpDown.xaml
  55. 97
      src/Avalonia.Visuals/CornerRadius.cs
  56. 24
      src/Avalonia.Visuals/Matrix.cs
  57. 23
      src/Avalonia.Visuals/Media/Brush.cs
  58. 434
      src/Avalonia.Visuals/Media/Brushes.cs
  59. 27
      src/Avalonia.Visuals/Media/Color.cs
  60. 284
      src/Avalonia.Visuals/Media/Colors.cs
  61. 2
      src/Avalonia.Visuals/Media/GradientBrush.cs
  62. 224
      src/Avalonia.Visuals/Media/KnownColors.cs
  63. 16
      src/Avalonia.Visuals/Point.cs
  64. 1
      src/Avalonia.Visuals/Properties/AssemblyInfo.cs
  65. 20
      src/Avalonia.Visuals/Rect.cs
  66. 26
      src/Avalonia.Visuals/RelativePoint.cs
  67. 55
      src/Avalonia.Visuals/RelativeRect.cs
  68. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  69. 15
      src/Avalonia.Visuals/Size.cs
  70. 42
      src/Avalonia.Visuals/Thickness.cs
  71. 5
      src/Gtk/Avalonia.Gtk3/CursorFactory.cs
  72. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  73. 19
      src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs
  74. 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  75. 1
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs
  76. 4
      src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj
  77. 4
      src/OSX/Avalonia.MonoMac/Cursor.cs
  78. 124
      src/OSX/Avalonia.MonoMac/DragSource.cs
  79. 90
      src/OSX/Avalonia.MonoMac/DraggingInfo.cs
  80. 3
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  81. 47
      src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
  82. 7
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  83. 11
      src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
  84. 80
      src/Windows/Avalonia.Win32/ClipboardFormats.cs
  85. 27
      src/Windows/Avalonia.Win32/CursorFactory.cs
  86. 361
      src/Windows/Avalonia.Win32/DataObject.cs
  87. 27
      src/Windows/Avalonia.Win32/DragSource.cs
  88. 102
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  89. 47
      src/Windows/Avalonia.Win32/OleContext.cs
  90. 171
      src/Windows/Avalonia.Win32/OleDataObject.cs
  91. 39
      src/Windows/Avalonia.Win32/OleDragSource.cs
  92. 160
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  93. 3
      src/Windows/Avalonia.Win32/Win32Platform.cs
  94. 9
      src/Windows/Avalonia.Win32/WindowImpl.cs
  95. 2
      tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs
  96. 1042
      tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs
  97. 24
      tests/Avalonia.Controls.UnitTests/BorderTests.cs
  98. 50
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs
  99. 7
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  100. 66
      tests/Avalonia.RenderTests/Controls/BorderTests.cs

2
readme.md

@ -35,7 +35,7 @@ https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts
## Documentation
As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/guides/quickstart) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
As mentioned above, Avalonia is still in beta and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
There's also a high-level [architecture document](http://avaloniaui.net/architecture/project-structure) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.

1
samples/ControlCatalog.Desktop/Program.cs

@ -10,6 +10,7 @@ namespace ControlCatalog
{
internal class Program
{
[STAThread]
static void Main(string[] args)
{
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args

2
samples/ControlCatalog.NetCore/Program.cs

@ -9,8 +9,10 @@ namespace ControlCatalog.NetCore
{
static class Program
{
static void Main(string[] args)
{
Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA);
if (args.Contains("--wait-for-attach"))
{
Console.WriteLine("Attach debugger and use 'Set next statement'");

200
samples/ControlCatalog/ControlCatalog.csproj

@ -1,189 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<None Remove="Pages\ContextMenuPage.xaml" />
</ItemGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<EmbeddedResource Include="App.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="MainView.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="DecoratedWindow.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ButtonSpinnerPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\DialogsPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\BorderPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ButtonPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CalendarPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CanvasPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CarouselPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CheckBoxPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ContextMenuPage.xaml" />
<EmbeddedResource Include="Pages\DropDownPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\DatePickerPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ExpanderPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ImagePage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\LayoutTransformControlPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\MenuPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ProgressBarPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\RadioButtonPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\SliderPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\TextBoxPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ToolTipPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="MainView.xaml.cs">
<DependentUpon>MainView.xaml</DependentUpon>
</Compile>
<Compile Include="DecoratedWindow.xaml.cs">
<DependentUpon>DecoratedWindow.xaml</DependentUpon>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\DialogsPage.xaml.cs">
<DependentUpon>DialogsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\BorderPage.xaml.cs">
<DependentUpon>BorderPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ButtonPage.xaml.cs">
<DependentUpon>ButtonPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CalendarPage.xaml.cs">
<DependentUpon>CalendarPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CanvasPage.xaml.cs">
<DependentUpon>CanvasPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CarouselPage.xaml.cs">
<DependentUpon>CarouselPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ContextMenuPage.xaml.cs">
<DependentUpon>ContextMenuPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CheckBoxPage.xaml.cs">
<DependentUpon>CheckBoxPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\DropDownPage.xaml.cs">
<DependentUpon>DropDownPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\DatePickerPage.xaml.cs">
<DependentUpon>DatePickerPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ExpanderPage.xaml.cs">
<DependentUpon>ExpanderPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ImagePage.xaml.cs">
<DependentUpon>ImagePage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\LayoutTransformControlPage.xaml.cs">
<DependentUpon>LayoutTransformControlPage.xaml</DependentUpon>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Include="Pages\MenuPage.xaml.cs">
<DependentUpon>MenuPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ProgressBarPage.xaml.cs">
<DependentUpon>ProgressBarPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\RadioButtonPage.xaml.cs">
<DependentUpon>RadioButtonPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\SliderPage.xaml.cs">
<DependentUpon>SliderPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\TreeViewPage.xaml.cs">
<DependentUpon>TreeViewPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\TextBoxPage.xaml.cs">
<DependentUpon>TextBoxPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ToolTipPage.xaml.cs">
<DependentUpon>ToolTipPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ButtonSpinnerPage.xaml.cs">
<DependentUpon>ButtonSpinnerPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ScreenPage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\delicate-arch-896885_640.jpg" />
<EmbeddedResource Include="Assets\github_icon.png" />
<EmbeddedResource Include="Assets\hirsch-899118_640.jpg" />
<EmbeddedResource Include="Assets\maple-leaf-888807_640.jpg" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="SideBar.xaml">
<EmbeddedResource Include="**\*.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Assets\*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
@ -200,20 +28,6 @@
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\test_icon.ico" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\TreeViewPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>
</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="..\..\build\Serilog.props" />
</Project>

7
samples/ControlCatalog/MainView.xaml

@ -5,20 +5,23 @@
<TabControl.Transition>
<CrossFade Duration="0.25"/>
</TabControl.Transition>
<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="Calendar"><pages:CalendarPage/></TabItem>
<TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
<TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="DropDown"><pages:DropDownPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>
@ -26,4 +29,4 @@
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
</TabControl>
</UserControl>
</UserControl>

59
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@ -0,0 +1,59 @@
<UserControl xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">AutoCompleteBox</TextBlock>
<TextBlock Classes="h2">A control into which the user can input text</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="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"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="ValueMemeberSelector"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
ValueMemberSelector="Capital"/>
<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"/>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

143
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -0,0 +1,143 @@
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ControlCatalog.Pages
{
public class AutoCompleteBoxPage : UserControl
{
public class StateData
{
public string Name { get; private set; }
public string Abbreviation { get; private set; }
public string Capital { get; private set; }
public StateData(string name, string abbreviatoin, string capital)
{
Name = name;
Abbreviation = abbreviatoin;
Capital = capital;
}
public override string ToString()
{
return Name;
}
}
private StateData[] BuildAllStates()
{
return new StateData[]
{
new StateData("Alabama","AL","Montgomery"),
new StateData("Alaska","AK","Juneau"),
new StateData("Arizona","AZ","Phoenix"),
new StateData("Arkansas","AR","Little Rock"),
new StateData("California","CA","Sacramento"),
new StateData("Colorado","CO","Denver"),
new StateData("Connecticut","CT","Hartford"),
new StateData("Delaware","DE","Dover"),
new StateData("Florida","FL","Tallahassee"),
new StateData("Georgia","GA","Atlanta"),
new StateData("Hawaii","HI","Honolulu"),
new StateData("Idaho","ID","Boise"),
new StateData("Illinois","IL","Springfield"),
new StateData("Indiana","IN","Indianapolis"),
new StateData("Iowa","IA","Des Moines"),
new StateData("Kansas","KS","Topeka"),
new StateData("Kentucky","KY","Frankfort"),
new StateData("Louisiana","LA","Baton Rouge"),
new StateData("Maine","ME","Augusta"),
new StateData("Maryland","MD","Annapolis"),
new StateData("Massachusetts","MA","Boston"),
new StateData("Michigan","MI","Lansing"),
new StateData("Minnesota","MN","St. Paul"),
new StateData("Mississippi","MS","Jackson"),
new StateData("Missouri","MO","Jefferson City"),
new StateData("Montana","MT","Helena"),
new StateData("Nebraska","NE","Lincoln"),
new StateData("Nevada","NV","Carson City"),
new StateData("New Hampshire","NH","Concord"),
new StateData("New Jersey","NJ","Trenton"),
new StateData("New Mexico","NM","Santa Fe"),
new StateData("New York","NY","Albany"),
new StateData("North Carolina","NC","Raleigh"),
new StateData("North Dakota","ND","Bismarck"),
new StateData("Ohio","OH","Columbus"),
new StateData("Oklahoma","OK","Oklahoma City"),
new StateData("Oregon","OR","Salem"),
new StateData("Pennsylvania","PA","Harrisburg"),
new StateData("Rhode Island","RI","Providence"),
new StateData("South Carolina","SC","Columbia"),
new StateData("South Dakota","SD","Pierre"),
new StateData("Tennessee","TN","Nashville"),
new StateData("Texas","TX","Austin"),
new StateData("Utah","UT","Salt Lake City"),
new StateData("Vermont","VT","Montpelier"),
new StateData("Virginia","VA","Richmond"),
new StateData("Washington","WA","Olympia"),
new StateData("West Virginia","WV","Charleston"),
new StateData("Wisconsin","WI","Madison"),
new StateData("Wyoming","WY","Cheyenne"),
};
}
public StateData[] States { get; private set; }
public AutoCompleteBoxPage()
{
this.InitializeComponent();
States = BuildAllStates();
foreach (AutoCompleteBox box in GetAllAutoCompleteBox())
{
box.Items = States;
}
var converter = new FuncMultiValueConverter<string, string>(parts =>
{
return String.Format("{0} ({1})", parts.ToArray());
});
var binding = new MultiBinding { Converter = converter };
binding.Bindings.Add(new Binding("Name"));
binding.Bindings.Add(new Binding("Abbreviation"));
var multibindingBox = this.FindControl<AutoCompleteBox>("MultiBindingBox");
multibindingBox.ValueMemberBinding = binding;
var asyncBox = this.FindControl<AutoCompleteBox>("AsyncBox");
asyncBox.AsyncPopulator = PopulateAsync;
}
private IEnumerable<AutoCompleteBox> GetAllAutoCompleteBox()
{
return
this.GetLogicalDescendants()
.OfType<AutoCompleteBox>();
}
private bool StringContains(string str, string query)
{
return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
}
private async Task<IEnumerable<object>> PopulateAsync(string searchText, CancellationToken cancellationToken)
{
await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken);
return
States.Where(data => StringContains(data.Name, searchText) || StringContains(data.Capital, searchText))
.ToList();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

19
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@ -0,0 +1,19 @@
<UserControl xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">Drag+Drop</TextBlock>
<TextBlock Classes="h2">Example of Drag+Drop capabilities</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMe">
<TextBlock Name="DragState">Drag Me</TextBlock>
</Border>
<Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16"
DragDrop.AllowDrop="True">
<TextBlock Name="DropState">Drop some text or files here</TextBlock>
</Border>
</StackPanel>
</StackPanel>
</UserControl>

71
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -0,0 +1,71 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using System;
using System.Collections.Generic;
using System.Text;
namespace ControlCatalog.Pages
{
public class DragAndDropPage : UserControl
{
private TextBlock _DropState;
private TextBlock _DragState;
private Border _DragMe;
private int DragCount = 0;
public DragAndDropPage()
{
this.InitializeComponent();
_DragMe.PointerPressed += DoDrag;
AddHandler(DragDrop.DropEvent, Drop);
AddHandler(DragDrop.DragOverEvent, DragOver);
}
private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
{
DataObject dragData = new DataObject();
dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
var result = await DragDrop.DoDragDrop(dragData, DragDropEffects.Copy);
switch(result)
{
case DragDropEffects.Copy:
_DragState.Text = "The text was copied"; break;
case DragDropEffects.Link:
_DragState.Text = "The text was linked"; break;
case DragDropEffects.None:
_DragState.Text = "The drag operation was canceled"; break;
}
}
private void DragOver(object sender, DragEventArgs e)
{
// Only allow Copy or Link as Drop Operations.
e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
// Only allow if the dragged data contains text or filenames.
if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames))
e.DragEffects = DragDropEffects.None;
}
private void Drop(object sender, DragEventArgs e)
{
if (e.Data.Contains(DataFormats.Text))
_DropState.Text = e.Data.GetText();
else if (e.Data.Contains(DataFormats.FileNames))
_DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_DropState = this.Find<TextBlock>("DropState");
_DragState = this.Find<TextBlock>("DragState");
_DragMe = this.Find<Border>("DragMe");
}
}
}

80
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -0,0 +1,80 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Gap="4">
<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="35,35,35,35,35">
<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"/>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">IsReadOnly:</TextBlock>
<CheckBox Grid.Row="1" Grid.Column="1" IsChecked="{Binding #upDown.IsReadOnly}" VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">AllowSpin:</TextBlock>
<CheckBox Grid.Row="2" Grid.Column="1" IsChecked="{Binding #upDown.AllowSpin}" IsEnabled="{Binding #upDown.!IsReadOnly}" VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">ClipValueToMinMax:</TextBlock>
<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="35,35,35,35,35">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock>
<DropDown Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
VerticalAlignment="Center" Margin="2">
<DropDown.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Gap="2">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="-"/>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
</DropDown.ItemTemplate>
</DropDown>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">ButtonSpinnerLocation:</TextBlock>
<DropDown Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock>
<DropDown Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}" SelectedItem="{Binding #upDown.CultureInfo}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding #upDown.Watermark}" VerticalAlignment="Center" Margin="2" />
<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="35,35,35,35,35" ColumnDefinitions="Auto, 120">
<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" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Maximum:</TextBlock>
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding #upDown.Maximum}"
CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Increment:</TextBlock>
<NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #upDown.Increment}" VerticalAlignment="Center"
Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock>
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center"
Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
</Grid>
</Grid>
<StackPanel Margin="2,10,2,2" Orientation="Horizontal" Gap="10">
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of NumericUpDown:</TextBlock>
<NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5"
CultureInfo="en-US" VerticalAlignment="Center" Height="25" Width="100"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
</StackPanel>
</StackPanel>
</UserControl>

94
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Xaml;
using ReactiveUI;
namespace ControlCatalog.Pages
{
public class NumericUpDownPage : UserControl
{
public NumericUpDownPage()
{
this.InitializeComponent();
var viewModel = new NumbersPageViewModel();
DataContext = viewModel;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
public class NumbersPageViewModel : ReactiveObject
{
private IList<FormatObject> _formats;
private FormatObject _selectedFormat;
private IList<Location> _spinnerLocations;
public NumbersPageViewModel()
{
SelectedFormat = Formats.FirstOrDefault();
}
public IList<FormatObject> Formats
{
get
{
return _formats ?? (_formats = new List<FormatObject>()
{
new FormatObject() {Name = "Currency", Value = "C2"},
new FormatObject() {Name = "Fixed point", Value = "F2"},
new FormatObject() {Name = "General", Value = "G"},
new FormatObject() {Name = "Number", Value = "N"},
new FormatObject() {Name = "Percent", Value = "P"},
new FormatObject() {Name = "Degrees", Value = "{0:N2} °"},
});
}
}
public IList<Location> SpinnerLocations
{
get
{
if (_spinnerLocations == null)
{
_spinnerLocations = new List<Location>();
foreach (Location value in Enum.GetValues(typeof(Location)))
{
_spinnerLocations.Add(value);
}
}
return _spinnerLocations ;
}
}
public IList<CultureInfo> Cultures { get; } = new List<CultureInfo>()
{
new CultureInfo("en-US"),
new CultureInfo("en-GB"),
new CultureInfo("fr-FR"),
new CultureInfo("ar-DZ"),
new CultureInfo("zh-CN"),
new CultureInfo("cs-CZ")
};
public FormatObject SelectedFormat
{
get { return _selectedFormat; }
set { this.RaiseAndSetIfChanged(ref _selectedFormat, value); }
}
}
public class FormatObject
{
public string Value { get; set; }
public string Name { get; set; }
}
}

36
samples/ControlCatalog/Properties/AssemblyInfo.cs

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ControlCatalog")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ControlCatalog")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("61bec86c-f307-4295-b5b8-9428610d7d55")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

4
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -17,7 +17,9 @@
<None Remove="MiniCube.fx" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="MiniCube.fx" />
<EmbeddedResource Include="MiniCube.fx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />

4
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@ -117,7 +117,7 @@ namespace Avalonia.Collections
_inner = new Dictionary<TKey, TValue>();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[]"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));
if (CollectionChanged != null)
@ -222,4 +222,4 @@ namespace Avalonia.Collections
}
}
}
}
}

205
src/Avalonia.Base/Utilities/StringTokenizer.cs

@ -0,0 +1,205 @@
using System;
using System.Globalization;
using static System.Char;
namespace Avalonia.Utilities
{
public struct StringTokenizer : IDisposable
{
private const char DefaultSeparatorChar = ',';
private readonly string _s;
private readonly int _length;
private readonly char _separator;
private readonly string _exceptionMessage;
private readonly IFormatProvider _formatProvider;
private int _index;
private int _tokenIndex;
private int _tokenLength;
public StringTokenizer(string s, IFormatProvider formatProvider, string exceptionMessage = null)
: this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage)
{
_formatProvider = formatProvider;
}
public StringTokenizer(string s, char separator = DefaultSeparatorChar, string exceptionMessage = null)
{
_s = s ?? throw new ArgumentNullException(nameof(s));
_length = s?.Length ?? 0;
_separator = separator;
_exceptionMessage = exceptionMessage;
_formatProvider = CultureInfo.InvariantCulture;
_index = 0;
_tokenIndex = -1;
_tokenLength = 0;
while (_index < _length && IsWhiteSpace(_s, _index))
{
_index++;
}
}
public string CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength);
public void Dispose()
{
if (_index != _length)
{
throw GetFormatException();
}
}
public bool TryReadInt32(out Int32 result, char? separator = null)
{
var success = TryReadString(out var stringResult, separator);
result = success ? int.Parse(stringResult, _formatProvider) : 0;
return success;
}
public int ReadInt32(char? separator = null)
{
if (!TryReadInt32(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadDouble(out double result, char? separator = null)
{
var success = TryReadString(out var stringResult, separator);
result = success ? double.Parse(stringResult, _formatProvider) : 0;
return success;
}
public double ReadDouble(char? separator = null)
{
if (!TryReadDouble(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadString(out string result, char? separator = null)
{
var success = TryReadToken(separator ?? _separator);
result = CurrentToken;
return success;
}
public string ReadString(char? separator = null)
{
if (!TryReadString(out var result, separator))
{
throw GetFormatException();
}
return result;
}
private bool TryReadToken(char separator)
{
_tokenIndex = -1;
if (_index >= _length)
{
return false;
}
var c = _s[_index];
var index = _index;
var length = 0;
while (_index < _length)
{
c = _s[_index];
if (IsWhiteSpace(c) || c == separator)
{
break;
}
_index++;
length++;
}
SkipToNextToken(separator);
_tokenIndex = index;
_tokenLength = length;
if (_tokenLength < 1)
{
throw GetFormatException();
}
return true;
}
private void SkipToNextToken(char separator)
{
if (_index < _length)
{
var c = _s[_index];
if (c != separator && !IsWhiteSpace(c))
{
throw GetFormatException();
}
var length = 0;
while (_index < _length)
{
c = _s[_index];
if (c == separator)
{
length++;
_index++;
if (length > 1)
{
throw GetFormatException();
}
}
else
{
if (!IsWhiteSpace(c))
{
break;
}
_index++;
}
}
if (length > 0 && _index >= _length)
{
throw GetFormatException();
}
}
}
private FormatException GetFormatException() =>
_exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException();
private static char GetSeparatorFromFormatProvider(IFormatProvider provider)
{
var c = DefaultSeparatorChar;
var formatInfo = NumberFormatInfo.GetInstance(provider);
if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0])
{
c = ';';
}
return c;
}
}
}

9
src/Avalonia.Controls/Application.cs

@ -2,16 +2,17 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Concurrency;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Rendering;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
using System.Reactive.Concurrency;
namespace Avalonia
{
@ -234,7 +235,9 @@ namespace Avalonia
.Bind<IStyler>().ToConstant(_styler)
.Bind<ILayoutManager>().ToSingleton<LayoutManager>()
.Bind<IApplicationLifecycle>().ToConstant(this)
.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance);
.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)
.Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
}
}
}

2726
src/Avalonia.Controls/AutoCompleteBox.cs

File diff suppressed because it is too large

51
src/Avalonia.Controls/Border.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia;
using Avalonia.Controls.Utils;
using Avalonia.Media;
namespace Avalonia.Controls
@ -8,7 +10,7 @@ namespace Avalonia.Controls
/// <summary>
/// A control which decorates a child with a border and background.
/// </summary>
public class Border : Decorator
public partial class Border : Decorator
{
/// <summary>
/// Defines the <see cref="Background"/> property.
@ -25,21 +27,24 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
AvaloniaProperty.Register<Border, double>(nameof(BorderThickness));
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
AvaloniaProperty.Register<Border, Thickness>(nameof(BorderThickness));
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
public static readonly StyledProperty<float> CornerRadiusProperty =
AvaloniaProperty.Register<Border, float>(nameof(CornerRadius));
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
AvaloniaProperty.Register<Border, CornerRadius>(nameof(CornerRadius));
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
/// <summary>
/// Initializes static members of the <see cref="Border"/> class.
/// </summary>
static Border()
{
AffectsRender(BackgroundProperty, BorderBrushProperty);
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure(BorderThicknessProperty);
}
/// <summary>
@ -63,7 +68,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the thickness of the border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
@ -72,7 +77,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
public float CornerRadius
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
@ -84,21 +89,7 @@ namespace Avalonia.Controls
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
{
var background = Background;
var borderBrush = BorderBrush;
var borderThickness = BorderThickness;
var cornerRadius = CornerRadius;
var rect = new Rect(Bounds.Size).Deflate(BorderThickness);
if (background != null)
{
context.FillRectangle(background, rect, cornerRadius);
}
if (borderBrush != null && borderThickness > 0)
{
context.DrawRectangle(new Pen(borderBrush, borderThickness), rect, cornerRadius);
}
_borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
}
/// <summary>
@ -120,10 +111,12 @@ namespace Avalonia.Controls
{
if (Child != null)
{
var padding = Padding + new Thickness(BorderThickness);
var padding = Padding + BorderThickness;
Child.Arrange(new Rect(finalSize).Deflate(padding));
}
_borderRenderHelper.Update(finalSize, BorderThickness, CornerRadius);
return finalSize;
}
@ -131,19 +124,17 @@ namespace Avalonia.Controls
Size availableSize,
IControl child,
Thickness padding,
double borderThickness)
Thickness borderThickness)
{
padding += new Thickness(borderThickness);
padding += borderThickness;
if (child != null)
{
child.Measure(availableSize.Deflate(padding));
return child.DesiredSize.Inflate(padding);
}
else
{
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
}
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
}
}
}

2
src/Avalonia.Controls/Button.cs

@ -245,7 +245,7 @@ namespace Avalonia.Controls
{
base.OnPointerReleased(e);
if (e.MouseButton == MouseButton.Left)
if (IsPressed && e.MouseButton == MouseButton.Left)
{
e.Device.Capture(null);
IsPressed = false;

5
src/Avalonia.Controls/ButtonSpinner.cs

@ -201,6 +201,11 @@ namespace Avalonia.Controls
}
}
protected override void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue)
{
SetButtonUsage();
}
/// <summary>
/// Called when the <see cref="AllowSpin"/> property value changed.
/// </summary>

1
src/Avalonia.Controls/DropDown.cs

@ -164,6 +164,7 @@ namespace Avalonia.Controls
else
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
}
}
base.OnPointerPressed(e);

9
src/Avalonia.Controls/GridLength.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
@ -210,7 +211,13 @@ namespace Avalonia.Controls
/// <returns>The <see cref="GridLength"/>.</returns>
public static IEnumerable<GridLength> ParseLengths(string s, CultureInfo culture)
{
return s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(x => Parse(x, culture));
using (var tokenizer = new StringTokenizer(s, culture))
{
while (tokenizer.TryReadString(out var item))
{
yield return Parse(item, culture);
}
}
}
}
}

35
src/Avalonia.Controls/MenuItem.cs

@ -93,6 +93,7 @@ namespace Avalonia.Controls
static MenuItem()
{
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
CommandProperty.Changed.Subscribe(CommandChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
@ -424,6 +425,40 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="Command"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void CommandChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is MenuItem menuItem)
{
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
}
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);
}
}
/// <summary>
/// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void CanExecuteChanged(object sender, EventArgs e)
{
// HACK: Just set the IsEnabled property for the moment. This needs to be changed to
// use IsEnabledCore etc. but it will do for now.
IsEnabled = Command == null || Command.CanExecute(CommandParameter);
}
/// <summary>
/// Called when the <see cref="Icon"/> property changes.
/// </summary>

998
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -0,0 +1,998 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
/// <summary>
/// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
/// </summary>
public class NumericUpDown : TemplatedControl
{
/// <summary>
/// Defines the <see cref="AllowSpin"/> property.
/// </summary>
public static readonly StyledProperty<bool> AllowSpinProperty =
ButtonSpinner.AllowSpinProperty.AddOwner<NumericUpDown>();
/// <summary>
/// Defines the <see cref="ButtonSpinnerLocation"/> property.
/// </summary>
public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty =
ButtonSpinner.ButtonSpinnerLocationProperty.AddOwner<NumericUpDown>();
/// <summary>
/// Defines the <see cref="ShowButtonSpinner"/> property.
/// </summary>
public static readonly StyledProperty<bool> ShowButtonSpinnerProperty =
ButtonSpinner.ShowButtonSpinnerProperty.AddOwner<NumericUpDown>();
/// <summary>
/// Defines the <see cref="ClipValueToMinMax"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, bool> ClipValueToMinMaxProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, bool>(nameof(ClipValueToMinMax),
updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b);
/// <summary>
/// Defines the <see cref="CultureInfo"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, CultureInfo> CultureInfoProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, CultureInfo>(nameof(CultureInfo), o => o.CultureInfo,
(o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture);
/// <summary>
/// Defines the <see cref="FormatString"/> property.
/// </summary>
public static readonly StyledProperty<string> FormatStringProperty =
AvaloniaProperty.Register<NumericUpDown, string>(nameof(FormatString), string.Empty);
/// <summary>
/// Defines the <see cref="Increment"/> property.
/// </summary>
public static readonly StyledProperty<double> IncrementProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Increment), 1.0d, validate: OnCoerceIncrement);
/// <summary>
/// Defines the <see cref="IsReadOnly"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsReadOnlyProperty =
AvaloniaProperty.Register<NumericUpDown, bool>(nameof(IsReadOnly));
/// <summary>
/// Defines the <see cref="Maximum"/> property.
/// </summary>
public static readonly StyledProperty<double> MaximumProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Maximum), double.MaxValue, validate: OnCoerceMaximum);
/// <summary>
/// Defines the <see cref="Minimum"/> property.
/// </summary>
public static readonly StyledProperty<double> MinimumProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Minimum), double.MinValue, validate: OnCoerceMinimum);
/// <summary>
/// Defines the <see cref="ParsingNumberStyle"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, NumberStyles> ParsingNumberStyleProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, NumberStyles>(nameof(ParsingNumberStyle),
updown => updown.ParsingNumberStyle, (updown, style) => updown.ParsingNumberStyle = style);
/// <summary>
/// Defines the <see cref="Text"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, string> TextProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, string>(nameof(Text), o => o.Text, (o, v) => o.Text = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="Value"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, double> ValueProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, double>(nameof(Value), updown => updown.Value,
(updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="Watermark"/> property.
/// </summary>
public static readonly StyledProperty<string> WatermarkProperty =
AvaloniaProperty.Register<NumericUpDown, string>(nameof(Watermark));
private IDisposable _textBoxTextChangedSubscription;
private double _value;
private string _text;
private bool _internalValueSet;
private bool _clipValueToMinMax;
private bool _isSyncingTextAndValueProperties;
private bool _isTextChangedFromUI;
private CultureInfo _cultureInfo;
private NumberStyles _parsingNumberStyle = NumberStyles.Any;
/// <summary>
/// Gets the Spinner template part.
/// </summary>
private Spinner Spinner { get; set; }
/// <summary>
/// Gets the TextBox template part.
/// </summary>
private TextBox TextBox { get; set; }
/// <summary>
/// Gets or sets the ability to perform increment/decrement operations via the keyboard, button spinners, or mouse wheel.
/// </summary>
public bool AllowSpin
{
get { return GetValue(AllowSpinProperty); }
set { SetValue(AllowSpinProperty, value); }
}
/// <summary>
/// Gets or sets current location of the <see cref="ButtonSpinner"/>.
/// </summary>
public Location ButtonSpinnerLocation
{
get { return GetValue(ButtonSpinnerLocationProperty); }
set { SetValue(ButtonSpinnerLocationProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the spin buttons should be shown.
/// </summary>
public bool ShowButtonSpinner
{
get { return GetValue(ShowButtonSpinnerProperty); }
set { SetValue(ShowButtonSpinnerProperty, value); }
}
/// <summary>
/// Gets or sets if the value should be clipped when minimum/maximum is reached.
/// </summary>
public bool ClipValueToMinMax
{
get { return _clipValueToMinMax; }
set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); }
}
/// <summary>
/// Gets or sets the current CultureInfo.
/// </summary>
public CultureInfo CultureInfo
{
get { return _cultureInfo; }
set { SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); }
}
/// <summary>
/// Gets or sets the display format of the <see cref="Value"/>.
/// </summary>
public string FormatString
{
get { return GetValue(FormatStringProperty); }
set { SetValue(FormatStringProperty, value); }
}
/// <summary>
/// Gets or sets the amount in which to increment the <see cref="Value"/>.
/// </summary>
public double Increment
{
get { return GetValue(IncrementProperty); }
set { SetValue(IncrementProperty, value); }
}
/// <summary>
/// Gets or sets if the control is read only.
/// </summary>
public bool IsReadOnly
{
get { return GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
/// <summary>
/// Gets or sets the maximum allowed value.
/// </summary>
public double Maximum
{
get { return GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
/// <summary>
/// Gets or sets the minimum allowed value.
/// </summary>
public double Minimum
{
get { return GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
/// <summary>
/// Gets or sets the parsing style (AllowLeadingWhite, Float, AllowHexSpecifier, ...). By default, Any.
/// </summary>
public NumberStyles ParsingNumberStyle
{
get { return _parsingNumberStyle; }
set { SetAndRaise(ParsingNumberStyleProperty, ref _parsingNumberStyle, value); }
}
/// <summary>
/// Gets or sets the formatted string representation of the value.
/// </summary>
public string Text
{
get { return _text; }
set { SetAndRaise(TextProperty, ref _text, value); }
}
/// <summary>
/// Gets or sets the value.
/// </summary>
public double Value
{
get { return _value; }
set
{
value = OnCoerceValue(value);
SetAndRaise(ValueProperty, ref _value, value);
}
}
/// <summary>
/// Gets or sets the object to use as a watermark if the <see cref="Value"/> is null.
/// </summary>
public string Watermark
{
get { return GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
/// <summary>
/// Initializes new instance of <see cref="NumericUpDown"/> class.
/// </summary>
public NumericUpDown()
{
Initialized += (sender, e) =>
{
if (!_internalValueSet && IsInitialized)
{
SyncTextAndValueProperties(false, null, true);
}
SetValidSpinDirection();
};
}
/// <summary>
/// Initializes static members of the <see cref="NumericUpDown"/> class.
/// </summary>
static NumericUpDown()
{
CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
FormatStringProperty.Changed.Subscribe(FormatStringChanged);
IncrementProperty.Changed.Subscribe(IncrementChanged);
IsReadOnlyProperty.Changed.Subscribe(OnIsReadOnlyChanged);
MaximumProperty.Changed.Subscribe(OnMaximumChanged);
MinimumProperty.Changed.Subscribe(OnMinimumChanged);
TextProperty.Changed.Subscribe(OnTextChanged);
ValueProperty.Changed.Subscribe(OnValueChanged);
}
/// <inheritdoc />
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
if (TextBox != null)
{
TextBox.PointerPressed -= TextBoxOnPointerPressed;
_textBoxTextChangedSubscription?.Dispose();
}
TextBox = e.NameScope.Find<TextBox>("PART_TextBox");
if (TextBox != null)
{
TextBox.Text = Text;
TextBox.PointerPressed += TextBoxOnPointerPressed;
_textBoxTextChangedSubscription = TextBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBoxOnTextChanged());
}
if (Spinner != null)
{
Spinner.Spin -= OnSpinnerSpin;
}
Spinner = e.NameScope.Find<Spinner>("PART_Spinner");
if (Spinner != null)
{
Spinner.Spin += OnSpinnerSpin;
}
SetValidSpinDirection();
}
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
var commitSuccess = CommitInput();
e.Handled = !commitSuccess;
break;
}
}
/// <summary>
/// Called when the <see cref="CultureInfo"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnCultureInfoChanged(CultureInfo oldValue, CultureInfo newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(false, null);
}
}
/// <summary>
/// Called when the <see cref="FormatString"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnFormatStringChanged(string oldValue, string newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(false, null);
}
}
/// <summary>
/// Called when the <see cref="Increment"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnIncrementChanged(double oldValue, double newValue)
{
if (IsInitialized)
{
SetValidSpinDirection();
}
}
/// <summary>
/// Called when the <see cref="IsReadOnly"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnIsReadOnlyChanged(bool oldValue, bool newValue)
{
SetValidSpinDirection();
}
/// <summary>
/// Called when the <see cref="Maximum"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnMaximumChanged(double oldValue, double newValue)
{
if (IsInitialized)
{
SetValidSpinDirection();
}
if (ClipValueToMinMax)
{
Value = MathUtilities.Clamp(Value, Minimum, Maximum);
}
}
/// <summary>
/// Called when the <see cref="Minimum"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnMinimumChanged(double oldValue, double newValue)
{
if (IsInitialized)
{
SetValidSpinDirection();
}
if (ClipValueToMinMax)
{
Value = MathUtilities.Clamp(Value, Minimum, Maximum);
}
}
/// <summary>
/// Called when the <see cref="Text"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnTextChanged(string oldValue, string newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(true, Text);
}
}
/// <summary>
/// Called when the <see cref="Value"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnValueChanged(double oldValue, double newValue)
{
if (!_internalValueSet && IsInitialized)
{
SyncTextAndValueProperties(false, null, true);
}
SetValidSpinDirection();
RaiseValueChangedEvent(oldValue, newValue);
}
/// <summary>
/// Called when the <see cref="Increment"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceIncrement(double baseValue)
{
return baseValue;
}
/// <summary>
/// Called when the <see cref="Maximum"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceMaximum(double baseValue)
{
return Math.Max(baseValue, Minimum);
}
/// <summary>
/// Called when the <see cref="Minimum"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceMinimum(double baseValue)
{
return Math.Min(baseValue, Maximum);
}
/// <summary>
/// Called when the <see cref="Value"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceValue(double baseValue)
{
return baseValue;
}
/// <summary>
/// Raises the OnSpin event when spinning is initiated by the end-user.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnSpin(SpinEventArgs e)
{
if (e == null)
{
throw new ArgumentNullException("e");
}
var handler = Spinned;
handler?.Invoke(this, e);
if (e.Direction == SpinDirection.Increase)
{
DoIncrement();
}
else
{
DoDecrement();
}
}
/// <summary>
/// Raises the <see cref="ValueChanged"/> event.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void RaiseValueChangedEvent(double oldValue, double newValue)
{
var e = new NumericUpDownValueChangedEventArgs(ValueChangedEvent, oldValue, newValue);
RaiseEvent(e);
}
/// <summary>
/// Converts the formatted text to a value.
/// </summary>
private double ConvertTextToValue(string text)
{
double result = 0;
if (string.IsNullOrEmpty(text))
{
return result;
}
// Since the conversion from Value to text using a FormartString may not be parsable,
// we verify that the already existing text is not the exact same value.
var currentValueText = ConvertValueToText();
if (Equals(currentValueText, text))
{
return Value;
}
result = ConvertTextToValueCore(currentValueText, text);
if (ClipValueToMinMax)
{
return MathUtilities.Clamp(result, Minimum, Maximum);
}
ValidateMinMax(result);
return result;
}
/// <summary>
/// Converts the value to formatted text.
/// </summary>
/// <returns></returns>
private string ConvertValueToText()
{
//Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
if (FormatString.Contains("{0"))
{
return string.Format(CultureInfo, FormatString, Value);
}
return Value.ToString(FormatString, CultureInfo);
}
/// <summary>
/// Called by OnSpin when the spin direction is SpinDirection.Increase.
/// </summary>
private void OnIncrement()
{
var result = Value + Increment;
Value = MathUtilities.Clamp(result, Minimum, Maximum);
}
/// <summary>
/// Called by OnSpin when the spin direction is SpinDirection.Descrease.
/// </summary>
private void OnDecrement()
{
var result = Value - Increment;
Value = MathUtilities.Clamp(result, Minimum, Maximum);
}
/// <summary>
/// Sets the valid spin directions.
/// </summary>
private void SetValidSpinDirection()
{
var validDirections = ValidSpinDirections.None;
// Zero increment always prevents spin.
if (Increment != 0 && !IsReadOnly)
{
if (Value < Maximum)
{
validDirections = validDirections | ValidSpinDirections.Increase;
}
if (Value > Minimum)
{
validDirections = validDirections | ValidSpinDirections.Decrease;
}
}
if (Spinner != null)
{
Spinner.ValidSpinDirection = validDirections;
}
}
/// <summary>
/// Called when the <see cref="CultureInfo"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnCultureInfoChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (CultureInfo)e.OldValue;
var newValue = (CultureInfo)e.NewValue;
upDown.OnCultureInfoChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Increment"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void IncrementChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
upDown.OnIncrementChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="FormatString"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void FormatStringChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (string)e.OldValue;
var newValue = (string)e.NewValue;
upDown.OnFormatStringChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="IsReadOnly"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnIsReadOnlyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (bool)e.OldValue;
var newValue = (bool)e.NewValue;
upDown.OnIsReadOnlyChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Maximum"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnMaximumChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
upDown.OnMaximumChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Minimum"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnMinimumChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
upDown.OnMinimumChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Text"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnTextChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (string)e.OldValue;
var newValue = (string)e.NewValue;
upDown.OnTextChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Value"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnValueChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
upDown.OnValueChanged(oldValue, newValue);
}
}
private void SetValueInternal(double value)
{
_internalValueSet = true;
try
{
Value = value;
}
finally
{
_internalValueSet = false;
}
}
private static double OnCoerceMaximum(NumericUpDown upDown, double value)
{
return upDown.OnCoerceMaximum(value);
}
private static double OnCoerceMinimum(NumericUpDown upDown, double value)
{
return upDown.OnCoerceMinimum(value);
}
private static double OnCoerceIncrement(NumericUpDown upDown, double value)
{
return upDown.OnCoerceIncrement(value);
}
private void TextBoxOnTextChanged()
{
try
{
_isTextChangedFromUI = true;
if (TextBox != null)
{
Text = TextBox.Text;
}
}
finally
{
_isTextChangedFromUI = false;
}
}
private void OnSpinnerSpin(object sender, SpinEventArgs e)
{
if (AllowSpin && !IsReadOnly)
{
var spin = !e.UsingMouseWheel;
spin |= ((TextBox != null) && TextBox.IsFocused);
if (spin)
{
e.Handled = true;
OnSpin(e);
}
}
}
private void DoDecrement()
{
if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease)
{
OnDecrement();
}
}
private void DoIncrement()
{
if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Increase) == ValidSpinDirections.Increase)
{
OnIncrement();
}
}
public event EventHandler<SpinEventArgs> Spinned;
private void TextBoxOnPointerPressed(object sender, PointerPressedEventArgs e)
{
if (e.Device.Captured != Spinner)
{
Dispatcher.UIThread.InvokeAsync(() => { e.Device.Capture(Spinner); }, DispatcherPriority.Input);
}
}
/// <summary>
/// Defines the <see cref="ValueChanged"/> event.
/// </summary>
public static readonly RoutedEvent<NumericUpDownValueChangedEventArgs> ValueChangedEvent =
RoutedEvent.Register<NumericUpDown, NumericUpDownValueChangedEventArgs>(nameof(ValueChanged), RoutingStrategies.Bubble);
/// <summary>
/// Raised when the <see cref="Value"/> changes.
/// </summary>
public event EventHandler<SpinEventArgs> ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
private bool CommitInput()
{
return SyncTextAndValueProperties(true, Text);
}
/// <summary>
/// Synchronize <see cref="Text"/> and <see cref="Value"/> properties.
/// </summary>
/// <param name="updateValueFromText">If value should be updated from text.</param>
/// <param name="text">The text.</param>
private bool SyncTextAndValueProperties(bool updateValueFromText, string text)
{
return SyncTextAndValueProperties(updateValueFromText, text, false);
}
/// <summary>
/// Synchronize <see cref="Text"/> and <see cref="Value"/> properties.
/// </summary>
/// <param name="updateValueFromText">If value should be updated from text.</param>
/// <param name="text">The text.</param>
/// <param name="forceTextUpdate">Force text update.</param>
private bool SyncTextAndValueProperties(bool updateValueFromText, string text, bool forceTextUpdate)
{
if (_isSyncingTextAndValueProperties)
return true;
_isSyncingTextAndValueProperties = true;
var parsedTextIsValid = true;
try
{
if (updateValueFromText)
{
if (!string.IsNullOrEmpty(text))
{
try
{
var newValue = ConvertTextToValue(text);
if (!Equals(newValue, Value))
{
SetValueInternal(newValue);
}
}
catch
{
parsedTextIsValid = false;
}
}
}
// Do not touch the ongoing text input from user.
if (!_isTextChangedFromUI)
{
var keepEmpty = !forceTextUpdate && string.IsNullOrEmpty(Text);
if (!keepEmpty)
{
var newText = ConvertValueToText();
if (!Equals(Text, newText))
{
Text = newText;
}
}
// Sync Text and textBox
if (TextBox != null)
{
TextBox.Text = Text;
}
}
if (_isTextChangedFromUI && !parsedTextIsValid)
{
// Text input was made from the user and the text
// repesents an invalid value. Disable the spinner in this case.
if (Spinner != null)
{
Spinner.ValidSpinDirection = ValidSpinDirections.None;
}
}
else
{
SetValidSpinDirection();
}
}
finally
{
_isSyncingTextAndValueProperties = false;
}
return parsedTextIsValid;
}
private double ConvertTextToValueCore(string currentValueText, string text)
{
double result;
if (IsPercent(FormatString))
{
result = decimal.ToDouble(ParsePercent(text, CultureInfo));
}
else
{
// Problem while converting new text
if (!double.TryParse(text, ParsingNumberStyle, CultureInfo, out var outputValue))
{
var shouldThrow = true;
// Check if CurrentValueText is also failing => it also contains special characters. ex : 90°
if (!double.TryParse(currentValueText, ParsingNumberStyle, CultureInfo, out var _))
{
// extract non-digit characters
var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c));
var textSpecialCharacters = text.Where(c => !char.IsDigit(c));
// same non-digit characters on currentValueText and new text => remove them on new Text to parse it again.
if (currentValueTextSpecialCharacters.Except(textSpecialCharacters).ToList().Count == 0)
{
foreach (var character in textSpecialCharacters)
{
text = text.Replace(character.ToString(), string.Empty);
}
// if without the special characters, parsing is good, do not throw
if (double.TryParse(text, ParsingNumberStyle, CultureInfo, out outputValue))
{
shouldThrow = false;
}
}
}
if (shouldThrow)
{
throw new InvalidDataException("Input string was not in a correct format.");
}
}
result = outputValue;
}
return result;
}
private void ValidateMinMax(double value)
{
if (value < Minimum)
{
throw new ArgumentOutOfRangeException(nameof(Minimum), string.Format("Value must be greater than Minimum value of {0}", Minimum));
}
else if (value > Maximum)
{
throw new ArgumentOutOfRangeException(nameof(Maximum), string.Format("Value must be less than Maximum value of {0}", Maximum));
}
}
/// <summary>
/// Parse percent format text
/// </summary>
/// <param name="text">Text to parse.</param>
/// <param name="cultureInfo">The culture info.</param>
private static decimal ParsePercent(string text, IFormatProvider cultureInfo)
{
var info = NumberFormatInfo.GetInstance(cultureInfo);
text = text.Replace(info.PercentSymbol, null);
var result = decimal.Parse(text, NumberStyles.Any, info);
result = result / 100;
return result;
}
private bool IsPercent(string stringToTest)
{
var PIndex = stringToTest.IndexOf("P", StringComparison.Ordinal);
if (PIndex >= 0)
{
//stringToTest contains a "P" between 2 "'", it's considered as text, not percent
var isText = stringToTest.Substring(0, PIndex).Contains("'")
&& stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'");
return !isText;
}
return false;
}
}
}

16
src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs

@ -0,0 +1,16 @@
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
public class NumericUpDownValueChangedEventArgs : RoutedEventArgs
{
public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, double oldValue, double newValue) : base(routedEvent)
{
OldValue = oldValue;
NewValue = newValue;
}
public double OldValue { get; }
public double NewValue { get; }
}
}

210
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@ -0,0 +1,210 @@
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Platform
{
class InProcessDragSource : IPlatformDragSource
{
private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton;
private readonly IDragDropDevice _dragDrop;
private readonly IInputManager _inputManager;
private readonly Subject<DragDropEffects> _result = new Subject<DragDropEffects>();
private DragDropEffects _allowedEffects;
private IDataObject _draggedData;
private IInputElement _lastRoot;
private Point _lastPosition;
private StandardCursorType _lastCursorType;
private object _originalCursor;
private InputModifiers? _initialInputModifiers;
public InProcessDragSource()
{
_inputManager = AvaloniaLocator.Current.GetService<IInputManager>();
_dragDrop = AvaloniaLocator.Current.GetService<IDragDropDevice>();
}
public async Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
{
Dispatcher.UIThread.VerifyAccess();
if (_draggedData == null)
{
_draggedData = data;
_lastRoot = null;
_lastPosition = default(Point);
_allowedEffects = allowedEffects;
using (_inputManager.PreProcess.OfType<RawMouseEventArgs>().Subscribe(ProcessMouseEvents))
{
using (_inputManager.PreProcess.OfType<RawKeyEventArgs>().Subscribe(ProcessKeyEvents))
{
var effect = await _result.FirstAsync();
return effect;
}
}
}
return DragDropEffects.None;
}
private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, InputModifiers modifiers)
{
_lastPosition = pt;
RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, _allowedEffects);
var tl = root.GetSelfAndVisualAncestors().OfType<TopLevel>().FirstOrDefault();
tl.PlatformImpl.Input(rawEvent);
var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers);
UpdateCursor(root, effect);
return effect;
}
private DragDropEffects GetPreferredEffect(DragDropEffects effect, InputModifiers modifiers)
{
if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None)
return effect; // No need to check for the modifiers.
if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(InputModifiers.Alt))
return DragDropEffects.Link;
if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(InputModifiers.Control))
return DragDropEffects.Copy;
return DragDropEffects.Move;
}
private StandardCursorType GetCursorForDropEffect(DragDropEffects effects)
{
if (effects.HasFlag(DragDropEffects.Copy))
return StandardCursorType.DragCopy;
if (effects.HasFlag(DragDropEffects.Move))
return StandardCursorType.DragMove;
if (effects.HasFlag(DragDropEffects.Link))
return StandardCursorType.DragLink;
return StandardCursorType.No;
}
private void UpdateCursor(IInputElement root, DragDropEffects effect)
{
if (_lastRoot != root)
{
if (_lastRoot is InputElement ieLast)
{
if (_originalCursor == AvaloniaProperty.UnsetValue)
ieLast.ClearValue(InputElement.CursorProperty);
else
ieLast.Cursor = _originalCursor as Cursor;
}
if (root is InputElement ieNew)
{
if (!ieNew.IsSet(InputElement.CursorProperty))
_originalCursor = AvaloniaProperty.UnsetValue;
else
_originalCursor = root.Cursor;
}
else
_originalCursor = null;
_lastCursorType = StandardCursorType.Arrow;
_lastRoot = root;
}
if (root is InputElement ie)
{
var ct = GetCursorForDropEffect(effect);
if (ct != _lastCursorType)
{
_lastCursorType = ct;
ie.Cursor = new Cursor(ct);
}
}
}
private void CancelDragging()
{
if (_lastRoot != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, InputModifiers.None);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None);
}
private void ProcessKeyEvents(RawKeyEventArgs e)
{
if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape)
{
if (_lastRoot != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, e.Modifiers);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None);
e.Handled = true;
}
else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers);
}
private void ProcessMouseEvents(RawMouseEventArgs e)
{
if (!_initialInputModifiers.HasValue)
_initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS;
void CheckDraggingAccepted(InputModifiers changedMouseButton)
{
if (_initialInputModifiers.Value.HasFlag(changedMouseButton))
{
var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(result);
}
else
CancelDragging();
e.Handled = true;
}
switch (e.Type)
{
case RawMouseEventType.LeftButtonDown:
case RawMouseEventType.RightButtonDown:
case RawMouseEventType.MiddleButtonDown:
case RawMouseEventType.NonClientLeftButtonDown:
CancelDragging();
e.Handled = true;
return;
case RawMouseEventType.LeaveWindow:
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position, e.InputModifiers); break;
case RawMouseEventType.LeftButtonUp:
CheckDraggingAccepted(InputModifiers.LeftMouseButton); break;
case RawMouseEventType.MiddleButtonUp:
CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break;
case RawMouseEventType.RightButtonUp:
CheckDraggingAccepted(InputModifiers.RightMouseButton); break;
case RawMouseEventType.Move:
var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS;
if (_initialInputModifiers.Value != mods)
{
CancelDragging();
e.Handled = true;
return;
}
if (e.Root != _lastRoot)
{
if (_lastRoot != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), e.InputModifiers);
RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers);
}
else
RaiseEventAndUpdateCursor(RawDragEventType.DragOver, e.Root, e.Position, e.InputModifiers);
break;
}
}
}
}

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

@ -4,6 +4,7 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
@ -31,7 +32,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner<ContentPresenter>();
/// <summary>
@ -57,7 +58,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
public static readonly StyledProperty<float> CornerRadiusProperty =
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ContentPresenter>();
/// <summary>
@ -76,17 +77,20 @@ namespace Avalonia.Controls.Presenters
/// Defines the <see cref="Padding"/> property.
/// </summary>
public static readonly StyledProperty<Thickness> PaddingProperty =
Border.PaddingProperty.AddOwner<ContentPresenter>();
Decorator.PaddingProperty.AddOwner<ContentPresenter>();
private IControl _child;
private bool _createdChild;
private IDataTemplate _dataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
/// <summary>
/// Initializes static members of the <see cref="ContentPresenter"/> class.
/// </summary>
static ContentPresenter()
{
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure(BorderThicknessProperty);
ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
@ -120,7 +124,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Gets or sets the thickness of the border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
@ -157,7 +161,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
public float CornerRadius
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
@ -221,7 +225,7 @@ namespace Avalonia.Controls.Presenters
{
var content = Content;
var oldChild = Child;
var newChild = CreateChild();
var newChild = CreateChild();
// Remove the old child if we're not recycling it.
if (oldChild != null && newChild != oldChild)
@ -277,21 +281,7 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
public override void Render(DrawingContext context)
{
var background = Background;
var borderBrush = BorderBrush;
var borderThickness = BorderThickness;
var cornerRadius = CornerRadius;
var rect = new Rect(Bounds.Size).Deflate(BorderThickness);
if (background != null)
{
context.FillRectangle(background, rect, cornerRadius);
}
if (borderBrush != null && borderThickness > 0)
{
context.DrawRectangle(new Pen(borderBrush, borderThickness), rect, cornerRadius);
}
_borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
}
/// <summary>
@ -344,7 +334,11 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
return ArrangeOverrideImpl(finalSize, new Vector());
finalSize = ArrangeOverrideImpl(finalSize, new Vector());
_borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
return finalSize;
}
/// <summary>
@ -372,74 +366,69 @@ namespace Avalonia.Controls.Presenters
internal Size ArrangeOverrideImpl(Size finalSize, Vector offset)
{
if (Child != null)
{
var padding = Padding;
var borderThickness = BorderThickness;
var horizontalContentAlignment = HorizontalContentAlignment;
var verticalContentAlignment = VerticalContentAlignment;
var useLayoutRounding = UseLayoutRounding;
var availableSizeMinusMargins = new Size(
Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness),
Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness));
var size = availableSizeMinusMargins;
var scale = GetLayoutScale();
var originX = offset.X + padding.Left + borderThickness;
var originY = offset.Y + padding.Top + borderThickness;
if (horizontalContentAlignment != HorizontalAlignment.Stretch)
{
size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
}
if (Child == null) return finalSize;
if (verticalContentAlignment != VerticalAlignment.Stretch)
{
size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
}
size = LayoutHelper.ApplyLayoutConstraints(Child, size);
var padding = Padding;
var borderThickness = BorderThickness;
var horizontalContentAlignment = HorizontalContentAlignment;
var verticalContentAlignment = VerticalContentAlignment;
var useLayoutRounding = UseLayoutRounding;
var availableSizeMinusMargins = new Size(
Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness.Left - borderThickness.Right),
Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness.Top - borderThickness.Bottom));
var size = availableSizeMinusMargins;
var scale = GetLayoutScale();
var originX = offset.X + padding.Left + borderThickness.Left;
var originY = offset.Y + padding.Top + borderThickness.Top;
if (horizontalContentAlignment != HorizontalAlignment.Stretch)
{
size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
}
if (useLayoutRounding)
{
size = new Size(
Math.Ceiling(size.Width * scale) / scale,
Math.Ceiling(size.Height * scale) / scale);
availableSizeMinusMargins = new Size(
Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
}
if (verticalContentAlignment != VerticalAlignment.Stretch)
{
size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
}
switch (horizontalContentAlignment)
{
case HorizontalAlignment.Center:
case HorizontalAlignment.Stretch:
originX += (availableSizeMinusMargins.Width - size.Width) / 2;
break;
case HorizontalAlignment.Right:
originX += availableSizeMinusMargins.Width - size.Width;
break;
}
if (useLayoutRounding)
{
size = new Size(
Math.Ceiling(size.Width * scale) / scale,
Math.Ceiling(size.Height * scale) / scale);
availableSizeMinusMargins = new Size(
Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
}
switch (verticalContentAlignment)
{
case VerticalAlignment.Center:
case VerticalAlignment.Stretch:
originY += (availableSizeMinusMargins.Height - size.Height) / 2;
break;
case VerticalAlignment.Bottom:
originY += availableSizeMinusMargins.Height - size.Height;
break;
}
switch (horizontalContentAlignment)
{
case HorizontalAlignment.Center:
originX += (availableSizeMinusMargins.Width - size.Width) / 2;
break;
case HorizontalAlignment.Right:
originX += availableSizeMinusMargins.Width - size.Width;
break;
}
if (useLayoutRounding)
{
originX = Math.Floor(originX * scale) / scale;
originY = Math.Floor(originY * scale) / scale;
}
switch (verticalContentAlignment)
{
case VerticalAlignment.Center:
originY += (availableSizeMinusMargins.Height - size.Height) / 2;
break;
case VerticalAlignment.Bottom:
originY += availableSizeMinusMargins.Height - size.Height;
break;
}
Child.Arrange(new Rect(originX, originY, size.Width, size.Height));
if (useLayoutRounding)
{
originX = Math.Floor(originX * scale) / scale;
originY = Math.Floor(originY * scale) / scale;
}
Child.Arrange(new Rect(originX, originY, Math.Max(0, size.Width), Math.Max(0, size.Height)));
return finalSize;
}

47
src/Avalonia.Controls/Primitives/Popup.cs

@ -215,7 +215,17 @@ namespace Avalonia.Controls.Primitives
{
var window = _topLevel as Window;
if (window != null)
window.Deactivated += WindowDeactivated;
{
window.Deactivated += WindowDeactivated;
}
else
{
var parentPopuproot = _topLevel as PopupRoot;
if(parentPopuproot != null && parentPopuproot.Parent!=null)
{
((Popup)(parentPopuproot.Parent)).Closed += ParentClosed;
}
}
_topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
_nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick);
}
@ -230,7 +240,7 @@ namespace Avalonia.Controls.Primitives
Opened?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Closes the popup.
/// </summary>
@ -244,6 +254,14 @@ namespace Avalonia.Controls.Primitives
var window = _topLevel as Window;
if (window != null)
window.Deactivated -= WindowDeactivated;
else
{
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot != null && parentPopuproot.Parent != null)
{
((Popup)parentPopuproot.Parent).Closed -= ParentClosed;
}
}
_nonClientListener?.Dispose();
_nonClientListener = null;
}
@ -381,16 +399,25 @@ namespace Avalonia.Controls.Primitives
{
if (!StaysOpen)
{
var root = ((IVisual)e.Source).GetVisualRoot();
if (root != this.PopupRoot)
{
if(!IsChildOrThis((IVisual)e.Source))
{
Close();
e.Handled = true;
}
}
}
private bool IsChildOrThis(IVisual child)
{
IVisual root = child.GetVisualRoot();
while (root is PopupRoot)
{
if (root == PopupRoot) return true;
root = ((PopupRoot)root).Parent.GetVisualRoot();
}
return false;
}
private void WindowDeactivated(object sender, EventArgs e)
{
if (!StaysOpen)
@ -398,5 +425,13 @@ namespace Avalonia.Controls.Primitives
Close();
}
}
private void ParentClosed(object sender, EventArgs e)
{
if (!StaysOpen)
{
Close();
}
}
}
}

4
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner<TemplatedControl>();
/// <summary>
@ -132,7 +132,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets or sets the thickness of the control's border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }

4
src/Avalonia.Controls/TextBlock.cs

@ -120,6 +120,7 @@ namespace Avalonia.Controls
.Subscribe(_ =>
{
InvalidateFormattedText();
InvalidateMeasure();
});
}
@ -370,8 +371,6 @@ namespace Avalonia.Controls
_constraint = _formattedText.Constraint;
_formattedText = null;
}
InvalidateMeasure();
}
/// <summary>
@ -402,6 +401,7 @@ namespace Avalonia.Controls
{
base.OnAttachedToLogicalTree(e);
InvalidateFormattedText();
InvalidateMeasure();
}
}
}

36
src/Avalonia.Controls/TextBox.cs

@ -85,6 +85,7 @@ namespace Avalonia.Controls
private int _selectionEnd;
private TextPresenter _presenter;
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing;
private bool _ignoreTextChanges;
private static readonly string[] invalidCharacters = new String[1]{"\u007f"};
@ -198,7 +199,11 @@ namespace Avalonia.Controls
if (!_ignoreTextChanges)
{
CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0);
SetAndRaise(TextProperty, ref _text, value);
if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing)
{
_undoRedoHelper.Clear();
}
}
}
}
@ -270,8 +275,11 @@ namespace Avalonia.Controls
protected override void OnTextInput(TextInputEventArgs e)
{
HandleTextInput(e.Text);
e.Handled = true;
if (!e.Handled)
{
HandleTextInput(e.Text);
e.Handled = true;
}
}
private void HandleTextInput(string input)
@ -367,14 +375,30 @@ namespace Avalonia.Controls
case Key.Z:
if (modifiers == InputModifiers.Control)
{
_undoRedoHelper.Undo();
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Undo();
}
finally
{
_isUndoingRedoing = false;
}
handled = true;
}
break;
case Key.Y:
if (modifiers == InputModifiers.Control)
{
_undoRedoHelper.Redo();
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Redo();
}
finally
{
_isUndoingRedoing = false;
}
handled = true;
}
break;
@ -791,7 +815,7 @@ namespace Avalonia.Controls
int pos = 0;
int i;
for (i = 0; i < lines.Count; ++i)
for (i = 0; i < lines.Count - 1; ++i)
{
var line = lines[i];
pos += line.Length;

279
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@ -0,0 +1,279 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Media;
namespace Avalonia.Controls.Utils
{
internal class BorderRenderHelper
{
private bool _useComplexRendering;
private StreamGeometry _backgroundGeometryCache;
private StreamGeometry _borderGeometryCache;
public void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
{
if (borderThickness.IsUniform && cornerRadius.IsUniform)
{
_backgroundGeometryCache = null;
_borderGeometryCache = null;
_useComplexRendering = false;
}
else
{
_useComplexRendering = true;
var boundRect = new Rect(finalSize);
var innerRect = boundRect.Deflate(borderThickness);
var innerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, false);
StreamGeometry backgroundGeometry = null;
if (innerRect.Width != 0 && innerRect.Height != 0)
{
backgroundGeometry = new StreamGeometry();
using (var ctx = backgroundGeometry.Open())
{
CreateGeometry(ctx, innerRect, innerCoordinates);
}
_backgroundGeometryCache = backgroundGeometry;
}
else
{
_backgroundGeometryCache = null;
}
if (boundRect.Width != 0 && innerRect.Height != 0)
{
var outerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, true);
var borderGeometry = new StreamGeometry();
using (var ctx = borderGeometry.Open())
{
CreateGeometry(ctx, boundRect, outerCoordinates);
if (backgroundGeometry != null)
{
CreateGeometry(ctx, innerRect, innerCoordinates);
}
}
_borderGeometryCache = borderGeometry;
}
else
{
_borderGeometryCache = null;
}
}
}
public void Render(DrawingContext context, Size size, Thickness borders, CornerRadius radii, IBrush background, IBrush borderBrush)
{
if (_useComplexRendering)
{
var backgroundGeometry = _backgroundGeometryCache;
if (backgroundGeometry != null)
{
context.DrawGeometry(background, null, backgroundGeometry);
}
var borderGeometry = _borderGeometryCache;
if (borderGeometry != null)
{
context.DrawGeometry(borderBrush, null, borderGeometry);
}
}
else
{
var borderThickness = borders.Left;
var cornerRadius = (float)radii.TopLeft;
var rect = new Rect(size);
if (background != null)
{
context.FillRectangle(background, rect.Deflate(borders), cornerRadius);
}
if (borderBrush != null && borderThickness > 0)
{
context.DrawRectangle(new Pen(borderBrush, borderThickness), rect.Deflate(borderThickness), cornerRadius);
}
}
}
private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderCoordinates borderCoordinates)
{
var topLeft = new Point(borderCoordinates.LeftTop, 0);
var topRight = new Point(boundRect.Width - borderCoordinates.RightTop, 0);
var rightTop = new Point(boundRect.Width, borderCoordinates.TopRight);
var rightBottom = new Point(boundRect.Width, boundRect.Height - borderCoordinates.BottomRight);
var bottomRight = new Point(boundRect.Width - borderCoordinates.RightBottom, boundRect.Height);
var bottomLeft = new Point(borderCoordinates.LeftBottom, boundRect.Height);
var leftBottom = new Point(0, boundRect.Height - borderCoordinates.BottomLeft);
var leftTop = new Point(0, borderCoordinates.TopLeft);
if (topLeft.X > topRight.X)
{
var scaledX = borderCoordinates.LeftTop / (borderCoordinates.LeftTop + borderCoordinates.RightTop) * boundRect.Width;
topLeft = new Point(scaledX, topLeft.Y);
topRight = new Point(scaledX, topRight.Y);
}
if (rightTop.Y > rightBottom.Y)
{
var scaledY = borderCoordinates.TopRight / (borderCoordinates.TopRight + borderCoordinates.BottomRight) * boundRect.Height;
rightTop = new Point(rightTop.X, scaledY);
rightBottom = new Point(rightBottom.X, scaledY);
}
if (bottomRight.X < bottomLeft.X)
{
var scaledX = borderCoordinates.LeftBottom / (borderCoordinates.LeftBottom + borderCoordinates.RightBottom) * boundRect.Width;
bottomRight = new Point(scaledX, bottomRight.Y);
bottomLeft = new Point(scaledX, bottomLeft.Y);
}
if (leftBottom.Y < leftTop.Y)
{
var scaledY = borderCoordinates.TopLeft / (borderCoordinates.TopLeft + borderCoordinates.BottomLeft) * boundRect.Height;
leftBottom = new Point(leftBottom.X, scaledY);
leftTop = new Point(leftTop.X, scaledY);
}
var offset = new Vector(boundRect.TopLeft.X, boundRect.TopLeft.Y);
topLeft += offset;
topRight += offset;
rightTop += offset;
rightBottom += offset;
bottomRight += offset;
bottomLeft += offset;
leftBottom += offset;
leftTop += offset;
context.BeginFigure(topLeft, true);
//Top
context.LineTo(topRight);
//TopRight corner
var radiusX = boundRect.TopRight.X - topRight.X;
var radiusY = rightTop.Y - boundRect.TopRight.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(rightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
}
//Right
context.LineTo(rightBottom);
//BottomRight corner
radiusX = boundRect.BottomRight.X - bottomRight.X;
radiusY = boundRect.BottomRight.Y - rightBottom.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(bottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
//Bottom
context.LineTo(bottomLeft);
//BottomLeft corner
radiusX = bottomLeft.X - boundRect.BottomLeft.X;
radiusY = boundRect.BottomLeft.Y - leftBottom.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(leftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
//Left
context.LineTo(leftTop);
//TopLeft corner
radiusX = topLeft.X - boundRect.TopLeft.X;
radiusY = leftTop.Y - boundRect.TopLeft.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(topLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
context.EndFigure(true);
}
private struct BorderCoordinates
{
internal BorderCoordinates(CornerRadius cornerRadius, Thickness borderThickness, bool isOuter)
{
var left = 0.5 * borderThickness.Left;
var top = 0.5 * borderThickness.Top;
var right = 0.5 * borderThickness.Right;
var bottom = 0.5 * borderThickness.Bottom;
if (isOuter)
{
if (cornerRadius.TopLeft == 0)
{
LeftTop = TopLeft = 0.0;
}
else
{
LeftTop = cornerRadius.TopLeft + left;
TopLeft = cornerRadius.TopLeft + top;
}
if (cornerRadius.TopRight == 0)
{
TopRight = RightTop = 0;
}
else
{
TopRight = cornerRadius.TopRight + top;
RightTop = cornerRadius.TopRight + right;
}
if (cornerRadius.BottomRight == 0)
{
RightBottom = BottomRight = 0;
}
else
{
RightBottom = cornerRadius.BottomRight + right;
BottomRight = cornerRadius.BottomRight + bottom;
}
if (cornerRadius.BottomLeft == 0)
{
BottomLeft = LeftBottom = 0;
}
else
{
BottomLeft = cornerRadius.BottomLeft + bottom;
LeftBottom = cornerRadius.BottomLeft + left;
}
}
else
{
LeftTop = Math.Max(0, cornerRadius.TopLeft - left);
TopLeft = Math.Max(0, cornerRadius.TopLeft - top);
TopRight = Math.Max(0, cornerRadius.TopRight - top);
RightTop = Math.Max(0, cornerRadius.TopRight - right);
RightBottom = Math.Max(0, cornerRadius.BottomRight - right);
BottomRight = Math.Max(0, cornerRadius.BottomRight - bottom);
BottomLeft = Math.Max(0, cornerRadius.BottomLeft - bottom);
LeftBottom = Math.Max(0, cornerRadius.BottomLeft - left);
}
}
internal readonly double LeftTop;
internal readonly double TopLeft;
internal readonly double TopRight;
internal readonly double RightTop;
internal readonly double RightBottom;
internal readonly double BottomRight;
internal readonly double BottomLeft;
internal readonly double LeftBottom;
}
}
}

64
src/Avalonia.Controls/Utils/ISelectionAdapter.cs

@ -0,0 +1,64 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Collections;
using Avalonia.Interactivity;
using Avalonia.Input;
namespace Avalonia.Controls.Utils
{
/// <summary>
/// Defines an item collection, selection members, and key handling for the
/// selection adapter contained in the drop-down portion of an
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
/// </summary>
public interface ISelectionAdapter
{
/// <summary>
/// Gets or sets the selected item.
/// </summary>
/// <value>The currently selected item.</value>
object SelectedItem { get; set; }
/// <summary>
/// Occurs when the
/// <see cref="P:Avalonia.Controls.Utils.ISelectionAdapter.SelectedItem" />
/// property value changes.
/// </summary>
event EventHandler<SelectionChangedEventArgs> SelectionChanged;
/// <summary>
/// Gets or sets a collection that is used to generate content for the
/// selection adapter.
/// </summary>
/// <value>The collection that is used to generate content for the
/// selection adapter.</value>
IEnumerable Items { get; set; }
/// <summary>
/// Occurs when a selected item is not cancelled and is committed as the
/// selected item.
/// </summary>
event EventHandler<RoutedEventArgs> Commit;
/// <summary>
/// Occurs when a selection has been canceled.
/// </summary>
event EventHandler<RoutedEventArgs> Cancel;
/// <summary>
/// Provides handling for the
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event that occurs
/// when a key is pressed while the drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus.
/// </summary>
/// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
/// that contains data about the
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event.</param>
void HandleKeyDown(KeyEventArgs e);
}
}

342
src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs

@ -0,0 +1,342 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Input;
using Avalonia.LogicalTree;
using System.Collections;
using System.Diagnostics;
namespace Avalonia.Controls.Utils
{
/// <summary>
/// Represents the selection adapter contained in the drop-down portion of
/// an <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
/// </summary>
public class SelectingItemsControlSelectionAdapter : ISelectionAdapter
{
/// <summary>
/// The SelectingItemsControl instance.
/// </summary>
private SelectingItemsControl _selector;
/// <summary>
/// Gets or sets a value indicating whether the selection change event
/// should not be fired.
/// </summary>
private bool IgnoringSelectionChanged { get; set; }
/// <summary>
/// Gets or sets the underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
/// <value>The underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.</value>
public SelectingItemsControl SelectorControl
{
get { return _selector; }
set
{
if (_selector != null)
{
_selector.SelectionChanged -= OnSelectionChanged;
_selector.PointerReleased -= OnSelectorPointerReleased;
}
_selector = value;
if (_selector != null)
{
_selector.SelectionChanged += OnSelectionChanged;
_selector.PointerReleased += OnSelectorPointerReleased;
}
}
}
/// <summary>
/// Occurs when the
/// <see cref="P:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.SelectedItem" />
/// property value changes.
/// </summary>
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
/// <summary>
/// Occurs when an item is selected and is committed to the underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
public event EventHandler<RoutedEventArgs> Commit;
/// <summary>
/// Occurs when a selection is canceled before it is committed.
/// </summary>
public event EventHandler<RoutedEventArgs> Cancel;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />
/// class.
/// </summary>
public SelectingItemsControlSelectionAdapter()
{
}
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapterr" />
/// class with the specified
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
/// <param name="selector">The
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" /> control
/// to wrap as a
/// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />.</param>
public SelectingItemsControlSelectionAdapter(SelectingItemsControl selector)
{
SelectorControl = selector;
}
/// <summary>
/// Gets or sets the selected item of the selection adapter.
/// </summary>
/// <value>The selected item of the underlying selection adapter.</value>
public object SelectedItem
{
get
{
return SelectorControl?.SelectedItem;
}
set
{
IgnoringSelectionChanged = true;
if (SelectorControl != null)
{
SelectorControl.SelectedItem = value;
}
// Attempt to reset the scroll viewer's position
if (value == null)
{
ResetScrollViewer();
}
IgnoringSelectionChanged = false;
}
}
/// <summary>
/// Gets or sets a collection that is used to generate the content of
/// the selection adapter.
/// </summary>
/// <value>The collection used to generate content for the selection
/// adapter.</value>
public IEnumerable Items
{
get
{
return SelectorControl?.Items;
}
set
{
if (SelectorControl != null)
{
SelectorControl.Items = value;
}
}
}
/// <summary>
/// If the control contains a ScrollViewer, this will reset the viewer
/// to be scrolled to the top.
/// </summary>
private void ResetScrollViewer()
{
if (SelectorControl != null)
{
ScrollViewer sv = SelectorControl.GetLogicalDescendants().OfType<ScrollViewer>().FirstOrDefault();
if (sv != null)
{
sv.Offset = new Vector(0, 0);
}
}
}
/// <summary>
/// Handles the mouse left button up event on the selector control.
/// </summary>
/// <param name="sender">The source object.</param>
/// <param name="e">The event data.</param>
private void OnSelectorPointerReleased(object sender, PointerReleasedEventArgs e)
{
if (e.MouseButton == MouseButton.Left)
{
OnCommit();
}
}
/// <summary>
/// Handles the SelectionChanged event on the SelectingItemsControl control.
/// </summary>
/// <param name="sender">The source object.</param>
/// <param name="e">The selection changed event data.</param>
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (IgnoringSelectionChanged)
{
return;
}
SelectionChanged?.Invoke(sender, e);
}
/// <summary>
/// Increments the
/// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
/// property of the underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
protected void SelectedIndexIncrement()
{
if (SelectorControl != null)
{
SelectorControl.SelectedIndex = SelectorControl.SelectedIndex + 1 >= SelectorControl.ItemCount ? -1 : SelectorControl.SelectedIndex + 1;
}
}
/// <summary>
/// Decrements the
/// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
/// property of the underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
protected void SelectedIndexDecrement()
{
if (SelectorControl != null)
{
int index = SelectorControl.SelectedIndex;
if (index >= 0)
{
SelectorControl.SelectedIndex--;
}
else if (index == -1)
{
SelectorControl.SelectedIndex = SelectorControl.ItemCount - 1;
}
}
}
/// <summary>
/// Provides handling for the
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event that occurs
/// when a key is pressed while the drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus.
/// </summary>
/// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
/// that contains data about the
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event.</param>
public void HandleKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
OnCommit();
e.Handled = true;
break;
case Key.Up:
SelectedIndexDecrement();
e.Handled = true;
break;
case Key.Down:
if ((e.Modifiers & InputModifiers.Alt) == InputModifiers.None)
{
SelectedIndexIncrement();
e.Handled = true;
}
break;
case Key.Escape:
OnCancel();
e.Handled = true;
break;
default:
break;
}
}
/// <summary>
/// Raises the
/// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Commit" />
/// event.
/// </summary>
protected virtual void OnCommit()
{
OnCommit(this, new RoutedEventArgs());
}
/// <summary>
/// Fires the Commit event.
/// </summary>
/// <param name="sender">The source object.</param>
/// <param name="e">The event data.</param>
private void OnCommit(object sender, RoutedEventArgs e)
{
Commit?.Invoke(sender, e);
AfterAdapterAction();
}
/// <summary>
/// Raises the
/// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Cancel" />
/// event.
/// </summary>
protected virtual void OnCancel()
{
OnCancel(this, new RoutedEventArgs());
}
/// <summary>
/// Fires the Cancel event.
/// </summary>
/// <param name="sender">The source object.</param>
/// <param name="e">The event data.</param>
private void OnCancel(object sender, RoutedEventArgs e)
{
Cancel?.Invoke(sender, e);
AfterAdapterAction();
}
/// <summary>
/// Change the selection after the actions are complete.
/// </summary>
private void AfterAdapterAction()
{
IgnoringSelectionChanged = true;
if (SelectorControl != null)
{
SelectorControl.SelectedItem = null;
SelectorControl.SelectedIndex = -1;
}
IgnoringSelectionChanged = false;
}
}
}

8
src/Avalonia.Controls/Utils/UndoRedoHelper.cs

@ -59,7 +59,7 @@ namespace Avalonia.Controls.Utils
public void UpdateLastState()
{
_states.Last.Value = _host.UndoRedoState;
UpdateLastState(_host.UndoRedoState);
}
public void DiscardRedo()
@ -91,6 +91,12 @@ namespace Avalonia.Controls.Utils
}
}
public void Clear()
{
_states.Clear();
_currentNode = null;
}
bool WeakTimer.IWeakTimerSubscriber.Tick()
{
Snapshot();

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

@ -86,7 +86,7 @@ namespace Avalonia.Diagnostics.ViewModels
private void UpdateFocusedControl()
{
_focusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
}
}
}

5
src/Avalonia.Input/Cursors.cs

@ -38,7 +38,10 @@ namespace Avalonia.Input
TopLeftCorner,
TopRightCorner,
BottomLeftCorner,
BottomRightCorner
BottomRightCorner,
DragMove,
DragCopy,
DragLink,
// Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/
// We might enable them later, preferably, by loading pixmax direclty from theme with fallback image

15
src/Avalonia.Input/DataFormats.cs

@ -0,0 +1,15 @@
namespace Avalonia.Input
{
public static class DataFormats
{
/// <summary>
/// Dataformat for plaintext
/// </summary>
public static string Text = nameof(Text);
/// <summary>
/// Dataformat for one or more filenames
/// </summary>
public static string FileNames = nameof(FileNames);
}
}

43
src/Avalonia.Input/DataObject.cs

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Input
{
public class DataObject : IDataObject
{
private readonly Dictionary<string, object> _items = new Dictionary<string, object>();
public bool Contains(string dataFormat)
{
return _items.ContainsKey(dataFormat);
}
public object Get(string dataFormat)
{
if (_items.ContainsKey(dataFormat))
return _items[dataFormat];
return null;
}
public IEnumerable<string> GetDataFormats()
{
return _items.Keys;
}
public IEnumerable<string> GetFileNames()
{
return Get(DataFormats.FileNames) as IEnumerable<string>;
}
public string GetText()
{
return Get(DataFormats.Text) as string;
}
public void Set(string dataFormat, object value)
{
_items[dataFormat] = value;
}
}
}

54
src/Avalonia.Input/DragDrop.cs

@ -0,0 +1,54 @@
using System.Threading.Tasks;
using Avalonia.Interactivity;
using Avalonia.Input.Platform;
namespace Avalonia.Input
{
public static class DragDrop
{
/// <summary>
/// Event which is raised, when a drag-and-drop operation enters the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DragEnterEvent = RoutedEvent.Register<DragEventArgs>("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation leaves the element.
/// </summary>
public static RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation is updated while over the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DragOverEvent = RoutedEvent.Register<DragEventArgs>("DragOver", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation should complete over the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DropEvent = RoutedEvent.Register<DragEventArgs>("Drop", RoutingStrategies.Bubble, typeof(DragDrop));
public static AvaloniaProperty<bool> AllowDropProperty = AvaloniaProperty.RegisterAttached<Interactive, bool>("AllowDrop", typeof(DragDrop), inherits: true);
/// <summary>
/// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation.
/// </summary>
public static bool GetAllowDrop(Interactive interactive)
{
return interactive.GetValue(AllowDropProperty);
}
/// <summary>
/// Sets a value indicating whether the given interactive can be used as the target of a drag-and-drop operation.
/// </summary>
public static void SetAllowDrop(Interactive interactive, bool value)
{
interactive.SetValue(AllowDropProperty, value);
}
/// <summary>
/// Starts a dragging operation with the given <see cref="IDataObject"/> and returns the applied drop effect from the target.
/// <seealso cref="DataObject"/>
/// </summary>
public static Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
{
var src = AvaloniaLocator.Current.GetService<IPlatformDragSource>();
return src?.DoDragDrop(data, allowedEffects) ?? Task.FromResult(DragDropEffects.None);
}
}
}

111
src/Avalonia.Input/DragDropDevice.cs

@ -0,0 +1,111 @@
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using System.Linq;
using Avalonia.Input.Raw;
namespace Avalonia.Input
{
public class DragDropDevice : IDragDropDevice
{
public static readonly DragDropDevice Instance = new DragDropDevice();
private Interactive _lastTarget = null;
private Interactive GetTarget(IInputElement root, Point local)
{
var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault();
if (target != null && DragDrop.GetAllowDrop(target))
return target;
return null;
}
private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data)
{
if (target == null)
return DragDropEffects.None;
var args = new DragEventArgs(routedEvent, data)
{
RoutedEvent = routedEvent,
DragEffects = operation
};
target.RaiseEvent(args);
return args.DragEffects;
}
private DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
{
_lastTarget = GetTarget(inputRoot, point);
return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data);
}
private DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
{
var target = GetTarget(inputRoot, point);
if (target == _lastTarget)
return RaiseDragEvent(target, DragDrop.DragOverEvent, effects, data);
try
{
if (_lastTarget != null)
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
return RaiseDragEvent(target, DragDrop.DragEnterEvent, effects, data);
}
finally
{
_lastTarget = target;
}
}
private void DragLeave(IInputElement inputRoot)
{
if (_lastTarget == null)
return;
try
{
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
}
finally
{
_lastTarget = null;
}
}
private DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
{
try
{
return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, effects, data);
}
finally
{
_lastTarget = null;
}
}
public void ProcessRawEvent(RawInputEventArgs e)
{
if (!e.Handled && e is RawDragEvent margs)
ProcessRawEvent(margs);
}
private void ProcessRawEvent(RawDragEvent e)
{
switch (e.Type)
{
case RawDragEventType.DragEnter:
e.Effects = DragEnter(e.InputRoot, e.Location, e.Data, e.Effects);
break;
case RawDragEventType.DragOver:
e.Effects = DragOver(e.InputRoot, e.Location, e.Data, e.Effects);
break;
case RawDragEventType.DragLeave:
DragLeave(e.InputRoot);
break;
case RawDragEventType.Drop:
e.Effects = Drop(e.InputRoot, e.Location, e.Data, e.Effects);
break;
}
}
}
}

13
src/Avalonia.Input/DragDropEffects.cs

@ -0,0 +1,13 @@
using System;
namespace Avalonia.Input
{
[Flags]
public enum DragDropEffects
{
None = 0,
Copy = 1,
Move = 2,
Link = 4,
}
}

18
src/Avalonia.Input/DragEventArgs.cs

@ -0,0 +1,18 @@
using Avalonia.Interactivity;
namespace Avalonia.Input
{
public class DragEventArgs : RoutedEventArgs
{
public DragDropEffects DragEffects { get; set; }
public IDataObject Data { get; private set; }
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data)
: base(routedEvent)
{
this.Data = data;
}
}
}

39
src/Avalonia.Input/IDataObject.cs

@ -0,0 +1,39 @@
using System.Collections.Generic;
namespace Avalonia.Input
{
/// <summary>
/// Interface to access information about the data of a drag-and-drop operation.
/// </summary>
public interface IDataObject
{
/// <summary>
/// Lists all formats which are present in the DataObject.
/// <seealso cref="DataFormats"/>
/// </summary>
IEnumerable<string> GetDataFormats();
/// <summary>
/// Checks wether a given DataFormat is present in this object
/// <seealso cref="DataFormats"/>
/// </summary>
bool Contains(string dataFormat);
/// <summary>
/// Returns the dragged text if the DataObject contains any text.
/// <seealso cref="DataFormats.Text"/>
/// </summary>
string GetText();
/// <summary>
/// Returns a list of filenames if the DataObject contains filenames.
/// <seealso cref="DataFormats.FileNames"/>
/// </summary>
IEnumerable<string> GetFileNames();
/// <summary>
/// Tries to get the data of the given DataFormat.
/// </summary>
object Get(string dataFormat);
}
}

2
src/Avalonia.Input/KeyboardDevice.cs

@ -46,13 +46,13 @@ namespace Avalonia.Input
if (element != FocusedElement)
{
var interactive = FocusedElement as IInteractive;
FocusedElement = element;
interactive?.RaiseEvent(new RoutedEventArgs
{
RoutedEvent = InputElement.LostFocusEvent,
});
FocusedElement = element;
interactive = element as IInteractive;
interactive?.RaiseEvent(new GotFocusEventArgs

14
src/Avalonia.Input/Platform/IPlatformDragSource.cs

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace Avalonia.Input.Platform
{
public interface IPlatformDragSource
{
Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects);
}
}

8
src/Avalonia.Input/Raw/IDragDropDevice.cs

@ -0,0 +1,8 @@
using Avalonia.Input;
namespace Avalonia.Input.Raw
{
public interface IDragDropDevice : IInputDevice
{
}
}

26
src/Avalonia.Input/Raw/RawDragEvent.cs

@ -0,0 +1,26 @@
using System;
using Avalonia.Input;
using Avalonia.Input.Raw;
namespace Avalonia.Input.Raw
{
public class RawDragEvent : RawInputEventArgs
{
public IInputElement InputRoot { get; }
public Point Location { get; }
public IDataObject Data { get; }
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }
public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type,
IInputElement inputRoot, Point location, IDataObject data, DragDropEffects effects)
:base(inputDevice, 0)
{
Type = type;
InputRoot = inputRoot;
Location = location;
Data = data;
Effects = effects;
}
}
}

10
src/Avalonia.Input/Raw/RawDragEventType.cs

@ -0,0 +1,10 @@
namespace Avalonia.Input.Raw
{
public enum RawDragEventType
{
DragEnter,
DragOver,
DragLeave,
Drop
}
}

2
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -20,7 +20,7 @@
<SolidColorBrush x:Key="ErrorBrush">Red</SolidColorBrush>
<SolidColorBrush x:Key="ErrorBrushLight">#10ff0000</SolidColorBrush>
<sys:Double x:Key="ThemeBorderThickness">2</sys:Double>
<Thickness x:Key="ThemeBorderThickness">2</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>
<sys:Double x:Key="FontSizeSmall">10</sys:Double>

43
src/Avalonia.Themes.Default/AutoCompleteBox.xaml

@ -0,0 +1,43 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="AutoCompleteBox">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="Template">
<ControlTemplate>
<Panel>
<TextBox Name="PART_TextBox"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
Watermark="{TemplateBinding Watermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" />
<Popup Name="PART_Popup"
MinWidth="{TemplateBinding Bounds.Width}"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
PlacementTarget="{TemplateBinding}"
StaysOpen="False">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">
<ListBox Name="PART_SelectingItemsControl"
BorderThickness="0"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding ValueMemberSelector}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto" />
</Border>
</Popup>
</Panel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="AutoCompleteBox ListBoxItem:pointerover">
<Setter Property="Background" Value="#ffd0d0d0"/>
</Style>
</Styles>

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

@ -23,7 +23,7 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.RadioButton.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.RepeatButton.xaml?assembly=Avalonia.Themes.Default" />
<StyleInclude Source="resm:Avalonia.Themes.Default.Separator.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Slider.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Slider.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ScrollBar.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ScrollViewer.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.TabStrip.xaml?assembly=Avalonia.Themes.Default"/>
@ -43,4 +43,6 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.Calendar.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.DatePicker.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.ButtonSpinner.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.NumericUpDown.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.AutoCompleteBox.xaml?assembly=Avalonia.Themes.Default"/>
</Styles>

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

@ -133,4 +133,8 @@
<Style Selector="MenuItem:empty /template/ Path#rightArrow">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="MenuItem:disabled">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
</Style>
</Styles>

41
src/Avalonia.Themes.Default/NumericUpDown.xaml

@ -0,0 +1,41 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="NumericUpDown">
<Setter Property="TemplatedControl.BorderBrush" Value="{DynamicResource ThemeBorderLightBrush}"/>
<Setter Property="TemplatedControl.BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="TemplatedControl.Background" Value="{DynamicResource ThemeBackgroundBrush}" />
<Setter Property="TemplatedControl.Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="TemplatedControl.Template">
<ControlTemplate>
<ButtonSpinner Name="PART_Spinner"
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
AllowSpin="{TemplateBinding AllowSpin}"
ShowButtonSpinner="{TemplateBinding ShowButtonSpinner}"
ButtonSpinnerLocation="{TemplateBinding ButtonSpinnerLocation}">
<TextBox Name="PART_TextBox"
BorderThickness="0"
Background="Transparent"
ContextMenu="{TemplateBinding ContextMenu}"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontStyle="{TemplateBinding FontStyle}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
Watermark="{TemplateBinding Watermark}"
IsReadOnly="{TemplateBinding IsReadOnly}"
Text="{TemplateBinding Text}"
Padding="{TemplateBinding Padding}"
TextAlignment="Left"
Margin="1"
MinWidth="20"
AcceptsReturn="False"
TextWrapping="NoWrap">
</TextBox>
</ButtonSpinner>
</ControlTemplate>
</Setter>
</Style>
</Styles>

97
src/Avalonia.Visuals/CornerRadius.cs

@ -0,0 +1,97 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Globalization;
using System.Linq;
namespace Avalonia
{
public struct CornerRadius
{
public CornerRadius(double uniformRadius)
{
TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius;
}
public CornerRadius(double top, double bottom)
{
TopLeft = TopRight = top;
BottomLeft = BottomRight = bottom;
}
public CornerRadius(double topLeft, double topRight, double bottomRight, double bottomLeft)
{
TopLeft = topLeft;
TopRight = topRight;
BottomRight = bottomRight;
BottomLeft = bottomLeft;
}
public double TopLeft { get; }
public double TopRight { get; }
public double BottomRight { get; }
public double BottomLeft { get; }
public bool IsEmpty => TopLeft.Equals(0) && IsUniform;
public bool IsUniform => TopLeft.Equals(TopRight) && BottomLeft.Equals(BottomRight) && TopRight.Equals(BottomRight);
public override bool Equals(object obj)
{
if (obj is CornerRadius)
{
return this == (CornerRadius)obj;
}
return false;
}
public override int GetHashCode()
{
return TopLeft.GetHashCode() ^ TopRight.GetHashCode() ^ BottomLeft.GetHashCode() ^ BottomRight.GetHashCode();
}
public override string ToString()
{
return $"{TopLeft},{TopRight},{BottomRight},{BottomLeft}";
}
public static CornerRadius Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
switch (parts.Count)
{
case 1:
var uniform = double.Parse(parts[0], culture);
return new CornerRadius(uniform);
case 2:
var top = double.Parse(parts[0], culture);
var bottom = double.Parse(parts[1], culture);
return new CornerRadius(top, bottom);
case 4:
var topLeft = double.Parse(parts[0], culture);
var topRight = double.Parse(parts[1], culture);
var bottomRight = double.Parse(parts[2], culture);
var bottomLeft = double.Parse(parts[3], culture);
return new CornerRadius(topLeft, topRight, bottomRight, bottomLeft);
default:
{
throw new FormatException("Invalid CornerRadius.");
}
}
}
public static bool operator ==(CornerRadius cr1, CornerRadius cr2)
{
return cr1.TopLeft.Equals(cr2.TopLeft)
&& cr1.TopRight.Equals(cr2.TopRight)
&& cr1.BottomRight.Equals(cr2.BottomRight)
&& cr1.BottomLeft.Equals(cr2.BottomLeft);
}
public static bool operator !=(CornerRadius cr1, CornerRadius cr2)
{
return !(cr1 == cr2);
}
}
}

24
src/Avalonia.Visuals/Matrix.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System;
using System.Globalization;
using System.Linq;
@ -317,23 +318,16 @@ namespace Avalonia
/// <returns>The <see cref="Matrix"/>.</returns>
public static Matrix Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToArray();
if (parts.Length == 6)
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Matrix"))
{
return new Matrix(
double.Parse(parts[0], culture),
double.Parse(parts[1], culture),
double.Parse(parts[2], culture),
double.Parse(parts[3], culture),
double.Parse(parts[4], culture),
double.Parse(parts[5], culture));
}
else
{
throw new FormatException("Invalid Matrix.");
tokenizer.ReadDouble(),
tokenizer.ReadDouble(),
tokenizer.ReadDouble(),
tokenizer.ReadDouble(),
tokenizer.ReadDouble(),
tokenizer.ReadDouble()
);
}
}
}

23
src/Avalonia.Visuals/Media/Brush.cs

@ -34,26 +34,21 @@ namespace Avalonia.Media
/// <returns>The <see cref="Color"/>.</returns>
public static IBrush Parse(string s)
{
Contract.Requires<ArgumentNullException>(s != null);
Contract.Requires<FormatException>(s.Length > 0);
if (s[0] == '#')
{
return new SolidColorBrush(Color.Parse(s));
}
else
{
var upper = s.ToUpperInvariant();
var member = typeof(Brushes).GetTypeInfo().DeclaredProperties
.FirstOrDefault(x => x.Name.ToUpperInvariant() == upper);
if (member != null)
{
var brush = (ISolidColorBrush)member.GetValue(null);
return new SolidColorBrush(brush.Color, brush.Opacity);
}
else
{
throw new FormatException($"Invalid brush string: '{s}'.");
}
var brush = KnownColors.GetKnownBrush(s);
if (brush != null)
{
return brush;
}
throw new FormatException($"Invalid brush string: '{s}'.");
}
}
}

434
src/Avalonia.Visuals/Media/Brushes.cs

File diff suppressed because it is too large

27
src/Avalonia.Visuals/Media/Color.cs

@ -88,6 +88,9 @@ namespace Avalonia.Media
/// <returns>The <see cref="Color"/>.</returns>
public static Color Parse(string s)
{
if (s == null) throw new ArgumentNullException(nameof(s));
if (s.Length == 0) throw new FormatException();
if (s[0] == '#')
{
var or = 0u;
@ -103,21 +106,15 @@ namespace Avalonia.Media
return FromUInt32(uint.Parse(s.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture) | or);
}
else
{
var upper = s.ToUpperInvariant();
var member = typeof(Colors).GetTypeInfo().DeclaredProperties
.FirstOrDefault(x => x.Name.ToUpperInvariant() == upper);
if (member != null)
{
return (Color)member.GetValue(null);
}
else
{
throw new FormatException($"Invalid color string: '{s}'.");
}
var knownColor = KnownColors.GetKnownColor(s);
if (knownColor != KnownColor.None)
{
return knownColor.ToColor();
}
throw new FormatException($"Invalid color string: '{s}'.");
}
/// <summary>
@ -128,8 +125,8 @@ namespace Avalonia.Media
/// </returns>
public override string ToString()
{
uint rgb = ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B;
return $"#{rgb:x8}";
uint rgb = ToUint32();
return KnownColors.GetKnownColorName(rgb) ?? $"#{rgb:x8}";
}
/// <summary>

284
src/Avalonia.Visuals/Media/Colors.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
namespace Avalonia.Media
{
/// <summary>
@ -11,706 +13,706 @@ namespace Avalonia.Media
/// <summary>
/// Gets a color with an ARGB value of #fff0f8ff.
/// </summary>
public static Color AliceBlue => Color.FromUInt32(0xfff0f8ff);
public static Color AliceBlue => KnownColor.AliceBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffaebd7.
/// </summary>
public static Color AntiqueWhite => Color.FromUInt32(0xfffaebd7);
public static Color AntiqueWhite => KnownColor.AntiqueWhite.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff00ffff.
/// </summary>
public static Color Aqua => Color.FromUInt32(0xff00ffff);
public static Color Aqua => KnownColor.Aqua.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff7fffd4.
/// </summary>
public static Color Aquamarine => Color.FromUInt32(0xff7fffd4);
public static Color Aquamarine => KnownColor.Aquamarine.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff0ffff.
/// </summary>
public static Color Azure => Color.FromUInt32(0xfff0ffff);
public static Color Azure => KnownColor.Azure.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff5f5dc.
/// </summary>
public static Color Beige => Color.FromUInt32(0xfff5f5dc);
public static Color Beige => KnownColor.Beige.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffe4c4.
/// </summary>
public static Color Bisque => Color.FromUInt32(0xffffe4c4);
public static Color Bisque => KnownColor.Bisque.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff000000.
/// </summary>
public static Color Black => Color.FromUInt32(0xff000000);
public static Color Black => KnownColor.Black.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffebcd.
/// </summary>
public static Color BlanchedAlmond => Color.FromUInt32(0xffffebcd);
public static Color BlanchedAlmond => KnownColor.BlanchedAlmond.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff0000ff.
/// </summary>
public static Color Blue => Color.FromUInt32(0xff0000ff);
public static Color Blue => KnownColor.Blue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff8a2be2.
/// </summary>
public static Color BlueViolet => Color.FromUInt32(0xff8a2be2);
public static Color BlueViolet => KnownColor.BlueViolet.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffa52a2a.
/// </summary>
public static Color Brown => Color.FromUInt32(0xffa52a2a);
public static Color Brown => KnownColor.Brown.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffdeb887.
/// </summary>
public static Color BurlyWood => Color.FromUInt32(0xffdeb887);
public static Color BurlyWood => KnownColor.BurlyWood.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff5f9ea0.
/// </summary>
public static Color CadetBlue => Color.FromUInt32(0xff5f9ea0);
public static Color CadetBlue => KnownColor.CadetBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff7fff00.
/// </summary>
public static Color Chartreuse => Color.FromUInt32(0xff7fff00);
public static Color Chartreuse => KnownColor.Chartreuse.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffd2691e.
/// </summary>
public static Color Chocolate => Color.FromUInt32(0xffd2691e);
public static Color Chocolate => KnownColor.Chocolate.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffff7f50.
/// </summary>
public static Color Coral => Color.FromUInt32(0xffff7f50);
public static Color Coral => KnownColor.Coral.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff6495ed.
/// </summary>
public static Color CornflowerBlue => Color.FromUInt32(0xff6495ed);
public static Color CornflowerBlue => KnownColor.CornflowerBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffff8dc.
/// </summary>
public static Color Cornsilk => Color.FromUInt32(0xfffff8dc);
public static Color Cornsilk => KnownColor.Cornsilk.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffdc143c.
/// </summary>
public static Color Crimson => Color.FromUInt32(0xffdc143c);
public static Color Crimson => KnownColor.Crimson.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff00ffff.
/// </summary>
public static Color Cyan => Color.FromUInt32(0xff00ffff);
public static Color Cyan => KnownColor.Cyan.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff00008b.
/// </summary>
public static Color DarkBlue => Color.FromUInt32(0xff00008b);
public static Color DarkBlue => KnownColor.DarkBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff008b8b.
/// </summary>
public static Color DarkCyan => Color.FromUInt32(0xff008b8b);
public static Color DarkCyan => KnownColor.DarkCyan.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffb8860b.
/// </summary>
public static Color DarkGoldenrod => Color.FromUInt32(0xffb8860b);
public static Color DarkGoldenrod => KnownColor.DarkGoldenrod.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffa9a9a9.
/// </summary>
public static Color DarkGray => Color.FromUInt32(0xffa9a9a9);
public static Color DarkGray => KnownColor.DarkGray.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff006400.
/// </summary>
public static Color DarkGreen => Color.FromUInt32(0xff006400);
public static Color DarkGreen => KnownColor.DarkGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffbdb76b.
/// </summary>
public static Color DarkKhaki => Color.FromUInt32(0xffbdb76b);
public static Color DarkKhaki => KnownColor.DarkKhaki.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff8b008b.
/// </summary>
public static Color DarkMagenta => Color.FromUInt32(0xff8b008b);
public static Color DarkMagenta => KnownColor.DarkMagenta.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff556b2f.
/// </summary>
public static Color DarkOliveGreen => Color.FromUInt32(0xff556b2f);
public static Color DarkOliveGreen => KnownColor.DarkOliveGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffff8c00.
/// </summary>
public static Color DarkOrange => Color.FromUInt32(0xffff8c00);
public static Color DarkOrange => KnownColor.DarkOrange.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff9932cc.
/// </summary>
public static Color DarkOrchid => Color.FromUInt32(0xff9932cc);
public static Color DarkOrchid => KnownColor.DarkOrchid.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff8b0000.
/// </summary>
public static Color DarkRed => Color.FromUInt32(0xff8b0000);
public static Color DarkRed => KnownColor.DarkRed.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffe9967a.
/// </summary>
public static Color DarkSalmon => Color.FromUInt32(0xffe9967a);
public static Color DarkSalmon => KnownColor.DarkSalmon.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff8fbc8f.
/// </summary>
public static Color DarkSeaGreen => Color.FromUInt32(0xff8fbc8f);
public static Color DarkSeaGreen => KnownColor.DarkSeaGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff483d8b.
/// </summary>
public static Color DarkSlateBlue => Color.FromUInt32(0xff483d8b);
public static Color DarkSlateBlue => KnownColor.DarkSlateBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff2f4f4f.
/// </summary>
public static Color DarkSlateGray => Color.FromUInt32(0xff2f4f4f);
public static Color DarkSlateGray => KnownColor.DarkSlateGray.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff00ced1.
/// </summary>
public static Color DarkTurquoise => Color.FromUInt32(0xff00ced1);
public static Color DarkTurquoise => KnownColor.DarkTurquoise.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff9400d3.
/// </summary>
public static Color DarkViolet => Color.FromUInt32(0xff9400d3);
public static Color DarkViolet => KnownColor.DarkViolet.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffff1493.
/// </summary>
public static Color DeepPink => Color.FromUInt32(0xffff1493);
public static Color DeepPink => KnownColor.DeepPink.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff00bfff.
/// </summary>
public static Color DeepSkyBlue => Color.FromUInt32(0xff00bfff);
public static Color DeepSkyBlue => KnownColor.DeepSkyBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff696969.
/// </summary>
public static Color DimGray => Color.FromUInt32(0xff696969);
public static Color DimGray => KnownColor.DimGray.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff1e90ff.
/// </summary>
public static Color DodgerBlue => Color.FromUInt32(0xff1e90ff);
public static Color DodgerBlue => KnownColor.DodgerBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffb22222.
/// </summary>
public static Color Firebrick => Color.FromUInt32(0xffb22222);
public static Color Firebrick => KnownColor.Firebrick.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffffaf0.
/// </summary>
public static Color FloralWhite => Color.FromUInt32(0xfffffaf0);
public static Color FloralWhite => KnownColor.FloralWhite.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff228b22.
/// </summary>
public static Color ForestGreen => Color.FromUInt32(0xff228b22);
public static Color ForestGreen => KnownColor.ForestGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffff00ff.
/// </summary>
public static Color Fuchsia => Color.FromUInt32(0xffff00ff);
public static Color Fuchsia => KnownColor.Fuchsia.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffdcdcdc.
/// </summary>
public static Color Gainsboro => Color.FromUInt32(0xffdcdcdc);
public static Color Gainsboro => KnownColor.Gainsboro.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff8f8ff.
/// </summary>
public static Color GhostWhite => Color.FromUInt32(0xfff8f8ff);
public static Color GhostWhite => KnownColor.GhostWhite.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffd700.
/// </summary>
public static Color Gold => Color.FromUInt32(0xffffd700);
public static Color Gold => KnownColor.Gold.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffdaa520.
/// </summary>
public static Color Goldenrod => Color.FromUInt32(0xffdaa520);
public static Color Goldenrod => KnownColor.Goldenrod.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff808080.
/// </summary>
public static Color Gray => Color.FromUInt32(0xff808080);
public static Color Gray => KnownColor.Gray.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff008000.
/// </summary>
public static Color Green => Color.FromUInt32(0xff008000);
public static Color Green => KnownColor.Green.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffadff2f.
/// </summary>
public static Color GreenYellow => Color.FromUInt32(0xffadff2f);
public static Color GreenYellow => KnownColor.GreenYellow.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff0fff0.
/// </summary>
public static Color Honeydew => Color.FromUInt32(0xfff0fff0);
public static Color Honeydew => KnownColor.Honeydew.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffff69b4.
/// </summary>
public static Color HotPink => Color.FromUInt32(0xffff69b4);
public static Color HotPink => KnownColor.HotPink.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffcd5c5c.
/// </summary>
public static Color IndianRed => Color.FromUInt32(0xffcd5c5c);
public static Color IndianRed => KnownColor.IndianRed.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff4b0082.
/// </summary>
public static Color Indigo => Color.FromUInt32(0xff4b0082);
public static Color Indigo => KnownColor.Indigo.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffffff0.
/// </summary>
public static Color Ivory => Color.FromUInt32(0xfffffff0);
public static Color Ivory => KnownColor.Ivory.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff0e68c.
/// </summary>
public static Color Khaki => Color.FromUInt32(0xfff0e68c);
public static Color Khaki => KnownColor.Khaki.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffe6e6fa.
/// </summary>
public static Color Lavender => Color.FromUInt32(0xffe6e6fa);
public static Color Lavender => KnownColor.Lavender.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffff0f5.
/// </summary>
public static Color LavenderBlush => Color.FromUInt32(0xfffff0f5);
public static Color LavenderBlush => KnownColor.LavenderBlush.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff7cfc00.
/// </summary>
public static Color LawnGreen => Color.FromUInt32(0xff7cfc00);
public static Color LawnGreen => KnownColor.LawnGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffffacd.
/// </summary>
public static Color LemonChiffon => Color.FromUInt32(0xfffffacd);
public static Color LemonChiffon => KnownColor.LemonChiffon.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffadd8e6.
/// </summary>
public static Color LightBlue => Color.FromUInt32(0xffadd8e6);
public static Color LightBlue => KnownColor.LightBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff08080.
/// </summary>
public static Color LightCoral => Color.FromUInt32(0xfff08080);
public static Color LightCoral => KnownColor.LightCoral.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffe0ffff.
/// </summary>
public static Color LightCyan => Color.FromUInt32(0xffe0ffff);
public static Color LightCyan => KnownColor.LightCyan.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffafad2.
/// </summary>
public static Color LightGoldenrodYellow => Color.FromUInt32(0xfffafad2);
public static Color LightGoldenrodYellow => KnownColor.LightGoldenrodYellow.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffd3d3d3.
/// </summary>
public static Color LightGray => Color.FromUInt32(0xffd3d3d3);
public static Color LightGray => KnownColor.LightGray.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff90ee90.
/// </summary>
public static Color LightGreen => Color.FromUInt32(0xff90ee90);
public static Color LightGreen => KnownColor.LightGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffb6c1.
/// </summary>
public static Color LightPink => Color.FromUInt32(0xffffb6c1);
public static Color LightPink => KnownColor.LightPink.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffa07a.
/// </summary>
public static Color LightSalmon => Color.FromUInt32(0xffffa07a);
public static Color LightSalmon => KnownColor.LightSalmon.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff20b2aa.
/// </summary>
public static Color LightSeaGreen => Color.FromUInt32(0xff20b2aa);
public static Color LightSeaGreen => KnownColor.LightSeaGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff87cefa.
/// </summary>
public static Color LightSkyBlue => Color.FromUInt32(0xff87cefa);
public static Color LightSkyBlue => KnownColor.LightSkyBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff778899.
/// </summary>
public static Color LightSlateGray => Color.FromUInt32(0xff778899);
public static Color LightSlateGray => KnownColor.LightSlateGray.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffb0c4de.
/// </summary>
public static Color LightSteelBlue => Color.FromUInt32(0xffb0c4de);
public static Color LightSteelBlue => KnownColor.LightSteelBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffffe0.
/// </summary>
public static Color LightYellow => Color.FromUInt32(0xffffffe0);
public static Color LightYellow => KnownColor.LightYellow.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff00ff00.
/// </summary>
public static Color Lime => Color.FromUInt32(0xff00ff00);
public static Color Lime => KnownColor.Lime.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff32cd32.
/// </summary>
public static Color LimeGreen => Color.FromUInt32(0xff32cd32);
public static Color LimeGreen => KnownColor.LimeGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffaf0e6.
/// </summary>
public static Color Linen => Color.FromUInt32(0xfffaf0e6);
public static Color Linen => KnownColor.Linen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffff00ff.
/// </summary>
public static Color Magenta => Color.FromUInt32(0xffff00ff);
public static Color Magenta => KnownColor.Magenta.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff800000.
/// </summary>
public static Color Maroon => Color.FromUInt32(0xff800000);
public static Color Maroon => KnownColor.Maroon.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff66cdaa.
/// </summary>
public static Color MediumAquamarine => Color.FromUInt32(0xff66cdaa);
public static Color MediumAquamarine => KnownColor.MediumAquamarine.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff0000cd.
/// </summary>
public static Color MediumBlue => Color.FromUInt32(0xff0000cd);
public static Color MediumBlue => KnownColor.MediumBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffba55d3.
/// </summary>
public static Color MediumOrchid => Color.FromUInt32(0xffba55d3);
public static Color MediumOrchid => KnownColor.MediumOrchid.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff9370db.
/// </summary>
public static Color MediumPurple => Color.FromUInt32(0xff9370db);
public static Color MediumPurple => KnownColor.MediumPurple.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff3cb371.
/// </summary>
public static Color MediumSeaGreen => Color.FromUInt32(0xff3cb371);
public static Color MediumSeaGreen => KnownColor.MediumSeaGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff7b68ee.
/// </summary>
public static Color MediumSlateBlue => Color.FromUInt32(0xff7b68ee);
public static Color MediumSlateBlue => KnownColor.MediumSlateBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff00fa9a.
/// </summary>
public static Color MediumSpringGreen => Color.FromUInt32(0xff00fa9a);
public static Color MediumSpringGreen => KnownColor.MediumSpringGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff48d1cc.
/// </summary>
public static Color MediumTurquoise => Color.FromUInt32(0xff48d1cc);
public static Color MediumTurquoise => KnownColor.MediumTurquoise.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffc71585.
/// </summary>
public static Color MediumVioletRed => Color.FromUInt32(0xffc71585);
public static Color MediumVioletRed => KnownColor.MediumVioletRed.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff191970.
/// </summary>
public static Color MidnightBlue => Color.FromUInt32(0xff191970);
public static Color MidnightBlue => KnownColor.MidnightBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff5fffa.
/// </summary>
public static Color MintCream => Color.FromUInt32(0xfff5fffa);
public static Color MintCream => KnownColor.MintCream.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffe4e1.
/// </summary>
public static Color MistyRose => Color.FromUInt32(0xffffe4e1);
public static Color MistyRose => KnownColor.MistyRose.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffe4b5.
/// </summary>
public static Color Moccasin => Color.FromUInt32(0xffffe4b5);
public static Color Moccasin => KnownColor.Moccasin.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffdead.
/// </summary>
public static Color NavajoWhite => Color.FromUInt32(0xffffdead);
public static Color NavajoWhite => KnownColor.NavajoWhite.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff000080.
/// </summary>
public static Color Navy => Color.FromUInt32(0xff000080);
public static Color Navy => KnownColor.Navy.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffdf5e6.
/// </summary>
public static Color OldLace => Color.FromUInt32(0xfffdf5e6);
public static Color OldLace => KnownColor.OldLace.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff808000.
/// </summary>
public static Color Olive => Color.FromUInt32(0xff808000);
public static Color Olive => KnownColor.Olive.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff6b8e23.
/// </summary>
public static Color OliveDrab => Color.FromUInt32(0xff6b8e23);
public static Color OliveDrab => KnownColor.OliveDrab.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffa500.
/// </summary>
public static Color Orange => Color.FromUInt32(0xffffa500);
public static Color Orange => KnownColor.Orange.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffff4500.
/// </summary>
public static Color OrangeRed => Color.FromUInt32(0xffff4500);
public static Color OrangeRed => KnownColor.OrangeRed.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffda70d6.
/// </summary>
public static Color Orchid => Color.FromUInt32(0xffda70d6);
public static Color Orchid => KnownColor.Orchid.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffeee8aa.
/// </summary>
public static Color PaleGoldenrod => Color.FromUInt32(0xffeee8aa);
public static Color PaleGoldenrod => KnownColor.PaleGoldenrod.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff98fb98.
/// </summary>
public static Color PaleGreen => Color.FromUInt32(0xff98fb98);
public static Color PaleGreen => KnownColor.PaleGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffafeeee.
/// </summary>
public static Color PaleTurquoise => Color.FromUInt32(0xffafeeee);
public static Color PaleTurquoise => KnownColor.PaleTurquoise.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffdb7093.
/// </summary>
public static Color PaleVioletRed => Color.FromUInt32(0xffdb7093);
public static Color PaleVioletRed => KnownColor.PaleVioletRed.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffefd5.
/// </summary>
public static Color PapayaWhip => Color.FromUInt32(0xffffefd5);
public static Color PapayaWhip => KnownColor.PapayaWhip.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffdab9.
/// </summary>
public static Color PeachPuff => Color.FromUInt32(0xffffdab9);
public static Color PeachPuff => KnownColor.PeachPuff.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffcd853f.
/// </summary>
public static Color Peru => Color.FromUInt32(0xffcd853f);
public static Color Peru => KnownColor.Peru.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffc0cb.
/// </summary>
public static Color Pink => Color.FromUInt32(0xffffc0cb);
public static Color Pink => KnownColor.Pink.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffdda0dd.
/// </summary>
public static Color Plum => Color.FromUInt32(0xffdda0dd);
public static Color Plum => KnownColor.Plum.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffb0e0e6.
/// </summary>
public static Color PowderBlue => Color.FromUInt32(0xffb0e0e6);
public static Color PowderBlue => KnownColor.PowderBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff800080.
/// </summary>
public static Color Purple => Color.FromUInt32(0xff800080);
public static Color Purple => KnownColor.Purple.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffff0000.
/// </summary>
public static Color Red => Color.FromUInt32(0xffff0000);
public static Color Red => KnownColor.Red.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffbc8f8f.
/// </summary>
public static Color RosyBrown => Color.FromUInt32(0xffbc8f8f);
public static Color RosyBrown => KnownColor.RosyBrown.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff4169e1.
/// </summary>
public static Color RoyalBlue => Color.FromUInt32(0xff4169e1);
public static Color RoyalBlue => KnownColor.RoyalBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff8b4513.
/// </summary>
public static Color SaddleBrown => Color.FromUInt32(0xff8b4513);
public static Color SaddleBrown => KnownColor.SaddleBrown.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffa8072.
/// </summary>
public static Color Salmon => Color.FromUInt32(0xfffa8072);
public static Color Salmon => KnownColor.Salmon.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff4a460.
/// </summary>
public static Color SandyBrown => Color.FromUInt32(0xfff4a460);
public static Color SandyBrown => KnownColor.SandyBrown.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff2e8b57.
/// </summary>
public static Color SeaGreen => Color.FromUInt32(0xff2e8b57);
public static Color SeaGreen => KnownColor.SeaGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffff5ee.
/// </summary>
public static Color SeaShell => Color.FromUInt32(0xfffff5ee);
public static Color SeaShell => KnownColor.SeaShell.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffa0522d.
/// </summary>
public static Color Sienna => Color.FromUInt32(0xffa0522d);
public static Color Sienna => KnownColor.Sienna.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffc0c0c0.
/// </summary>
public static Color Silver => Color.FromUInt32(0xffc0c0c0);
public static Color Silver => KnownColor.Silver.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff87ceeb.
/// </summary>
public static Color SkyBlue => Color.FromUInt32(0xff87ceeb);
public static Color SkyBlue => KnownColor.SkyBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff6a5acd.
/// </summary>
public static Color SlateBlue => Color.FromUInt32(0xff6a5acd);
public static Color SlateBlue => KnownColor.SlateBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff708090.
/// </summary>
public static Color SlateGray => Color.FromUInt32(0xff708090);
public static Color SlateGray => KnownColor.SlateGray.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fffffafa.
/// </summary>
public static Color Snow => Color.FromUInt32(0xfffffafa);
public static Color Snow => KnownColor.Snow.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff00ff7f.
/// </summary>
public static Color SpringGreen => Color.FromUInt32(0xff00ff7f);
public static Color SpringGreen => KnownColor.SpringGreen.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff4682b4.
/// </summary>
public static Color SteelBlue => Color.FromUInt32(0xff4682b4);
public static Color SteelBlue => KnownColor.SteelBlue.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffd2b48c.
/// </summary>
public static Color Tan => Color.FromUInt32(0xffd2b48c);
public static Color Tan => KnownColor.Tan.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff008080.
/// </summary>
public static Color Teal => Color.FromUInt32(0xff008080);
public static Color Teal => KnownColor.Teal.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffd8bfd8.
/// </summary>
public static Color Thistle => Color.FromUInt32(0xffd8bfd8);
public static Color Thistle => KnownColor.Thistle.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffff6347.
/// </summary>
public static Color Tomato => Color.FromUInt32(0xffff6347);
public static Color Tomato => KnownColor.Tomato.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #00ffffff.
/// </summary>
public static Color Transparent => Color.FromUInt32(0x00ffffff);
public static Color Transparent => KnownColor.Transparent.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff40e0d0.
/// </summary>
public static Color Turquoise => Color.FromUInt32(0xff40e0d0);
public static Color Turquoise => KnownColor.Turquoise.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffee82ee.
/// </summary>
public static Color Violet => Color.FromUInt32(0xffee82ee);
public static Color Violet => KnownColor.Violet.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff5deb3.
/// </summary>
public static Color Wheat => Color.FromUInt32(0xfff5deb3);
public static Color Wheat => KnownColor.Wheat.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffffff.
/// </summary>
public static Color White => Color.FromUInt32(0xffffffff);
public static Color White => KnownColor.White.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #fff5f5f5.
/// </summary>
public static Color WhiteSmoke => Color.FromUInt32(0xfff5f5f5);
public static Color WhiteSmoke => KnownColor.WhiteSmoke.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ffffff00.
/// </summary>
public static Color Yellow => Color.FromUInt32(0xffffff00);
public static Color Yellow => KnownColor.Yellow.ToColor();
/// <summary>
/// Gets a color with an ARGB value of #ff9acd32.
/// </summary>
public static Color YellowGreen => Color.FromUInt32(0xff9acd32);
public static Color YellowGreen => KnownColor.YellowGreen.ToColor();
}
}

2
src/Avalonia.Visuals/Media/GradientBrush.cs

@ -22,7 +22,7 @@ namespace Avalonia.Media
/// Defines the <see cref="GradientStops"/> property.
/// </summary>
public static readonly StyledProperty<IList<GradientStop>> GradientStopsProperty =
AvaloniaProperty.Register<GradientBrush, IList<GradientStop>>(nameof(Opacity));
AvaloniaProperty.Register<GradientBrush, IList<GradientStop>>(nameof(GradientStops));
/// <summary>
/// Initializes a new instance of the <see cref="GradientBrush"/> class.

224
src/Avalonia.Visuals/Media/KnownColors.cs

@ -0,0 +1,224 @@
using System;
using System.Reflection;
using System.Collections.Generic;
namespace Avalonia.Media
{
internal static class KnownColors
{
private static readonly IReadOnlyDictionary<string, KnownColor> _knownColorNames;
private static readonly IReadOnlyDictionary<uint, string> _knownColors;
private static readonly Dictionary<KnownColor, ISolidColorBrush> _knownBrushes;
static KnownColors()
{
var knownColorNames = new Dictionary<string, KnownColor>(StringComparer.OrdinalIgnoreCase);
var knownColors = new Dictionary<uint, string>();
foreach (var field in typeof(KnownColor).GetRuntimeFields())
{
if (field.FieldType != typeof(KnownColor)) continue;
var knownColor = (KnownColor)field.GetValue(null);
if (knownColor == KnownColor.None) continue;
knownColorNames.Add(field.Name, knownColor);
// some known colors have the same value, so use the first
if (!knownColors.ContainsKey((uint)knownColor))
{
knownColors.Add((uint)knownColor, field.Name);
}
}
_knownColorNames = knownColorNames;
_knownColors = knownColors;
_knownBrushes = new Dictionary<KnownColor, ISolidColorBrush>();
}
public static ISolidColorBrush GetKnownBrush(string s)
{
var color = GetKnownColor(s);
return color != KnownColor.None ? color.ToBrush() : null;
}
public static KnownColor GetKnownColor(string s)
{
if (_knownColorNames.TryGetValue(s, out var color))
{
return color;
}
return KnownColor.None;
}
public static string GetKnownColorName(uint rgb)
{
return _knownColors.TryGetValue(rgb, out var name) ? name : null;
}
public static Color ToColor(this KnownColor color)
{
return Color.FromUInt32((uint)color);
}
public static ISolidColorBrush ToBrush(this KnownColor color)
{
lock (_knownBrushes)
{
if (!_knownBrushes.TryGetValue(color, out var brush))
{
brush = new Immutable.ImmutableSolidColorBrush(color.ToColor());
_knownBrushes.Add(color, brush);
}
return brush;
}
}
}
internal enum KnownColor : uint
{
None,
AliceBlue = 0xfff0f8ff,
AntiqueWhite = 0xfffaebd7,
Aqua = 0xff00ffff,
Aquamarine = 0xff7fffd4,
Azure = 0xfff0ffff,
Beige = 0xfff5f5dc,
Bisque = 0xffffe4c4,
Black = 0xff000000,
BlanchedAlmond = 0xffffebcd,
Blue = 0xff0000ff,
BlueViolet = 0xff8a2be2,
Brown = 0xffa52a2a,
BurlyWood = 0xffdeb887,
CadetBlue = 0xff5f9ea0,
Chartreuse = 0xff7fff00,
Chocolate = 0xffd2691e,
Coral = 0xffff7f50,
CornflowerBlue = 0xff6495ed,
Cornsilk = 0xfffff8dc,
Crimson = 0xffdc143c,
Cyan = 0xff00ffff,
DarkBlue = 0xff00008b,
DarkCyan = 0xff008b8b,
DarkGoldenrod = 0xffb8860b,
DarkGray = 0xffa9a9a9,
DarkGreen = 0xff006400,
DarkKhaki = 0xffbdb76b,
DarkMagenta = 0xff8b008b,
DarkOliveGreen = 0xff556b2f,
DarkOrange = 0xffff8c00,
DarkOrchid = 0xff9932cc,
DarkRed = 0xff8b0000,
DarkSalmon = 0xffe9967a,
DarkSeaGreen = 0xff8fbc8f,
DarkSlateBlue = 0xff483d8b,
DarkSlateGray = 0xff2f4f4f,
DarkTurquoise = 0xff00ced1,
DarkViolet = 0xff9400d3,
DeepPink = 0xffff1493,
DeepSkyBlue = 0xff00bfff,
DimGray = 0xff696969,
DodgerBlue = 0xff1e90ff,
Firebrick = 0xffb22222,
FloralWhite = 0xfffffaf0,
ForestGreen = 0xff228b22,
Fuchsia = 0xffff00ff,
Gainsboro = 0xffdcdcdc,
GhostWhite = 0xfff8f8ff,
Gold = 0xffffd700,
Goldenrod = 0xffdaa520,
Gray = 0xff808080,
Green = 0xff008000,
GreenYellow = 0xffadff2f,
Honeydew = 0xfff0fff0,
HotPink = 0xffff69b4,
IndianRed = 0xffcd5c5c,
Indigo = 0xff4b0082,
Ivory = 0xfffffff0,
Khaki = 0xfff0e68c,
Lavender = 0xffe6e6fa,
LavenderBlush = 0xfffff0f5,
LawnGreen = 0xff7cfc00,
LemonChiffon = 0xfffffacd,
LightBlue = 0xffadd8e6,
LightCoral = 0xfff08080,
LightCyan = 0xffe0ffff,
LightGoldenrodYellow = 0xfffafad2,
LightGreen = 0xff90ee90,
LightGray = 0xffd3d3d3,
LightPink = 0xffffb6c1,
LightSalmon = 0xffffa07a,
LightSeaGreen = 0xff20b2aa,
LightSkyBlue = 0xff87cefa,
LightSlateGray = 0xff778899,
LightSteelBlue = 0xffb0c4de,
LightYellow = 0xffffffe0,
Lime = 0xff00ff00,
LimeGreen = 0xff32cd32,
Linen = 0xfffaf0e6,
Magenta = 0xffff00ff,
Maroon = 0xff800000,
MediumAquamarine = 0xff66cdaa,
MediumBlue = 0xff0000cd,
MediumOrchid = 0xffba55d3,
MediumPurple = 0xff9370db,
MediumSeaGreen = 0xff3cb371,
MediumSlateBlue = 0xff7b68ee,
MediumSpringGreen = 0xff00fa9a,
MediumTurquoise = 0xff48d1cc,
MediumVioletRed = 0xffc71585,
MidnightBlue = 0xff191970,
MintCream = 0xfff5fffa,
MistyRose = 0xffffe4e1,
Moccasin = 0xffffe4b5,
NavajoWhite = 0xffffdead,
Navy = 0xff000080,
OldLace = 0xfffdf5e6,
Olive = 0xff808000,
OliveDrab = 0xff6b8e23,
Orange = 0xffffa500,
OrangeRed = 0xffff4500,
Orchid = 0xffda70d6,
PaleGoldenrod = 0xffeee8aa,
PaleGreen = 0xff98fb98,
PaleTurquoise = 0xffafeeee,
PaleVioletRed = 0xffdb7093,
PapayaWhip = 0xffffefd5,
PeachPuff = 0xffffdab9,
Peru = 0xffcd853f,
Pink = 0xffffc0cb,
Plum = 0xffdda0dd,
PowderBlue = 0xffb0e0e6,
Purple = 0xff800080,
Red = 0xffff0000,
RosyBrown = 0xffbc8f8f,
RoyalBlue = 0xff4169e1,
SaddleBrown = 0xff8b4513,
Salmon = 0xfffa8072,
SandyBrown = 0xfff4a460,
SeaGreen = 0xff2e8b57,
SeaShell = 0xfffff5ee,
Sienna = 0xffa0522d,
Silver = 0xffc0c0c0,
SkyBlue = 0xff87ceeb,
SlateBlue = 0xff6a5acd,
SlateGray = 0xff708090,
Snow = 0xfffffafa,
SpringGreen = 0xff00ff7f,
SteelBlue = 0xff4682b4,
Tan = 0xffd2b48c,
Teal = 0xff008080,
Thistle = 0xffd8bfd8,
Tomato = 0xffff6347,
Transparent = 0x00ffffff,
Turquoise = 0xff40e0d0,
Violet = 0xffee82ee,
Wheat = 0xfff5deb3,
White = 0xffffffff,
WhiteSmoke = 0xfff5f5f5,
Yellow = 0xffffff00,
YellowGreen = 0xff9acd32
}
}

16
src/Avalonia.Visuals/Point.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System;
using System.Globalization;
using System.Linq;
@ -173,17 +174,12 @@ namespace Avalonia
/// <returns>The <see cref="Thickness"/>.</returns>
public static Point Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
if (parts.Count == 2)
{
return new Point(double.Parse(parts[0], culture), double.Parse(parts[1], culture));
}
else
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Point"))
{
throw new FormatException("Invalid Point.");
return new Point(
tokenizer.ReadDouble(),
tokenizer.ReadDouble()
);
}
}

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

@ -9,6 +9,7 @@ using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests")]
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests")]

20
src/Avalonia.Visuals/Rect.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System;
using System.Globalization;
using System.Linq;
@ -490,21 +491,14 @@ namespace Avalonia
/// <returns>The parsed <see cref="Rect"/>.</returns>
public static Rect Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
if (parts.Count == 4)
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Rect"))
{
return new Rect(
double.Parse(parts[0], culture),
double.Parse(parts[1], culture),
double.Parse(parts[2], culture),
double.Parse(parts[3], culture));
}
else
{
throw new FormatException("Invalid Rect.");
tokenizer.ReadDouble(),
tokenizer.ReadDouble(),
tokenizer.ReadDouble(),
tokenizer.ReadDouble()
);
}
}
}

26
src/Avalonia.Visuals/RelativePoint.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System;
using System.Globalization;
using System.Linq;
@ -157,37 +158,32 @@ namespace Avalonia
/// <returns>The parsed <see cref="RelativePoint"/>.</returns>
public static RelativePoint Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
if (parts.Count == 2)
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativePoint"))
{
var x = tokenizer.ReadString();
var y = tokenizer.ReadString();
var unit = RelativeUnit.Absolute;
var scale = 1.0;
if (parts[0].EndsWith("%"))
if (x.EndsWith("%"))
{
if (!parts[1].EndsWith("%"))
if (!y.EndsWith("%"))
{
throw new FormatException("If one coordinate is relative, both must be.");
}
parts[0] = parts[0].TrimEnd('%');
parts[1] = parts[1].TrimEnd('%');
x = x.TrimEnd('%');
y = y.TrimEnd('%');
unit = RelativeUnit.Relative;
scale = 0.01;
}
return new RelativePoint(
double.Parse(parts[0], culture) * scale,
double.Parse(parts[1], culture) * scale,
double.Parse(x, culture) * scale,
double.Parse(y, culture) * scale,
unit);
}
else
{
throw new FormatException("Invalid Point.");
}
}
}
}

55
src/Avalonia.Visuals/RelativeRect.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System;
using System.Globalization;
using System.Linq;
@ -12,6 +13,8 @@ namespace Avalonia
/// </summary>
public struct RelativeRect : IEquatable<RelativeRect>
{
private static readonly char[] PercentChar = { '%' };
/// <summary>
/// A rectangle that represents 100% of an area.
/// </summary>
@ -159,7 +162,7 @@ namespace Avalonia
Rect.Width * size.Width,
Rect.Height * size.Height);
}
/// <summary>
/// Parses a <see cref="RelativeRect"/> string.
/// </summary>
@ -168,43 +171,43 @@ namespace Avalonia
/// <returns>The parsed <see cref="RelativeRect"/>.</returns>
public static RelativeRect Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
if (parts.Count == 4)
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativeRect"))
{
var x = tokenizer.ReadString();
var y = tokenizer.ReadString();
var width = tokenizer.ReadString();
var height = tokenizer.ReadString();
var unit = RelativeUnit.Absolute;
var scale = 1.0;
if (parts[0].EndsWith("%"))
var xRelative = x.EndsWith("%", StringComparison.Ordinal);
var yRelative = y.EndsWith("%", StringComparison.Ordinal);
var widthRelative = width.EndsWith("%", StringComparison.Ordinal);
var heightRelative = height.EndsWith("%", StringComparison.Ordinal);
if (xRelative && yRelative && widthRelative && heightRelative)
{
if (!parts[1].EndsWith("%")
|| !parts[2].EndsWith("%")
|| !parts[3].EndsWith("%"))
{
throw new FormatException("If one coordinate is relative, all other must be too.");
}
parts[0] = parts[0].TrimEnd('%');
parts[1] = parts[1].TrimEnd('%');
parts[2] = parts[2].TrimEnd('%');
parts[3] = parts[3].TrimEnd('%');
x = x.TrimEnd(PercentChar);
y = y.TrimEnd(PercentChar);
width = width.TrimEnd(PercentChar);
height = height.TrimEnd(PercentChar);
unit = RelativeUnit.Relative;
scale = 0.01;
}
else if (xRelative || yRelative || widthRelative || heightRelative)
{
throw new FormatException("If one coordinate is relative, all must be.");
}
return new RelativeRect(
double.Parse(parts[0], culture) * scale,
double.Parse(parts[1], culture) * scale,
double.Parse(parts[2], culture) * scale,
double.Parse(parts[3], culture) * scale,
double.Parse(x, culture) * scale,
double.Parse(y, culture) * scale,
double.Parse(width, culture) * scale,
double.Parse(height, culture) * scale,
unit);
}
else
{
throw new FormatException("Invalid RelativeRect.");
}
}
}
}

4
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -167,7 +167,9 @@ namespace Avalonia.Rendering.SceneGraph
using (context.PushPostTransform(m))
using (context.PushTransformContainer())
{
var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip);
var clipBounds = clipToBounds ?
bounds.TransformToAABB(contextImpl.Transform).Intersect(clip) :
clip;
forceRecurse = forceRecurse ||
node.ClipBounds != clipBounds ||

15
src/Avalonia.Visuals/Size.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System;
using System.Globalization;
using System.Linq;
@ -153,17 +154,11 @@ namespace Avalonia
/// <returns>The <see cref="Size"/>.</returns>
public static Size Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
if (parts.Count == 2)
{
return new Size(double.Parse(parts[0], culture), double.Parse(parts[1], culture));
}
else
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Size"))
{
throw new FormatException("Invalid Size.");
return new Size(
tokenizer.ReadDouble(),
tokenizer.ReadDouble());
}
}

42
src/Avalonia.Visuals/Thickness.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System;
using System.Globalization;
using System.Linq;
@ -90,7 +91,12 @@ namespace Avalonia
/// <summary>
/// Gets a value indicating whether all sides are set to 0.
/// </summary>
public bool IsEmpty => Left == 0 && Top == 0 && Right == 0 && Bottom == 0;
public bool IsEmpty => Left.Equals(0) && IsUniform;
/// <summary>
/// Gets a value indicating whether all sides are equal.
/// </summary>
public bool IsUniform => Left.Equals(Right) && Top.Equals(Bottom) && Right.Equals(Bottom);
/// <summary>
/// Compares two Thicknesses.
@ -163,28 +169,22 @@ namespace Avalonia
/// <returns>The <see cref="Thickness"/>.</returns>
public static Thickness Parse(string s, CultureInfo culture)
{
var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim())
.ToList();
switch (parts.Count)
using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Thickness"))
{
case 1:
var uniform = double.Parse(parts[0], culture);
return new Thickness(uniform);
case 2:
var horizontal = double.Parse(parts[0], culture);
var vertical = double.Parse(parts[1], culture);
return new Thickness(horizontal, vertical);
case 4:
var left = double.Parse(parts[0], culture);
var top = double.Parse(parts[1], culture);
var right = double.Parse(parts[2], culture);
var bottom = double.Parse(parts[3], culture);
return new Thickness(left, top, right, bottom);
var a = tokenizer.ReadDouble();
if (tokenizer.TryReadDouble(out var b))
{
if (tokenizer.TryReadDouble(out var c))
{
return new Thickness(a, b, c, tokenizer.ReadDouble());
}
return new Thickness(a, b);
}
return new Thickness(a);
}
throw new FormatException("Invalid Thickness.");
}
/// <summary>

5
src/Gtk/Avalonia.Gtk3/CursorFactory.cs

@ -32,7 +32,10 @@ namespace Avalonia.Gtk3
{StandardCursorType.TopLeftCorner, CursorType.TopLeftCorner},
{StandardCursorType.TopRightCorner, CursorType.TopRightCorner},
{StandardCursorType.BottomLeftCorner, CursorType.BottomLeftCorner},
{StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner}
{StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner},
{StandardCursorType.DragCopy, CursorType.CenterPtr},
{StandardCursorType.DragMove, CursorType.Fleur},
{StandardCursorType.DragLink, CursorType.Cross},
};
private static readonly Dictionary<StandardCursorType, IPlatformHandle> Cache =

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -31,6 +31,7 @@
</Compile>
<Compile Include="AvaloniaXamlLoaderPortableXaml.cs" />
<Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\CornerRadiusTypeConverter.cs" />
<Compile Include="Converters\MatrixTypeConverter.cs" />
<Compile Include="Converters\RectTypeConverter.cs" />
<Compile Include="Converters\SetterValueTypeConverter.cs" />

19
src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs

@ -0,0 +1,19 @@
using System;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Markup.Xaml.Converters
{
public class CornerRadiusTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return CornerRadius.Parse((string)value, culture);
}
}
}

1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -43,6 +43,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Mode = Mode,
Path = pathInfo.Path,
Priority = Priority,
Source = Source,
RelativeSource = pathInfo.RelativeSource ?? RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
};

1
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs

@ -43,6 +43,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
{ typeof(Selector), typeof(SelectorTypeConverter)},
{ typeof(SolidColorBrush), typeof(BrushTypeConverter) },
{ typeof(Thickness), typeof(ThicknessTypeConverter) },
{ typeof(CornerRadius), typeof(CornerRadiusTypeConverter) },
{ typeof(TimeSpan), typeof(TimeSpanTypeConverter) },
//{ typeof(Uri), typeof(Converters.UriTypeConverter) },
{ typeof(Cursor), typeof(CursorTypeConverter) },

4
src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
@ -19,4 +19,4 @@
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\MonoMac.props" />
</Project>
</Project>

4
src/OSX/Avalonia.MonoMac/Cursor.cs

@ -51,6 +51,10 @@ namespace Avalonia.MonoMac
[StandardCursorType.TopSide] = NSCursor.ResizeUpCursor,
[StandardCursorType.UpArrow] = NSCursor.ResizeUpCursor,
[StandardCursorType.Wait] = NSCursor.ArrowCursor, //TODO
[StandardCursorType.DragMove] = NSCursor.DragCopyCursor, // TODO
[StandardCursorType.DragCopy] = NSCursor.DragCopyCursor,
[StandardCursorType.DragLink] = NSCursor.DragLinkCursor,
};
}

124
src/OSX/Avalonia.MonoMac/DragSource.cs

@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using MonoMac;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
using MonoMac.Foundation;
using MonoMac.OpenGL;
namespace Avalonia.MonoMac
{
public class DragSource : NSDraggingSource, IPlatformDragSource
{
private const string NSPasteboardTypeString = "public.utf8-plain-text";
private const string NSPasteboardTypeFileUrl = "public.file-url";
private readonly Subject<DragDropEffects> _result = new Subject<DragDropEffects>();
private readonly IInputManager _inputManager;
private DragDropEffects _allowedEffects;
public override bool IgnoreModifierKeysWhileDragging => false;
public DragSource()
{
_inputManager = AvaloniaLocator.Current.GetService<IInputManager>();
}
private string DataFormatToUTI(string s)
{
if (s == DataFormats.FileNames)
return NSPasteboardTypeFileUrl;
if (s == DataFormats.Text)
return NSPasteboardTypeString;
return s;
}
private NSDraggingItem CreateDraggingItem(string format, object data)
{
var pasteboardItem = new NSPasteboardItem();
NSData nsData;
if (data is string s)
{
if (format == DataFormats.FileNames)
s = new Uri(s).AbsoluteUri; // Ensure file uris...
nsData = NSData.FromString(s);
}
else if (data is Stream strm)
nsData = NSData.FromStream(strm);
else if (data is byte[] bytes)
nsData = NSData.FromArray(bytes);
else
{
BinaryFormatter bf = new BinaryFormatter();
using (var ms = new MemoryStream())
{
bf.Serialize(ms, data);
ms.Position = 0;
nsData = NSData.FromStream(ms);
}
}
pasteboardItem.SetDataForType(nsData, DataFormatToUTI(format));
NSPasteboardWriting writing = new NSPasteboardWriting(pasteboardItem.Handle);
return new NSDraggingItem(writing);
}
public IEnumerable<NSDraggingItem> CreateDraggingItems(string format, object data)
{
if (format == DataFormats.FileNames && data is IEnumerable<string> files)
{
foreach (var file in files)
yield return CreateDraggingItem(format, file);
yield break;
}
yield return CreateDraggingItem(format, data);
}
public async Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
{
// We need the TopLevelImpl + a mouse location so we just wait for the next event.
var mouseEv = await _inputManager.PreProcess.OfType<RawMouseEventArgs>().FirstAsync();
var view = ((mouseEv.Root as TopLevel)?.PlatformImpl as TopLevelImpl)?.View;
if (view == null)
return DragDropEffects.None;
// Prepare the source event:
var pt = view.TranslateLocalPoint(mouseEv.Position).ToMonoMacPoint();
var ev = NSEvent.MouseEvent(NSEventType.LeftMouseDown, pt, 0, 0, 0, null, 0, 0, 0);
_allowedEffects = allowedEffects;
var items = data.GetDataFormats().SelectMany(fmt => CreateDraggingItems(fmt, data.Get(fmt))).ToArray();
view.BeginDraggingSession(items ,ev, this);
return await _result;
}
public override NSDragOperation DraggingSourceOperationMaskForLocal(bool flag)
{
return DraggingInfo.ConvertDragOperation(_allowedEffects);
}
public override void DraggedImageEndedAtOperation(NSImage image, CGPoint screenPoint, NSDragOperation operation)
{
_result.OnNext(DraggingInfo.ConvertDragOperation(operation));
_result.OnCompleted();
}
}
}

90
src/OSX/Avalonia.MonoMac/DraggingInfo.cs

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input;
using MonoMac.AppKit;
using MonoMac.Foundation;
namespace Avalonia.MonoMac
{
class DraggingInfo : IDataObject
{
private readonly NSDraggingInfo _info;
public DraggingInfo(NSDraggingInfo info)
{
_info = info;
}
internal static NSDragOperation ConvertDragOperation(DragDropEffects d)
{
NSDragOperation result = NSDragOperation.None;
if (d.HasFlag(DragDropEffects.Copy))
result |= NSDragOperation.Copy;
if (d.HasFlag(DragDropEffects.Link))
result |= NSDragOperation.Link;
if (d.HasFlag(DragDropEffects.Move))
result |= NSDragOperation.Move;
return result;
}
internal static DragDropEffects ConvertDragOperation(NSDragOperation d)
{
DragDropEffects result = DragDropEffects.None;
if (d.HasFlag(NSDragOperation.Copy))
result |= DragDropEffects.Copy;
if (d.HasFlag(NSDragOperation.Link))
result |= DragDropEffects.Link;
if (d.HasFlag(NSDragOperation.Move))
result |= DragDropEffects.Move;
return result;
}
public Point Location => new Point(_info.DraggingLocation.X, _info.DraggingLocation.Y);
public IEnumerable<string> GetDataFormats()
{
return _info.DraggingPasteboard.Types.Select(NSTypeToWellknownType);
}
private string NSTypeToWellknownType(string type)
{
if (type == NSPasteboard.NSStringType)
return DataFormats.Text;
if (type == NSPasteboard.NSFilenamesType)
return DataFormats.FileNames;
return type;
}
public string GetText()
{
return _info.DraggingPasteboard.GetStringForType(NSPasteboard.NSStringType);
}
public IEnumerable<string> GetFileNames()
{
using(var fileNames = (NSArray)_info.DraggingPasteboard.GetPropertyListForType(NSPasteboard.NSFilenamesType))
{
if (fileNames != null)
return NSArray.StringArrayFromHandle(fileNames.Handle);
}
return Enumerable.Empty<string>();
}
public bool Contains(string dataFormat)
{
return GetDataFormats().Any(f => f == dataFormat);
}
public object Get(string dataFormat)
{
if (dataFormat == DataFormats.Text)
return GetText();
if (dataFormat == DataFormats.FileNames)
return GetFileNames();
return _info.DraggingPasteboard.GetDataForType(dataFormat).ToArray();
}
}
}

3
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@ -35,7 +35,8 @@ namespace Avalonia.MonoMac
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsImpl>()
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IRenderLoop>().ToConstant(s_renderLoop)
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
/*.Bind<IPlatformDragSource>().ToTransient<DragSource>()*/;
}
public static void Initialize()

47
src/OSX/Avalonia.MonoMac/TopLevelImpl.cs

@ -18,6 +18,7 @@ namespace Avalonia.MonoMac
{
public TopLevelView View { get; }
private readonly IMouseDevice _mouse = AvaloniaLocator.Current.GetService<IMouseDevice>();
private readonly IDragDropDevice _dragDevice = AvaloniaLocator.Current.GetService<IDragDropDevice>();
protected TopLevelImpl()
{
View = new TopLevelView(this);
@ -53,6 +54,10 @@ namespace Avalonia.MonoMac
_tl = tl;
_mouse = AvaloniaLocator.Current.GetService<IMouseDevice>();
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
RegisterForDraggedTypes(new string[] {
"public.data" // register for any kind of data.
});
}
protected override void Dispose(bool disposing)
@ -149,6 +154,48 @@ namespace Avalonia.MonoMac
UpdateCursor();
}
private NSDragOperation SendRawDragEvent(NSDraggingInfo sender, RawDragEventType type)
{
Action<RawInputEventArgs> input = _tl.Input;
IDragDropDevice dragDevice = _tl._dragDevice;
IInputRoot root = _tl?.InputRoot;
if (root == null || dragDevice == null || input == null)
return NSDragOperation.None;
var dragOp = DraggingInfo.ConvertDragOperation(sender.DraggingSourceOperationMask);
DraggingInfo info = new DraggingInfo(sender);
var pt = TranslateLocalPoint(info.Location);
var args = new RawDragEvent(dragDevice, type, root, pt, info, dragOp);
input(args);
return DraggingInfo.ConvertDragOperation(args.Effects);
}
public override NSDragOperation DraggingEntered(NSDraggingInfo sender)
{
return SendRawDragEvent(sender, RawDragEventType.DragEnter);
}
public override NSDragOperation DraggingUpdated(NSDraggingInfo sender)
{
return SendRawDragEvent(sender, RawDragEventType.DragOver);
}
public override void DraggingExited(NSDraggingInfo sender)
{
SendRawDragEvent(sender, RawDragEventType.DragLeave);
}
public override bool PrepareForDragOperation(NSDraggingInfo sender)
{
return SendRawDragEvent(sender, RawDragEventType.DragOver) != NSDragOperation.None;
}
public override bool PerformDragOperation(NSDraggingInfo sender)
{
return SendRawDragEvent(sender, RawDragEventType.Drop) != NSDragOperation.None;
}
public override void SetFrameSize(CGSize newSize)
{
lock (SyncRoot)

7
src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs

@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Platform;
using SharpDX.Direct2D1;
@ -20,14 +19,12 @@ namespace Avalonia.Direct2D1.Media
/// <inheritdoc/>
public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia();
/// <inheritdoc/>
public Geometry Geometry { get; }
/// <inheritdoc/>
public Rect GetRenderBounds(Avalonia.Media.Pen pen)
{
var factory = AvaloniaLocator.Current.GetService<Factory>();
return Geometry.GetWidenedBounds((float)pen.Thickness).ToAvalonia();
return Geometry.GetWidenedBounds((float)(pen?.Thickness ?? 0)).ToAvalonia();
}
/// <inheritdoc/>
@ -51,7 +48,7 @@ namespace Avalonia.Direct2D1.Media
/// <inheritdoc/>
public bool StrokeContains(Avalonia.Media.Pen pen, Point point)
{
return Geometry.StrokeContainsPoint(point.ToSharpDX(), (float)pen.Thickness);
return Geometry.StrokeContainsPoint(point.ToSharpDX(), (float)(pen?.Thickness ?? 0));
}
public ITransformedGeometryImpl WithTransform(Matrix transform)

11
src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs

@ -103,7 +103,7 @@ namespace Avalonia.Direct2D1
/// Converts a pen to a Direct2D stroke style.
/// </summary>
/// <param name="pen">The pen to convert.</param>
/// <param name="target">The render target.</param>
/// <param name="renderTarget">The render target.</param>
/// <returns>The Direct2D brush.</returns>
public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, SharpDX.Direct2D1.RenderTarget renderTarget)
{
@ -114,7 +114,7 @@ namespace Avalonia.Direct2D1
/// Converts a pen to a Direct2D stroke style.
/// </summary>
/// <param name="pen">The pen to convert.</param>
/// <param name="target">The render target.</param>
/// <param name="factory">The factory associated with this resource.</param>
/// <returns>The Direct2D brush.</returns>
public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, Factory factory)
{
@ -127,13 +127,16 @@ namespace Avalonia.Direct2D1
EndCap = pen.EndLineCap.ToDirect2D(),
DashCap = pen.DashCap.ToDirect2D()
};
var dashes = new float[0];
float[] dashes = null;
if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0)
{
properties.DashStyle = DashStyle.Custom;
properties.DashOffset = (float)pen.DashStyle.Offset;
dashes = pen.DashStyle?.Dashes.Select(x => (float)x).ToArray();
dashes = pen.DashStyle.Dashes.Select(x => (float)x).ToArray();
}
dashes = dashes ?? Array.Empty<float>();
return new StrokeStyle(factory, properties, dashes);
}

80
src/Windows/Avalonia.Win32/ClipboardFormats.cs

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using Avalonia.Input;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
static class ClipboardFormats
{
private const int MAX_FORMAT_NAME_LENGTH = 260;
class ClipboardFormat
{
public short Format { get; private set; }
public string Name { get; private set; }
public short[] Synthesized { get; private set; }
public ClipboardFormat(string name, short format, params short[] synthesized)
{
Format = format;
Name = name;
Synthesized = synthesized;
}
}
private static readonly List<ClipboardFormat> FormatList = new List<ClipboardFormat>()
{
new ClipboardFormat(DataFormats.Text, (short)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, (short)UnmanagedMethods.ClipboardFormat.CF_TEXT),
new ClipboardFormat(DataFormats.FileNames, (short)UnmanagedMethods.ClipboardFormat.CF_HDROP),
};
private static string QueryFormatName(short format)
{
StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH);
if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0)
return sb.ToString();
return null;
}
public static string GetFormat(short format)
{
lock (FormatList)
{
var pd = FormatList.FirstOrDefault(f => f.Format == format || Array.IndexOf(f.Synthesized, format) >= 0);
if (pd == null)
{
string name = QueryFormatName(format);
if (string.IsNullOrEmpty(name))
name = string.Format("Unknown_Format_{0}", format);
pd = new ClipboardFormat(name, format);
FormatList.Add(pd);
}
return pd.Name;
}
}
public static short GetFormat(string format)
{
lock (FormatList)
{
var pd = FormatList.FirstOrDefault(f => StringComparer.OrdinalIgnoreCase.Equals(f.Name, format));
if (pd == null)
{
int id = UnmanagedMethods.RegisterClipboardFormat(format);
if (id == 0)
throw new Win32Exception();
pd = new ClipboardFormat(format, (short)id);
FormatList.Add(pd);
}
return pd.Format;
}
}
}
}

27
src/Windows/Avalonia.Win32/CursorFactory.cs

@ -9,6 +9,7 @@ using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Platform;
using System.Runtime.InteropServices;
namespace Avalonia.Win32
{
@ -20,6 +21,27 @@ namespace Avalonia.Win32
{
}
static CursorFactory()
{
LoadModuleCursor(StandardCursorType.DragMove, "ole32.dll", 2);
LoadModuleCursor(StandardCursorType.DragCopy, "ole32.dll", 3);
LoadModuleCursor(StandardCursorType.DragLink, "ole32.dll", 4);
}
private static void LoadModuleCursor(StandardCursorType cursorType, string module, int id)
{
IntPtr mh = UnmanagedMethods.GetModuleHandle(module);
if (mh != IntPtr.Zero)
{
IntPtr cursor = UnmanagedMethods.LoadCursor(mh, new IntPtr(id));
if (cursor != IntPtr.Zero)
{
PlatformHandle phCursor = new PlatformHandle(cursor, PlatformConstants.CursorHandleType);
Cache.Add(cursorType, phCursor);
}
}
}
private static readonly Dictionary<StandardCursorType, int> CursorTypeMapping = new Dictionary
<StandardCursorType, int>
{
@ -47,6 +69,11 @@ namespace Avalonia.Win32
//Using SizeNorthEastSouthWest
{StandardCursorType.TopRightCorner, 32643},
{StandardCursorType.BottomLeftCorner, 32643},
// Fallback, should have been loaded from ole32.dll
{StandardCursorType.DragMove, 32516},
{StandardCursorType.DragCopy, 32516},
{StandardCursorType.DragLink, 32516},
};
private static readonly Dictionary<StandardCursorType, IPlatformHandle> Cache =

361
src/Windows/Avalonia.Win32/DataObject.cs

@ -0,0 +1,361 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using Avalonia.Input;
using Avalonia.Win32.Interop;
using IDataObject = Avalonia.Input.IDataObject;
using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace Avalonia.Win32
{
class DataObject : IDataObject, IOleDataObject
{
// Compatibility with WinForms + WPF...
internal static readonly byte[] SerializedObjectGUID = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray();
class FormatEnumerator : IEnumFORMATETC
{
private FORMATETC[] _formats;
private int _current;
private FormatEnumerator(FORMATETC[] formats, int current)
{
_formats = formats;
_current = current;
}
public FormatEnumerator(IDataObject dataobj)
{
_formats = dataobj.GetDataFormats().Select(ConvertToFormatEtc).ToArray();
_current = 0;
}
private FORMATETC ConvertToFormatEtc(string aFormatName)
{
FORMATETC result = default(FORMATETC);
result.cfFormat = ClipboardFormats.GetFormat(aFormatName);
result.dwAspect = DVASPECT.DVASPECT_CONTENT;
result.ptd = IntPtr.Zero;
result.lindex = -1;
result.tymed = TYMED.TYMED_HGLOBAL;
return result;
}
public void Clone(out IEnumFORMATETC newEnum)
{
newEnum = new FormatEnumerator(_formats, _current);
}
public int Next(int celt, FORMATETC[] rgelt, int[] pceltFetched)
{
if (rgelt == null)
return unchecked((int)UnmanagedMethods.HRESULT.E_INVALIDARG);
int i = 0;
while (i < celt && _current < _formats.Length)
{
rgelt[i] = _formats[_current];
_current++;
i++;
}
if (pceltFetched != null)
pceltFetched[0] = i;
if (i != celt)
return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE);
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
}
public int Reset()
{
_current = 0;
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
}
public int Skip(int celt)
{
_current += Math.Min(celt, int.MaxValue - _current);
if (_current >= _formats.Length)
return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE);
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
}
}
private const int DV_E_TYMED = unchecked((int)0x80040069);
private const int DV_E_DVASPECT = unchecked((int)0x8004006B);
private const int DV_E_FORMATETC = unchecked((int)0x80040064);
private const int OLE_E_ADVISENOTSUPPORTED = unchecked((int)0x80040003);
private const int STG_E_MEDIUMFULL = unchecked((int)0x80030070);
private const int GMEM_ZEROINIT = 0x0040;
private const int GMEM_MOVEABLE = 0x0002;
IDataObject _wrapped;
public DataObject(IDataObject wrapped)
{
_wrapped = wrapped;
}
#region IDataObject
bool IDataObject.Contains(string dataFormat)
{
return _wrapped.Contains(dataFormat);
}
IEnumerable<string> IDataObject.GetDataFormats()
{
return _wrapped.GetDataFormats();
}
IEnumerable<string> IDataObject.GetFileNames()
{
return _wrapped.GetFileNames();
}
string IDataObject.GetText()
{
return _wrapped.GetText();
}
object IDataObject.Get(string dataFormat)
{
return _wrapped.Get(dataFormat);
}
#endregion
#region IOleDataObject
int IOleDataObject.DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection)
{
if (_wrapped is IOleDataObject ole)
return ole.DAdvise(ref pFormatetc, advf, adviseSink, out connection);
connection = 0;
return OLE_E_ADVISENOTSUPPORTED;
}
void IOleDataObject.DUnadvise(int connection)
{
if (_wrapped is IOleDataObject ole)
ole.DUnadvise(connection);
Marshal.ThrowExceptionForHR(OLE_E_ADVISENOTSUPPORTED);
}
int IOleDataObject.EnumDAdvise(out IEnumSTATDATA enumAdvise)
{
if (_wrapped is IOleDataObject ole)
return ole.EnumDAdvise(out enumAdvise);
enumAdvise = null;
return OLE_E_ADVISENOTSUPPORTED;
}
IEnumFORMATETC IOleDataObject.EnumFormatEtc(DATADIR direction)
{
if (_wrapped is IOleDataObject ole)
return ole.EnumFormatEtc(direction);
if (direction == DATADIR.DATADIR_GET)
return new FormatEnumerator(_wrapped);
throw new NotSupportedException();
}
int IOleDataObject.GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut)
{
if (_wrapped is IOleDataObject ole)
return ole.GetCanonicalFormatEtc(ref formatIn, out formatOut);
formatOut = new FORMATETC();
formatOut.ptd = IntPtr.Zero;
return unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL);
}
void IOleDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium)
{
if (_wrapped is IOleDataObject ole)
{
ole.GetData(ref format, out medium);
return;
}
if(!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
Marshal.ThrowExceptionForHR(DV_E_TYMED);
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
Marshal.ThrowExceptionForHR(DV_E_DVASPECT);
string fmt = ClipboardFormats.GetFormat(format.cfFormat);
if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt))
Marshal.ThrowExceptionForHR(DV_E_FORMATETC);
medium = default(STGMEDIUM);
medium.tymed = TYMED.TYMED_HGLOBAL;
int result = WriteDataToHGlobal(fmt, ref medium.unionmember);
Marshal.ThrowExceptionForHR(result);
}
void IOleDataObject.GetDataHere(ref FORMATETC format, ref STGMEDIUM medium)
{
if (_wrapped is IOleDataObject ole)
{
ole.GetDataHere(ref format, ref medium);
return;
}
if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
Marshal.ThrowExceptionForHR(DV_E_TYMED);
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
Marshal.ThrowExceptionForHR(DV_E_DVASPECT);
string fmt = ClipboardFormats.GetFormat(format.cfFormat);
if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt))
Marshal.ThrowExceptionForHR(DV_E_FORMATETC);
if (medium.unionmember == IntPtr.Zero)
Marshal.ThrowExceptionForHR(STG_E_MEDIUMFULL);
int result = WriteDataToHGlobal(fmt, ref medium.unionmember);
Marshal.ThrowExceptionForHR(result);
}
int IOleDataObject.QueryGetData(ref FORMATETC format)
{
if (_wrapped is IOleDataObject ole)
return ole.QueryGetData(ref format);
if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
return DV_E_DVASPECT;
if (!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
return DV_E_TYMED;
string dataFormat = ClipboardFormats.GetFormat(format.cfFormat);
if (!string.IsNullOrEmpty(dataFormat) && _wrapped.Contains(dataFormat))
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
return DV_E_FORMATETC;
}
void IOleDataObject.SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release)
{
if (_wrapped is IOleDataObject ole)
{
ole.SetData(ref formatIn, ref medium, release);
return;
}
Marshal.ThrowExceptionForHR(unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL));
}
private int WriteDataToHGlobal(string dataFormat, ref IntPtr hGlobal)
{
object data = _wrapped.Get(dataFormat);
if (dataFormat == DataFormats.Text || data is string)
return WriteStringToHGlobal(ref hGlobal, Convert.ToString(data));
if (dataFormat == DataFormats.FileNames && data is IEnumerable<string> files)
return WriteFileListToHGlobal(ref hGlobal, files);
if (data is Stream stream)
{
byte[] buffer = new byte[stream.Length - stream.Position];
stream.Read(buffer, 0, buffer.Length);
return WriteBytesToHGlobal(ref hGlobal, buffer);
}
if (data is IEnumerable<byte> bytes)
{
var byteArr = bytes is byte[] ? (byte[])bytes : bytes.ToArray();
return WriteBytesToHGlobal(ref hGlobal, byteArr);
}
return WriteBytesToHGlobal(ref hGlobal, SerializeObject(data));
}
private byte[] SerializeObject(object data)
{
using (var ms = new MemoryStream())
{
ms.Write(SerializedObjectGUID, 0, SerializedObjectGUID.Length);
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(ms, data);
return ms.ToArray();
}
}
private int WriteBytesToHGlobal(ref IntPtr hGlobal, byte[] data)
{
int required = data.Length;
if (hGlobal == IntPtr.Zero)
hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required);
long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
if (required > available)
return STG_E_MEDIUMFULL;
IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
try
{
Marshal.Copy(data, 0, ptr, data.Length);
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
}
finally
{
UnmanagedMethods.GlobalUnlock(hGlobal);
}
}
private int WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable<string> files)
{
if (!files?.Any() ?? false)
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
char[] filesStr = (string.Join("\0", files) + "\0\0").ToCharArray();
_DROPFILES df = new _DROPFILES();
df.pFiles = Marshal.SizeOf<_DROPFILES>();
df.fWide = true;
int required = (filesStr.Length * sizeof(char)) + Marshal.SizeOf<_DROPFILES>();
if (hGlobal == IntPtr.Zero)
hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required);
long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
if (required > available)
return STG_E_MEDIUMFULL;
IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
try
{
Marshal.StructureToPtr(df, ptr, false);
Marshal.Copy(filesStr, 0, ptr + Marshal.SizeOf<_DROPFILES>(), filesStr.Length);
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
}
finally
{
UnmanagedMethods.GlobalUnlock(hGlobal);
}
}
private int WriteStringToHGlobal(ref IntPtr hGlobal, string data)
{
int required = (data.Length + 1) * sizeof(char);
if (hGlobal == IntPtr.Zero)
hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, required);
long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
if (required > available)
return STG_E_MEDIUMFULL;
IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
try
{
char[] chars = (data + '\0').ToCharArray();
Marshal.Copy(chars, 0, ptr, chars.Length);
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
}
finally
{
UnmanagedMethods.GlobalUnlock(hGlobal);
}
}
#endregion
}
}

27
src/Windows/Avalonia.Win32/DragSource.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Threading;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
class DragSource : IPlatformDragSource
{
public Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
{
Dispatcher.UIThread.VerifyAccess();
OleDragSource src = new OleDragSource();
DataObject dataObject = new DataObject(data);
int allowed = (int)OleDropTarget.ConvertDropEffect(allowedEffects);
int[] finalEffect = new int[1];
UnmanagedMethods.DoDragDrop(dataObject, src, allowed, finalEffect);
return Task.FromResult(OleDropTarget.ConvertDropEffect((DropEffect)finalEffect[0]));}
}
}

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

@ -5,6 +5,7 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
// ReSharper disable InconsistentNaming
@ -951,6 +952,32 @@ namespace Avalonia.Win32.Interop
[DllImport("msvcrt.dll", EntryPoint="memcpy", SetLastError = false, CallingConvention=CallingConvention.Cdecl)]
public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IDropTarget target);
[DllImport("ole32.dll", EntryPoint = "OleInitialize")]
public static extern HRESULT OleInitialize(IntPtr val);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern void ReleaseStgMedium(ref STGMEDIUM medium);
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetClipboardFormatName(int format, StringBuilder lpString, int cchMax);
[DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]
public static extern int RegisterClipboardFormat(string format);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr GlobalSize(IntPtr hGlobal);
[DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)]
public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
public static extern void DoDragDrop(IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
public enum MONITOR
{
MONITOR_DEFAULTTONULL = 0x00000000,
@ -990,10 +1017,28 @@ namespace Avalonia.Win32.Interop
MDT_DEFAULT = MDT_EFFECTIVE_DPI
}
public enum ClipboardFormat
public enum ClipboardFormat
{
/// <summary>
/// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. Use this format for ANSI text.
/// </summary>
CF_TEXT = 1,
CF_UNICODETEXT = 13
/// <summary>
/// A handle to a bitmap
/// </summary>
CF_BITMAP = 2,
/// <summary>
/// A memory object containing a BITMAPINFO structure followed by the bitmap bits.
/// </summary>
CF_DIB = 3,
/// <summary>
/// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data.
/// </summary>
CF_UNICODETEXT = 13,
/// <summary>
/// A handle to type HDROP that identifies a list of files.
/// </summary>
CF_HDROP = 15,
}
public struct MSG
@ -1136,7 +1181,9 @@ namespace Avalonia.Win32.Interop
S_FALSE = 0x0001,
S_OK = 0x0000,
E_INVALIDARG = 0x80070057,
E_OUTOFMEMORY = 0x8007000E
E_OUTOFMEMORY = 0x8007000E,
E_NOTIMPL = 0x80004001,
E_UNEXPECTED = 0x8000FFFF,
}
public enum Icons
@ -1300,4 +1347,53 @@ namespace Avalonia.Win32.Interop
uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder);
}
[Flags]
internal enum DropEffect : int
{
None = 0,
Copy = 1,
Move = 2,
Link = 4,
Scroll = -2147483648,
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00000122-0000-0000-C000-000000000046")]
internal interface IDropTarget
{
[PreserveSig]
UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
[PreserveSig]
UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
[PreserveSig]
UnmanagedMethods.HRESULT DragLeave();
[PreserveSig]
UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00000121-0000-0000-C000-000000000046")]
internal interface IDropSource
{
[PreserveSig]
int QueryContinueDrag(int fEscapePressed, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState);
[PreserveSig]
int GiveFeedback([MarshalAs(UnmanagedType.U4)] [In] int dwEffect);
}
[StructLayoutAttribute(LayoutKind.Sequential)]
internal struct _DROPFILES
{
public Int32 pFiles;
public Int32 X;
public Int32 Y;
public bool fNC;
public bool fWide;
}
}

47
src/Windows/Avalonia.Win32/OleContext.cs

@ -0,0 +1,47 @@
using System;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
class OleContext
{
private static OleContext fCurrent;
internal static OleContext Current
{
get
{
if (!IsValidOleThread())
return null;
if (fCurrent == null)
fCurrent = new OleContext();
return fCurrent;
}
}
private OleContext()
{
if (UnmanagedMethods.OleInitialize(IntPtr.Zero) != UnmanagedMethods.HRESULT.S_OK)
throw new SystemException("Failed to initialize OLE");
}
private static bool IsValidOleThread()
{
return Dispatcher.UIThread.CheckAccess() &&
Thread.CurrentThread.GetApartmentState() == ApartmentState.STA;
}
internal bool RegisterDragDrop(IPlatformHandle hwnd, IDropTarget target)
{
if (hwnd?.HandleDescriptor != "HWND" || target == null)
return false;
return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, target) == UnmanagedMethods.HRESULT.S_OK;
}
}
}

171
src/Windows/Avalonia.Win32/OleDataObject.cs

@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Avalonia.Input;
using Avalonia.Win32.Interop;
using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
namespace Avalonia.Win32
{
class OleDataObject : Avalonia.Input.IDataObject
{
private IDataObject _wrapped;
public OleDataObject(IDataObject wrapped)
{
_wrapped = wrapped;
}
public bool Contains(string dataFormat)
{
return GetDataFormatsCore().Any(df => StringComparer.OrdinalIgnoreCase.Equals(df, dataFormat));
}
public IEnumerable<string> GetDataFormats()
{
return GetDataFormatsCore().Distinct();
}
public string GetText()
{
return GetDataFromOleHGLOBAL(DataFormats.Text, DVASPECT.DVASPECT_CONTENT) as string;
}
public IEnumerable<string> GetFileNames()
{
return GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT) as IEnumerable<string>;
}
public object Get(string dataFormat)
{
return GetDataFromOleHGLOBAL(dataFormat, DVASPECT.DVASPECT_CONTENT);
}
private object GetDataFromOleHGLOBAL(string format, DVASPECT aspect)
{
FORMATETC formatEtc = new FORMATETC();
formatEtc.cfFormat = ClipboardFormats.GetFormat(format);
formatEtc.dwAspect = aspect;
formatEtc.lindex = -1;
formatEtc.tymed = TYMED.TYMED_HGLOBAL;
if (_wrapped.QueryGetData(ref formatEtc) == 0)
{
_wrapped.GetData(ref formatEtc, out STGMEDIUM medium);
try
{
if (medium.unionmember != IntPtr.Zero && medium.tymed == TYMED.TYMED_HGLOBAL)
{
if (format == DataFormats.Text)
return ReadStringFromHGlobal(medium.unionmember);
if (format == DataFormats.FileNames)
return ReadFileNamesFromHGlobal(medium.unionmember);
byte[] data = ReadBytesFromHGlobal(medium.unionmember);
if (IsSerializedObject(data))
{
using (var ms = new MemoryStream(data))
{
ms.Position = DataObject.SerializedObjectGUID.Length;
BinaryFormatter binaryFormatter = new BinaryFormatter();
return binaryFormatter.Deserialize(ms);
}
}
return data;
}
}
finally
{
UnmanagedMethods.ReleaseStgMedium(ref medium);
}
}
return null;
}
private bool IsSerializedObject(byte[] data)
{
if (data.Length < DataObject.SerializedObjectGUID.Length)
return false;
for (int i = 0; i < DataObject.SerializedObjectGUID.Length; i++)
if (data[i] != DataObject.SerializedObjectGUID[i])
return false;
return true;
}
private static IEnumerable<string> ReadFileNamesFromHGlobal(IntPtr hGlobal)
{
List<string> files = new List<string>();
int fileCount = UnmanagedMethods.DragQueryFile(hGlobal, -1, null, 0);
if (fileCount > 0)
{
for (int i = 0; i < fileCount; i++)
{
int pathLen = UnmanagedMethods.DragQueryFile(hGlobal, i, null, 0);
StringBuilder sb = new StringBuilder(pathLen+1);
if (UnmanagedMethods.DragQueryFile(hGlobal, i, sb, sb.Capacity) == pathLen)
{
files.Add(sb.ToString());
}
}
}
return files;
}
private static string ReadStringFromHGlobal(IntPtr hGlobal)
{
IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
try
{
return Marshal.PtrToStringAuto(ptr);
}
finally
{
UnmanagedMethods.GlobalUnlock(hGlobal);
}
}
private static byte[] ReadBytesFromHGlobal(IntPtr hGlobal)
{
IntPtr source = UnmanagedMethods.GlobalLock(hGlobal);
try
{
int size = (int)UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
byte[] data = new byte[size];
Marshal.Copy(source, data, 0, size);
return data;
}
finally
{
UnmanagedMethods.GlobalUnlock(hGlobal);
}
}
private IEnumerable<string> GetDataFormatsCore()
{
var enumFormat = _wrapped.EnumFormatEtc(DATADIR.DATADIR_GET);
if (enumFormat != null)
{
enumFormat.Reset();
FORMATETC[] formats = new FORMATETC[1];
int[] fetched = { 1 };
while (fetched[0] > 0)
{
fetched[0] = 0;
if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0)
{
if (formats[0].ptd != IntPtr.Zero)
Marshal.FreeCoTaskMem(formats[0].ptd);
yield return ClipboardFormats.GetFormat(formats[0].cfFormat);
}
}
}
}
}
}

39
src/Windows/Avalonia.Win32/OleDragSource.cs

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
class OleDragSource : IDropSource
{
private const int DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102;
private const int DRAGDROP_S_DROP = 0x00040100;
private const int DRAGDROP_S_CANCEL = 0x00040101;
private const int KEYSTATE_LEFTMB = 1;
private const int KEYSTATE_MIDDLEMB = 16;
private const int KEYSTATE_RIGHTMB = 2;
private static readonly int[] MOUSE_BUTTONS = new int[] { KEYSTATE_LEFTMB, KEYSTATE_MIDDLEMB, KEYSTATE_RIGHTMB };
public int QueryContinueDrag(int fEscapePressed, int grfKeyState)
{
if (fEscapePressed != 0)
return DRAGDROP_S_CANCEL;
int pressedMouseButtons = MOUSE_BUTTONS.Where(mb => (grfKeyState & mb) == mb).Count();
if (pressedMouseButtons >= 2)
return DRAGDROP_S_CANCEL;
if (pressedMouseButtons == 0)
return DRAGDROP_S_DROP;
return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
}
public int GiveFeedback(int dwEffect)
{
return DRAGDROP_S_USEDEFAULTCURSORS;
}
}
}

160
src/Windows/Avalonia.Win32/OleDropTarget.cs

@ -0,0 +1,160 @@
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using IDataObject = Avalonia.Input.IDataObject;
using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
namespace Avalonia.Win32
{
class OleDropTarget : IDropTarget
{
private readonly IInputElement _target;
private readonly ITopLevelImpl _tl;
private readonly IDragDropDevice _dragDevice;
private IDataObject _currentDrag = null;
public OleDropTarget(ITopLevelImpl tl, IInputElement target)
{
_dragDevice = AvaloniaLocator.Current.GetService<IDragDropDevice>();
_tl = tl;
_target = target;
}
public static DropEffect ConvertDropEffect(DragDropEffects operation)
{
DropEffect result = DropEffect.None;
if (operation.HasFlag(DragDropEffects.Copy))
result |= DropEffect.Copy;
if (operation.HasFlag(DragDropEffects.Move))
result |= DropEffect.Move;
if (operation.HasFlag(DragDropEffects.Link))
result |= DropEffect.Link;
return result;
}
public static DragDropEffects ConvertDropEffect(DropEffect effect)
{
DragDropEffects result = DragDropEffects.None;
if (effect.HasFlag(DropEffect.Copy))
result |= DragDropEffects.Copy;
if (effect.HasFlag(DropEffect.Move))
result |= DragDropEffects.Move;
if (effect.HasFlag(DropEffect.Link))
result |= DragDropEffects.Link;
return result;
}
UnmanagedMethods.HRESULT IDropTarget.DragEnter(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect)
{
var dispatch = _tl?.Input;
if (dispatch == null)
{
pdwEffect = DropEffect.None;
return UnmanagedMethods.HRESULT.S_OK;
}
_currentDrag = pDataObj as IDataObject;
if (_currentDrag == null)
_currentDrag = new OleDataObject(pDataObj);
var args = new RawDragEvent(
_dragDevice,
RawDragEventType.DragEnter,
_target,
GetDragLocation(pt),
_currentDrag,
ConvertDropEffect(pdwEffect)
);
dispatch(args);
pdwEffect = ConvertDropEffect(args.Effects);
return UnmanagedMethods.HRESULT.S_OK;
}
UnmanagedMethods.HRESULT IDropTarget.DragOver(int grfKeyState, long pt, ref DropEffect pdwEffect)
{
var dispatch = _tl?.Input;
if (dispatch == null)
{
pdwEffect = DropEffect.None;
return UnmanagedMethods.HRESULT.S_OK;
}
var args = new RawDragEvent(
_dragDevice,
RawDragEventType.DragOver,
_target,
GetDragLocation(pt),
_currentDrag,
ConvertDropEffect(pdwEffect)
);
dispatch(args);
pdwEffect = ConvertDropEffect(args.Effects);
return UnmanagedMethods.HRESULT.S_OK;
}
UnmanagedMethods.HRESULT IDropTarget.DragLeave()
{
try
{
_tl?.Input(new RawDragEvent(
_dragDevice,
RawDragEventType.DragLeave,
_target,
default(Point),
null,
DragDropEffects.None
));
return UnmanagedMethods.HRESULT.S_OK;
}
finally
{
_currentDrag = null;
}
}
UnmanagedMethods.HRESULT IDropTarget.Drop(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect)
{
try
{
var dispatch = _tl?.Input;
if (dispatch == null)
{
pdwEffect = DropEffect.None;
return UnmanagedMethods.HRESULT.S_OK;
}
_currentDrag = pDataObj as IDataObject;
if (_currentDrag == null)
_currentDrag= new OleDataObject(pDataObj);
var args = new RawDragEvent(
_dragDevice,
RawDragEventType.Drop,
_target,
GetDragLocation(pt),
_currentDrag,
ConvertDropEffect(pdwEffect)
);
dispatch(args);
pdwEffect = ConvertDropEffect(args.Effects);
return UnmanagedMethods.HRESULT.S_OK;
}
finally
{
_currentDrag = null;
}
}
private Point GetDragLocation(long dragPoint)
{
int x = (int)dragPoint;
int y = (int)(dragPoint >> 32);
Point screenPt = new Point(x, y);
return _target.PointToClient(screenPt);
}
}
}

3
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -86,6 +86,9 @@ namespace Avalonia.Win32
.Bind<IWindowingPlatform>().ToConstant(s_instance)
.Bind<IPlatformIconLoader>().ToConstant(s_instance);
if (OleContext.Current != null)
AvaloniaLocator.CurrentMutable.Bind<IPlatformDragSource>().ToSingleton<DragSource>();
UseDeferredRendering = deferredRendering;
_uiThread = UnmanagedMethods.GetCurrentThreadId();
}

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

@ -34,6 +34,7 @@ namespace Avalonia.Win32
private double _scaling = 1;
private WindowState _showWindowState;
private FramebufferManager _framebuffer;
private OleDropTarget _dropTarget;
#if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag;
#endif
@ -310,6 +311,7 @@ namespace Avalonia.Win32
public void SetInputRoot(IInputRoot inputRoot)
{
_owner = inputRoot;
CreateDropTarget();
}
public void SetTitle(string title)
@ -699,6 +701,13 @@ namespace Avalonia.Win32
}
}
private void CreateDropTarget()
{
OleDropTarget odt = new OleDropTarget(this, _owner);
if (OleContext.Current?.RegisterDragDrop(Handle, odt) ?? false)
_dropTarget = odt;
}
private Point DipFromLParam(IntPtr lParam)
{
return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling;

2
tests/Avalonia.Benchmarks/Styling/ApplyStyling.cs

@ -33,7 +33,7 @@ namespace Avalonia.Benchmarks.Styling
var border = (Border)textBox.GetVisualChildren().Single();
if (border.BorderThickness != 2)
if (border.BorderThickness != new Thickness(2))
{
throw new Exception("Styles not applied.");
}

1042
tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs

File diff suppressed because it is too large

24
tests/Avalonia.Controls.UnitTests/BorderTests.cs

@ -13,12 +13,34 @@ namespace Avalonia.Controls.UnitTests
var target = new Border
{
Padding = new Thickness(6),
BorderThickness = 4,
BorderThickness = new Thickness(4)
};
target.Measure(new Size(100, 100));
Assert.Equal(new Size(20, 20), target.DesiredSize);
}
[Fact]
public void Child_Should_Arrange_With_Zero_Height_Width_If_Padding_Greater_Than_Child_Size()
{
Border content;
var target = new Border
{
Padding = new Thickness(6),
MaxHeight = 12,
MaxWidth = 12,
Child = content = new Border
{
Height = 0,
Width = 0
}
};
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(6, 6, 0, 0), content.Bounds);
}
}
}

50
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Layout.cs

@ -80,6 +80,31 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), content.Bounds);
}
[Fact]
public void Should_Correctly_Align_Child_With_Fixed_Size()
{
Border content;
var target = new ContentPresenter
{
HorizontalContentAlignment = HorizontalAlignment.Stretch,
VerticalContentAlignment = VerticalAlignment.Stretch,
Content = content = new Border
{
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Bottom,
Width = 16,
Height = 16,
},
};
target.UpdateChild();
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
// Check correct result for Issue #1447.
Assert.Equal(new Rect(0, 84, 16, 16), content.Bounds);
}
[Fact]
public void Content_Can_Be_Stretched()
{
@ -185,5 +210,30 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(new Rect(84, 0, 16, 16), content.Bounds);
}
[Fact]
public void Child_Arrange_With_Zero_Height_When_Padding_Height_Greater_Than_Child_Height()
{
Border content;
var target = new ContentPresenter
{
Padding = new Thickness(32),
MaxHeight = 32,
MaxWidth = 32,
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center,
Content = content = new Border
{
Height = 0,
Width = 0,
},
};
target.UpdateChild();
target.Arrange(new Rect(0, 0, 100, 100));
Assert.Equal(new Rect(48, 48, 0, 0), content.Bounds);
}
}
}

7
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -8,6 +8,7 @@ using Avalonia.Markup.Xaml.Data;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Styling;
using Avalonia.UnitTests;
using System.Collections;
@ -359,8 +360,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var control = AvaloniaXamlLoader.Parse<UserControl>(xaml);
var bk = control.Background;
Assert.IsType<SolidColorBrush>(bk);
Assert.Equal(Colors.White, (bk as SolidColorBrush).Color);
Assert.IsType<ImmutableSolidColorBrush>(bk);
Assert.Equal(Colors.White, (bk as ISolidColorBrush).Color);
}
[Fact]
@ -496,7 +497,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.NotNull(brush);
Assert.Equal(Colors.White, ((SolidColorBrush)brush).Color);
Assert.Equal(Colors.White, ((ISolidColorBrush)brush).Color);
style.TryGetResource("Double", out var d);

66
tests/Avalonia.RenderTests/Controls/BorderTests.cs

@ -31,7 +31,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 1,
BorderThickness = new Thickness(1),
}
};
@ -50,7 +50,47 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
}
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task Border_Uniform_CornerRadius()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = new Thickness(2),
CornerRadius = new CornerRadius(16),
}
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task Border_NonUniform_CornerRadius()
{
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = new Thickness(2),
CornerRadius = new CornerRadius(16, 4, 7, 10),
}
};
@ -87,7 +127,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new Border
{
Background = Brushes.Red,
@ -110,7 +150,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Padding = new Thickness(2),
Child = new Border
{
@ -134,7 +174,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new Border
{
Background = Brushes.Red,
@ -159,7 +199,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@ -186,7 +226,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@ -213,7 +253,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@ -240,7 +280,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@ -267,7 +307,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@ -294,7 +334,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@ -321,7 +361,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",
@ -348,7 +388,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
Child = new Border
{
BorderBrush = Brushes.Black,
BorderThickness = 2,
BorderThickness = new Thickness(2),
Child = new TextBlock
{
Text = "Foo",

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

Loading…
Cancel
Save