Browse Source

Merge branch 'master' into fixes/5027-avaloniaobject-batching

pull/5070/head
Dariusz Komosiński 5 years ago
committed by GitHub
parent
commit
88bc11b9e5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 8
      .github/ISSUE_TEMPLATE/config.yml
  3. 1
      .github/ISSUE_TEMPLATE/feature_request.md
  4. 22
      native/Avalonia.Native/src/OSX/cursor.mm
  5. 8
      native/Avalonia.Native/src/OSX/window.mm
  6. BIN
      samples/ControlCatalog/Assets/avalonia-32.png
  7. 4
      samples/ControlCatalog/MainView.xaml
  8. 8
      samples/ControlCatalog/Pages/AcrylicPage.xaml
  9. 29
      samples/ControlCatalog/Pages/CursorPage.xaml
  10. 20
      samples/ControlCatalog/Pages/CursorPage.xaml.cs
  11. 5
      samples/ControlCatalog/Pages/DataGridPage.xaml
  12. 6
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  13. 7
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  14. 15
      samples/ControlCatalog/Pages/SliderPage.xaml
  15. 44
      samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
  16. 28
      src/Avalonia.Base/Data/BindingOperations.cs
  17. 30
      src/Avalonia.Base/EnumExtensions.cs
  18. 5
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  19. 4
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  20. 2
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  21. 21
      src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
  22. 16
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  23. 5
      src/Avalonia.Controls.DataGrid/DataGridColumns.cs
  24. 6
      src/Avalonia.Controls/ApiCompatBaseline.txt
  25. 13
      src/Avalonia.Controls/Button.cs
  26. 2
      src/Avalonia.Controls/ComboBox.cs
  27. 77
      src/Avalonia.Controls/ContextMenu.cs
  28. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  29. 55
      src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
  30. 45
      src/Avalonia.Controls/Grid.cs
  31. 8
      src/Avalonia.Controls/ListBox.cs
  32. 54
      src/Avalonia.Controls/MenuItem.cs
  33. 6
      src/Avalonia.Controls/NativeControlHost.cs
  34. 2
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  35. 12
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  36. 4
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  37. 21
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  38. 48
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  39. 8
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  40. 4
      src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs
  41. 47
      src/Avalonia.Controls/Slider.cs
  42. 4
      src/Avalonia.Controls/TextBox.cs
  43. 2
      src/Avalonia.Controls/TopLevel.cs
  44. 12
      src/Avalonia.Controls/TreeView.cs
  45. 2
      src/Avalonia.Controls/Utils/AncestorFinder.cs
  46. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  47. 12
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  48. 2
      src/Avalonia.Dialogs/ManagedFileChooserSources.cs
  49. 8
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  50. 4
      src/Avalonia.Headless.Vnc/HeadlessVncFramebufferSource.cs
  51. 2
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  52. 8
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  53. 2
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  54. 2
      src/Avalonia.Input/AccessKeyHandler.cs
  55. 4
      src/Avalonia.Input/ApiCompatBaseline.txt
  56. 39
      src/Avalonia.Input/Cursor.cs
  57. 4
      src/Avalonia.Input/FocusManager.cs
  58. 12
      src/Avalonia.Input/Platform/ICursorFactory.cs
  59. 14
      src/Avalonia.Input/Platform/ICursorImpl.cs
  60. 9
      src/Avalonia.Input/Platform/IStandardCursorFactory.cs
  61. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  62. 25
      src/Avalonia.Native/Cursor.cs
  63. 6
      src/Avalonia.Native/WindowImplBase.cs
  64. 1
      src/Avalonia.Native/avn.idl
  65. 2
      src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs
  66. 2
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  67. 12
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  68. 14
      src/Avalonia.ReactiveUI/ReactiveWindow.cs
  69. 16
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  70. 16
      src/Avalonia.ReactiveUI/TransitioningContentControl.cs
  71. 12
      src/Avalonia.ReactiveUI/ViewModelViewHost.cs
  72. 5
      src/Avalonia.Themes.Default/ProgressBar.xaml
  73. 5
      src/Avalonia.Themes.Default/Slider.xaml
  74. 6
      src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml
  75. 12
      src/Avalonia.Themes.Fluent/Controls/Slider.xaml
  76. 4
      src/Avalonia.Visuals/Media/FormattedText.cs
  77. 18
      src/Avalonia.Visuals/Media/PathGeometryCollections.cs
  78. 56
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
  79. 27
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs
  80. 540
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  81. 74
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs
  82. 73
      src/Avalonia.X11/X11CursorFactory.cs
  83. 2
      src/Avalonia.X11/X11Platform.cs
  84. 2
      src/Avalonia.X11/X11Structs.cs
  85. 4
      src/Avalonia.X11/X11Window.Ime.cs
  86. 26
      src/Avalonia.X11/X11Window.cs
  87. 10
      src/Avalonia.X11/XI2Manager.cs
  88. 7
      src/Avalonia.X11/XLib.cs
  89. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  90. 9
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  91. 2
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
  92. 9
      src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
  93. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  94. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  95. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  96. 11
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  97. 2
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  98. 49
      src/Markup/Avalonia.Markup/Data/Binding.cs
  99. 49
      src/Markup/Avalonia.Markup/Data/BindingBase.cs
  100. 14
      src/Markup/Avalonia.Markup/Data/MultiBinding.cs

7
.github/ISSUE_TEMPLATE/bug_report.md

@ -4,7 +4,6 @@ about: Create a report to help us improve Avalonia
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@ -24,8 +24,9 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Mac, Linux (State distribution)]
- Version [e.g. 0.10.0-rc1 or 0.9.12]
- OS: [e.g. Windows, Mac, Linux (State distribution)]
- Version [e.g. 0.10.0-rc1 or 0.9.12]
**Additional context**
Add any other context about the problem here.

8
.github/ISSUE_TEMPLATE/config.yml

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Questions, Discussions, Ideas
url: https://github.com/AvaloniaUI/Avalonia/discussions/new
about: Please ask and answer questions here.
- name: Avalonia Community Support on Gitter
url: https://gitter.im/AvaloniaUI/Avalonia
about: Please ask and answer questions here.

1
.github/ISSUE_TEMPLATE/feature_request.md

@ -4,7 +4,6 @@ about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**

22
native/Avalonia.Native/src/OSX/cursor.mm

@ -62,6 +62,28 @@ public:
return S_OK;
}
virtual HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut) override
{
if(bitmapData == nullptr || retOut == nullptr)
{
return E_POINTER;
}
NSData *imageData = [NSData dataWithBytes:bitmapData length:length];
NSImage *image = [[NSImage alloc] initWithData:imageData];
NSPoint hotSpot;
hotSpot.x = hotPixel.Width;
hotSpot.y = hotPixel.Height;
*retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]);
(*retOut)->AddRef();
return S_OK;
}
};
extern IAvnCursorFactory* CreateCursorFactory()

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

@ -2068,17 +2068,17 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
-(void)becomeKeyWindow
{
[self showWindowMenuWithAppMenu];
if([self activateAppropriateChild: true])
{
[self showWindowMenuWithAppMenu];
if(_parent != nullptr)
{
_parent->BaseEvents->Activated();
}
[super becomeKeyWindow];
}
[super becomeKeyWindow];
}
-(void) restoreParentWindow;

BIN
samples/ControlCatalog/Assets/avalonia-32.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

4
samples/ControlCatalog/MainView.xaml

@ -22,6 +22,10 @@
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="Cursor"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:CursorPage/>
</TabItem>
<TabItem Header="DataGrid"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">

8
samples/ControlCatalog/Pages/AcrylicPage.xaml

@ -16,13 +16,13 @@
<StackPanel Spacing="5" Margin="40 10">
<StackPanel Orientation="Horizontal">
<TextBlock Text="TintOpacity" Foreground="Black" />
<Slider Name="TintOpacitySlider" Minimum="0" Maximum="1" Value="0.9" Width="400" />
<TextBlock Text="{Binding #TintOpacitySlider.Value}" Foreground="Black" />
<Slider Name="TintOpacitySlider" Minimum="0" Maximum="1" Value="0.9" SmallChange="0.1" LargeChange="0.2" Width="400" />
<TextBlock Text="{Binding #TintOpacitySlider.Value, StringFormat=\{0:0.#\}}" Foreground="Black" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="MaterialOpacity" Foreground="Black" />
<Slider Name="MaterialOpacitySlider" Minimum="0" Maximum="1" Value="0.8" Width="400" />
<TextBlock Text="{Binding #MaterialOpacitySlider.Value}" Foreground="Black" />
<Slider Name="MaterialOpacitySlider" Minimum="0" Maximum="1" Value="0.8" SmallChange="0.1" LargeChange="0.2" Width="400" />
<TextBlock Text="{Binding #MaterialOpacitySlider.Value, StringFormat=\{0:0.#\}}" Foreground="Black" />
</StackPanel>
</StackPanel>
</ExperimentalAcrylicBorder>

29
samples/ControlCatalog/Pages/CursorPage.xaml

@ -0,0 +1,29 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.CursorPage">
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*">
<StackPanel Grid.ColumnSpan="2" Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Cursor</TextBlock>
<TextBlock Classes="h2">Defines a cursor (mouse pointer)</TextBlock>
</StackPanel>
<ListBox Grid.Row="1" Items="{Binding StandardCursors}" Margin="0 8 8 8">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Cursor" Value="{Binding Cursor}"/>
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Grid.Row="1" Margin="8 8 0 8">
<Button Cursor="{Binding CustomCursor}" Margin="0 8" Padding="16">
<TextBlock>Custom Cursor</TextBlock>
</Button>
</StackPanel>
</Grid>
</UserControl>

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

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

5
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -1,5 +1,5 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:local="clr-namespace:ControlCatalog.Models;assembly=ControlCatalog"
xmlns:local="using:ControlCatalog.Models"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.DataGridPage">
<UserControl.Resources>
@ -26,7 +26,8 @@
<DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All">
<DataGrid.Columns>
<DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" />
<DataGridTextColumn Header="Region" Binding="{Binding Region}" Width="4*" />
<!-- CompiledBinding example of usage. -->
<DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" />
<DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" />
<DataGridTextColumn Header="Area" Binding="{Binding Area}" Width="3*" />
<DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*" CellStyleClasses="gdp" />

6
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@ -24,8 +24,10 @@ namespace ControlCatalog.Pages
dg1.LoadingRow += Dg1_LoadingRow;
dg1.Sorting += (s, a) =>
{
var property = ((a.Column as DataGridBoundColumn)?.Binding as Binding).Path;
if (property == dataGridSortDescription.PropertyPath
var binding = (a.Column as DataGridBoundColumn)?.Binding as Binding;
if (binding?.Path is string property
&& property == dataGridSortDescription.PropertyPath
&& !collectionView1.SortDescriptions.Contains(dataGridSortDescription))
{
collectionView1.SortDescriptions.Add(dataGridSortDescription);

7
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@ -15,6 +15,13 @@
<Slider Name="hprogress" Maximum="100" Value="40" />
<Slider Name="vprogress" Maximum="100" Value="60" />
</StackPanel>
<StackPanel Spacing="10">
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
<ProgressBar VerticalAlignment="Center" Value="5" Maximum="10" />
<ProgressBar VerticalAlignment="Center" Value="50" />
<ProgressBar VerticalAlignment="Center" Value="50" Minimum="25" Maximum="75" />
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

15
samples/ControlCatalog/Pages/SliderPage.xaml

@ -45,6 +45,12 @@
<sys:Exception />
</DataValidationErrors.Error>
</Slider>
<Slider Value="0"
IsDirectionReversed="True"
Minimum="0"
Maximum="100"
TickFrequency="10"
Width="300" />
</StackPanel>
<Slider Value="0"
Minimum="0"
@ -54,6 +60,15 @@
TickPlacement="Outside"
TickFrequency="10"
Height="300"/>
<Slider Value="0"
IsDirectionReversed="True"
Minimum="0"
Maximum="100"
Orientation="Vertical"
IsSnapToTickEnabled="True"
TickPlacement="Outside"
TickFrequency="10"
Height="300"/>
</StackPanel>
</StackPanel>

44
samples/ControlCatalog/ViewModels/CursorPageViewModel.cs

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class CursorPageViewModel : ViewModelBase
{
public CursorPageViewModel()
{
StandardCursors = Enum.GetValues(typeof(StandardCursorType))
.Cast<StandardCursorType>()
.Select(x => new StandardCursorModel(x))
.ToList();
var loader = AvaloniaLocator.Current.GetService<IAssetLoader>();
var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png"));
var bitmap = new Bitmap(s);
CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16));
}
public IEnumerable<StandardCursorModel> StandardCursors { get; }
public Cursor CustomCursor { get; }
public class StandardCursorModel
{
public StandardCursorModel(StandardCursorType type)
{
Type = type;
Cursor = new Cursor(type);
}
public StandardCursorType Type { get; }
public Cursor Cursor { get; }
}
}
}

28
src/Avalonia.Base/Data/BindingOperations.cs

@ -45,7 +45,7 @@ namespace Avalonia.Data
case BindingMode.OneWay:
return target.Bind(property, binding.Observable ?? binding.Subject, binding.Priority);
case BindingMode.TwoWay:
return new CompositeDisposable(
return new TwoWayBindingDisposable(
target.Bind(property, binding.Subject, binding.Priority),
target.GetObservable(property).Subscribe(binding.Subject));
case BindingMode.OneTime:
@ -88,6 +88,32 @@ namespace Avalonia.Data
throw new ArgumentException("Invalid binding mode.");
}
}
private sealed class TwoWayBindingDisposable : IDisposable
{
private readonly IDisposable _first;
private readonly IDisposable _second;
private bool _isDisposed;
public TwoWayBindingDisposable(IDisposable first, IDisposable second)
{
_first = first;
_second = second;
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
_first.Dispose();
_second.Dispose();
_isDisposed = true;
}
}
}
public sealed class DoNothingType

30
src/Avalonia.Base/EnumExtensions.cs

@ -11,10 +11,32 @@ namespace Avalonia
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
{
var intValue = *(int*)&value;
var intFlag = *(int*)&flag;
return (intValue & intFlag) == intFlag;
if (sizeof(T) == 1)
{
var byteValue = Unsafe.As<T, byte>(ref value);
var byteFlag = Unsafe.As<T, byte>(ref flag);
return (byteValue & byteFlag) == byteFlag;
}
else if (sizeof(T) == 2)
{
var shortValue = Unsafe.As<T, short>(ref value);
var shortFlag = Unsafe.As<T, short>(ref flag);
return (shortValue & shortFlag) == shortFlag;
}
else if (sizeof(T) == 4)
{
var intValue = Unsafe.As<T, int>(ref value);
var intFlag = Unsafe.As<T, int>(ref flag);
return (intValue & intFlag) == intFlag;
}
else if (sizeof(T) == 8)
{
var longValue = Unsafe.As<T, long>(ref value);
var longFlag = Unsafe.As<T, long>(ref flag);
return (longValue & longFlag) == longFlag;
}
else
throw new NotSupportedException("Enum with size of " + Unsafe.SizeOf<T>() + " are not supported");
}
}
}

5
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Reactive
{
@ -55,9 +56,9 @@ namespace Avalonia.Reactive
newValue = (T)e.Sender.GetValue(e.Property);
}
if (!Equals(newValue, _value))
if (!EqualityComparer<T>.Default.Equals(newValue, _value))
{
_value = (T)newValue;
_value = newValue;
PublishNext(_value);
}
}

4
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -372,8 +372,8 @@ namespace Avalonia.Utilities
const string implicitName = "op_Implicit";
const string explicitName = "op_Explicit";
bool allowImplicit = (operatorType & OperatorType.Implicit) != 0;
bool allowExplicit = (operatorType & OperatorType.Explicit) != 0;
bool allowImplicit = operatorType.HasFlagCustom(OperatorType.Implicit);
bool allowExplicit = operatorType.HasFlagCustom(OperatorType.Explicit);
foreach (MethodInfo method in fromType.GetMethods())
{

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

@ -2595,7 +2595,7 @@ namespace Avalonia.Collections
/// <returns>Whether the specified flag is set</returns>
private bool CheckFlag(CollectionViewFlags flags)
{
return (_flags & flags) != 0;
return _flags.HasFlagCustom(flags);
}
/// <summary>

21
src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs

@ -10,7 +10,8 @@ using System.Reactive.Disposables;
using System.Reactive.Subjects;
using Avalonia.Reactive;
using System.Diagnostics;
using Avalonia.Controls.Utils;
using Avalonia.Controls.Utils;
using Avalonia.Markup.Xaml.MarkupExtensions;
namespace Avalonia.Controls
{
@ -47,14 +48,15 @@ namespace Avalonia.Controls
if (_binding != null)
{
if(_binding is Avalonia.Data.Binding binding)
if(_binding is BindingBase binding)
{
if (binding.Mode == BindingMode.OneWayToSource)
{
throw new InvalidOperationException("DataGridColumn doesn't support BindingMode.OneWayToSource. Use BindingMode.TwoWay instead.");
}
if (!String.IsNullOrEmpty(binding.Path) && binding.Mode == BindingMode.Default)
var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString();
if (!string.IsNullOrEmpty(path) && binding.Mode == BindingMode.Default)
{
binding.Mode = BindingMode.TwoWay;
}
@ -136,13 +138,16 @@ namespace Avalonia.Controls
internal void SetHeaderFromBinding()
{
if (OwningGrid != null && OwningGrid.DataConnection.DataType != null
&& Header == null && Binding != null && Binding is Binding binding
&& !String.IsNullOrWhiteSpace(binding.Path))
&& Header == null && Binding != null && Binding is BindingBase binding)
{
string header = OwningGrid.DataConnection.DataType.GetDisplayName(binding.Path);
if (header != null)
var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString();
if (!string.IsNullOrWhiteSpace(path))
{
Header = header;
var header = OwningGrid.DataConnection.DataType.GetDisplayName(path);
if (header != null)
{
Header = header;
}
}
}
}

16
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -12,6 +12,7 @@ using System;
using System.Linq;
using System.Diagnostics;
using Avalonia.Controls.Utils;
using Avalonia.Markup.Xaml.MarkupExtensions;
namespace Avalonia.Controls
{
@ -1033,13 +1034,16 @@ namespace Avalonia.Controls
if (String.IsNullOrEmpty(result))
{
if(this is DataGridBoundColumn boundColumn &&
boundColumn.Binding != null &&
boundColumn.Binding is Binding binding &&
binding.Path != null)
if (this is DataGridBoundColumn boundColumn)
{
result = binding.Path;
if (boundColumn.Binding is Binding binding)
{
result = binding.Path;
}
else if (boundColumn.Binding is CompiledBindingExtension compiledBinding)
{
result = compiledBinding.Path.ToString();
}
}
}

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

@ -5,6 +5,7 @@
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Utilities;
using System;
using System.Collections.Generic;
@ -141,9 +142,9 @@ namespace Avalonia.Controls
Debug.Assert(dataGridColumn != null);
if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn &&
dataGridBoundColumn.Binding is Binding binding)
dataGridBoundColumn.Binding is BindingBase binding)
{
string path = binding.Path;
var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString();
if (string.IsNullOrWhiteSpace(path))
{

6
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -0,0 +1,6 @@
Compat issues with assembly Avalonia.Controls:
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 4

13
src/Avalonia.Controls/Button.cs

@ -80,6 +80,7 @@ namespace Avalonia.Controls
private ICommand _command;
private bool _commandCanExecute = true;
private KeyGesture _hotkey;
/// <summary>
/// Initializes static members of the <see cref="Button"/> class.
@ -207,6 +208,11 @@ namespace Avalonia.Controls
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
{
HotKey = _hotkey;
}
base.OnAttachedToLogicalTree(e);
if (Command != null)
@ -217,6 +223,13 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
// This will cause the hotkey manager to dispose the observer and the reference to this control
if (HotKey != null)
{
_hotkey = HotKey;
HotKey = null;
}
base.OnDetachedFromLogicalTree(e);
if (Command != null)

2
src/Avalonia.Controls/ComboBox.cs

@ -188,7 +188,7 @@ namespace Avalonia.Controls
return;
if (e.Key == Key.F4 ||
((e.Key == Key.Down || e.Key == Key.Up) && ((e.KeyModifiers & KeyModifiers.Alt) != 0)))
((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasFlagCustom(KeyModifiers.Alt)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;

77
src/Avalonia.Controls/ContextMenu.cs

@ -269,7 +269,43 @@ namespace Avalonia.Controls
}
control ??= _attachedControls![0];
Open(control, PlacementTarget ?? control);
}
/// <summary>
/// Closes the menu.
/// </summary>
public override void Close()
{
if (!IsOpen)
{
return;
}
if (_popup != null && _popup.IsVisible)
{
_popup.IsOpen = false;
}
}
void ISetterValue.Initialize(ISetter setter)
{
// ContextMenu can be assigned to the ContextMenu property in a setter. This overrides
// the behavior defined in Control which requires controls to be wrapped in a <template>.
if (!(setter is Setter s && s.Property == ContextMenuProperty))
{
throw new InvalidOperationException(
"Cannot use a control as a Setter value. Wrap the control in a <Template>.");
}
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
}
private void Open(Control control, Control placementTarget)
{
if (IsOpen)
{
return;
@ -286,7 +322,6 @@ namespace Avalonia.Controls
PlacementGravity = PlacementGravity,
PlacementMode = PlacementMode,
PlacementRect = PlacementRect,
PlacementTarget = PlacementTarget ?? control,
IsLightDismissEnabled = true,
OverlayDismissEventPassThrough = true,
WindowManagerAddShadowHint = WindowManagerAddShadowHint,
@ -302,11 +337,7 @@ namespace Avalonia.Controls
((ISetLogicalParent)_popup).SetParent(control);
}
if (PlacementTarget is null && _popup.PlacementTarget != control)
{
_popup.PlacementTarget = control;
}
_popup.PlacementTarget = placementTarget;
_popup.Child = this;
IsOpen = true;
_popup.IsOpen = true;
@ -318,38 +349,6 @@ namespace Avalonia.Controls
});
}
/// <summary>
/// Closes the menu.
/// </summary>
public override void Close()
{
if (!IsOpen)
{
return;
}
if (_popup != null && _popup.IsVisible)
{
_popup.IsOpen = false;
}
}
void ISetterValue.Initialize(ISetter setter)
{
// ContextMenu can be assigned to the ContextMenu property in a setter. This overrides
// the behavior defined in Control which requires controls to be wrapped in a <template>.
if (!(setter is Setter s && s.Property == ContextMenuProperty))
{
throw new InvalidOperationException(
"Cannot use a control as a Setter value. Wrap the control in a <Template>.");
}
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
}
private void PopupOpened(object sender, EventArgs e)
{
_previousFocus = FocusManager.Instance?.Current;
@ -403,7 +402,7 @@ namespace Avalonia.Controls
if (contextMenu.CancelOpening())
return;
contextMenu.Open(control);
contextMenu.Open(control, e.Source as Control ?? control);
e.Handled = true;
}
}

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

@ -61,7 +61,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
public virtual PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1);
public virtual void SetCursor(IPlatformHandle cursor)
public virtual void SetCursor(ICursorImpl cursor)
{
}

55
src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs

@ -1,4 +1,10 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.LogicalTree;
using Avalonia.Reactive;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Generators
{
@ -16,11 +22,15 @@ namespace Avalonia.Controls.Generators
{
var tabItem = (TabItem)base.CreateContainer(item);
tabItem[~TabControl.TabStripPlacementProperty] = Owner[~TabControl.TabStripPlacementProperty];
tabItem.Bind(TabItem.TabStripPlacementProperty, new OwnerBinding<Dock>(
tabItem,
TabControl.TabStripPlacementProperty));
if (tabItem.HeaderTemplate == null)
{
tabItem[~HeaderedContentControl.HeaderTemplateProperty] = Owner[~ItemsControl.ItemTemplateProperty];
tabItem.Bind(TabItem.HeaderTemplateProperty, new OwnerBinding<IDataTemplate>(
tabItem,
TabControl.ItemTemplateProperty));
}
if (tabItem.Header == null)
@ -40,10 +50,49 @@ namespace Avalonia.Controls.Generators
if (!(tabItem.Content is IControl))
{
tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty];
tabItem.Bind(TabItem.ContentTemplateProperty, new OwnerBinding<IDataTemplate>(
tabItem,
TabControl.ContentTemplateProperty));
}
return tabItem;
}
private class OwnerBinding<T> : SingleSubscriberObservableBase<T>
{
private readonly TabItem _item;
private readonly StyledProperty<T> _ownerProperty;
private IDisposable _ownerSubscription;
private IDisposable _propertySubscription;
public OwnerBinding(TabItem item, StyledProperty<T> ownerProperty)
{
_item = item;
_ownerProperty = ownerProperty;
}
protected override void Subscribed()
{
_ownerSubscription = ControlLocator.Track(_item, 0, typeof(TabControl)).Subscribe(OwnerChanged);
}
protected override void Unsubscribed()
{
_ownerSubscription?.Dispose();
_ownerSubscription = null;
}
private void OwnerChanged(ILogical c)
{
_propertySubscription?.Dispose();
_propertySubscription = null;
if (c is TabControl tabControl)
{
_propertySubscription = tabControl.GetObservable(_ownerProperty)
.Subscribe(x => PublishNext(x));
}
}
}
}
}

45
src/Avalonia.Controls/Grid.cs

@ -637,7 +637,7 @@ namespace Avalonia.Controls
/// </summary>
internal bool MeasureOverrideInProgress
{
get { return (CheckFlagsAnd(Flags.MeasureOverrideInProgress)); }
get { return CheckFlags(Flags.MeasureOverrideInProgress); }
set { SetFlags(value, Flags.MeasureOverrideInProgress); }
}
@ -646,7 +646,7 @@ namespace Avalonia.Controls
/// </summary>
internal bool ArrangeOverrideInProgress
{
get { return (CheckFlagsAnd(Flags.ArrangeOverrideInProgress)); }
get { return CheckFlags(Flags.ArrangeOverrideInProgress); }
set { SetFlags(value, Flags.ArrangeOverrideInProgress); }
}
@ -2350,25 +2350,12 @@ namespace Avalonia.Controls
}
/// <summary>
/// CheckFlagsAnd returns <c>true</c> if all the flags in the
/// CheckFlags returns <c>true</c> if all the flags in the
/// given bitmask are set on the object.
/// </summary>
private bool CheckFlagsAnd(Flags flags)
private bool CheckFlags(Flags flags)
{
return ((_flags & flags) == flags);
}
/// <summary>
/// CheckFlagsOr returns <c>true</c> if at least one flag in the
/// given bitmask is set.
/// </summary>
/// <remarks>
/// If no bits are set in the given bitmask, the method returns
/// <c>true</c>.
/// </remarks>
private bool CheckFlagsOr(Flags flags)
{
return (flags == 0 || (_flags & flags) != 0);
return _flags.HasFlagCustom(flags);
}
private static void OnShowGridLinesPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
@ -2535,7 +2522,7 @@ namespace Avalonia.Controls
/// </summary>
private bool CellsStructureDirty
{
get { return (!CheckFlagsAnd(Flags.ValidCellsStructure)); }
get { return !CheckFlags(Flags.ValidCellsStructure); }
set { SetFlags(!value, Flags.ValidCellsStructure); }
}
@ -2544,7 +2531,7 @@ namespace Avalonia.Controls
/// </summary>
private bool ListenToNotifications
{
get { return (CheckFlagsAnd(Flags.ListenToNotifications)); }
get { return CheckFlags(Flags.ListenToNotifications); }
set { SetFlags(value, Flags.ListenToNotifications); }
}
@ -2553,7 +2540,7 @@ namespace Avalonia.Controls
/// </summary>
private bool SizeToContentU
{
get { return (CheckFlagsAnd(Flags.SizeToContentU)); }
get { return CheckFlags(Flags.SizeToContentU); }
set { SetFlags(value, Flags.SizeToContentU); }
}
@ -2562,7 +2549,7 @@ namespace Avalonia.Controls
/// </summary>
private bool SizeToContentV
{
get { return (CheckFlagsAnd(Flags.SizeToContentV)); }
get { return CheckFlags(Flags.SizeToContentV); }
set { SetFlags(value, Flags.SizeToContentV); }
}
@ -2571,7 +2558,7 @@ namespace Avalonia.Controls
/// </summary>
private bool HasStarCellsU
{
get { return (CheckFlagsAnd(Flags.HasStarCellsU)); }
get { return CheckFlags(Flags.HasStarCellsU); }
set { SetFlags(value, Flags.HasStarCellsU); }
}
@ -2580,7 +2567,7 @@ namespace Avalonia.Controls
/// </summary>
private bool HasStarCellsV
{
get { return (CheckFlagsAnd(Flags.HasStarCellsV)); }
get { return CheckFlags(Flags.HasStarCellsV); }
set { SetFlags(value, Flags.HasStarCellsV); }
}
@ -2589,7 +2576,7 @@ namespace Avalonia.Controls
/// </summary>
private bool HasGroup3CellsInAutoRows
{
get { return (CheckFlagsAnd(Flags.HasGroup3CellsInAutoRows)); }
get { return CheckFlags(Flags.HasGroup3CellsInAutoRows); }
set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); }
}
@ -2803,10 +2790,10 @@ namespace Avalonia.Controls
internal LayoutTimeSizeType SizeTypeU;
internal LayoutTimeSizeType SizeTypeV;
internal int Next;
internal bool IsStarU { get { return ((SizeTypeU & LayoutTimeSizeType.Star) != 0); } }
internal bool IsAutoU { get { return ((SizeTypeU & LayoutTimeSizeType.Auto) != 0); } }
internal bool IsStarV { get { return ((SizeTypeV & LayoutTimeSizeType.Star) != 0); } }
internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } }
internal bool IsStarU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Star);
internal bool IsAutoU => SizeTypeU.HasFlagCustom(LayoutTimeSizeType.Auto);
internal bool IsStarV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Star);
internal bool IsAutoV => SizeTypeV.HasFlagCustom(LayoutTimeSizeType.Auto);
}
/// <summary>

8
src/Avalonia.Controls/ListBox.cs

@ -135,8 +135,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0,
(e.KeyModifiers & KeyModifiers.Control) != 0);
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
e.KeyModifiers.HasFlagCustom(KeyModifiers.Control));
}
}
@ -154,8 +154,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0,
(e.KeyModifiers & KeyModifiers.Control) != 0,
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
e.KeyModifiers.HasFlagCustom(KeyModifiers.Control),
point.Properties.IsRightButtonPressed);
}
}

54
src/Avalonia.Controls/MenuItem.cs

@ -102,6 +102,8 @@ namespace Avalonia.Controls
private ICommand? _command;
private bool _commandCanExecute = true;
private Popup? _popup;
private KeyGesture _hotkey;
private bool _isEmbeddedInMenu;
/// <summary>
/// Initializes static members of the <see cref="MenuItem"/> class.
@ -111,6 +113,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
PressedMixin.Attach<MenuItem>();
CommandProperty.Changed.Subscribe(CommandChanged);
CommandParameterProperty.Changed.Subscribe(CommandParameterChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
HeaderProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.HeaderChanged(e));
IconProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.IconChanged(e));
@ -145,7 +148,7 @@ namespace Avalonia.Controls
{
var parent = x as Control;
return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ??
Observable.Return<DefinitionBase.SharedSizeScope?>(null);
Observable.Return<DefinitionBase.SharedSizeScope?>(null);
});
this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope);
@ -273,7 +276,7 @@ namespace Avalonia.Controls
public bool IsTopLevel => Parent is Menu;
/// <inheritdoc/>
bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false;
bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false;
/// <inheritdoc/>
IMenuElement? IMenuItem.Parent => Parent as IMenuElement;
@ -308,7 +311,7 @@ namespace Avalonia.Controls
.Select(x => x.ContainerControl)
.OfType<IMenuItem>();
}
}
}
/// <summary>
/// Opens the submenu.
@ -335,18 +338,51 @@ namespace Avalonia.Controls
return new MenuItemContainerGenerator(this);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (!_isEmbeddedInMenu)
{
//Normally the Menu's IMenuInteractionHandler is sending the click events for us
//However when the item is not embedded into a menu we need to send them ourselves.
RaiseEvent(new RoutedEventArgs(ClickEvent));
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control
{
HotKey = _hotkey;
}
base.OnAttachedToLogicalTree(e);
if (Command != null)
{
Command.CanExecuteChanged += CanExecuteChanged;
}
var parent = Parent;
while (parent is MenuItem)
{
parent = parent.Parent;
}
_isEmbeddedInMenu = parent is IMenu;
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
// This will cause the hotkey manager to dispose the observer and the reference to this control
if (HotKey != null)
{
_hotkey = HotKey;
HotKey = null;
}
base.OnDetachedFromLogicalTree(e);
if (Command != null)
@ -493,6 +529,18 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="CommandParameter"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void CommandParameterChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is MenuItem menuItem)
{
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);
}
}
/// <summary>
/// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
/// </summary>

6
src/Avalonia.Controls/NativeControlHost.cs

@ -157,10 +157,14 @@ namespace Avalonia.Controls
var needsShow = IsEffectivelyVisible && bounds.HasValue;
if (needsShow)
{
if (bounds.Value.IsEmpty)
return false;
_attachment?.ShowInBounds(bounds.Value);
}
else
_attachment?.HideWithSize(Bounds.Size);
return false;
return true;
}
private void CheckDestruction()

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

@ -98,7 +98,7 @@ namespace Avalonia.Platform
/// Sets the cursor associated with the toplevel.
/// </summary>
/// <param name="cursor">The cursor. Use null for default cursor</param>
void SetCursor(IPlatformHandle cursor);
void SetCursor(ICursorImpl cursor);
/// <summary>
/// Gets or sets a method called when the underlying implementation is destroyed.

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

@ -73,20 +73,20 @@ namespace Avalonia.Platform
{
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(RawInputModifiers.Alt))
if (effect.HasFlagCustom(DragDropEffects.Link) && modifiers.HasFlagCustom(RawInputModifiers.Alt))
return DragDropEffects.Link;
if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(RawInputModifiers.Control))
if (effect.HasFlagCustom(DragDropEffects.Copy) && modifiers.HasFlagCustom(RawInputModifiers.Control))
return DragDropEffects.Copy;
return DragDropEffects.Move;
}
private StandardCursorType GetCursorForDropEffect(DragDropEffects effects)
{
if (effects.HasFlag(DragDropEffects.Copy))
if (effects.HasFlagCustom(DragDropEffects.Copy))
return StandardCursorType.DragCopy;
if (effects.HasFlag(DragDropEffects.Move))
if (effects.HasFlagCustom(DragDropEffects.Move))
return StandardCursorType.DragMove;
if (effects.HasFlag(DragDropEffects.Link))
if (effects.HasFlagCustom(DragDropEffects.Link))
return StandardCursorType.DragLink;
return StandardCursorType.No;
}
@ -161,7 +161,7 @@ namespace Avalonia.Platform
void CheckDraggingAccepted(RawInputModifiers changedMouseButton)
{
if (_initialInputModifiers.Value.HasFlag(changedMouseButton))
if (_initialInputModifiers.Value.HasFlagCustom(changedMouseButton))
{
var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers);
UpdateCursor(null, DragDropEffects.None);

4
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -80,7 +80,9 @@ namespace Avalonia.Controls.Presenters
static TextPresenter()
{
AffectsRender<TextPresenter>(SelectionBrushProperty, TextBlock.ForegroundProperty,
SelectionForegroundBrushProperty, CaretBrushProperty);
SelectionForegroundBrushProperty, CaretBrushProperty,
SelectionStartProperty, SelectionEndProperty);
AffectsMeasure<TextPresenter>(TextProperty, PasswordCharProperty, RevealPasswordProperty,
TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty,
TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty);

21
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -253,9 +253,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
{
public static void ValidateEdge(this PopupAnchor edge)
{
if (((edge & PopupAnchor.Left) != 0 && (edge & PopupAnchor.Right) != 0)
||
((edge & PopupAnchor.Top) != 0 && (edge & PopupAnchor.Bottom) != 0))
if (edge.HasFlagCustom(PopupAnchor.Left) && edge.HasFlagCustom(PopupAnchor.Right) ||
edge.HasFlagCustom(PopupAnchor.Top) && edge.HasFlagCustom(PopupAnchor.Bottom))
throw new ArgumentException("Opposite edges specified");
}
@ -266,25 +265,25 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
public static PopupAnchor Flip(this PopupAnchor edge)
{
var hmask = PopupAnchor.Left | PopupAnchor.Right;
var vmask = PopupAnchor.Top | PopupAnchor.Bottom;
if ((edge & hmask) != 0)
edge ^= hmask;
if ((edge & vmask) != 0)
edge ^= vmask;
if (edge.HasFlagCustom(PopupAnchor.HorizontalMask))
edge ^= PopupAnchor.HorizontalMask;
if (edge.HasFlagCustom(PopupAnchor.VerticalMask))
edge ^= PopupAnchor.VerticalMask;
return edge;
}
public static PopupAnchor FlipX(this PopupAnchor edge)
{
if ((edge & PopupAnchor.HorizontalMask) != 0)
if (edge.HasFlagCustom(PopupAnchor.HorizontalMask))
edge ^= PopupAnchor.HorizontalMask;
return edge;
}
public static PopupAnchor FlipY(this PopupAnchor edge)
{
if ((edge & PopupAnchor.VerticalMask) != 0)
if (edge.HasFlagCustom(PopupAnchor.VerticalMask))
edge ^= PopupAnchor.VerticalMask;
return edge;
}

48
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@ -42,16 +42,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
private static Point GetAnchorPoint(Rect anchorRect, PopupAnchor edge)
{
double x, y;
if ((edge & PopupAnchor.Left) != 0)
if (edge.HasFlagCustom(PopupAnchor.Left))
x = anchorRect.X;
else if ((edge & PopupAnchor.Right) != 0)
else if (edge.HasFlagCustom(PopupAnchor.Right))
x = anchorRect.Right;
else
x = anchorRect.X + anchorRect.Width / 2;
if ((edge & PopupAnchor.Top) != 0)
if (edge.HasFlagCustom(PopupAnchor.Top))
y = anchorRect.Y;
else if ((edge & PopupAnchor.Bottom) != 0)
else if (edge.HasFlagCustom(PopupAnchor.Bottom))
y = anchorRect.Bottom;
else
y = anchorRect.Y + anchorRect.Height / 2;
@ -61,16 +61,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
private static Point Gravitate(Point anchorPoint, Size size, PopupGravity gravity)
{
double x, y;
if ((gravity & PopupGravity.Left) != 0)
if (gravity.HasFlagCustom(PopupGravity.Left))
x = -size.Width;
else if ((gravity & PopupGravity.Right) != 0)
else if (gravity.HasFlagCustom(PopupGravity.Right))
x = 0;
else
x = -size.Width / 2;
if ((gravity & PopupGravity.Top) != 0)
if (gravity.HasFlagCustom(PopupGravity.Top))
y = -size.Height;
else if ((gravity & PopupGravity.Bottom) != 0)
else if (gravity.HasFlagCustom(PopupGravity.Bottom))
y = 0;
else
y = -size.Height / 2;
@ -125,21 +125,13 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
bool FitsInBounds(Rect rc, PopupAnchor edge = PopupAnchor.AllMask)
{
if ((edge & PopupAnchor.Left) != 0
&& rc.X < bounds.X)
return false;
if ((edge & PopupAnchor.Top) != 0
&& rc.Y < bounds.Y)
return false;
if ((edge & PopupAnchor.Right) != 0
&& rc.Right > bounds.Right)
return false;
if ((edge & PopupAnchor.Bottom) != 0
&& rc.Bottom > bounds.Bottom)
if (edge.HasFlagCustom(PopupAnchor.Left) && rc.X < bounds.X ||
edge.HasFlagCustom(PopupAnchor.Top) && rc.Y < bounds.Y ||
edge.HasFlagCustom(PopupAnchor.Right) && rc.Right > bounds.Right ||
edge.HasFlagCustom(PopupAnchor.Bottom) && rc.Bottom > bounds.Bottom)
{
return false;
}
return true;
}
@ -155,7 +147,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
// If flipping geometry and anchor is allowed and helps, use the flipped one,
// otherwise leave it as is
if (!FitsInBounds(geo, PopupAnchor.HorizontalMask)
&& (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipX) != 0)
&& constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipX))
{
var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX());
if (FitsInBounds(flipped, PopupAnchor.HorizontalMask))
@ -163,7 +155,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
}
// If sliding is allowed, try moving the rect into the bounds
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideX) != 0)
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideX))
{
geo = geo.WithX(Math.Max(geo.X, bounds.X));
if (geo.Right > bounds.Right)
@ -171,7 +163,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
}
// Resize the rect horizontally if allowed.
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.ResizeX) != 0)
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeX))
{
var unconstrainedRect = geo;
@ -194,7 +186,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
// If flipping geometry and anchor is allowed and helps, use the flipped one,
// otherwise leave it as is
if (!FitsInBounds(geo, PopupAnchor.VerticalMask)
&& (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipY) != 0)
&& constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.FlipY))
{
var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY());
if (FitsInBounds(flipped, PopupAnchor.VerticalMask))
@ -202,7 +194,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
}
// If sliding is allowed, try moving the rect into the bounds
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideY) != 0)
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.SlideY))
{
geo = geo.WithY(Math.Max(geo.Y, bounds.Y));
if (geo.Bottom > bounds.Bottom)
@ -210,7 +202,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
}
// Resize the rect vertically if allowed.
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.ResizeY) != 0)
if (constraintAdjustment.HasFlagCustom(PopupPositionerConstraintAdjustment.ResizeY))
{
var unconstrainedRect = geo;

8
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -321,7 +321,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets a value indicating whether <see cref="SelectionMode.AlwaysSelected"/> is set.
/// </summary>
protected bool AlwaysSelected => (SelectionMode & SelectionMode.AlwaysSelected) != 0;
protected bool AlwaysSelected => SelectionMode.HasFlagCustom(SelectionMode.AlwaysSelected);
/// <inheritdoc/>
public override void BeginInit()
@ -487,7 +487,7 @@ namespace Avalonia.Controls.Primitives
if (ItemCount > 0 &&
Match(keymap.SelectAll) &&
SelectionMode.HasFlag(SelectionMode.Multiple))
SelectionMode.HasFlagCustom(SelectionMode.Multiple))
{
Selection.SelectAll();
e.Handled = true;
@ -577,8 +577,8 @@ namespace Avalonia.Controls.Primitives
}
var mode = SelectionMode;
var multi = (mode & SelectionMode.Multiple) != 0;
var toggle = (toggleModifier || (mode & SelectionMode.Toggle) != 0);
var multi = mode.HasFlagCustom(SelectionMode.Multiple);
var toggle = toggleModifier || mode.HasFlagCustom(SelectionMode.Toggle);
var range = multi && rangeModifier;
if (!select)

4
src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs

@ -53,8 +53,8 @@ namespace Avalonia.Controls
{
return _owner.GetElementImpl(
index,
(options & ElementRealizationOptions.ForceCreate) != 0,
(options & ElementRealizationOptions.SuppressAutoRecycle) != 0);
options.HasFlagCustom(ElementRealizationOptions.ForceCreate),
options.HasFlagCustom(ElementRealizationOptions.SuppressAutoRecycle));
}
protected override object GetItemAtCore(int index) => _owner.ItemsSourceView.GetAt(index);

47
src/Avalonia.Controls/Slider.cs

@ -49,6 +49,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Orientation> OrientationProperty =
ScrollBar.OrientationProperty.AddOwner<Slider>();
/// <summary>
/// Defines the <see cref="IsDirectionReversed"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsDirectionReversedProperty =
Track.IsDirectionReversedProperty.AddOwner<Slider>();
/// <summary>
/// Defines the <see cref="IsSnapToTickEnabled"/> property.
/// </summary>
@ -83,7 +89,6 @@ namespace Avalonia.Controls
private IDisposable _increaseButtonSubscription;
private IDisposable _increaseButtonReleaseDispose;
private IDisposable _pointerMovedDispose;
private IDisposable _trackOnKeyDownDispose;
private const double Tolerance = 0.0001;
@ -93,6 +98,7 @@ namespace Avalonia.Controls
static Slider()
{
PressedMixin.Attach<Slider>();
FocusableProperty.OverrideDefaultValue<Slider>(true);
OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e),
@ -127,6 +133,19 @@ namespace Avalonia.Controls
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// Gets or sets the direction of increasing value.
/// </summary>
/// <value>
/// true if the direction of increasing value is to the left for a horizontal slider or
/// down for a vertical slider; otherwise, false. The default is false.
/// </value>
public bool IsDirectionReversed
{
get { return GetValue(IsDirectionReversedProperty); }
set { SetValue(IsDirectionReversedProperty, value); }
}
/// <summary>
/// Gets or sets a value that indicates whether the <see cref="Slider"/> automatically moves the <see cref="Thumb"/> to the closest tick mark.
/// </summary>
@ -165,7 +184,6 @@ namespace Avalonia.Controls
_increaseButtonSubscription?.Dispose();
_increaseButtonReleaseDispose?.Dispose();
_pointerMovedDispose?.Dispose();
_trackOnKeyDownDispose?.Dispose();
_decreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
_track = e.NameScope.Find<Track>("PART_Track");
@ -174,7 +192,6 @@ namespace Avalonia.Controls
if (_track != null)
{
_track.IsThumbDragHandled = true;
_trackOnKeyDownDispose = _track.AddDisposableHandler(KeyDownEvent, TrackOnKeyDown);
}
if (_decreaseButton != null)
@ -192,26 +209,32 @@ namespace Avalonia.Controls
_pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel);
}
private void TrackOnKeyDown(object sender, KeyEventArgs e)
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyModifiers != KeyModifiers.None) return;
base.OnKeyDown(e);
if (e.Handled || e.KeyModifiers != KeyModifiers.None) return;
var handled = true;
switch (e.Key)
{
case Key.Down:
case Key.Left:
MoveToNextTick(-SmallChange);
MoveToNextTick(IsDirectionReversed ? SmallChange : -SmallChange);
break;
case Key.Up:
case Key.Right:
MoveToNextTick(SmallChange);
MoveToNextTick(IsDirectionReversed ? -SmallChange : SmallChange);
break;
case Key.PageUp:
MoveToNextTick(-LargeChange);
MoveToNextTick(IsDirectionReversed ? -LargeChange : LargeChange);
break;
case Key.PageDown:
MoveToNextTick(LargeChange);
MoveToNextTick(IsDirectionReversed ? LargeChange : -LargeChange);
break;
case Key.Home:
@ -221,7 +244,13 @@ namespace Avalonia.Controls
case Key.End:
Value = Maximum;
break;
default:
handled = false;
break;
}
e.Handled = handled;
}
private void MoveToNextTick(double direction)

4
src/Avalonia.Controls/TextBox.cs

@ -585,7 +585,7 @@ namespace Avalonia.Controls
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.KeyModifiers.HasFlag(keymap.SelectionModifiers);
bool DetectSelection() => e.KeyModifiers.HasFlagCustom(keymap.SelectionModifiers);
if (Match(keymap.SelectAll))
{
@ -703,7 +703,7 @@ namespace Avalonia.Controls
}
else
{
bool hasWholeWordModifiers = modifiers.HasFlag(keymap.WholeWordTextActionModifiers);
bool hasWholeWordModifiers = modifiers.HasFlagCustom(keymap.WholeWordTextActionModifiers);
switch (e.Key)
{
case Key.Left:

2
src/Avalonia.Controls/TopLevel.cs

@ -165,7 +165,7 @@ namespace Avalonia.Controls
this.GetObservable(PointerOverElementProperty)
.Select(
x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
.Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor));
.Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformImpl));
if (((IStyleHost)this).StylingParent is IResourceHost applicationResources)
{

12
src/Avalonia.Controls/TreeView.cs

@ -412,7 +412,7 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0);
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift));
}
}
@ -521,8 +521,8 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0,
(e.KeyModifiers & KeyModifiers.Control) != 0,
e.KeyModifiers.HasFlagCustom(KeyModifiers.Shift),
e.KeyModifiers.HasFlagCustom(KeyModifiers.Control),
point.Properties.IsRightButtonPressed);
}
}
@ -558,9 +558,9 @@ namespace Avalonia.Controls
}
var mode = SelectionMode;
var toggle = toggleModifier || (mode & SelectionMode.Toggle) != 0;
var multi = (mode & SelectionMode.Multiple) != 0;
var range = multi && selectedContainer != null && rangeModifier;
var toggle = toggleModifier || mode.HasFlagCustom(SelectionMode.Toggle);
var multi = mode.HasFlagCustom(SelectionMode.Multiple);
var range = multi && rangeModifier && selectedContainer != null;
if (rightButton)
{

2
src/Avalonia.Controls/Utils/AncestorFinder.cs

@ -47,6 +47,8 @@ namespace Avalonia.Controls.Utils
public void Dispose()
{
_child?.Dispose();
_subject.Dispose();
_disposable.Dispose();
}
}

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -47,7 +47,7 @@ namespace Avalonia.DesignerSupport.Remote
var threading = new InternalPlatformThreadingInterface();
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<IStandardCursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformThreadingInterface>().ToConstant(threading)

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

@ -73,7 +73,7 @@ namespace Avalonia.DesignerSupport.Remote
public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
}
@ -192,9 +192,15 @@ namespace Avalonia.DesignerSupport.Remote
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
}
class CursorFactoryStub : IStandardCursorFactory
class CursorFactoryStub : ICursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB");
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
private class CursorStub : ICursorImpl
{
public void Dispose() { }
}
}
class IconLoaderStub : IPlatformIconLoader

2
src/Avalonia.Dialogs/ManagedFileChooserSources.cs

@ -67,7 +67,7 @@ namespace Avalonia.Dialogs
{
Directory.GetFiles(x.VolumePath);
}
catch (UnauthorizedAccessException _)
catch (Exception _)
{
return null;
}

8
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -223,13 +223,13 @@ namespace Avalonia.FreeDesktop
return null;
var lst = new List<string>();
var mod = item.Gesture;
if ((mod.KeyModifiers & KeyModifiers.Control) != 0)
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Control))
lst.Add("Control");
if ((mod.KeyModifiers & KeyModifiers.Alt) != 0)
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Alt))
lst.Add("Alt");
if ((mod.KeyModifiers & KeyModifiers.Shift) != 0)
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Shift))
lst.Add("Shift");
if ((mod.KeyModifiers & KeyModifiers.Meta) != 0)
if (mod.KeyModifiers.HasFlagCustom(KeyModifiers.Meta))
lst.Add("Super");
lst.Add(item.Gesture.Key.ToString());
return new[] { lst.ToArray() };

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

@ -33,11 +33,11 @@ namespace Avalonia.Headless.Vnc
{
Window?.MouseMove(pt);
foreach (var btn in CheckedButtons)
if (_previousButtons.HasFlag(btn) && !buttons.HasFlag(btn))
if (_previousButtons.HasFlagCustom(btn) && !buttons.HasFlagCustom(btn))
Window?.MouseUp(pt, TranslateButton(btn), modifiers);
foreach (var btn in CheckedButtons)
if (!_previousButtons.HasFlag(btn) && buttons.HasFlag(btn))
if (!_previousButtons.HasFlagCustom(btn) && buttons.HasFlagCustom(btn))
Window?.MouseDown(pt, TranslateButton(btn), modifiers);
_previousButtons = buttons;
}, DispatcherPriority.Input);

2
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -58,7 +58,7 @@ namespace Avalonia.Headless
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>().ToConstant(new HeadlessPlatformThreadingInterface())
.Bind<IClipboard>().ToSingleton<HeadlessClipboardStub>()
.Bind<IStandardCursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<ICursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<IPlatformSettings>().ToConstant(new HeadlessPlatformSettingsStub())
.Bind<ISystemDialogImpl>().ToSingleton<HeadlessSystemDialogsStub>()
.Bind<IPlatformIconLoader>().ToSingleton<HeadlessIconLoaderStub>()

8
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -52,12 +52,14 @@ namespace Avalonia.Headless
}
}
class HeadlessCursorFactoryStub : IStandardCursorFactory
class HeadlessCursorFactoryStub : ICursorFactory
{
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
public IPlatformHandle GetCursor(StandardCursorType cursorType)
private class CursorStub : ICursorImpl
{
return new PlatformHandle(new IntPtr((int)cursorType), "STUB");
public void Dispose() { }
}
}

2
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -67,7 +67,7 @@ namespace Avalonia.Headless
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling);
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
}

2
src/Avalonia.Input/AccessKeyHandler.cs

@ -177,7 +177,7 @@ namespace Avalonia.Input
{
bool menuIsOpen = MainMenu?.IsOpen == true;
if ((e.KeyModifiers & KeyModifiers.Alt) != 0 || menuIsOpen)
if (e.KeyModifiers.HasFlagCustom(KeyModifiers.Alt) || menuIsOpen)
{
// If any other key is pressed with the Alt key held down, or the main menu is open,
// find all controls who have registered that access key.

4
src/Avalonia.Input/ApiCompatBaseline.txt

@ -0,0 +1,4 @@
Compat issues with assembly Avalonia.Input:
MembersMustExist : Member 'public Avalonia.Platform.IPlatformHandle Avalonia.Input.Cursor.PlatformCursor.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Platform.IStandardCursorFactory' does not exist in the implementation but it does exist in the contract.
Total Issues: 2

39
src/Avalonia.Input/Cursors.cs → src/Avalonia.Input/Cursor.cs

@ -1,15 +1,11 @@
using System;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Input
{
/*
=========================================================================================
NOTE: Cursors are NOT disposable and are cached in platform implementation.
To support loading custom cursors some measures about that should be taken beforehand
=========================================================================================
*/
public enum StandardCursorType
{
Arrow,
@ -46,21 +42,28 @@ namespace Avalonia.Input
// SizeNorthEastSouthWest,
}
public class Cursor
public class Cursor : IDisposable
{
public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow);
internal Cursor(IPlatformHandle platformCursor)
internal Cursor(ICursorImpl platformImpl)
{
PlatformCursor = platformCursor;
PlatformImpl = platformImpl;
}
public Cursor(StandardCursorType cursorType)
: this(GetCursor(cursorType))
: this(GetCursorFactory().GetCursor(cursorType))
{
}
public Cursor(IBitmap cursor, PixelPoint hotSpot)
: this(GetCursorFactory().CreateCursor(cursor.PlatformImpl.Item, hotSpot))
{
}
public IPlatformHandle PlatformCursor { get; }
public ICursorImpl PlatformImpl { get; }
public void Dispose() => PlatformImpl.Dispose();
public static Cursor Parse(string s)
{
@ -69,16 +72,10 @@ namespace Avalonia.Input
throw new ArgumentException($"Unrecognized cursor type '{s}'.");
}
private static IPlatformHandle GetCursor(StandardCursorType type)
private static ICursorFactory GetCursorFactory()
{
var platform = AvaloniaLocator.Current.GetService<IStandardCursorFactory>();
if (platform == null)
{
throw new Exception("Could not create Cursor: IStandardCursorFactory not registered.");
}
return platform.GetCursor(type);
return AvaloniaLocator.Current.GetService<ICursorFactory>() ??
throw new Exception("Could not create Cursor: ICursorFactory not registered.");
}
}
}

4
src/Avalonia.Input/FocusManager.cs

@ -75,7 +75,9 @@ namespace Avalonia.Input
// If control is null, set focus to the topmost focus scope.
foreach (var scope in GetFocusScopeAncestors(Current).Reverse().ToList())
{
if (_focusScopes.TryGetValue(scope, out var element) && element != null)
if (scope != Scope &&
_focusScopes.TryGetValue(scope, out var element) &&
element != null)
{
Focus(element, method);
return;

12
src/Avalonia.Input/Platform/ICursorFactory.cs

@ -0,0 +1,12 @@
using Avalonia.Input;
#nullable enable
namespace Avalonia.Platform
{
public interface ICursorFactory
{
ICursorImpl GetCursor(StandardCursorType cursorType);
ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot);
}
}

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

@ -0,0 +1,14 @@
using System;
using Avalonia.Input;
#nullable enable
namespace Avalonia.Platform
{
/// <summary>
/// Represents a platform implementation of a <see cref="Cursor"/>.
/// </summary>
public interface ICursorImpl : IDisposable
{
}
}

9
src/Avalonia.Input/Platform/IStandardCursorFactory.cs

@ -1,9 +0,0 @@
using Avalonia.Input;
namespace Avalonia.Platform
{
public interface IStandardCursorFactory
{
IPlatformHandle GetCursor(StandardCursorType cursorType);
}
}

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -97,7 +97,7 @@ namespace Avalonia.Native
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>()
.ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface()))
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<ICursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IPlatformSettings>().ToConstant(this)

25
src/Avalonia.Native/Cursor.cs

@ -1,11 +1,12 @@
using System;
using System.IO;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Native.Interop;
namespace Avalonia.Native
{
class AvaloniaNativeCursor : IPlatformHandle, IDisposable
class AvaloniaNativeCursor : ICursorImpl, IDisposable
{
public IAvnCursor Cursor { get; private set; }
public IntPtr Handle => IntPtr.Zero;
@ -24,7 +25,7 @@ namespace Avalonia.Native
}
}
class CursorFactory : IStandardCursorFactory
class CursorFactory : ICursorFactory
{
IAvnCursorFactory _native;
@ -33,10 +34,28 @@ namespace Avalonia.Native
_native = native;
}
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl GetCursor(StandardCursorType cursorType)
{
var cursor = _native.GetCursor((AvnStandardCursorType)cursorType);
return new AvaloniaNativeCursor( cursor );
}
public unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
{
using(var ms = new MemoryStream())
{
cursor.Save(ms);
var imageData = ms.ToArray();
fixed(void* ptr = imageData)
{
var avnCursor = _native.CreateCustomCursor(ptr, new IntPtr(imageData.Length),
new AvnPixelSize { Width = hotSpot.X, Height = hotSpot.Y });
return new AvaloniaNativeCursor(avnCursor);
}
}
}
}
}

6
src/Avalonia.Native/WindowImplBase.cs

@ -53,7 +53,7 @@ namespace Avalonia.Native
private bool _gpu = false;
private readonly MouseDevice _mouse;
private readonly IKeyboardDevice _keyboard;
private readonly IStandardCursorFactory _cursorFactory;
private readonly ICursorFactory _cursorFactory;
private Size _savedLogicalSize;
private Size _lastRenderedLogicalSize;
private double _savedScaling;
@ -68,7 +68,7 @@ namespace Avalonia.Native
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_mouse = new MouseDevice();
_cursorFactory = AvaloniaLocator.Current.GetService<IStandardCursorFactory>();
_cursorFactory = AvaloniaLocator.Current.GetService<ICursorFactory>();
}
protected void Init(IAvnWindowBase window, IAvnScreens screens, IGlContext glContext)
@ -398,7 +398,7 @@ namespace Avalonia.Native
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
if (_native == null)
{

1
src/Avalonia.Native/avn.idl

@ -619,6 +619,7 @@ interface IAvnCursor : IUnknown
interface IAvnCursorFactory : IUnknown
{
HRESULT GetCursor(AvnStandardCursorType cursorType, IAvnCursor** retOut);
HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut);
}
[uuid(60452465-8616-40af-bc00-042e69828ce7)]

2
src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs

@ -29,7 +29,7 @@ namespace Avalonia.ReactiveUI
/// <inheritdoc/>
public bool ExecuteHook(
object source, object target,
object? source, object target,
Func<IObservedChange<object, object>[]> getCurrentViewModelProperties,
Func<IObservedChange<object, object>[]> getCurrentViewProperties,
BindingDirection direction)

2
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -3,6 +3,8 @@
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Avalonia.ReactiveUI</PackageId>
<SignAssembly>false</SignAssembly>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />

12
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -17,8 +17,8 @@ namespace Avalonia.ReactiveUI
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
public class ReactiveUserControl<TViewModel> : UserControl, IViewFor<TViewModel> where TViewModel : class
{
public static readonly StyledProperty<TViewModel> ViewModelProperty = AvaloniaProperty
.Register<ReactiveUserControl<TViewModel>, TViewModel>(nameof(ViewModel));
public static readonly StyledProperty<TViewModel?> ViewModelProperty = AvaloniaProperty
.Register<ReactiveUserControl<TViewModel>, TViewModel?>(nameof(ViewModel));
/// <summary>
/// Initializes a new instance of the <see cref="ReactiveUserControl{TViewModel}"/> class.
@ -34,16 +34,16 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// The ViewModel.
/// </summary>
public TViewModel ViewModel
public TViewModel? ViewModel
{
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
object? IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (TViewModel)value;
set => ViewModel = (TViewModel?)value;
}
protected override void OnDataContextChanged(EventArgs e)
@ -51,7 +51,7 @@ namespace Avalonia.ReactiveUI
ViewModel = DataContext as TViewModel;
}
private void OnViewModelChanged(object value)
private void OnViewModelChanged(object? value)
{
if (value == null)
{

14
src/Avalonia.ReactiveUI/ReactiveWindow.cs

@ -17,8 +17,8 @@ namespace Avalonia.ReactiveUI
/// <typeparam name="TViewModel">ViewModel type.</typeparam>
public class ReactiveWindow<TViewModel> : Window, IViewFor<TViewModel> where TViewModel : class
{
public static readonly StyledProperty<TViewModel> ViewModelProperty = AvaloniaProperty
.Register<ReactiveWindow<TViewModel>, TViewModel>(nameof(ViewModel));
public static readonly StyledProperty<TViewModel?> ViewModelProperty = AvaloniaProperty
.Register<ReactiveWindow<TViewModel>, TViewModel?>(nameof(ViewModel));
/// <summary>
/// Initializes a new instance of the <see cref="ReactiveWindow{TViewModel}"/> class.
@ -35,19 +35,19 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// The ViewModel.
/// </summary>
public TViewModel ViewModel
public TViewModel? ViewModel
{
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
object IViewFor.ViewModel
object? IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (TViewModel)value;
set => ViewModel = (TViewModel?)value;
}
private void OnDataContextChanged(object value)
private void OnDataContextChanged(object? value)
{
if (value is TViewModel viewModel)
{
@ -59,7 +59,7 @@ namespace Avalonia.ReactiveUI
}
}
private void OnViewModelChanged(object value)
private void OnViewModelChanged(object? value)
{
if (value == null)
{

16
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -55,8 +55,8 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="Router"/> property.
/// </summary>
public static readonly StyledProperty<RoutingState> RouterProperty =
AvaloniaProperty.Register<RoutedViewHost, RoutingState>(nameof(Router));
public static readonly StyledProperty<RoutingState?> RouterProperty =
AvaloniaProperty.Register<RoutedViewHost, RoutingState?>(nameof(Router));
/// <summary>
/// Initializes a new instance of the <see cref="RoutedViewHost"/> class.
@ -67,12 +67,12 @@ namespace Avalonia.ReactiveUI
{
var routerRemoved = this
.WhenAnyValue(x => x.Router)
.Where(router => router == null)
.Cast<object>();
.Where(router => router == null)!
.Cast<object?>();
this.WhenAnyValue(x => x.Router)
.Where(router => router != null)
.SelectMany(router => router.CurrentViewModel)
.SelectMany(router => router!.CurrentViewModel)
.Merge(routerRemoved)
.Subscribe(NavigateToViewModel)
.DisposeWith(disposables);
@ -82,7 +82,7 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// Gets or sets the <see cref="RoutingState"/> of the view model stack.
/// </summary>
public RoutingState Router
public RoutingState? Router
{
get => GetValue(RouterProperty);
set => SetValue(RouterProperty, value);
@ -91,13 +91,13 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// Gets or sets the ReactiveUI view locator used by this router.
/// </summary>
public IViewLocator ViewLocator { get; set; }
public IViewLocator? ViewLocator { get; set; }
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
private void NavigateToViewModel(object viewModel)
private void NavigateToViewModel(object? viewModel)
{
if (Router == null)
{

16
src/Avalonia.ReactiveUI/TransitioningContentControl.cs

@ -13,20 +13,20 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="PageTransition"/> property.
/// </summary>
public static readonly StyledProperty<IPageTransition> PageTransitionProperty =
AvaloniaProperty.Register<TransitioningContentControl, IPageTransition>(nameof(PageTransition),
public static readonly StyledProperty<IPageTransition?> PageTransitionProperty =
AvaloniaProperty.Register<TransitioningContentControl, IPageTransition?>(nameof(PageTransition),
new CrossFade(TimeSpan.FromSeconds(0.5)));
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="DefaultContent"/> property.
/// </summary>
public static readonly StyledProperty<object> DefaultContentProperty =
AvaloniaProperty.Register<TransitioningContentControl, object>(nameof(DefaultContent));
public static readonly StyledProperty<object?> DefaultContentProperty =
AvaloniaProperty.Register<TransitioningContentControl, object?>(nameof(DefaultContent));
/// <summary>
/// Gets or sets the animation played when content appears and disappears.
/// </summary>
public IPageTransition PageTransition
public IPageTransition? PageTransition
{
get => GetValue(PageTransitionProperty);
set => SetValue(PageTransitionProperty, value);
@ -35,7 +35,7 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// Gets or sets the content displayed whenever there is no page currently routed.
/// </summary>
public object DefaultContent
public object? DefaultContent
{
get => GetValue(DefaultContentProperty);
set => SetValue(DefaultContentProperty, value);
@ -44,7 +44,7 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// Gets or sets the content with animation.
/// </summary>
public new object Content
public new object? Content
{
get => base.Content;
set => UpdateContentWithTransition(value);
@ -60,7 +60,7 @@ namespace Avalonia.ReactiveUI
/// Updates the content with transitions.
/// </summary>
/// <param name="content">New content to set.</param>
private async void UpdateContentWithTransition(object content)
private async void UpdateContentWithTransition(object? content)
{
if (PageTransition != null)
await PageTransition.Start(this, null, true);

12
src/Avalonia.ReactiveUI/ViewModelViewHost.cs

@ -15,8 +15,8 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewModel"/> property.
/// </summary>
public static readonly AvaloniaProperty<object> ViewModelProperty =
AvaloniaProperty.Register<ViewModelViewHost, object>(nameof(ViewModel));
public static readonly AvaloniaProperty<object?> ViewModelProperty =
AvaloniaProperty.Register<ViewModelViewHost, object?>(nameof(ViewModel));
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelViewHost"/> class.
@ -34,7 +34,7 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// Gets or sets the ViewModel to display.
/// </summary>
public object ViewModel
public object? ViewModel
{
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
@ -43,13 +43,13 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// Gets or sets the view locator.
/// </summary>
public IViewLocator ViewLocator { get; set; }
public IViewLocator? ViewLocator { get; set; }
/// <summary>
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
private void NavigateToViewModel(object viewModel)
private void NavigateToViewModel(object? viewModel)
{
if (viewModel == null)
{
@ -74,4 +74,4 @@ namespace Avalonia.ReactiveUI
Content = viewInstance;
}
}
}
}

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

@ -1,8 +1,11 @@
<Styles xmlns="https://github.com/avaloniaui">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel>
<StackPanel Spacing="10">
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
<ProgressBar VerticalAlignment="Center" Value="5" Maximum="10" />
<ProgressBar VerticalAlignment="Center" Value="50" />
<ProgressBar VerticalAlignment="Center" Value="50" Minimum="25" Maximum="75" />
<ProgressBar HorizontalAlignment="Left" IsIndeterminate="True" Orientation="Vertical" />
</StackPanel>
</Border>

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

@ -11,7 +11,7 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Name="TrackBackground" Grid.Row="1" Height="4" Margin="6,0" VerticalAlignment="Center"/>
<Track Name="PART_Track" Grid.Row="1" Orientation="Horizontal">
<Track Name="PART_Track" Grid.Row="1" IsDirectionReversed="{TemplateBinding IsDirectionReversed}" Orientation="Horizontal">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton"
Classes="repeattrack" />
@ -46,7 +46,7 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Name="TrackBackground" Grid.Column="1" Width="4" Margin="0,6" HorizontalAlignment="Center"/>
<Track Name="PART_Track" Grid.Column="1" Orientation="Vertical">
<Track Name="PART_Track" Grid.Column="1" IsDirectionReversed="{TemplateBinding IsDirectionReversed}" Orientation="Vertical">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton"
Classes="repeattrack" />
@ -80,6 +80,7 @@
</Style>
<Style Selector="Slider /template/ RepeatButton.repeattrack">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeBorderLowBrush}"/>
<Setter Property="Template">
<ControlTemplate>

6
src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml

@ -1,8 +1,11 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith>
<Border Padding="20">
<StackPanel>
<StackPanel Spacing="10">
<ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
<ProgressBar VerticalAlignment="Center" Value="5" Maximum="10" />
<ProgressBar VerticalAlignment="Center" Value="50" />
<ProgressBar VerticalAlignment="Center" Value="50" Minimum="25" Maximum="75" />
<ProgressBar HorizontalAlignment="Left" IsIndeterminate="True" Orientation="Vertical" />
</StackPanel>
</Border>
@ -12,7 +15,6 @@
<Setter Property="Background" Value="{DynamicResource SystemControlBackgroundBaseLowBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ProgressBarBorderThemeThickness}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightTransparentBrush}" />
<Setter Property="Maximum" Value="100" />
<Setter Property="MinHeight" Value="{DynamicResource ProgressBarThemeMinHeight}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">

12
src/Avalonia.Themes.Fluent/Controls/Slider.xaml

@ -62,9 +62,9 @@
<TickBar Name="TopTickBar" Placement="Top" Height="{DynamicResource SliderOutsideTickBarThemeHeight}" VerticalAlignment="Bottom" Margin="0,0,0,4" Grid.ColumnSpan="3" />
<!-- <TickBar Name="HorizontalInlineTickBar" Placement="Top" Fill="{DynamicResource SliderInlineTickBarFill}" Height="{DynamicResource SliderTrackThemeHeight}" Grid.Row="1" Grid.ColumnSpan="3" /> -->
<TickBar Name="BottomTickBar" Placement="Bottom" Height="{DynamicResource SliderOutsideTickBarThemeHeight}" VerticalAlignment="Top" Margin="0,4,0,0" Grid.Row="2" Grid.ColumnSpan="3" />
<Track Name="PART_Track" Grid.Row="1" Grid.ColumnSpan="3" Orientation="Horizontal">
<Track Name="PART_Track" Grid.Row="1" Grid.ColumnSpan="3" IsDirectionReversed="{TemplateBinding IsDirectionReversed}" Orientation="Horizontal">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton" Background="{TemplateBinding Foreground}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<RepeatButton Name="PART_DecreaseButton" Background="{TemplateBinding Foreground}" Focusable="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Grid>
@ -76,7 +76,7 @@
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton" Background="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<RepeatButton Name="PART_IncreaseButton" Background="{TemplateBinding Background}" Focusable="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Grid>
@ -124,9 +124,9 @@
<TickBar Name="LeftTickBar" Placement="Left" Width="{DynamicResource SliderOutsideTickBarThemeHeight}" HorizontalAlignment="Right" Margin="0,0,4,0" Grid.RowSpan="3" />
<!-- <TickBar Name="VerticalInlineTickBar" Placement="Inline" Fill="{DynamicResource SliderInlineTickBarFill}" Width="{DynamicResource SliderTrackThemeHeight}" Grid.Column="1" Grid.RowSpan="3" /> -->
<TickBar Name="RightTickBar" Placement="Right" Width="{DynamicResource SliderOutsideTickBarThemeHeight}" HorizontalAlignment="Left" Margin="4,0,0,0" Grid.Column="2" Grid.RowSpan="3" />
<Track Name="PART_Track" Grid.Column="1" Grid.ColumnSpan="1" Grid.RowSpan="3" Orientation="Vertical">
<Track Name="PART_Track" Grid.Column="1" Grid.ColumnSpan="1" Grid.RowSpan="3" IsDirectionReversed="{TemplateBinding IsDirectionReversed}" Orientation="Vertical">
<Track.DecreaseButton>
<RepeatButton Name="PART_DecreaseButton" Background="{TemplateBinding Foreground}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<RepeatButton Name="PART_DecreaseButton" Background="{TemplateBinding Foreground}" Focusable="False" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Grid>
@ -138,7 +138,7 @@
</RepeatButton>
</Track.DecreaseButton>
<Track.IncreaseButton>
<RepeatButton Name="PART_IncreaseButton" Background="{TemplateBinding Background}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<RepeatButton Name="PART_IncreaseButton" Background="{TemplateBinding Background}" Focusable="False" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<RepeatButton.Template>
<ControlTemplate>
<Grid>

4
src/Avalonia.Visuals/Media/FormattedText.cs

@ -36,7 +36,7 @@ namespace Avalonia.Media
}
/// <summary>
///
/// Initializes a new instance of the <see cref="FormattedText"/> class.
/// </summary>
/// <param name="text"></param>
/// <param name="typeface"></param>
@ -45,7 +45,7 @@ namespace Avalonia.Media
/// <param name="textWrapping"></param>
/// <param name="constraint"></param>
public FormattedText(string text, Typeface typeface, double fontSize, TextAlignment textAlignment,
TextWrapping textWrapping, Size constraint)
TextWrapping textWrapping, Size constraint) : this()
{
_text = text;

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

@ -1,9 +1,27 @@
using Avalonia.Collections;
using Avalonia.Visuals.Platform;
namespace Avalonia.Media
{
public sealed class PathFigures : AvaloniaList<PathFigure>
{
/// <summary>
/// Parses the specified path data to a <see cref="PathFigures"/>.
/// </summary>
/// <param name="pathData">The s.</param>
/// <returns></returns>
public static PathFigures Parse(string pathData)
{
var pathGeometry = new PathGeometry();
using (var context = new PathGeometryContext(pathGeometry))
using (var parser = new PathMarkupParser(context))
{
parser.Parse(pathData);
}
return pathGeometry.Figures;
}
}
public sealed class PathSegments : AvaloniaList<PathSegment>

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

@ -1,56 +0,0 @@
namespace Avalonia.Media.TextFormatting.Unicode
{
internal static class BreakPairTable
{
private static readonly byte[][] s_breakPairTable =
{
new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,4},
new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1,0},
new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
new byte[] {0,4,4,1,1,0,4,4,4,0,0,0,0,0,0,0,0,0,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0},
};
public static PairBreakType Map(LineBreakClass first, LineBreakClass second)
{
return (PairBreakType)s_breakPairTable[(int)first][(int)second];
}
}
internal enum PairBreakType : byte
{
DI = 0, // Direct break opportunity
IN = 1, // Indirect break opportunity
CI = 2, // Indirect break opportunity for combining marks
CP = 3, // Prohibited break for combining marks
PR = 4 // Prohibited break
}
}

27
src/Avalonia.Visuals/Media/TextFormatting/Unicode/Codepoint.cs

@ -9,37 +9,40 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// </summary>
public static readonly Codepoint ReplacementCodepoint = new Codepoint('\uFFFD');
private readonly int _value;
public Codepoint(int value)
{
_value = value;
Value = value;
}
/// <summary>
/// Get the codepoint's value.
/// </summary>
public int Value { get; }
/// <summary>
/// Gets the <see cref="Unicode.GeneralCategory"/>.
/// </summary>
public GeneralCategory GeneralCategory => UnicodeData.GetGeneralCategory(_value);
public GeneralCategory GeneralCategory => UnicodeData.GetGeneralCategory(Value);
/// <summary>
/// Gets the <see cref="Unicode.Script"/>.
/// </summary>
public Script Script => UnicodeData.GetScript(_value);
public Script Script => UnicodeData.GetScript(Value);
/// <summary>
/// Gets the <see cref="Unicode.BiDiClass"/>.
/// </summary>
public BiDiClass BiDiClass => UnicodeData.GetBiDiClass(_value);
public BiDiClass BiDiClass => UnicodeData.GetBiDiClass(Value);
/// <summary>
/// Gets the <see cref="Unicode.LineBreakClass"/>.
/// </summary>
public LineBreakClass LineBreakClass => UnicodeData.GetLineBreakClass(_value);
public LineBreakClass LineBreakClass => UnicodeData.GetLineBreakClass(Value);
/// <summary>
/// Gets the <see cref="GraphemeBreakClass"/>.
/// </summary>
public GraphemeBreakClass GraphemeBreakClass => UnicodeData.GetGraphemeClusterBreak(_value);
public GraphemeBreakClass GraphemeBreakClass => UnicodeData.GetGraphemeClusterBreak(Value);
/// <summary>
/// Determines whether this <see cref="Codepoint"/> is a break char.
@ -51,7 +54,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
get
{
switch (_value)
switch (Value)
{
case '\u000A':
case '\u000B':
@ -93,12 +96,12 @@ namespace Avalonia.Media.TextFormatting.Unicode
public static implicit operator int(Codepoint codepoint)
{
return codepoint._value;
return codepoint.Value;
}
public static implicit operator uint(Codepoint codepoint)
{
return (uint)codepoint._value;
return (uint)codepoint.Value;
}
/// <summary>
@ -112,7 +115,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
count = 1;
if (index > text.Length)
if (index >= text.Length)
{
return ReplacementCodepoint;
}

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

@ -1,160 +1,460 @@
// RichTextKit
// Copyright © 2019 Topten Software. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this product except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
// Ported from: https://github.com/foliojs/linebreak
// Copied from: https://github.com/toptensoftware/RichTextKit
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Ported from: https://github.com/SixLabors/Fonts/
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
{
/// <summary>
/// Implementation of the Unicode Line Break Algorithm
/// Implementation of the Unicode Line Break Algorithm. UAX:14
/// <see href="https://www.unicode.org/reports/tr14/tr14-37.html"/>
/// </summary>
public ref struct LineBreakEnumerator
{
// State
private readonly ReadOnlySlice<char> _text;
private int _pos;
private int _lastPos;
private LineBreakClass? _curClass;
private LineBreakClass? _nextClass;
private int _position;
private int _lastPosition;
private LineBreakClass _currentClass;
private LineBreakClass _nextClass;
private bool _first;
private int _alphaNumericCount;
private bool _lb8a;
private bool _lb21a;
private bool _lb22ex;
private bool _lb24ex;
private bool _lb25ex;
private bool _lb30;
private int _lb30a;
private bool _lb31;
public LineBreakEnumerator(ReadOnlySlice<char> text)
: this()
{
_text = text;
_pos = 0;
_lastPos = 0;
_curClass = null;
_nextClass = null;
Current = default;
_position = 0;
_currentClass = LineBreakClass.Unknown;
_nextClass = LineBreakClass.Unknown;
_first = true;
_lb8a = false;
_lb21a = false;
_lb22ex = false;
_lb24ex = false;
_lb25ex = false;
_alphaNumericCount = 0;
_lb31 = false;
_lb30 = false;
_lb30a = 0;
}
public LineBreak Current { get; private set; }
public bool MoveNext()
{
// get the first char if we're at the beginning of the string
if (!_curClass.HasValue)
// Get the first char if we're at the beginning of the string.
if (_first)
{
_curClass = PeekCharClass() == LineBreakClass.Space ? LineBreakClass.WordJoiner : MapFirst(ReadCharClass());
var firstClass = NextCharClass();
_first = false;
_currentClass = MapFirst(firstClass);
_nextClass = firstClass;
_lb8a = firstClass == LineBreakClass.ZWJ;
_lb30a = 0;
}
while (_pos < _text.Length)
while (_position < _text.Length)
{
_lastPos = _pos;
_lastPosition = _position;
var lastClass = _nextClass;
_nextClass = ReadCharClass();
_nextClass = NextCharClass();
// explicit newline
if (_curClass.HasValue && (_curClass == LineBreakClass.MandatoryBreak || _curClass == LineBreakClass.CarriageReturn && _nextClass != LineBreakClass.LineFeed))
// Explicit newline
switch (_currentClass)
{
_curClass = MapFirst(MapClass(_nextClass.Value));
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true);
case LineBreakClass.MandatoryBreak:
case LineBreakClass.CarriageReturn when _nextClass != LineBreakClass.LineFeed:
{
_currentClass = MapFirst(_nextClass);
Current = new LineBreak(FindPriorNonWhitespace(_lastPosition), _lastPosition, true);
return true;
}
}
var shouldBreak = GetSimpleBreak() ?? (bool?)GetPairTableBreak(lastClass);
// Rule LB8a
_lb8a = _nextClass == LineBreakClass.ZWJ;
if (shouldBreak.Value)
{
Current = new LineBreak(FindPriorNonWhitespace(_lastPosition), _lastPosition);
return true;
}
}
// handle classes not handled by the pair table
LineBreakClass? cur = null;
switch (_nextClass.Value)
if (_position >= _text.Length)
{
if (_lastPosition < _text.Length)
{
case LineBreakClass.Space:
cur = _curClass;
break;
_lastPosition = _text.Length;
var required = false;
switch (_currentClass)
{
case LineBreakClass.MandatoryBreak:
case LineBreakClass.CarriageReturn when _nextClass != LineBreakClass.LineFeed:
required = true;
break;
}
Current = new LineBreak(FindPriorNonWhitespace(_lastPosition), _lastPosition, required);
return true;
}
}
Current = default;
return false;
}
private static LineBreakClass MapClass(Codepoint cp)
{
if (cp.Value == 327685)
{
return LineBreakClass.Alphabetic;
}
// LB 1
// ==========================================
// Resolved Original General_Category
// ==========================================
// AL AI, SG, XX Any
// CM SA Only Mn or Mc
// AL SA Any except Mn and Mc
// NS CJ Any
switch (cp.LineBreakClass)
{
case LineBreakClass.Ambiguous:
case LineBreakClass.Surrogate:
case LineBreakClass.Unknown:
return LineBreakClass.Alphabetic;
case LineBreakClass.ComplexContext:
return cp.GeneralCategory == GeneralCategory.NonspacingMark || cp.GeneralCategory == GeneralCategory.SpacingMark
? LineBreakClass.CombiningMark
: LineBreakClass.Alphabetic;
case LineBreakClass.ConditionalJapaneseStarter:
return LineBreakClass.Nonstarter;
default:
return cp.LineBreakClass;
}
}
private static LineBreakClass MapFirst(LineBreakClass c)
{
switch (c)
{
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
return LineBreakClass.MandatoryBreak;
case LineBreakClass.Space:
return LineBreakClass.WordJoiner;
default:
return c;
}
}
private static bool IsAlphaNumeric(LineBreakClass cls)
=> cls == LineBreakClass.Alphabetic
|| cls == LineBreakClass.HebrewLetter
|| cls == LineBreakClass.Numeric;
private LineBreakClass PeekNextCharClass()
{
var cp = Codepoint.ReadAt(_text, _position, out _);
return MapClass(cp);
}
// Get the next character class
private LineBreakClass NextCharClass()
{
var cp = Codepoint.ReadAt(_text, _position, out var count);
var cls = MapClass(cp);
_position += count;
// Keep track of alphanumeric + any combining marks.
// This is used for LB22 and LB30.
if (IsAlphaNumeric(_currentClass) || _alphaNumericCount > 0 && cls == LineBreakClass.CombiningMark)
{
_alphaNumericCount++;
}
// Track combining mark exceptions. LB22
if (cls == LineBreakClass.CombiningMark)
{
switch (_currentClass)
{
case LineBreakClass.MandatoryBreak:
case LineBreakClass.ContingentBreak:
case LineBreakClass.Exclamation:
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
cur = LineBreakClass.MandatoryBreak;
break;
case LineBreakClass.Space:
case LineBreakClass.ZWSpace:
case LineBreakClass.CarriageReturn:
cur = LineBreakClass.CarriageReturn;
_lb22ex = true;
break;
}
}
// Track combining mark exceptions. LB31
if (_first && cls == LineBreakClass.CombiningMark)
{
_lb31 = true;
}
if (cls == LineBreakClass.CombiningMark)
{
switch (_currentClass)
{
case LineBreakClass.MandatoryBreak:
case LineBreakClass.ContingentBreak:
cur = LineBreakClass.BreakAfter;
case LineBreakClass.Exclamation:
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
case LineBreakClass.Space:
case LineBreakClass.ZWSpace:
case LineBreakClass.CarriageReturn:
case LineBreakClass.ZWJ:
_lb31 = true;
break;
}
}
if (_first
&& (cls == LineBreakClass.PostfixNumeric || cls == LineBreakClass.PrefixNumeric || cls == LineBreakClass.Space))
{
_lb31 = true;
}
if (_currentClass == LineBreakClass.Alphabetic &&
(cls == LineBreakClass.PostfixNumeric || cls == LineBreakClass.PrefixNumeric || cls == LineBreakClass.Space))
{
_lb31 = true;
}
// Reset LB31 if next is U+0028 (Left Opening Parenthesis)
if (_lb31
&& _currentClass != LineBreakClass.PostfixNumeric
&& _currentClass != LineBreakClass.PrefixNumeric
&& cls == LineBreakClass.OpenPunctuation && cp.Value == 0x0028)
{
_lb31 = false;
}
// Rule LB24
if (_first && (cls == LineBreakClass.ClosePunctuation || cls == LineBreakClass.CloseParenthesis))
{
_lb24ex = true;
}
if (cur != null)
// Rule LB25
if (_first
&& (cls == LineBreakClass.ClosePunctuation || cls == LineBreakClass.InfixNumeric || cls == LineBreakClass.BreakSymbols))
{
_lb25ex = true;
}
if (cls == LineBreakClass.Space || cls == LineBreakClass.WordJoiner || cls == LineBreakClass.Alphabetic)
{
var next = PeekNextCharClass();
if (next == LineBreakClass.ClosePunctuation || next == LineBreakClass.InfixNumeric || next == LineBreakClass.BreakSymbols)
{
_curClass = cur;
_lb25ex = true;
}
}
// AlphaNumeric + and combining marks can break for OP except.
// - U+0028 (Left Opening Parenthesis)
// - U+005B (Opening Square Bracket)
// - U+007B (Left Curly Bracket)
// See custom colums|rules in the text pair table.
// https://www.unicode.org/Public/13.0.0/ucd/auxiliary/LineBreakTest.html
_lb30 = _alphaNumericCount > 0
&& cls == LineBreakClass.OpenPunctuation
&& cp.Value != 0x0028
&& cp.Value != 0x005B
&& cp.Value != 0x007B;
return cls;
}
private bool? GetSimpleBreak()
{
// handle classes not handled by the pair table
switch (_nextClass)
{
case LineBreakClass.Space:
return false;
if (_nextClass.Value == LineBreakClass.MandatoryBreak)
case LineBreakClass.MandatoryBreak:
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
_currentClass = LineBreakClass.MandatoryBreak;
return false;
case LineBreakClass.CarriageReturn:
_currentClass = LineBreakClass.CarriageReturn;
return false;
}
return null;
}
private bool GetPairTableBreak(LineBreakClass lastClass)
{
// If not handled already, use the pair table
bool shouldBreak = false;
switch (LineBreakPairTable.Table[(int)_currentClass][(int)_nextClass])
{
case LineBreakPairTable.DIBRK: // Direct break
shouldBreak = true;
break;
// TODO: Rewrite this so that it defaults to true and rules are set as exceptions.
case LineBreakPairTable.INBRK: // Possible indirect break
// LB31
if (_lb31 && _nextClass == LineBreakClass.OpenPunctuation)
{
_lastPos = _pos;
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true);
return true;
shouldBreak = true;
_lb31 = false;
break;
}
continue;
}
// if not handled already, use the pair table
var shouldBreak = false;
switch (BreakPairTable.Map(_curClass.Value,_nextClass.Value))
{
case PairBreakType.DI: // Direct break
// LB30
if (_lb30)
{
shouldBreak = true;
_lb30 = false;
_alphaNumericCount = 0;
break;
}
case PairBreakType.IN: // possible indirect break
shouldBreak = lastClass.HasValue && lastClass.Value == LineBreakClass.Space;
// LB25
if (_lb25ex && (_nextClass == LineBreakClass.PrefixNumeric || _nextClass == LineBreakClass.Numeric))
{
shouldBreak = true;
_lb25ex = false;
break;
}
case PairBreakType.CI:
shouldBreak = lastClass.HasValue && lastClass.Value == LineBreakClass.Space;
if (!shouldBreak)
{
continue;
}
// LB24
if (_lb24ex && (_nextClass == LineBreakClass.PostfixNumeric || _nextClass == LineBreakClass.PrefixNumeric))
{
shouldBreak = true;
_lb24ex = false;
break;
}
// LB18
shouldBreak = lastClass == LineBreakClass.Space;
break;
case LineBreakPairTable.CIBRK:
shouldBreak = lastClass == LineBreakClass.Space;
if (!shouldBreak)
{
return false;
}
case PairBreakType.CP: // prohibited for combining marks
if (!lastClass.HasValue || lastClass.Value != LineBreakClass.Space)
break;
case LineBreakPairTable.CPBRK: // prohibited for combining marks
if (lastClass != LineBreakClass.Space)
{
return false;
}
break;
case LineBreakPairTable.PRBRK:
break;
}
// Rule LB22
if (_nextClass == LineBreakClass.Inseparable)
{
switch (lastClass)
{
case LineBreakClass.MandatoryBreak:
case LineBreakClass.ContingentBreak:
case LineBreakClass.Exclamation:
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
case LineBreakClass.Space:
case LineBreakClass.ZWSpace:
// Allow break
break;
case LineBreakClass.CombiningMark:
if (_lb22ex)
{
continue;
// Allow break
_lb22ex = false;
break;
}
shouldBreak = false;
break;
default:
shouldBreak = false;
break;
}
}
_curClass = _nextClass;
if (_lb8a)
{
shouldBreak = false;
}
if (shouldBreak)
{
Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos);
return true;
}
// Rule LB21a
if (_lb21a && (_currentClass == LineBreakClass.Hyphen || _currentClass == LineBreakClass.BreakAfter))
{
shouldBreak = false;
_lb21a = false;
}
else
{
_lb21a = _currentClass == LineBreakClass.HebrewLetter;
}
if (_pos >= _text.Length)
// Rule LB30a
if (_currentClass == LineBreakClass.RegionalIndicator)
{
if (_lastPos < _text.Length)
_lb30a++;
if (_lb30a == 2 && _nextClass == LineBreakClass.RegionalIndicator)
{
_lastPos = _text.Length;
var cls = Codepoint.ReadAt(_text, _text.Length - 1, out _).LineBreakClass;
bool required = cls == LineBreakClass.MandatoryBreak || cls == LineBreakClass.LineFeed || cls == LineBreakClass.CarriageReturn;
Current = new LineBreak(FindPriorNonWhitespace(_text.Length), _text.Length, required);
return true;
shouldBreak = true;
_lb30a = 0;
}
}
else
{
_lb30a = 0;
}
return false;
}
_currentClass = _nextClass;
return shouldBreak;
}
private int FindPriorNonWhitespace(int from)
{
if (from > 0)
@ -163,7 +463,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
var cls = cp.LineBreakClass;
if (cls == LineBreakClass.MandatoryBreak || cls == LineBreakClass.LineFeed || cls == LineBreakClass.CarriageReturn)
if (cls == LineBreakClass.MandatoryBreak || cls == LineBreakClass.LineFeed ||
cls == LineBreakClass.CarriageReturn)
{
from -= count;
}
@ -184,61 +485,8 @@ namespace Avalonia.Media.TextFormatting.Unicode
break;
}
}
return from;
}
// Get the next character class
private LineBreakClass ReadCharClass()
{
var cp = Codepoint.ReadAt(_text, _pos, out var count);
_pos += count;
return MapClass(cp.LineBreakClass);
}
private LineBreakClass PeekCharClass()
{
return MapClass(Codepoint.ReadAt(_text, _pos, out _).LineBreakClass);
}
private static LineBreakClass MapClass(LineBreakClass c)
{
switch (c)
{
case LineBreakClass.Ambiguous:
return LineBreakClass.Alphabetic;
case LineBreakClass.ComplexContext:
case LineBreakClass.Surrogate:
case LineBreakClass.Unknown:
return LineBreakClass.Alphabetic;
case LineBreakClass.ConditionalJapaneseStarter:
return LineBreakClass.Nonstarter;
default:
return c;
}
}
private static LineBreakClass MapFirst(LineBreakClass c)
{
switch (c)
{
case LineBreakClass.LineFeed:
case LineBreakClass.NextLine:
return LineBreakClass.MandatoryBreak;
case LineBreakClass.ContingentBreak:
return LineBreakClass.BreakAfter;
case LineBreakClass.Space:
return LineBreakClass.WordJoiner;
default:
return c;
}
return from;
}
}
}

74
src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakPairTable.cs

@ -0,0 +1,74 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Ported from: https://github.com/SixLabors/Fonts/
namespace Avalonia.Media.TextFormatting.Unicode
{
internal static class LineBreakPairTable
{
/// <summary>
/// Direct break opportunity
/// </summary>
public const byte DIBRK = 0;
/// <summary>
/// Indirect break opportunity
/// </summary>
public const byte INBRK = 1;
/// <summary>
/// Indirect break opportunity for combining marks
/// </summary>
public const byte CIBRK = 2;
/// <summary>
/// Prohibited break for combining marks
/// </summary>
public const byte CPBRK = 3;
/// <summary>
/// Prohibited break
/// </summary>
public const byte PRBRK = 4;
// Based on example pair table from https://www.unicode.org/reports/tr14/tr14-37.html#Table2
// - ZWJ special processing for LB8a
// - CB manually added as per Rule LB20
public static byte[][] Table { get; } = {
// . OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ CB
new[] { PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, CPBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK, PRBRK }, // OP
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // CL
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // CP
new[] { PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK }, // QU
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK }, // GL
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // NS
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // EX
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, DIBRK, INBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // SY
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // IS
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK }, // PR
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // PO
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // NU
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // AL
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // HL
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // ID
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // IN
new[] { DIBRK, PRBRK, PRBRK, INBRK, DIBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // HY
new[] { DIBRK, PRBRK, PRBRK, INBRK, DIBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // BA
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK }, // BB
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, PRBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // B2
new[] { DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK }, // ZW
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // CM
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK, INBRK }, // WJ
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // H2
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // H3
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // JL
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // JV
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // JT
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK, DIBRK, INBRK, DIBRK }, // RI
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, DIBRK }, // EB
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, DIBRK, INBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // EM
new[] { INBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, PRBRK, PRBRK, PRBRK, INBRK, INBRK, INBRK, INBRK, INBRK, DIBRK, INBRK, INBRK, INBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK }, // ZWJ
new[] { DIBRK, PRBRK, PRBRK, INBRK, INBRK, DIBRK, PRBRK, PRBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, PRBRK, CIBRK, PRBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, DIBRK, INBRK, DIBRK } // CB
};
}
}

73
src/Avalonia.X11/X11CursorFactory.cs

@ -1,12 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.X11
{
class X11CursorFactory : IStandardCursorFactory
class X11CursorFactory : ICursorFactory
{
private static readonly byte[] NullCursorData = new byte[] { 0 };
@ -51,7 +56,7 @@ namespace Avalonia.X11
.ToDictionary(id => id, id => XLib.XCreateFontCursor(_display, id));
}
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl GetCursor(StandardCursorType cursorType)
{
IntPtr handle;
if (cursorType == StandardCursorType.None)
@ -64,7 +69,12 @@ namespace Avalonia.X11
? _cursors[shape]
: _cursors[CursorFontShape.XC_top_left_arrow];
}
return new PlatformHandle(handle, "XCURSOR");
return new CursorImpl(handle);
}
public unsafe ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
{
return new XImageCursor(_display, cursor, hotSpot);
}
private static IntPtr GetNullCursor(IntPtr display)
@ -74,5 +84,62 @@ namespace Avalonia.X11
IntPtr pixmap = XLib.XCreateBitmapFromData(display, window, NullCursorData, 1, 1);
return XLib.XCreatePixmapCursor(display, pixmap, pixmap, ref color, ref color, 0, 0);
}
private unsafe class XImageCursor : CursorImpl, IFramebufferPlatformSurface, IPlatformHandle
{
private readonly PixelSize _pixelSize;
private readonly IUnmanagedBlob _blob;
public XImageCursor(IntPtr display, IBitmapImpl bitmap, PixelPoint hotSpot)
{
var size = Marshal.SizeOf<XcursorImage>() +
(bitmap.PixelSize.Width * bitmap.PixelSize.Height * 4);
_pixelSize = bitmap.PixelSize;
_blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size);
var image = (XcursorImage*)_blob.Address;
image->version = 1;
image->size = Marshal.SizeOf<XcursorImage>();
image->width = bitmap.PixelSize.Width;
image->height = bitmap.PixelSize.Height;
image->xhot = hotSpot.X;
image->yhot = hotSpot.Y;
image->pixels = (IntPtr)(image + 1);
using (var renderTarget = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateRenderTarget(new[] { this }))
using (var ctx = renderTarget.CreateDrawingContext(null))
{
var r = new Rect(_pixelSize.ToSize(1));
ctx.DrawBitmap(RefCountable.CreateUnownedNotClonable(bitmap), 1, r, r);
}
Handle = XLib.XcursorImageLoadCursor(display, _blob.Address);
}
public string HandleDescriptor => "XCURSOR";
public override void Dispose()
{
XLib.XcursorImageDestroy(Handle);
_blob.Dispose();
}
public ILockedFramebuffer Lock()
{
return new LockedFramebuffer(
_blob.Address + Marshal.SizeOf<XcursorImage>(),
_pixelSize, _pixelSize.Width * 4,
new Vector(96, 96), PixelFormat.Bgra8888, null);
}
}
}
class CursorImpl : ICursorImpl
{
public CursorImpl() { }
public CursorImpl(IntPtr handle) => Handle = handle;
public IntPtr Handle { get; protected set; }
public virtual void Dispose() { }
}
}

2
src/Avalonia.X11/X11Platform.cs

@ -71,7 +71,7 @@ namespace Avalonia.X11
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control))
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<IStandardCursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<ICursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))

2
src/Avalonia.X11/X11Structs.cs

@ -1693,7 +1693,7 @@ namespace Avalonia.X11 {
[StructLayout (LayoutKind.Sequential)]
internal struct XcursorImage
{
private int version;
public int version;
public int size; /* nominal size for matching */
public int width; /* actual width */
public int height; /* actual height */

4
src/Avalonia.X11/X11Window.Ime.cs

@ -96,14 +96,14 @@ namespace Avalonia.X11
void HandleKeyEvent(ref XEvent ev)
{
var index = ev.KeyEvent.state.HasFlag(XModifierMask.ShiftMask);
var index = ev.KeyEvent.state.HasFlagCustom(XModifierMask.ShiftMask);
// We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway
var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
// Manually switch the Shift index for the keypad,
// there should be a proper way to do this
if (ev.KeyEvent.state.HasFlag(XModifierMask.Mod2Mask)
if (ev.KeyEvent.state.HasFlagCustom(XModifierMask.Mod2Mask)
&& key > X11Key.Num_Lock && key <= X11Key.KP_9)
key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();

26
src/Avalonia.X11/X11Window.cs

@ -639,23 +639,23 @@ namespace Avalonia.X11
RawInputModifiers TranslateModifiers(XModifierMask state)
{
var rv = default(RawInputModifiers);
if (state.HasFlag(XModifierMask.Button1Mask))
if (state.HasFlagCustom(XModifierMask.Button1Mask))
rv |= RawInputModifiers.LeftMouseButton;
if (state.HasFlag(XModifierMask.Button2Mask))
if (state.HasFlagCustom(XModifierMask.Button2Mask))
rv |= RawInputModifiers.RightMouseButton;
if (state.HasFlag(XModifierMask.Button3Mask))
if (state.HasFlagCustom(XModifierMask.Button3Mask))
rv |= RawInputModifiers.MiddleMouseButton;
if (state.HasFlag(XModifierMask.Button4Mask))
if (state.HasFlagCustom(XModifierMask.Button4Mask))
rv |= RawInputModifiers.XButton1MouseButton;
if (state.HasFlag(XModifierMask.Button5Mask))
if (state.HasFlagCustom(XModifierMask.Button5Mask))
rv |= RawInputModifiers.XButton2MouseButton;
if (state.HasFlag(XModifierMask.ShiftMask))
if (state.HasFlagCustom(XModifierMask.ShiftMask))
rv |= RawInputModifiers.Shift;
if (state.HasFlag(XModifierMask.ControlMask))
if (state.HasFlagCustom(XModifierMask.ControlMask))
rv |= RawInputModifiers.Control;
if (state.HasFlag(XModifierMask.Mod1Mask))
if (state.HasFlagCustom(XModifierMask.Mod1Mask))
rv |= RawInputModifiers.Alt;
if (state.HasFlag(XModifierMask.Mod4Mask))
if (state.HasFlagCustom(XModifierMask.Mod4Mask))
rv |= RawInputModifiers.Meta;
return rv;
}
@ -872,15 +872,13 @@ namespace Avalonia.X11
UpdateSizeHints(null);
}
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
if (cursor == null)
XDefineCursor(_x11.Display, _handle, _x11.DefaultCursor);
else
else if (cursor is CursorImpl impl)
{
if (cursor.HandleDescriptor != "XCURSOR")
throw new ArgumentException("Expected XCURSOR handle type");
XDefineCursor(_x11.Display, _handle, cursor.Handle);
XDefineCursor(_x11.Display, _handle, impl.Handle);
}
}

10
src/Avalonia.X11/XI2Manager.cs

@ -342,13 +342,13 @@ namespace Avalonia.X11
Type = ev->evtype;
Timestamp = (ulong)ev->time.ToInt64();
var state = (XModifierMask)ev->mods.Effective;
if (state.HasFlag(XModifierMask.ShiftMask))
if (state.HasFlagCustom(XModifierMask.ShiftMask))
Modifiers |= RawInputModifiers.Shift;
if (state.HasFlag(XModifierMask.ControlMask))
if (state.HasFlagCustom(XModifierMask.ControlMask))
Modifiers |= RawInputModifiers.Control;
if (state.HasFlag(XModifierMask.Mod1Mask))
if (state.HasFlagCustom(XModifierMask.Mod1Mask))
Modifiers |= RawInputModifiers.Alt;
if (state.HasFlag(XModifierMask.Mod4Mask))
if (state.HasFlagCustom(XModifierMask.Mod4Mask))
Modifiers |= RawInputModifiers.Meta;
Modifiers |= ParseButtonState(ev->buttons.MaskLen, ev->buttons.Mask);
@ -364,7 +364,7 @@ namespace Avalonia.X11
if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease)
Button = ev->detail;
Detail = ev->detail;
Emulated = ev->flags.HasFlag(XiDeviceEventFlags.XIPointerEmulated);
Emulated = ev->flags.HasFlagCustom(XiDeviceEventFlags.XIPointerEmulated);
}
}

7
src/Avalonia.X11/XLib.cs

@ -20,6 +20,7 @@ namespace Avalonia.X11
const string libX11Randr = "libXrandr.so.2";
const string libX11Ext = "libXext.so.6";
const string libXInput = "libXi.so.6";
const string libXCursor = "libXcursor.so.1";
[DllImport(libX11)]
public static extern IntPtr XOpenDisplay(IntPtr display);
@ -569,6 +570,12 @@ namespace Avalonia.X11
[DllImport(libXInput)]
public static extern void XIFreeDeviceInfo(XIDeviceInfo* info);
[DllImport(libXCursor)]
public static extern IntPtr XcursorImageLoadCursor(IntPtr display, IntPtr image);
[DllImport(libXCursor)]
public static extern IntPtr XcursorImageDestroy(IntPtr image);
public static void XISetMask(ref int mask, XiEventType ev)
{
mask |= (1 << (int)ev);

2
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -57,7 +57,7 @@ namespace Avalonia.LinuxFramebuffer
public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
public void SetCursor(IPlatformHandle cursor)
public void SetCursor(ICursorImpl cursor)
{
}

9
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Threading;
using Avalonia.Controls;
@ -38,7 +38,7 @@ namespace Avalonia.LinuxFramebuffer
.Bind<IPlatformThreadingInterface>().ToConstant(Threading)
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<ICursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
@ -79,6 +79,11 @@ namespace Avalonia.LinuxFramebuffer
tl.Prepare();
_topLevel = tl;
_topLevel.Renderer.Start();
if (_topLevel is IFocusScope scope)
{
FocusManager.Instance?.SetFocusScope(scope);
}
}
_topLevel.Content = value;

2
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs

@ -54,7 +54,7 @@ namespace Avalonia.LinuxFramebuffer.Output
}
public PixelSize Resolution => new PixelSize(Mode.hdisplay, Mode.vdisplay);
public bool IsPreferred => Mode.type.HasFlag(DrmModeType.DRM_MODE_TYPE_PREFERRED);
public bool IsPreferred => Mode.type.HasFlagCustom(DrmModeType.DRM_MODE_TYPE_PREFERRED);
public string Name { get; }
}

9
src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs

@ -4,11 +4,14 @@ using Avalonia.Platform;
namespace Avalonia.LinuxFramebuffer
{
internal class CursorFactoryStub : IStandardCursorFactory
internal class CursorFactoryStub : ICursorFactory
{
public IPlatformHandle GetCursor(StandardCursorType cursorType)
public ICursorImpl GetCursor(StandardCursorType cursorType) => new CursorStub();
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => new CursorStub();
private class CursorStub : ICursorImpl
{
return new PlatformHandle(IntPtr.Zero, null);
public void Dispose() { }
}
}
internal class PlatformSettings : IPlatformSettings

9
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@ -198,6 +198,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return ConvertDefinitionList(node, text, types, types.RowDefinitions, types.RowDefinition, "row definitions", out result);
}
if (type.Equals(types.Classes))
{
var classes = text.Split(' ');
var classNodes = classes.Select(c => new XamlAstTextNode(node, c, types.XamlIlTypes.String)).ToArray();
result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, types.Classes, types.XamlIlTypes.String, classNodes);
return true;
}
result = null;
return false;
}

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -77,6 +77,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType RowDefinitions { get; }
public IXamlType ColumnDefinition { get; }
public IXamlType ColumnDefinitions { get; }
public IXamlType Classes { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
@ -166,6 +167,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ColumnDefinitions = cfg.TypeSystem.GetType("Avalonia.Controls.ColumnDefinitions");
RowDefinition = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinition");
RowDefinitions = cfg.TypeSystem.GetType("Avalonia.Controls.RowDefinitions");
Classes = cfg.TypeSystem.GetType("Avalonia.Controls.Classes");
}
}

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

@ -1 +1 @@
Subproject commit ea80a607c5e9d8f000160dbbb48c27ed4cfafbc9
Subproject commit f3ca2028f4f64be3556a6afd22f192902de095e5

11
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs

@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal object RawSource { get; }
public override string ToString()
=> string.Concat(_elements.Select(e => e.ToString()));
=> string.Concat(_elements);
}
public class CompiledBindingPathBuilder
@ -88,7 +88,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public CompiledBindingPathBuilder Property(IPropertyInfo info, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
{
_elements.Add(new PropertyElement(info, accessorFactory));
_elements.Add(new PropertyElement(info, accessorFactory, _elements.Count == 0));
return this;
}
@ -161,10 +161,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class PropertyElement : ICompiledBindingPathElement
{
public PropertyElement(IPropertyInfo property, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory)
private readonly bool _isFirstElement;
public PropertyElement(IPropertyInfo property, Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> accessorFactory, bool isFirstElement)
{
Property = property;
AccessorFactory = accessorFactory;
_isFirstElement = isFirstElement;
}
public IPropertyInfo Property { get; }
@ -172,7 +175,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
public override string ToString()
=> $".{Property.Name}";
=> _isFirstElement ? Property.Name : $".{Property.Name}";
}
internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement

2
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -2,6 +2,8 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia</RootNamespace>
<Nullable>Enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<None Remove="Markup\Parsers\Nodes\ExpressionGrammer" />

49
src/Markup/Avalonia.Markup/Data/Binding.cs

@ -39,17 +39,17 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the name of the element to use as the binding source.
/// </summary>
public string ElementName { get; set; }
public string? ElementName { get; set; }
/// <summary>
/// Gets or sets the relative source for the binding.
/// </summary>
public RelativeSource RelativeSource { get; set; }
public RelativeSource? RelativeSource { get; set; }
/// <summary>
/// Gets or sets the source for the binding.
/// </summary>
public object Source { get; set; }
public object? Source { get; set; }
/// <summary>
/// Gets or sets the binding path.
@ -59,24 +59,36 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets a function used to resolve types from names in the binding path.
/// </summary>
public Func<string, string, Type> TypeResolver { get; set; }
public Func<string, string, Type>? TypeResolver { get; set; }
protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object? anchor, bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
anchor = anchor ?? DefaultAnchor?.Target;
_ = target ?? throw new ArgumentNullException(nameof(target));
anchor ??= DefaultAnchor?.Target;
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
INameScope nameScope = null;
INameScope? nameScope = null;
NameScope?.TryGetTarget(out nameScope);
var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope);
if (node is null)
{
throw new InvalidOperationException("Could not parse binding expression.");
}
IStyledElement GetSource()
{
return target as IStyledElement ??
anchor as IStyledElement ??
throw new ArgumentException("Could not find binding source: either target or anchor must be an IStyledElement.");
}
if (ElementName != null)
{
return CreateElementObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
GetSource(),
ElementName,
node);
}
@ -96,9 +108,7 @@ namespace Avalonia.Data
}
else
{
return CreateSourceObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
node);
return CreateSourceObserver(GetSource(), node);
}
}
else if (RelativeSource.Mode == RelativeSourceMode.DataContext)
@ -111,15 +121,11 @@ namespace Avalonia.Data
}
else if (RelativeSource.Mode == RelativeSourceMode.Self)
{
return CreateSourceObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
node);
return CreateSourceObserver(GetSource(), node);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
return CreateTemplatedParentObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
node);
return CreateTemplatedParentObserver(GetSource(), node);
}
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
{
@ -128,10 +134,7 @@ namespace Avalonia.Data
throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
}
return CreateFindAncestorObserver(
(target as IStyledElement) ?? (anchor as IStyledElement),
RelativeSource,
node);
return CreateFindAncestorObserver(GetSource(), RelativeSource, node);
}
else
{

49
src/Markup/Avalonia.Markup/Data/BindingBase.cs

@ -38,22 +38,22 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the <see cref="IValueConverter"/> to use.
/// </summary>
public IValueConverter Converter { get; set; }
public IValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object ConverterParameter { get; set; }
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding is unable to produce a value.
/// </summary>
public object FallbackValue { get; set; }
public object? FallbackValue { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding result is null.
/// </summary>
public object TargetNullValue { get; set; }
public object? TargetNullValue { get; set; }
/// <summary>
/// Gets or sets the binding mode.
@ -68,26 +68,27 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the string format.
/// </summary>
public string StringFormat { get; set; }
public string? StringFormat { get; set; }
public WeakReference DefaultAnchor { get; set; }
public WeakReference? DefaultAnchor { get; set; }
public WeakReference<INameScope> NameScope { get; set; }
public WeakReference<INameScope>? NameScope { get; set; }
protected abstract ExpressionObserver CreateExpressionObserver(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor,
object? anchor,
bool enableDataValidation);
/// <inheritdoc/>
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
object? anchor = null,
bool enableDataValidation = false)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
anchor = anchor ?? DefaultAnchor?.Target;
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
@ -133,18 +134,13 @@ namespace Avalonia.Data
IAvaloniaObject target,
ExpressionNode node,
bool targetIsDataContext,
object anchor)
object? anchor)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
if (!(target is IDataContextProvider))
{
target = anchor as IDataContextProvider;
if (target == null)
{
throw new InvalidOperationException("Cannot find a DataContext to bind to.");
}
target = anchor as IDataContextProvider ?? throw new InvalidOperationException("Cannot find a DataContext to bind to.");
}
if (!targetIsDataContext)
@ -171,10 +167,9 @@ namespace Avalonia.Data
string elementName,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
NameScope.TryGetTarget(out var scope);
if (scope == null)
if (NameScope is null || !NameScope.TryGetTarget(out var scope) || scope is null)
throw new InvalidOperationException("Name scope is null or was already collected");
var result = new ExpressionObserver(
NameScopeLocator.Track(scope, elementName),
@ -188,9 +183,9 @@ namespace Avalonia.Data
RelativeSource relativeSource,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
IObservable<object> controlLocator;
IObservable<object?> controlLocator;
switch (relativeSource.Tree)
{
@ -220,7 +215,7 @@ namespace Avalonia.Data
object source,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(source != null);
_ = source ?? throw new ArgumentNullException(nameof(source));
return new ExpressionObserver(source, node);
}
@ -229,7 +224,7 @@ namespace Avalonia.Data
IAvaloniaObject target,
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
_ = target ?? throw new ArgumentNullException(nameof(target));
var result = new ExpressionObserver(
() => target.GetValue(StyledElement.TemplatedParentProperty),
@ -240,7 +235,7 @@ namespace Avalonia.Data
return result;
}
protected IObservable<object> GetParentDataContext(IAvaloniaObject target)
protected IObservable<object?> GetParentDataContext(IAvaloniaObject target)
{
// The DataContext is based on the visual parent and not the logical parent: this may
// seem counter intuitive considering the fact that property inheritance works on the logical
@ -252,7 +247,7 @@ namespace Avalonia.Data
.Select(x =>
{
return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ??
Observable.Return((object)null);
Observable.Return((object?)null);
}).Switch();
}

14
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@ -22,12 +22,12 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the <see cref="IMultiValueConverter"/> to use.
/// </summary>
public IMultiValueConverter Converter { get; set; }
public IMultiValueConverter? Converter { get; set; }
/// <summary>
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
/// </summary>
public object ConverterParameter { get; set; }
public object? ConverterParameter { get; set; }
/// <summary>
/// Gets or sets the value to use when the binding is unable to produce a value.
@ -52,12 +52,12 @@ namespace Avalonia.Data
/// <summary>
/// Gets or sets the relative source for the binding.
/// </summary>
public RelativeSource RelativeSource { get; set; }
public RelativeSource? RelativeSource { get; set; }
/// <summary>
/// Gets or sets the string format.
/// </summary>
public string StringFormat { get; set; }
public string? StringFormat { get; set; }
public MultiBinding()
{
@ -69,7 +69,7 @@ namespace Avalonia.Data
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
object? anchor = null,
bool enableDataValidation = false)
{
var targetType = targetProperty?.PropertyType ?? typeof(object);
@ -105,7 +105,7 @@ namespace Avalonia.Data
}
}
private object ConvertValue(IList<object> values, Type targetType, IMultiValueConverter converter)
private object ConvertValue(IList<object?> values, Type targetType, IMultiValueConverter? converter)
{
for (var i = 0; i < values.Count; ++i)
{
@ -116,7 +116,7 @@ namespace Avalonia.Data
}
var culture = CultureInfo.CurrentCulture;
values = new System.Collections.ObjectModel.ReadOnlyCollection<object>(values);
values = new System.Collections.ObjectModel.ReadOnlyCollection<object?>(values);
object converted;
if (converter != null)
{

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

Loading…
Cancel
Save