Browse Source

Merge branch 'master' into dotnet-core-sdk-update

pull/3034/head
jp2masa 6 years ago
committed by GitHub
parent
commit
1f4c9dc3da
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      Avalonia.sln
  2. 2
      azure-pipelines.yml
  3. 6
      build/HarfBuzzSharp.props
  4. 4
      build/SkiaSharp.props
  5. 11
      native/Avalonia.Native/src/OSX/window.mm
  6. 12
      samples/ControlCatalog/App.xaml
  7. 11
      samples/ControlCatalog/App.xaml.cs
  8. 24
      samples/ControlCatalog/MainWindow.xaml
  9. 14
      samples/ControlCatalog/MainWindow.xaml.cs
  10. 3
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  11. 9
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  12. 36
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  13. 20
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  14. 1
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  15. 2
      src/Avalonia.Animation/TransitionInstance.cs
  16. 11
      src/Avalonia.Base/AvaloniaObject.cs
  17. 63
      src/Avalonia.Base/AvaloniaProperty.cs
  18. 23
      src/Avalonia.Base/EnumExtensions.cs
  19. 5
      src/Avalonia.Base/Utilities/MathUtilities.cs
  20. 12
      src/Avalonia.Base/ValueStore.cs
  21. 5
      src/Avalonia.Controls/AppBuilderBase.cs
  22. 8
      src/Avalonia.Controls/Application.cs
  23. 9
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  24. 2
      src/Avalonia.Controls/AutoCompleteBox.cs
  25. 13
      src/Avalonia.Controls/ColumnDefinition.cs
  26. 54
      src/Avalonia.Controls/DefinitionBase.cs
  27. 1
      src/Avalonia.Controls/DesktopApplicationExtensions.cs
  28. 7
      src/Avalonia.Controls/Generators/TabItemContainerGenerator.cs
  29. 12
      src/Avalonia.Controls/Grid.cs
  30. 867
      src/Avalonia.Controls/GridSplitter.cs
  31. 10
      src/Avalonia.Controls/ItemsControl.cs
  32. 22
      src/Avalonia.Controls/ListBox.cs
  33. 4
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  34. 7
      src/Avalonia.Controls/Presenters/IItemsPresenter.cs
  35. 15
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  36. 3
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  37. 2
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  38. 39
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  39. 10
      src/Avalonia.Controls/Primitives/TabStrip.cs
  40. 2
      src/Avalonia.Controls/Primitives/Thumb.cs
  41. 12
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  42. 13
      src/Avalonia.Controls/RowDefinition.cs
  43. 15
      src/Avalonia.Controls/Shapes/Shape.cs
  44. 57
      src/Avalonia.Controls/TabControl.cs
  45. 21
      src/Avalonia.Controls/TabItem.cs
  46. 5
      src/Avalonia.Controls/TextBlock.cs
  47. 8
      src/Avalonia.Controls/TextBox.cs
  48. 6
      src/Avalonia.Controls/ToolTipService.cs
  49. 19
      src/Avalonia.Controls/TreeView.cs
  50. 2
      src/Avalonia.Controls/WrapPanel.cs
  51. 4
      src/Avalonia.Diagnostics/Views/TreePageView.xaml
  52. 105
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  53. 62
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
  54. BIN
      src/Avalonia.Dialogs/Assets/Roboto-Light.ttf
  55. 1
      src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
  56. 7
      src/Avalonia.Dialogs/ManagedFileChooserViewModel.cs
  57. 2
      src/Avalonia.Input/AccessKeyHandler.cs
  58. 2
      src/Avalonia.Input/FocusManager.cs
  59. 13
      src/Avalonia.Input/MouseDevice.cs
  60. 2
      src/Avalonia.Input/Pointer.cs
  61. 9
      src/Avalonia.Input/PointerPoint.cs
  62. 21
      src/Avalonia.Input/TouchDevice.cs
  63. 7
      src/Avalonia.Layout/LayoutHelper.cs
  64. 8
      src/Avalonia.Layout/LayoutManager.cs
  65. 25
      src/Avalonia.Layout/LayoutQueue.cs
  66. 51
      src/Avalonia.Layout/Layoutable.cs
  67. 1
      src/Avalonia.Native/Avalonia.Native.csproj
  68. 34
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  69. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  70. 7
      src/Avalonia.Native/WindowImplBase.cs
  71. 58
      src/Avalonia.Themes.Default/GridSplitter.xaml
  72. 28
      src/Avalonia.Visuals/Media/FontFamily.cs
  73. 112
      src/Avalonia.Visuals/Media/FontManager.cs
  74. 6
      src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs
  75. 47
      src/Avalonia.Visuals/Media/FormattedText.cs
  76. 111
      src/Avalonia.Visuals/Media/GlyphTypeface.cs
  77. 99
      src/Avalonia.Visuals/Media/Typeface.cs
  78. 48
      src/Avalonia.Visuals/Platform/IFontManagerImpl.cs
  79. 89
      src/Avalonia.Visuals/Platform/IGlyphTypefaceImpl.cs
  80. 16
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  81. 6
      src/Avalonia.Visuals/Rendering/RendererBase.cs
  82. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  83. 2
      src/Avalonia.X11/X11Platform.cs
  84. 10
      src/Avalonia.X11/X11Window.cs
  85. 12
      src/Avalonia.X11/XI2Manager.cs
  86. 1
      src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
  87. 40
      src/Skia/Avalonia.Skia/FontKey.cs
  88. 82
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  89. 80
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  90. 179
      src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs
  91. 14
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  92. 91
      src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
  93. 8
      src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
  94. 5
      src/Skia/Avalonia.Skia/SkiaPlatform.cs
  95. 86
      src/Skia/Avalonia.Skia/TypefaceCache.cs
  96. 19
      src/Skia/Avalonia.Skia/TypefaceCollectionEntry.cs
  97. 1
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  98. 25
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  99. 49
      src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs
  100. 71
      src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs

3
Avalonia.sln

@ -128,6 +128,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
build\BuildTargets.targets = build\BuildTargets.targets
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
@ -201,7 +202,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution

2
azure-pipelines.yml

@ -60,7 +60,7 @@ jobs:
sdk: 'macosx10.14'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: 'default' # Options: 8, 9, default, specifyPath
xcodeVersion: '10' # Options: 8, 9, default, specifyPath
args: '-derivedDataPath ./'
- task: CmdLine@2

6
build/HarfBuzzSharp.props

@ -0,0 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="HarfBuzzSharp" Version="2.6.1-rc.153" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1-rc.153" />
</ItemGroup>
</Project>

4
build/SkiaSharp.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.2" />
<PackageReference Include="SkiaSharp" Version="1.68.1-rc.153" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1-rc.153" />
</ItemGroup>
</Project>

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

@ -855,8 +855,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
if(type == Wheel)
{
delta.X = [event scrollingDeltaX] / 50;
delta.Y = [event scrollingDeltaY] / 50;
auto speed = 5;
if([event hasPreciseScrollingDeltas])
{
speed = 50;
}
delta.X = [event scrollingDeltaX] / speed;
delta.Y = [event scrollingDeltaY] / speed;
if(delta.X == 0 && delta.Y == 0)
{

12
samples/ControlCatalog/App.xaml

@ -17,16 +17,4 @@
</Style>
<StyleInclude Source="/SideBar.xaml"/>
</Application.Styles>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="Open" Clicked="OnOpenClicked"/>
<NativeMenuItem Header="Recent">
<NativeMenuItem.Menu>
<NativeMenu/>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Quit Avalonia" Gesture="CMD+Q"/>
</NativeMenu>
</NativeMenu.Menu>
</Application>

11
samples/ControlCatalog/App.xaml.cs

@ -8,20 +8,9 @@ namespace ControlCatalog
{
public class App : Application
{
private NativeMenu _recentMenu;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
Name = "Avalonia";
_recentMenu = (NativeMenu.GetMenu(this).Items[1] as NativeMenuItem).Menu;
}
public void OnOpenClicked(object sender, EventArgs args)
{
_recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
}
public override void OnFrameworkInitializationCompleted()

24
samples/ControlCatalog/MainWindow.xaml

@ -37,12 +37,20 @@
</NativeMenu>
</NativeMenu.Menu>
<Window.DataTemplates>
<DataTemplate DataType="vm:NotificationViewModel">
<v:CustomNotificationView />
</DataTemplate>
</Window.DataTemplates>
<Panel>
<local:MainView/>
</Panel>
<Window.DataTemplates>
<DataTemplate DataType="vm:NotificationViewModel">
<v:CustomNotificationView />
</DataTemplate>
</Window.DataTemplates>
<DockPanel LastChildFill="True">
<Menu Name="MainMenu" DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Header="Exit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="Help">
<MenuItem Header="About" Command="{Binding AboutCommand}" />
</MenuItem>
</Menu>
<local:MainView />
</DockPanel>
</Window>

14
samples/ControlCatalog/MainWindow.xaml.cs

@ -31,20 +31,28 @@ namespace ControlCatalog
DataContext = new MainWindowViewModel(_notificationArea);
_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
var mainMenu = this.FindControl<Menu>("MainMenu");
mainMenu.AttachedToVisualTree += MenuAttached;
}
public void MenuAttached(object sender, VisualTreeAttachmentEventArgs e)
{
if (NativeMenu.GetIsNativeMenuExported(this) && sender is Menu mainMenu)
{
mainMenu.IsVisible = false;
}
}
public void OnOpenClicked(object sender, EventArgs args)
{
_recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
}
public void OnCloseClicked(object sender, EventArgs args)
{
Close();
}
private void InitializeComponent()
{
// TODO: iOS does not support dynamically loading assemblies

3
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -15,6 +15,7 @@
</ComboBox>
<Button Command="{Binding AddItem}">Add Item</Button>
<Button Command="{Binding RandomizeHeights}">Randomize Heights</Button>
<Button Command="{Binding ResetItems}">Reset items</Button>
</StackPanel>
<Border BorderThickness="1" BorderBrush="{DynamicResource ThemeBorderMidBrush}" Margin="0 0 0 16">
<ScrollViewer Name="scroller"
@ -23,7 +24,7 @@
<ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Height="{Binding Height}" Text="{Binding Text}"/>
<TextBlock Focusable="True" Height="{Binding Height}" Text="{Binding Text}"/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>

9
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@ -20,6 +20,7 @@ namespace ControlCatalog.Pages
_repeater = this.FindControl<ItemsRepeater>("repeater");
_scroller = this.FindControl<ScrollViewer>("scroller");
_repeater.PointerPressed += RepeaterClick;
_repeater.KeyDown += RepeaterOnKeyDown;
DataContext = new ItemsRepeaterPageViewModel();
}
@ -77,5 +78,13 @@ namespace ControlCatalog.Pages
var item = (e.Source as TextBlock)?.DataContext as ItemsRepeaterPageViewModel.Item;
((ItemsRepeaterPageViewModel)DataContext).SelectedItem = item;
}
private void RepeaterOnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F5)
{
((ItemsRepeaterPageViewModel)DataContext).ResetItems();
}
}
}
}

36
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@ -7,25 +7,27 @@ namespace ControlCatalog.ViewModels
{
public class ItemsRepeaterPageViewModel : ReactiveObject
{
private int newItemIndex = 1;
private int _newItemIndex = 1;
private int _newGenerationIndex = 0;
private ObservableCollection<Item> _items;
public ItemsRepeaterPageViewModel()
{
Items = new ObservableCollection<Item>(
Enumerable.Range(1, 100000).Select(i => new Item
{
Text = $"Item {i.ToString()}",
}));
Items = CreateItems();
}
public ObservableCollection<Item> Items { get; }
public ObservableCollection<Item> Items
{
get => _items;
set => this.RaiseAndSetIfChanged(ref _items, value);
}
public Item SelectedItem { get; set; }
public void AddItem()
{
var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1;
Items.Insert(index + 1, new Item { Text = $"New Item {newItemIndex++}" });
Items.Insert(index + 1, new Item { Text = $"New Item {_newItemIndex++}" });
}
public void RandomizeHeights()
@ -38,6 +40,24 @@ namespace ControlCatalog.ViewModels
}
}
public void ResetItems()
{
Items = CreateItems();
}
private ObservableCollection<Item> CreateItems()
{
var suffix = _newGenerationIndex == 0 ? string.Empty : $"[{_newGenerationIndex.ToString()}]";
_newGenerationIndex++;
return new ObservableCollection<Item>(
Enumerable.Range(1, 100000).Select(i => new Item
{
Text = $"Item {i.ToString()} {suffix}"
}));
}
public class Item : ReactiveObject
{
private double _height = double.NaN;

20
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -1,5 +1,7 @@
using System.Reactive;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications;
using Avalonia.Dialogs;
using ReactiveUI;
namespace ControlCatalog.ViewModels
@ -26,6 +28,20 @@ namespace ControlCatalog.ViewModels
{
NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error));
});
AboutCommand = ReactiveCommand.CreateFromTask(async () =>
{
var dialog = new AboutAvaloniaDialog();
var mainWindow = (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
await dialog.ShowDialog(mainWindow);
});
ExitCommand = ReactiveCommand.Create(() =>
{
(App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown();
});
}
public IManagedNotificationManager NotificationManager
@ -39,5 +55,9 @@ namespace ControlCatalog.ViewModels
public ReactiveCommand<Unit, Unit> ShowManagedNotificationCommand { get; }
public ReactiveCommand<Unit, Unit> ShowNativeNotificationCommand { get; }
public ReactiveCommand<Unit, Unit> AboutCommand { get; }
public ReactiveCommand<Unit, Unit> ExitCommand { get; }
}
}

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

@ -10,3 +10,4 @@ using System.Runtime.CompilerServices;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]
[assembly: InternalsVisibleTo("Avalonia.LeakTests")]
[assembly: InternalsVisibleTo("Avalonia.Animation.UnitTests")]

2
src/Avalonia.Animation/TransitionInstance.cs

@ -28,7 +28,7 @@ namespace Avalonia.Animation
private void TimerTick(TimeSpan t)
{
var interpVal = (double)t.Ticks / _duration.Ticks;
var interpVal = _duration.Ticks == 0 ? 1d : (double)t.Ticks / _duration.Ticks;
// Clamp interpolation value.
if (interpVal >= 1d | interpVal < 0d)

11
src/Avalonia.Base/AvaloniaObject.cs

@ -210,7 +210,11 @@ namespace Avalonia
/// <returns>The value.</returns>
public object GetValue(AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(property != null);
if (property is null)
{
throw new ArgumentNullException(nameof(property));
}
VerifyAccess();
if (property.IsDirect)
@ -231,7 +235,10 @@ namespace Avalonia
/// <returns>The value.</returns>
public T GetValue<T>(AvaloniaProperty<T> property)
{
Contract.Requires<ArgumentNullException>(property != null);
if (property is null)
{
throw new ArgumentNullException(nameof(property));
}
return (T)GetValue((AvaloniaProperty)property);
}

63
src/Avalonia.Base/AvaloniaProperty.cs

@ -28,6 +28,8 @@ namespace Avalonia
private readonly Dictionary<Type, PropertyMetadata> _metadata;
private readonly Dictionary<Type, PropertyMetadata> _metadataCache = new Dictionary<Type, PropertyMetadata>();
private bool _hasMetadataOverrides;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaProperty"/> class.
/// </summary>
@ -92,6 +94,9 @@ namespace Avalonia
Id = source.Id;
_defaultMetadata = source._defaultMetadata;
// Properties that have different owner can't use fast path for metadata.
_hasMetadataOverrides = true;
if (metadata != null)
{
_metadata.Add(ownerType, metadata);
@ -446,31 +451,12 @@ namespace Avalonia
///
public PropertyMetadata GetMetadata(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
PropertyMetadata result;
Type currentType = type;
if (_metadataCache.TryGetValue(type, out result))
if (!_hasMetadataOverrides)
{
return result;
return _defaultMetadata;
}
while (currentType != null)
{
if (_metadata.TryGetValue(currentType, out result))
{
_metadataCache[type] = result;
return result;
}
currentType = currentType.GetTypeInfo().BaseType;
}
_metadataCache[type] = _defaultMetadata;
return _defaultMetadata;
return GetMetadataWithOverrides(type);
}
/// <summary>
@ -535,6 +521,39 @@ namespace Avalonia
metadata.Merge(baseMetadata, this);
_metadata.Add(type, metadata);
_metadataCache.Clear();
_hasMetadataOverrides = true;
}
private PropertyMetadata GetMetadataWithOverrides(Type type)
{
if (type is null)
{
throw new ArgumentNullException(nameof(type));
}
if (_metadataCache.TryGetValue(type, out PropertyMetadata result))
{
return result;
}
Type currentType = type;
while (currentType != null)
{
if (_metadata.TryGetValue(currentType, out result))
{
_metadataCache[type] = result;
return result;
}
currentType = currentType.GetTypeInfo().BaseType;
}
_metadataCache[type] = _defaultMetadata;
return _defaultMetadata;
}
[DebuggerHidden]

23
src/Avalonia.Base/EnumExtensions.cs

@ -0,0 +1,23 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Runtime.CompilerServices;
namespace Avalonia
{
/// <summary>
/// Provides extension methods for enums.
/// </summary>
public static class EnumExtensions
{
[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;
}
}
}

5
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -159,6 +159,11 @@ namespace Avalonia.Utilities
/// <returns>The clamped value.</returns>
public static int Clamp(int val, int min, int max)
{
if (min > max)
{
throw new ArgumentException($"{min} cannot be greater than {max}.");
}
if (val < min)
{
return min;

12
src/Avalonia.Base/ValueStore.cs

@ -57,7 +57,8 @@ namespace Avalonia
{
if (priority == (int)BindingPriority.LocalValue)
{
_propertyValues.SetValue(property, Validate(property, value));
Validate(property, ref value);
_propertyValues.SetValue(property, value);
Changed(property, priority, v, value);
return;
}
@ -78,7 +79,8 @@ namespace Avalonia
if (priority == (int)BindingPriority.LocalValue)
{
_propertyValues.AddValue(property, Validate(property, value));
Validate(property, ref value);
_propertyValues.AddValue(property, value);
Changed(property, priority, AvaloniaProperty.UnsetValue, value);
return;
}
@ -166,16 +168,14 @@ namespace Avalonia
validate2);
}
private object Validate(AvaloniaProperty property, object value)
private void Validate(AvaloniaProperty property, ref object value)
{
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType());
if (validate != null && value != AvaloniaProperty.UnsetValue)
{
return validate(_owner, value);
value = validate(_owner, value);
}
return value;
}
private DeferredSetter<T> GetDeferredSetter<T>(AvaloniaProperty property)

5
src/Avalonia.Controls/AppBuilderBase.cs

@ -125,9 +125,8 @@ namespace Avalonia.Controls
});
// Copy-pasted because we can't call extension methods due to generic constraints
var lifetime = new ClassicDesktopStyleApplicationLifetime(Instance) {ShutdownMode = ShutdownMode.OnMainWindowClose};
Instance.ApplicationLifetime = lifetime;
SetupWithoutStarting();
var lifetime = new ClassicDesktopStyleApplicationLifetime() {ShutdownMode = ShutdownMode.OnMainWindowClose};
SetupWithLifetime(lifetime);
lifetime.Start(Array.Empty<string>());
}

8
src/Avalonia.Controls/Application.cs

@ -48,6 +48,14 @@ namespace Avalonia
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Creates an instance of the <see cref="Application"/> class.
/// </summary>
public Application()
{
Name = "Avalonia Application";
}
/// <summary>
/// Gets the current instance of the <see cref="Application"/> class.
/// </summary>

9
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -5,12 +5,12 @@ using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using Avalonia.Threading;
namespace Avalonia.Controls.ApplicationLifetimes
{
public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable
{
private readonly Application _app;
private int _exitCode;
private CancellationTokenSource _cts;
private bool _isShuttingDown;
@ -34,12 +34,11 @@ namespace Avalonia.Controls.ApplicationLifetimes
_activeLifetime?._windows.Add((Window)sender);
}
public ClassicDesktopStyleApplicationLifetime(Application app)
public ClassicDesktopStyleApplicationLifetime()
{
if (_activeLifetime != null)
throw new InvalidOperationException(
"Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
_app = app;
_activeLifetime = this;
}
@ -103,7 +102,7 @@ namespace Avalonia.Controls.ApplicationLifetimes
Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));
_cts = new CancellationTokenSource();
MainWindow?.Show();
_app.Run(_cts.Token);
Dispatcher.UIThread.MainLoop(_cts.Token);
Environment.ExitCode = _exitCode;
return _exitCode;
}
@ -124,7 +123,7 @@ namespace Avalonia
this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
where T : AppBuilderBase<T>, new()
{
var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode};
var lifetime = new ClassicDesktopStyleApplicationLifetime() {ShutdownMode = shutdownMode};
builder.SetupWithLifetime(lifetime);
return lifetime.Start(args);
}

2
src/Avalonia.Controls/AutoCompleteBox.cs

@ -704,7 +704,7 @@ namespace Avalonia.Controls
added.Add(e.NewValue);
}
OnSelectionChanged(new SelectionChangedEventArgs(SelectionChangedEvent, removed, added));
OnSelectionChanged(new SelectionChangedEventArgs(SelectionChangedEvent, added, removed));
}
/// <summary>

13
src/Avalonia.Controls/ColumnDefinition.cs

@ -26,6 +26,16 @@ namespace Avalonia.Controls
public static readonly StyledProperty<GridLength> WidthProperty =
AvaloniaProperty.Register<ColumnDefinition, GridLength>(nameof(Width), new GridLength(1, GridUnitType.Star));
/// <summary>
/// Initializes static members of the <see cref="ColumnDefinition"/> class.
/// </summary>
static ColumnDefinition()
{
AffectsParentMeasure(MinWidthProperty, MaxWidthProperty);
WidthProperty.Changed.AddClassHandler<DefinitionBase>(OnUserSizePropertyChanged);
}
/// <summary>
/// Initializes a new instance of the <see cref="ColumnDefinition"/> class.
/// </summary>
@ -68,7 +78,6 @@ namespace Avalonia.Controls
}
set
{
Parent?.InvalidateMeasure();
SetValue(MaxWidthProperty, value);
}
}
@ -84,7 +93,6 @@ namespace Avalonia.Controls
}
set
{
Parent?.InvalidateMeasure();
SetValue(MinWidthProperty, value);
}
}
@ -100,7 +108,6 @@ namespace Avalonia.Controls
}
set
{
Parent?.InvalidateMeasure();
SetValue(WidthProperty, value);
}
}

54
src/Avalonia.Controls/DefinitionBase.cs

@ -7,9 +7,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Utilities;
namespace Avalonia.Controls
@ -50,6 +47,8 @@ namespace Avalonia.Controls
}
}
}
Parent?.InvalidateMeasure();
}
/// <summary>
@ -63,6 +62,8 @@ namespace Avalonia.Controls
_sharedState.RemoveMember(this);
_sharedState = null;
}
Parent?.InvalidateMeasure();
}
/// <summary>
@ -114,6 +115,36 @@ namespace Avalonia.Controls
}
}
/// <remarks>
/// Notifies parent <see cref="Grid"/> or size scope that definition size has been changed.
/// </remarks>
internal static void OnUserSizePropertyChanged(DefinitionBase definition, AvaloniaPropertyChangedEventArgs e)
{
if (definition.Parent == null)
{
return;
}
if (definition._sharedState != null)
{
definition._sharedState.Invalidate();
}
else
{
GridUnitType oldUnitType = ((GridLength)e.OldValue).GridUnitType;
GridUnitType newUnitType = ((GridLength)e.NewValue).GridUnitType;
if (oldUnitType != newUnitType)
{
definition.Parent.Invalidate();
}
else
{
definition.Parent.InvalidateMeasure();
}
}
}
/// <summary>
/// Returns <c>true</c> if this definition is a part of shared group.
/// </summary>
@ -730,5 +761,22 @@ namespace Avalonia.Controls
SharedSizeGroupProperty.Changed.AddClassHandler<DefinitionBase>(OnSharedSizeGroupPropertyChanged);
PrivateSharedSizeScopeProperty.Changed.AddClassHandler<DefinitionBase>(OnPrivateSharedSizeScopePropertyChanged);
}
/// <summary>
/// Marks a property on a definition as affecting the parent grid's measurement.
/// </summary>
/// <param name="properties">The properties.</param>
protected static void AffectsParentMeasure(params AvaloniaProperty[] properties)
{
void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as DefinitionBase)?.Parent?.InvalidateMeasure();
}
foreach (var property in properties)
{
property.Changed.Subscribe(Invalidate);
}
}
}
}

1
src/Avalonia.Controls/DesktopApplicationExtensions.cs

@ -50,6 +50,7 @@ namespace Avalonia.Controls
/// On desktop-style platforms runs the application's main loop with custom CancellationToken
/// without setting a lifetime.
/// </summary>
/// <param name="app">The application.</param>
/// <param name="token">The token to track.</param>
public static void Run(this Application app, CancellationToken token)
{

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

@ -19,8 +19,6 @@ namespace Avalonia.Controls.Generators
{
var tabItem = (TabItem)base.CreateContainer(item);
tabItem.ParentTabControl = Owner;
tabItem[~TabControl.TabStripPlacementProperty] = Owner[~TabControl.TabStripPlacementProperty];
if (tabItem.HeaderTemplate == null)
@ -48,11 +46,6 @@ namespace Avalonia.Controls.Generators
tabItem[~ContentControl.ContentTemplateProperty] = Owner[~TabControl.ContentTemplateProperty];
}
if (tabItem.Content == null)
{
tabItem[~ContentControl.ContentProperty] = tabItem[~StyledElement.DataContextProperty];
}
return tabItem;
}
}

12
src/Avalonia.Controls/Grid.cs

@ -6,6 +6,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Threading;
@ -178,6 +179,7 @@ namespace Avalonia.Controls
if (_data == null) { _data = new ExtendedData(); }
_data.ColumnDefinitions = value;
_data.ColumnDefinitions.Parent = this;
InvalidateMeasure();
}
}
@ -198,6 +200,7 @@ namespace Avalonia.Controls
if (_data == null) { _data = new ExtendedData(); }
_data.RowDefinitions = value;
_data.RowDefinitions.Parent = this;
InvalidateMeasure();
}
}
@ -569,6 +572,15 @@ namespace Avalonia.Controls
return (arrangeSize);
}
/// <summary>
/// <see cref="Panel.ChildrenChanged"/>
/// </summary>
protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
{
CellsStructureDirty = true;
base.ChildrenChanged(sender, e);
}
/// <summary>
/// Invalidates grid caches and makes the grid dirty for measure.
/// </summary>

867
src/Avalonia.Controls/GridSplitter.cs

@ -1,210 +1,841 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
// This source file is adapted from the Windows Presentation Foundation project.
// (https://github.com/dotnet/wpf/)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Avalonia.Collections;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.VisualTree;
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
/// <summary>
/// Represents the control that redistributes space between columns or rows of a Grid control.
/// Represents the control that redistributes space between columns or rows of a <see cref="Grid"/> control.
/// </summary>
/// <remarks>
/// Unlike WPF GridSplitter, Avalonia GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext.
/// </remarks>
public class GridSplitter : Thumb
{
private List<DefinitionBase> _definitions;
/// <summary>
/// Defines the <see cref="ResizeDirection"/> property.
/// </summary>
public static readonly AvaloniaProperty<GridResizeDirection> ResizeDirectionProperty =
AvaloniaProperty.Register<GridSplitter, GridResizeDirection>(nameof(ResizeDirection));
private Grid _grid;
/// <summary>
/// Defines the <see cref="ResizeBehavior"/> property.
/// </summary>
public static readonly AvaloniaProperty<GridResizeBehavior> ResizeBehaviorProperty =
AvaloniaProperty.Register<GridSplitter, GridResizeBehavior>(nameof(ResizeBehavior));
private DefinitionBase _nextDefinition;
/// <summary>
/// Defines the <see cref="ShowsPreview"/> property.
/// </summary>
public static readonly AvaloniaProperty<bool> ShowsPreviewProperty =
AvaloniaProperty.Register<GridSplitter, bool>(nameof(ShowsPreview));
private Orientation _orientation;
/// <summary>
/// Defines the <see cref="KeyboardIncrement"/> property.
/// </summary>
public static readonly AvaloniaProperty<double> KeyboardIncrementProperty =
AvaloniaProperty.Register<GridSplitter, double>(nameof(KeyboardIncrement), 10d);
private DefinitionBase _prevDefinition;
/// <summary>
/// Defines the <see cref="DragIncrement"/> property.
/// </summary>
public static readonly AvaloniaProperty<double> DragIncrementProperty =
AvaloniaProperty.Register<GridSplitter, double>(nameof(DragIncrement), 1d);
private void GetDeltaConstraints(out double min, out double max)
/// <summary>
/// Defines the <see cref="PreviewContent"/> property.
/// </summary>
public static readonly AvaloniaProperty<ITemplate<IControl>> PreviewContentProperty =
AvaloniaProperty.Register<GridSplitter, ITemplate<IControl>>(nameof(PreviewContent));
private static readonly Cursor s_columnSplitterCursor = new Cursor(StandardCursorType.SizeWestEast);
private static readonly Cursor s_rowSplitterCursor = new Cursor(StandardCursorType.SizeNorthSouth);
private ResizeData _resizeData;
/// <summary>
/// Indicates whether the Splitter resizes the Columns, Rows, or Both.
/// </summary>
public GridResizeDirection ResizeDirection
{
var prevDefinitionLen = GetActualLength(_prevDefinition);
var prevDefinitionMin = GetMinLength(_prevDefinition);
var prevDefinitionMax = GetMaxLength(_prevDefinition);
get => GetValue(ResizeDirectionProperty);
set => SetValue(ResizeDirectionProperty, value);
}
var nextDefinitionLen = GetActualLength(_nextDefinition);
var nextDefinitionMin = GetMinLength(_nextDefinition);
var nextDefinitionMax = GetMaxLength(_nextDefinition);
// Determine the minimum and maximum the columns can be resized
min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen);
max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin);
/// <summary>
/// Indicates which Columns or Rows the Splitter resizes.
/// </summary>
public GridResizeBehavior ResizeBehavior
{
get => GetValue(ResizeBehaviorProperty);
set => SetValue(ResizeBehaviorProperty, value);
}
protected override void OnDragDelta(VectorEventArgs e)
/// <summary>
/// Indicates whether to Preview the column resizing without updating layout.
/// </summary>
public bool ShowsPreview
{
// WPF doesn't change anything when spliter is in the last row/column
// but resizes the splitter row/column when it's the first one.
// this is different, but more internally consistent.
if (_prevDefinition == null || _nextDefinition == null)
return;
get => GetValue(ShowsPreviewProperty);
set => SetValue(ShowsPreviewProperty, value);
}
/// <summary>
/// The Distance to move the splitter when pressing the keyboard arrow keys.
/// </summary>
public double KeyboardIncrement
{
get => GetValue(KeyboardIncrementProperty);
set => SetValue(KeyboardIncrementProperty, value);
}
/// <summary>
/// Restricts splitter to move a multiple of the specified units.
/// </summary>
public double DragIncrement
{
get => GetValue(DragIncrementProperty);
set => SetValue(DragIncrementProperty, value);
}
/// <summary>
/// Gets or sets content that will be shown when <see cref="ShowsPreview"/> is enabled and user starts resize operation.
/// </summary>
public ITemplate<IControl> PreviewContent
{
get => GetValue(PreviewContentProperty);
set => SetValue(PreviewContentProperty, value);
}
/// <summary>
/// Converts BasedOnAlignment direction to Rows, Columns, or Both depending on its width/height.
/// </summary>
internal GridResizeDirection GetEffectiveResizeDirection()
{
GridResizeDirection direction = ResizeDirection;
if (direction != GridResizeDirection.Auto)
{
return direction;
}
// When HorizontalAlignment is Left, Right or Center, resize Columns.
if (HorizontalAlignment != HorizontalAlignment.Stretch)
{
direction = GridResizeDirection.Columns;
}
else if (VerticalAlignment != VerticalAlignment.Stretch)
{
direction = GridResizeDirection.Rows;
}
else if (Bounds.Width <= Bounds.Height) // Fall back to Width vs Height.
{
direction = GridResizeDirection.Columns;
}
else
{
direction = GridResizeDirection.Rows;
}
var delta = _orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y;
double max;
double min;
GetDeltaConstraints(out min, out max);
delta = Math.Min(Math.Max(delta, min), max);
return direction;
}
var prevIsStar = IsStar(_prevDefinition);
var nextIsStar = IsStar(_nextDefinition);
/// <summary>
/// Convert BasedOnAlignment to Next/Prev/Both depending on alignment and Direction.
/// </summary>
private GridResizeBehavior GetEffectiveResizeBehavior(GridResizeDirection direction)
{
GridResizeBehavior resizeBehavior = ResizeBehavior;
if (prevIsStar && nextIsStar)
if (resizeBehavior == GridResizeBehavior.BasedOnAlignment)
{
foreach (var definition in _definitions)
if (direction == GridResizeDirection.Columns)
{
if (definition == _prevDefinition)
switch (HorizontalAlignment)
{
SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta);
case HorizontalAlignment.Left:
resizeBehavior = GridResizeBehavior.PreviousAndCurrent;
break;
case HorizontalAlignment.Right:
resizeBehavior = GridResizeBehavior.CurrentAndNext;
break;
default:
resizeBehavior = GridResizeBehavior.PreviousAndNext;
break;
}
else if (definition == _nextDefinition)
}
else
{
switch (VerticalAlignment)
{
SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta);
case VerticalAlignment.Top:
resizeBehavior = GridResizeBehavior.PreviousAndCurrent;
break;
case VerticalAlignment.Bottom:
resizeBehavior = GridResizeBehavior.CurrentAndNext;
break;
default:
resizeBehavior = GridResizeBehavior.PreviousAndNext;
break;
}
else if (IsStar(definition))
}
}
return resizeBehavior;
}
/// <summary>
/// Removes preview adorner from the grid.
/// </summary>
private void RemovePreviewAdorner()
{
if (_resizeData.Adorner != null)
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(this);
layer.Children.Remove(_resizeData.Adorner);
}
}
/// <summary>
/// Initialize the data needed for resizing.
/// </summary>
private void InitializeData(bool showsPreview)
{
// If not in a grid or can't resize, do nothing.
if (Parent is Grid grid)
{
GridResizeDirection resizeDirection = GetEffectiveResizeDirection();
// Setup data used for resizing.
_resizeData = new ResizeData
{
Grid = grid,
ShowsPreview = showsPreview,
ResizeDirection = resizeDirection,
SplitterLength = Math.Min(Bounds.Width, Bounds.Height),
ResizeBehavior = GetEffectiveResizeBehavior(resizeDirection)
};
// Store the rows and columns to resize on drag events.
if (!SetupDefinitionsToResize())
{
// Unable to resize, clear data.
_resizeData = null;
return;
}
// Setup the preview in the adorner if ShowsPreview is true.
SetupPreviewAdorner();
}
}
/// <summary>
/// Returns true if GridSplitter can resize rows/columns.
/// </summary>
private bool SetupDefinitionsToResize()
{
int gridSpan = GetValue(_resizeData.ResizeDirection == GridResizeDirection.Columns ?
Grid.ColumnSpanProperty :
Grid.RowSpanProperty);
if (gridSpan == 1)
{
var splitterIndex = GetValue(_resizeData.ResizeDirection == GridResizeDirection.Columns ?
Grid.ColumnProperty :
Grid.RowProperty);
// Select the columns based on behavior.
int index1, index2;
switch (_resizeData.ResizeBehavior)
{
case GridResizeBehavior.PreviousAndCurrent:
// Get current and previous.
index1 = splitterIndex - 1;
index2 = splitterIndex;
break;
case GridResizeBehavior.CurrentAndNext:
// Get current and next.
index1 = splitterIndex;
index2 = splitterIndex + 1;
break;
default: // GridResizeBehavior.PreviousAndNext.
// Get previous and next.
index1 = splitterIndex - 1;
index2 = splitterIndex + 1;
break;
}
// Get count of rows/columns in the resize direction.
int count = _resizeData.ResizeDirection == GridResizeDirection.Columns ?
_resizeData.Grid.ColumnDefinitions.Count :
_resizeData.Grid.RowDefinitions.Count;
if (index1 >= 0 && index2 < count)
{
_resizeData.SplitterIndex = splitterIndex;
_resizeData.Definition1Index = index1;
_resizeData.Definition1 = GetGridDefinition(_resizeData.Grid, index1, _resizeData.ResizeDirection);
_resizeData.OriginalDefinition1Length =
_resizeData.Definition1.UserSizeValueCache; // Save Size if user cancels.
_resizeData.OriginalDefinition1ActualLength = GetActualLength(_resizeData.Definition1);
_resizeData.Definition2Index = index2;
_resizeData.Definition2 = GetGridDefinition(_resizeData.Grid, index2, _resizeData.ResizeDirection);
_resizeData.OriginalDefinition2Length =
_resizeData.Definition2.UserSizeValueCache; // Save Size if user cancels.
_resizeData.OriginalDefinition2ActualLength = GetActualLength(_resizeData.Definition2);
// Determine how to resize the columns.
bool isStar1 = IsStar(_resizeData.Definition1);
bool isStar2 = IsStar(_resizeData.Definition2);
if (isStar1 && isStar2)
{
SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars.
// If they are both stars, resize both.
_resizeData.SplitBehavior = SplitBehavior.Split;
}
else
{
// One column is fixed width, resize the first one that is fixed.
_resizeData.SplitBehavior = !isStar1 ? SplitBehavior.Resize1 : SplitBehavior.Resize2;
}
return true;
}
}
else if (prevIsStar)
return false;
}
/// <summary>
/// Create the preview adorner and add it to the adorner layer.
/// </summary>
private void SetupPreviewAdorner()
{
if (_resizeData.ShowsPreview)
{
SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta);
// Get the adorner layer and add an adorner to it.
var adornerLayer = AdornerLayer.GetAdornerLayer(_resizeData.Grid);
var previewContent = PreviewContent;
// Can't display preview.
if (adornerLayer == null)
{
return;
}
IControl builtPreviewContent = previewContent?.Build();
_resizeData.Adorner = new PreviewAdorner(builtPreviewContent);
AdornerLayer.SetAdornedElement(_resizeData.Adorner, this);
adornerLayer.Children.Add(_resizeData.Adorner);
// Get constraints on preview's translation.
GetDeltaConstraints(out _resizeData.MinChange, out _resizeData.MaxChange);
}
else if (nextIsStar)
}
protected override void OnPointerEnter(PointerEventArgs e)
{
base.OnPointerEnter(e);
GridResizeDirection direction = GetEffectiveResizeDirection();
switch (direction)
{
SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta);
case GridResizeDirection.Columns:
Cursor = s_columnSplitterCursor;
break;
case GridResizeDirection.Rows:
Cursor = s_rowSplitterCursor;
break;
}
else
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (_resizeData != null)
{
SetLength(_prevDefinition, GetActualLength(_prevDefinition) + delta);
SetLength(_nextDefinition, GetActualLength(_nextDefinition) - delta);
CancelResize();
}
}
private double GetActualLength(DefinitionBase definition)
protected override void OnDragStarted(VectorEventArgs e)
{
if (definition == null)
return 0;
var columnDefinition = definition as ColumnDefinition;
return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight;
base.OnDragStarted(e);
// TODO: Looks like that sometimes thumb will raise multiple drag started events.
// Debug.Assert(_resizeData == null, "_resizeData is not null, DragCompleted was not called");
if (_resizeData != null)
{
return;
}
InitializeData(ShowsPreview);
}
private double GetMinLength(DefinitionBase definition)
protected override void OnDragDelta(VectorEventArgs e)
{
if (definition == null)
return 0;
var columnDefinition = definition as ColumnDefinition;
return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight;
base.OnDragDelta(e);
if (_resizeData != null)
{
double horizontalChange = e.Vector.X;
double verticalChange = e.Vector.Y;
// Round change to nearest multiple of DragIncrement.
double dragIncrement = DragIncrement;
horizontalChange = Math.Round(horizontalChange / dragIncrement) * dragIncrement;
verticalChange = Math.Round(verticalChange / dragIncrement) * dragIncrement;
if (_resizeData.ShowsPreview)
{
// Set the Translation of the Adorner to the distance from the thumb.
if (_resizeData.ResizeDirection == GridResizeDirection.Columns)
{
_resizeData.Adorner.OffsetX = Math.Min(
Math.Max(horizontalChange, _resizeData.MinChange),
_resizeData.MaxChange);
}
else
{
_resizeData.Adorner.OffsetY = Math.Min(
Math.Max(verticalChange, _resizeData.MinChange),
_resizeData.MaxChange);
}
}
else
{
// Directly update the grid.
MoveSplitter(horizontalChange, verticalChange);
}
}
}
private double GetMaxLength(DefinitionBase definition)
protected override void OnDragCompleted(VectorEventArgs e)
{
if (definition == null)
return 0;
var columnDefinition = definition as ColumnDefinition;
return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight;
base.OnDragCompleted(e);
if (_resizeData != null)
{
if (_resizeData.ShowsPreview)
{
// Update the grid.
MoveSplitter(_resizeData.Adorner.OffsetX, _resizeData.Adorner.OffsetY);
RemovePreviewAdorner();
}
_resizeData = null;
}
}
private bool IsStar(DefinitionBase definition)
protected override void OnKeyDown(KeyEventArgs e)
{
var columnDefinition = definition as ColumnDefinition;
return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar;
Key key = e.Key;
switch (key)
{
case Key.Escape:
if (_resizeData != null)
{
CancelResize();
e.Handled = true;
}
break;
case Key.Left:
e.Handled = KeyboardMoveSplitter(-KeyboardIncrement, 0);
break;
case Key.Right:
e.Handled = KeyboardMoveSplitter(KeyboardIncrement, 0);
break;
case Key.Up:
e.Handled = KeyboardMoveSplitter(0, -KeyboardIncrement);
break;
case Key.Down:
e.Handled = KeyboardMoveSplitter(0, KeyboardIncrement);
break;
}
}
private void SetLengthInStars(DefinitionBase definition, double value)
/// <summary>
/// Cancels the resize operation.
/// </summary>
private void CancelResize()
{
var columnDefinition = definition as ColumnDefinition;
if (columnDefinition != null)
// Restore original column/row lengths.
if (_resizeData.ShowsPreview)
{
columnDefinition.Width = new GridLength(value, GridUnitType.Star);
RemovePreviewAdorner();
}
else
else // Reset the columns/rows lengths to the saved values.
{
((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star);
SetDefinitionLength(_resizeData.Definition1, _resizeData.OriginalDefinition1Length);
SetDefinitionLength(_resizeData.Definition2, _resizeData.OriginalDefinition2Length);
}
_resizeData = null;
}
/// <summary>
/// Returns true if the row/column has a star length.
/// </summary>
private static bool IsStar(DefinitionBase definition)
{
return definition.UserSizeValueCache.IsStar;
}
private void SetLength(DefinitionBase definition, double value)
/// <summary>
/// Gets Column or Row definition at index from grid based on resize direction.
/// </summary>
private static DefinitionBase GetGridDefinition(Grid grid, int index, GridResizeDirection direction)
{
var columnDefinition = definition as ColumnDefinition;
if (columnDefinition != null)
return direction == GridResizeDirection.Columns ?
(DefinitionBase)grid.ColumnDefinitions[index] :
(DefinitionBase)grid.RowDefinitions[index];
}
/// <summary>
/// Retrieves the ActualWidth or ActualHeight of the definition depending on its type Column or Row.
/// </summary>
private double GetActualLength(DefinitionBase definition)
{
var column = definition as ColumnDefinition;
return column?.ActualWidth ?? ((RowDefinition)definition).ActualHeight;
}
/// <summary>
/// Gets Column or Row definition at index from grid based on resize direction.
/// </summary>
private static void SetDefinitionLength(DefinitionBase definition, GridLength length)
{
definition.SetValue(
definition is ColumnDefinition ? ColumnDefinition.WidthProperty : RowDefinition.HeightProperty, length);
}
/// <summary>
/// Get the minimum and maximum Delta can be given definition constraints (MinWidth/MaxWidth).
/// </summary>
private void GetDeltaConstraints(out double minDelta, out double maxDelta)
{
double definition1Len = GetActualLength(_resizeData.Definition1);
double definition1Min = _resizeData.Definition1.UserMinSizeValueCache;
double definition1Max = _resizeData.Definition1.UserMaxSizeValueCache;
double definition2Len = GetActualLength(_resizeData.Definition2);
double definition2Min = _resizeData.Definition2.UserMinSizeValueCache;
double definition2Max = _resizeData.Definition2.UserMaxSizeValueCache;
// Set MinWidths to be greater than width of splitter.
if (_resizeData.SplitterIndex == _resizeData.Definition1Index)
{
columnDefinition.Width = new GridLength(value);
definition1Min = Math.Max(definition1Min, _resizeData.SplitterLength);
}
else
else if (_resizeData.SplitterIndex == _resizeData.Definition2Index)
{
((RowDefinition)definition).Height = new GridLength(value);
definition2Min = Math.Max(definition2Min, _resizeData.SplitterLength);
}
// Determine the minimum and maximum the columns can be resized.
minDelta = -Math.Min(definition1Len - definition1Min, definition2Max - definition2Len);
maxDelta = Math.Min(definition1Max - definition1Len, definition2Len - definition2Min);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
/// <summary>
/// Sets the length of definition1 and definition2.
/// </summary>
private void SetLengths(double definition1Pixels, double definition2Pixels)
{
base.OnAttachedToVisualTree(e);
_grid = this.GetVisualParent<Grid>();
// For the case where both definition1 and 2 are stars, update all star values to match their current pixel values.
if (_resizeData.SplitBehavior == SplitBehavior.Split)
{
var definitions = _resizeData.ResizeDirection == GridResizeDirection.Columns ?
(IAvaloniaReadOnlyList<DefinitionBase>)_resizeData.Grid.ColumnDefinitions :
(IAvaloniaReadOnlyList<DefinitionBase>)_resizeData.Grid.RowDefinitions;
_orientation = DetectOrientation();
var definitionsCount = definitions.Count;
for (var i = 0; i < definitionsCount; i++)
{
DefinitionBase definition = definitions[i];
int definitionIndex; //row or col
if (_orientation == Orientation.Vertical)
// For each definition, if it is a star, set is value to ActualLength in stars
// This makes 1 star == 1 pixel in length
if (i == _resizeData.Definition1Index)
{
SetDefinitionLength(definition, new GridLength(definition1Pixels, GridUnitType.Star));
}
else if (i == _resizeData.Definition2Index)
{
SetDefinitionLength(definition, new GridLength(definition2Pixels, GridUnitType.Star));
}
else if (IsStar(definition))
{
SetDefinitionLength(definition, new GridLength(GetActualLength(definition), GridUnitType.Star));
}
}
}
else if (_resizeData.SplitBehavior == SplitBehavior.Resize1)
{
Cursor = new Cursor(StandardCursorType.SizeWestEast);
_definitions = _grid.ColumnDefinitions.Cast<DefinitionBase>().ToList();
definitionIndex = GetValue(Grid.ColumnProperty);
PseudoClasses.Add(":vertical");
SetDefinitionLength(_resizeData.Definition1, new GridLength(definition1Pixels));
}
else
{
Cursor = new Cursor(StandardCursorType.SizeNorthSouth);
definitionIndex = GetValue(Grid.RowProperty);
_definitions = _grid.RowDefinitions.Cast<DefinitionBase>().ToList();
PseudoClasses.Add(":horizontal");
SetDefinitionLength(_resizeData.Definition2, new GridLength(definition2Pixels));
}
}
/// <summary>
/// Move the splitter by the given Delta's in the horizontal and vertical directions.
/// </summary>
private void MoveSplitter(double horizontalChange, double verticalChange)
{
Debug.Assert(_resizeData != null, "_resizeData should not be null when calling MoveSplitter");
// Calculate the offset to adjust the splitter.
var delta = _resizeData.ResizeDirection == GridResizeDirection.Columns ? horizontalChange : verticalChange;
DefinitionBase definition1 = _resizeData.Definition1;
DefinitionBase definition2 = _resizeData.Definition2;
if (definition1 != null && definition2 != null)
{
double actualLength1 = GetActualLength(definition1);
double actualLength2 = GetActualLength(definition2);
// When splitting, Check to see if the total pixels spanned by the definitions
// is the same asbefore starting resize. If not cancel the drag
if (_resizeData.SplitBehavior == SplitBehavior.Split &&
!MathUtilities.AreClose(
actualLength1 + actualLength2,
_resizeData.OriginalDefinition1ActualLength + _resizeData.OriginalDefinition2ActualLength))
{
CancelResize();
return;
}
GetDeltaConstraints(out var min, out var max);
// Constrain Delta to Min/MaxWidth of columns
delta = Math.Min(Math.Max(delta, min), max);
double definition1LengthNew = actualLength1 + delta;
double definition2LengthNew = actualLength1 + actualLength2 - definition1LengthNew;
SetLengths(definition1LengthNew, definition2LengthNew);
}
}
if (definitionIndex > 0)
_prevDefinition = _definitions[definitionIndex - 1];
/// <summary>
/// Move the splitter using the Keyboard (Don't show preview).
/// </summary>
private bool KeyboardMoveSplitter(double horizontalChange, double verticalChange)
{
// If moving with the mouse, ignore keyboard motion.
if (_resizeData != null)
{
return false; // Don't handle the event.
}
// Don't show preview.
InitializeData(false);
// Check that we are actually able to resize.
if (_resizeData == null)
{
return false; // Don't handle the event.
}
if (definitionIndex < _definitions.Count - 1)
_nextDefinition = _definitions[definitionIndex + 1];
MoveSplitter(horizontalChange, verticalChange);
_resizeData = null;
return true;
}
private Orientation DetectOrientation()
/// <summary>
/// This adorner draws the preview for the <see cref="GridSplitter"/>.
/// It also positions the adorner.
/// </summary>
private sealed class PreviewAdorner : Decorator
{
if (!_grid.ColumnDefinitions.Any())
return Orientation.Horizontal;
if (!_grid.RowDefinitions.Any())
return Orientation.Vertical;
private readonly TranslateTransform _translation;
private readonly Decorator _decorator;
public PreviewAdorner(IControl previewControl)
{
// Add a decorator to perform translations.
_translation = new TranslateTransform();
_decorator = new Decorator
{
Child = previewControl,
RenderTransform = _translation
};
Child = _decorator;
}
var col = GetValue(Grid.ColumnProperty);
var row = GetValue(Grid.RowProperty);
var width = _grid.ColumnDefinitions[col].Width;
var height = _grid.RowDefinitions[row].Height;
if (width.IsAuto && !height.IsAuto)
/// <summary>
/// The Preview's Offset in the X direction from the GridSplitter.
/// </summary>
public double OffsetX
{
return Orientation.Vertical;
get => _translation.X;
set => _translation.X = value;
}
if (!width.IsAuto && height.IsAuto)
/// <summary>
/// The Preview's Offset in the Y direction from the GridSplitter.
/// </summary>
public double OffsetY
{
return Orientation.Horizontal;
get => _translation.Y;
set => _translation.Y = value;
}
if (_grid.Children.OfType<Control>() // Decision based on other controls in the same column
.Where(c => Grid.GetColumn(c) == col)
.Any(c => c.GetType() != typeof(GridSplitter)))
protected override Size ArrangeOverride(Size finalSize)
{
return Orientation.Horizontal;
// Adorners always get clipped to the owner control. In this case we want
// to constrain size to the splitter size but draw on top of the parent grid.
Clip = null;
return base.ArrangeOverride(finalSize);
}
return Orientation.Vertical;
}
/// <summary>
/// <see cref="GridSplitter"/> has special Behavior when columns are fixed.
/// If the left column is fixed, splitter will only resize that column.
/// Else if the right column is fixed, splitter will only resize the right column.
/// </summary>
private enum SplitBehavior
{
/// <summary>
/// Both columns/rows are star lengths.
/// </summary>
Split,
/// <summary>
/// Resize 1 only.
/// </summary>
Resize1,
/// <summary>
/// Resize 2 only.
/// </summary>
Resize2
}
/// <summary>
/// Stores data during the resizing operation.
/// </summary>
private class ResizeData
{
public bool ShowsPreview;
public PreviewAdorner Adorner;
// The constraints to keep the Preview within valid ranges.
public double MinChange;
public double MaxChange;
// The grid to Resize.
public Grid Grid;
// Cache of Resize Direction and Behavior.
public GridResizeDirection ResizeDirection;
public GridResizeBehavior ResizeBehavior;
// The columns/rows to resize.
public DefinitionBase Definition1;
public DefinitionBase Definition2;
// Are the columns/rows star lengths.
public SplitBehavior SplitBehavior;
// The index of the splitter.
public int SplitterIndex;
// The indices of the columns/rows.
public int Definition1Index;
public int Definition2Index;
// The original lengths of Definition1 and Definition2 (to restore lengths if user cancels resize).
public GridLength OriginalDefinition1Length;
public GridLength OriginalDefinition2Length;
public double OriginalDefinition1ActualLength;
public double OriginalDefinition2ActualLength;
// The minimum of Width/Height of Splitter. Used to ensure splitter
// isn't hidden by resizing a row/column smaller than the splitter.
public double SplitterLength;
}
}
/// <summary>
/// Enum to indicate whether <see cref="GridSplitter"/> resizes Columns or Rows.
/// </summary>
public enum GridResizeDirection
{
/// <summary>
/// Determines whether to resize rows or columns based on its Alignment and
/// width compared to height.
/// </summary>
Auto,
/// <summary>
/// Resize columns when dragging Splitter.
/// </summary>
Columns,
/// <summary>
/// Resize rows when dragging Splitter.
/// </summary>
Rows
}
/// <summary>
/// Enum to indicate what Columns or Rows the <see cref="GridSplitter"/> resizes.
/// </summary>
public enum GridResizeBehavior
{
/// <summary>
/// Determine which columns or rows to resize based on its Alignment.
/// </summary>
BasedOnAlignment,
/// <summary>
/// Resize the current and next Columns or Rows.
/// </summary>
CurrentAndNext,
/// <summary>
/// Resize the previous and current Columns or Rows.
/// </summary>
PreviousAndCurrent,
/// <summary>
/// Resize the previous and next Columns or Rows.
/// </summary>
PreviousAndNext
}
}

10
src/Avalonia.Controls/ItemsControl.cs

@ -359,6 +359,12 @@ namespace Avalonia.Controls
UpdateItemCount();
RemoveControlItemsFromLogicalChildren(oldValue);
AddControlItemsToLogicalChildren(newValue);
if (Presenter != null)
{
Presenter.Items = newValue;
}
SubscribeToItems(newValue);
}
@ -370,6 +376,8 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
protected virtual void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateItemCount();
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
@ -381,7 +389,7 @@ namespace Avalonia.Controls
break;
}
UpdateItemCount();
Presenter?.ItemsChanged(e);
var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0);

22
src/Avalonia.Controls/ListBox.cs

@ -7,6 +7,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -132,21 +133,26 @@ namespace Avalonia.Controls
{
base.OnPointerPressed(e);
if (e.MouseButton == MouseButton.Left || e.MouseButton == MouseButton.Right)
if (e.Source is IVisual source)
{
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.InputModifiers & InputModifiers.Shift) != 0,
(e.InputModifiers & InputModifiers.Control) != 0,
e.MouseButton == MouseButton.Right);
var point = e.GetCurrentPoint(source);
if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed)
{
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0,
(e.KeyModifiers & KeyModifiers.Control) != 0,
point.Properties.IsRightButtonPressed);
}
}
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
Scroll = e.NameScope.Find<IScrollable>("PART_ScrollViewer");
base.OnTemplateApplied(e);
}
}
}

4
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -804,9 +804,9 @@ namespace Avalonia.Controls
private void TextBoxOnPointerPressed(object sender, PointerPressedEventArgs e)
{
if (e.Device.Captured != Spinner)
if (e.Pointer.Captured != Spinner)
{
Dispatcher.UIThread.InvokeAsync(() => { e.Device.Capture(Spinner); }, DispatcherPriority.Input);
Dispatcher.UIThread.InvokeAsync(() => { e.Pointer.Capture(Spinner); }, DispatcherPriority.Input);
}
}

7
src/Avalonia.Controls/Presenters/IItemsPresenter.cs

@ -1,12 +1,19 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections;
using System.Collections.Specialized;
namespace Avalonia.Controls.Presenters
{
public interface IItemsPresenter : IPresenter
{
IEnumerable Items { get; set; }
IPanel Panel { get; }
void ItemsChanged(NotifyCollectionChangedEventArgs e);
void ScrollIntoView(object item);
}
}

15
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -63,7 +63,7 @@ namespace Avalonia.Controls.Presenters
_itemsSubscription?.Dispose();
_itemsSubscription = null;
if (_createdPanel && value is INotifyCollectionChanged incc)
if (!IsHosted && _createdPanel && value is INotifyCollectionChanged incc)
{
_itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
}
@ -130,6 +130,8 @@ namespace Avalonia.Controls.Presenters
private set;
}
protected bool IsHosted => TemplatedParent is IItemsPresenterHost;
/// <inheritdoc/>
public override sealed void ApplyTemplate()
{
@ -144,6 +146,15 @@ namespace Avalonia.Controls.Presenters
{
}
/// <inheritdoc/>
void IItemsPresenter.ItemsChanged(NotifyCollectionChangedEventArgs e)
{
if (Panel != null)
{
ItemsChanged(e);
}
}
/// <summary>
/// Creates the <see cref="ItemContainerGenerator"/> for the control.
/// </summary>
@ -215,7 +226,7 @@ namespace Avalonia.Controls.Presenters
_createdPanel = true;
if (_itemsSubscription == null && Items is INotifyCollectionChanged incc)
if (!IsHosted && _itemsSubscription == null && Items is INotifyCollectionChanged incc)
{
_itemsSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
}

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

@ -297,7 +297,8 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Text = "X",
Typeface = new Typeface(FontFamily, FontSize, FontStyle, FontWeight),
Typeface = new Typeface(FontFamily, FontWeight, FontStyle),
FontSize = FontSize,
TextAlignment = TextAlignment,
Constraint = availableSize,
}.Bounds.Size;

2
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -41,6 +41,8 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Initializes a new instance of the <see cref="PopupRoot"/> class.
/// </summary>
/// <param name="parent">The popup parent.</param>
/// <param name="impl">The popup implementation.</param>
/// <param name="dependencyResolver">
/// The dependency resolver to use. If null the default dependency resolver will be used.
/// </param>

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

@ -302,13 +302,24 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/>
protected override void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
base.ItemsCollectionChanged(sender, e);
if (_updateCount > 0)
{
base.ItemsCollectionChanged(sender, e);
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
_selection.ItemsInserted(e.NewStartingIndex, e.NewItems.Count);
break;
case NotifyCollectionChangedAction.Remove:
_selection.ItemsRemoved(e.OldStartingIndex, e.OldItems.Count);
break;
}
base.ItemsCollectionChanged(sender, e);
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
@ -318,14 +329,12 @@ namespace Avalonia.Controls.Primitives
}
else
{
_selection.ItemsInserted(e.NewStartingIndex, e.NewItems.Count);
UpdateSelectedItem(_selection.First(), false);
}
break;
case NotifyCollectionChangedAction.Remove:
_selection.ItemsRemoved(e.OldStartingIndex, e.OldItems.Count);
UpdateSelectedItem(_selection.First(), false);
ResetSelectedItems();
break;
@ -358,17 +367,17 @@ namespace Avalonia.Controls.Primitives
{
if ((container.ContainerControl as ISelectable)?.IsSelected == true)
{
if (SelectedIndex == -1)
{
SelectedIndex = container.Index;
}
else
if (SelectionMode.HasFlag(SelectionMode.Multiple))
{
if (_selection.Add(container.Index))
{
resetSelectedItems = true;
}
}
else
{
SelectedIndex = container.Index;
}
MarkContainerSelected(container.ContainerControl, true);
}
@ -1088,9 +1097,15 @@ namespace Avalonia.Controls.Primitives
}
else
{
SelectedIndex = _updateSelectedIndex != int.MinValue ?
_updateSelectedIndex :
AlwaysSelected ? 0 : -1;
if (_updateSelectedIndex != int.MinValue)
{
SelectedIndex = _updateSelectedIndex;
}
if (AlwaysSelected && SelectedIndex == -1)
{
SelectedIndex = 0;
}
}
}
}

10
src/Avalonia.Controls/Primitives/TabStrip.cs

@ -5,6 +5,7 @@ using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
@ -44,9 +45,14 @@ namespace Avalonia.Controls.Primitives
{
base.OnPointerPressed(e);
if (e.MouseButton == MouseButton.Left)
if (e.Source is IVisual source)
{
e.Handled = UpdateSelectionFromEventSource(e.Source);
var point = e.GetCurrentPoint(source);
if (point.Properties.IsLeftButtonPressed)
{
e.Handled = UpdateSelectionFromEventSource(e.Source);
}
}
}
}

2
src/Avalonia.Controls/Primitives/Thumb.cs

@ -73,7 +73,6 @@ namespace Avalonia.Controls.Primitives
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
e.Device.Capture(this);
e.Handled = true;
_lastPoint = e.GetPosition(this);
@ -92,7 +91,6 @@ namespace Avalonia.Controls.Primitives
{
if (_lastPoint.HasValue)
{
e.Device.Capture(null);
e.Handled = true;
_lastPoint = null;

12
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -565,7 +565,17 @@ namespace Avalonia.Controls
if (Layout is VirtualizingLayout virtualLayout)
{
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
_processingItemsSourceChange = args;
try
{
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
}
finally
{
_processingItemsSourceChange = null;
}
}
else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{

13
src/Avalonia.Controls/RowDefinition.cs

@ -26,6 +26,16 @@ namespace Avalonia.Controls
public static readonly StyledProperty<GridLength> HeightProperty =
AvaloniaProperty.Register<RowDefinition, GridLength>(nameof(Height), new GridLength(1, GridUnitType.Star));
/// <summary>
/// Initializes static members of the <see cref="RowDefinition"/> class.
/// </summary>
static RowDefinition()
{
AffectsParentMeasure(MaxHeightProperty, MinHeightProperty);
HeightProperty.Changed.AddClassHandler<DefinitionBase>(OnUserSizePropertyChanged);
}
/// <summary>
/// Initializes a new instance of the <see cref="RowDefinition"/> class.
/// </summary>
@ -68,7 +78,6 @@ namespace Avalonia.Controls
}
set
{
Parent?.InvalidateMeasure();
SetValue(MaxHeightProperty, value);
}
}
@ -84,7 +93,6 @@ namespace Avalonia.Controls
}
set
{
Parent?.InvalidateMeasure();
SetValue(MinHeightProperty, value);
}
}
@ -100,7 +108,6 @@ namespace Avalonia.Controls
}
set
{
Parent?.InvalidateMeasure();
SetValue(HeightProperty, value);
}
}

15
src/Avalonia.Controls/Shapes/Shape.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reflection;
using Avalonia.Collections;
using Avalonia.Media;
@ -166,12 +165,9 @@ namespace Avalonia.Controls.Shapes
{
property.Changed.Subscribe(e =>
{
var senderType = e.Sender.GetType().GetTypeInfo();
var affectedType = typeof(TShape).GetTypeInfo();
if (affectedType.IsAssignableFrom(senderType))
if (e.Sender is TShape shape)
{
AffectsGeometryInvalidate(e);
AffectsGeometryInvalidate(shape, e);
}
});
}
@ -322,13 +318,8 @@ namespace Avalonia.Controls.Shapes
return (size, transform);
}
private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e)
private static void AffectsGeometryInvalidate(Shape control, AvaloniaPropertyChangedEventArgs e)
{
if (!(e.Sender is Shape control))
{
return;
}
// If the geometry is invalidated when Bounds changes, only invalidate when the Size
// portion changes.
if (e.Property == BoundsProperty)

57
src/Avalonia.Controls/TabControl.cs

@ -4,7 +4,6 @@
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -70,6 +69,7 @@ namespace Avalonia.Controls
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
AffectsMeasure<TabControl>(TabStripPlacementProperty);
SelectedIndexProperty.Changed.AddClassHandler<TabControl>((x, e) => x.UpdateSelectedContent(e));
}
/// <summary>
@ -145,6 +145,61 @@ namespace Avalonia.Controls
return RegisterContentPresenter(presenter);
}
protected override void OnContainersMaterialized(ItemContainerEventArgs e)
{
base.OnContainersMaterialized(e);
if (SelectedContent != null || SelectedIndex == -1)
{
return;
}
var container = (TabItem)ItemContainerGenerator.ContainerFromIndex(SelectedIndex);
if (container == null)
{
return;
}
UpdateSelectedContent(container);
}
private void UpdateSelectedContent(AvaloniaPropertyChangedEventArgs e)
{
var index = (int)e.NewValue;
if (index == -1)
{
SelectedContentTemplate = null;
SelectedContent = null;
return;
}
var container = (TabItem)ItemContainerGenerator.ContainerFromIndex(index);
if (container == null)
{
return;
}
UpdateSelectedContent(container);
}
private void UpdateSelectedContent(IContentControl item)
{
if (SelectedContentTemplate != item.ContentTemplate)
{
SelectedContentTemplate = item.ContentTemplate;
}
if (SelectedContent != item.Content)
{
SelectedContent = item.Content;
}
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>

21
src/Avalonia.Controls/TabItem.cs

@ -30,7 +30,6 @@ namespace Avalonia.Controls
{
SelectableMixin.Attach<TabItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue(typeof(TabItem), true);
IsSelectedProperty.Changed.AddClassHandler<TabItem>((x, e) => x.UpdateSelectedContent(e));
DataContextProperty.Changed.AddClassHandler<TabItem>((x, e) => x.UpdateHeader(e));
}
@ -54,8 +53,6 @@ namespace Avalonia.Controls
set { SetValue(IsSelectedProperty, value); }
}
internal TabControl ParentTabControl { get; set; }
private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj)
{
if (Header == null)
@ -83,23 +80,5 @@ namespace Avalonia.Controls
}
}
}
private void UpdateSelectedContent(AvaloniaPropertyChangedEventArgs e)
{
if (!IsSelected)
{
return;
}
if (ParentTabControl.SelectedContentTemplate != ContentTemplate)
{
ParentTabControl.SelectedContentTemplate = ContentTemplate;
}
if (ParentTabControl.SelectedContent != Content)
{
ParentTabControl.SelectedContent = Content;
}
}
}
}

5
src/Avalonia.Controls/TextBlock.cs

@ -352,10 +352,11 @@ namespace Avalonia.Controls
return new FormattedText
{
Constraint = constraint,
Typeface = new Typeface(FontFamily, FontSize, FontStyle, FontWeight),
Typeface = new Typeface(FontFamily, FontWeight, FontStyle),
FontSize = FontSize,
Text = text ?? string.Empty,
TextAlignment = TextAlignment,
Wrapping = TextWrapping,
TextWrapping = TextWrapping,
};
}

8
src/Avalonia.Controls/TextBox.cs

@ -677,13 +677,13 @@ namespace Avalonia.Controls
}
}
e.Device.Capture(_presenter);
e.Pointer.Capture(_presenter);
e.Handled = true;
}
protected override void OnPointerMoved(PointerEventArgs e)
{
if (_presenter != null && e.Device.Captured == _presenter)
if (_presenter != null && e.Pointer.Captured == _presenter)
{
var point = e.GetPosition(_presenter);
@ -694,9 +694,9 @@ namespace Avalonia.Controls
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
if (_presenter != null && e.Device.Captured == _presenter)
if (_presenter != null && e.Pointer.Captured == _presenter)
{
e.Device.Capture(null);
e.Pointer.Capture(null);
}
}

6
src/Avalonia.Controls/ToolTipService.cs

@ -1,6 +1,7 @@
using System;
using Avalonia.Input;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -79,7 +80,10 @@ namespace Avalonia.Controls
{
StopTimer();
ToolTip.SetIsOpen(control, true);
if ((control as IVisual).IsAttachedToVisualTree)
{
ToolTip.SetIsOpen(control, true);
}
}
private void Close(Control control)

19
src/Avalonia.Controls/TreeView.cs

@ -507,14 +507,19 @@ namespace Avalonia.Controls
{
base.OnPointerPressed(e);
if (e.MouseButton == MouseButton.Left || e.MouseButton == MouseButton.Right)
if (e.Source is IVisual source)
{
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.InputModifiers & InputModifiers.Shift) != 0,
(e.InputModifiers & InputModifiers.Control) != 0,
e.MouseButton == MouseButton.Right);
var point = e.GetCurrentPoint(source);
if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed)
{
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
(e.KeyModifiers & KeyModifiers.Shift) != 0,
(e.KeyModifiers & KeyModifiers.Control) != 0,
point.Properties.IsRightButtonPressed);
}
}
}

2
src/Avalonia.Controls/WrapPanel.cs

@ -42,7 +42,7 @@ namespace Avalonia.Controls
/// </summary>
static WrapPanel()
{
AffectsMeasure<WrapPanel>(OrientationProperty);
AffectsMeasure<WrapPanel>(OrientationProperty, ItemWidthProperty, ItemHeightProperty);
}
/// <summary>

4
src/Avalonia.Diagnostics/Views/TreePageView.xaml

@ -2,7 +2,7 @@
xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Avalonia.Diagnostics.Views.TreePageView">
<Grid ColumnDefinitions="*,4,3*">
<Grid ColumnDefinitions="*,Auto,3*">
<TreeView Name="tree" Items="{Binding Nodes}" SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TreeNode"
@ -20,7 +20,7 @@
</TreeView.Styles>
</TreeView>
<GridSplitter Width="4" Grid.Column="1" />
<GridSplitter Grid.Column="1" />
<ContentControl Content="{Binding Details}" Grid.Column="2" />
</Grid>
</UserControl>

105
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@ -0,0 +1,105 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MaxWidth="400"
MaxHeight="475"
MinWidth="430"
MinHeight="475"
Title="About Avalonia"
Background="Purple"
FontFamily="/Assets/Roboto-Light.ttf#Roboto"
x:Class="Avalonia.Dialogs.AboutAvaloniaDialog">
<Window.Styles>
<Style>
<Style.Resources>
<DrawingGroup x:Key="AvaloniaLogo">
<GeometryDrawing Geometry="m 150.66581 0.66454769 c -54.77764 0 -101.0652 38.86360031 -112.62109 90.33008031 a 26.1 26.1 0 0 1 18.92187 25.070312 26.1 26.1 0 0 1 -18.91992 25.08202 c 11.56024 51.46073 57.8456 90.31837 112.61914 90.31837 63.37832 0 115.40039 -52.02207 115.40039 -115.40039 0 -63.378322 -52.02207 -115.40039231 -115.40039 -115.40039231 z m 0 60.00000031 c 30.95192 0 55.40039 24.44847 55.40039 55.400392 0 30.9519 -24.44847 55.40039 -55.40039 55.40039 -30.95191 0 -55.40039 -24.44848 -55.40039 -55.40039 0 -30.951922 24.44848 -55.400392 55.40039 -55.400392 z">
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="272,411" EndPoint="435,248">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#B0B0B0" Offset="0" />
<GradientStop Color="#FFFFFF" Offset="0.6784" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="#B0B0B0">
<GeometryDrawing.Geometry>
<EllipseGeometry Rect="9.6,95.8,40.6,40.6" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="White">
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="206.06355, 114.56503,60,116.2" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</Style.Resources>
</Style>
<Style Selector="Rectangle.Abstract">
<Setter Property="Fill" Value="White" />
<Setter Property="Width" Value="750" />
<Setter Property="Height" Value="700" />
</Style>
<Style Selector="Button.Hyperlink">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="-5"/>
<Setter Property="Foreground" Value="#419df2" />
<Setter Property="Command" Value="{Binding OpenBrowser}" />
<Setter Property="Content" Value="{Binding $self.CommandParameter}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="Cursor" Value="Hand" />
</Style>
</Window.Styles>
<Grid Background="#4A255D">
<Canvas>
<Rectangle Classes="Abstract" Canvas.Top="90" Opacity="0.132">
<Rectangle.RenderTransform>
<RotateTransform Angle="-2" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Classes="Abstract" Canvas.Top="95" Opacity="0.3">
<Rectangle.RenderTransform>
<RotateTransform Angle="-4" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Classes="Abstract" Canvas.Top="100" Opacity="0.3">
<Rectangle.RenderTransform>
<RotateTransform Angle="-8" />
</Rectangle.RenderTransform>
</Rectangle>
<Rectangle Classes="Abstract" Canvas.Top="105" Opacity="0.7">
<Rectangle.RenderTransform>
<RotateTransform Angle="-12" />
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="18">
<Border Height="70" Width="70">
<DrawingPresenter Drawing="{DynamicResource AvaloniaLogo}" />
</Border>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,-10,0,0">
<TextBlock Text="Avalonia 0.9" FontSize="40" Foreground="White" />
<TextBlock Text="Development Build" Margin="0,-10,0,0" FontSize="15" Foreground="White" />
</StackPanel>
</StackPanel>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="20" Margin="10 60 10 0">
<TextBlock Text="This product is built with the Avalonia cross-platform UI Framework. &#x0A;&#x0A;Avalonia is made possible by the generous support of it's contributors and community." TextWrapping="Wrap" TextAlignment="Center" HorizontalAlignment="Center" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Main source repository | " />
<Button Classes="Hyperlink" CommandParameter="https://github.com/AvaloniaUI/Avalonia/" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Documentation and Information | " />
<Button Classes="Hyperlink" CommandParameter="https://avaloniaui.net/" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Chat Room | " />
<Button Classes="Hyperlink" CommandParameter="https://gitter.im/AvaloniaUI/Avalonia/" />
</StackPanel>
</StackPanel>
<StackPanel VerticalAlignment="Bottom" Margin="10">
<TextBlock Text="© 2019 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
</StackPanel>
</Grid>
</Window>

62
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs

@ -0,0 +1,62 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Diagnostics;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Avalonia.Dialogs
{
public class AboutAvaloniaDialog : Window
{
public AboutAvaloniaDialog()
{
AvaloniaXamlLoader.Load(this);
DataContext = this;
}
public static void OpenBrowser(string url)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// If no associated application/json MimeType is found xdg-open opens retrun error
// but it tries to open it anyway using the console editor (nano, vim, other..)
ShellExec($"xdg-open {url}", waitForExit: false);
}
else
{
using (Process process = Process.Start(new ProcessStartInfo
{
FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? url : "open",
Arguments = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? $"-e {url}" : "",
CreateNoWindow = true,
UseShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
}));
}
}
private static void ShellExec(string cmd, bool waitForExit = true)
{
var escapedArgs = cmd.Replace("\"", "\\\"");
using (var process = Process.Start(
new ProcessStartInfo
{
FileName = "/bin/sh",
Arguments = $"-c \"{escapedArgs}\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
}
))
{
if (waitForExit)
{
process.WaitForExit();
}
}
}
}
}

BIN
src/Avalonia.Dialogs/Assets/Roboto-Light.ttf

Binary file not shown.

1
src/Avalonia.Dialogs/Avalonia.Dialogs.csproj

@ -7,6 +7,7 @@
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<AvaloniaResource Include="Assets\*" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />

7
src/Avalonia.Dialogs/ManagedFileChooserViewModel.cs

@ -210,7 +210,12 @@ namespace Avalonia.Dialogs
if (!_selectingDirectory)
{
FileName = SelectedItems.FirstOrDefault()?.DisplayName;
var selectedItem = SelectedItems.FirstOrDefault();
if (selectedItem != null)
{
FileName = selectedItem.DisplayName;
}
}
}
}

2
src/Avalonia.Input/AccessKeyHandler.cs

@ -182,7 +182,7 @@ namespace Avalonia.Input
{
bool menuIsOpen = MainMenu?.IsOpen == true;
if ((e.Modifiers & InputModifiers.Alt) != 0 || menuIsOpen)
if ((e.KeyModifiers & KeyModifiers.Alt) != 0 || 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.

2
src/Avalonia.Input/FocusManager.cs

@ -180,7 +180,7 @@ namespace Avalonia.Input
if (sender == e.Source && ev.MouseButton == MouseButton.Left)
{
var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement);
var element = (ev.Pointer?.Captured as IInputElement) ?? (e.Source as IInputElement);
if (element == null || !CanFocus(element))
{

13
src/Avalonia.Input/MouseDevice.cs

@ -14,13 +14,14 @@ namespace Avalonia.Input
/// <summary>
/// Represents a mouse device.
/// </summary>
public class MouseDevice : IMouseDevice
public class MouseDevice : IMouseDevice, IDisposable
{
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
private readonly Pointer _pointer;
private bool _disposed;
public MouseDevice(Pointer pointer = null)
{
@ -126,7 +127,9 @@ namespace Avalonia.Input
{
Contract.Requires<ArgumentNullException>(e != null);
var mouse = (IMouseDevice)e.Device;
var mouse = (MouseDevice)e.Device;
if(mouse._disposed)
return;
Position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e);
@ -441,5 +444,11 @@ namespace Avalonia.Input
el = (IInputElement)el.VisualParent;
}
}
public void Dispose()
{
_disposed = true;
_pointer?.Dispose();
}
}
}

2
src/Avalonia.Input/Pointer.cs

@ -37,7 +37,7 @@ namespace Avalonia.Input
{
if (Captured != null)
Captured.DetachedFromVisualTree -= OnCaptureDetached;
var oldCapture = control;
var oldCapture = Captured;
Captured = control;
PlatformCapture(control);
if (oldCapture != null)

9
src/Avalonia.Input/PointerPoint.cs

@ -1,3 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Input
{
public sealed class PointerPoint
@ -27,9 +30,9 @@ namespace Avalonia.Input
public PointerPointProperties(RawInputModifiers modifiers, PointerUpdateKind kind)
{
PointerUpdateKind = kind;
IsLeftButtonPressed = modifiers.HasFlag(RawInputModifiers.LeftMouseButton);
IsMiddleButtonPressed = modifiers.HasFlag(RawInputModifiers.MiddleMouseButton);
IsRightButtonPressed = modifiers.HasFlag(RawInputModifiers.RightMouseButton);
IsLeftButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.LeftMouseButton);
IsMiddleButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.MiddleMouseButton);
IsRightButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.RightMouseButton);
// The underlying input source might be reporting the previous state,
// so make sure that we reflect the current state

21
src/Avalonia.Input/TouchDevice.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input.Raw;
@ -11,10 +12,11 @@ namespace Avalonia.Input
/// This class is supposed to be used on per-toplevel basis, don't use a shared one
/// </remarks>
/// </summary>
public class TouchDevice : IInputDevice
public class TouchDevice : IInputDevice, IDisposable
{
Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>();
private readonly Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>();
private bool _disposed;
KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) =>
(KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask);
@ -28,6 +30,8 @@ namespace Avalonia.Input
public void ProcessRawEvent(RawInputEventArgs ev)
{
if(_disposed)
return;
var args = (RawTouchEventArgs)ev;
if (!_pointers.TryGetValue(args.TouchPointId, out var pointer))
{
@ -82,6 +86,17 @@ namespace Avalonia.Input
}
public void Dispose()
{
if(_disposed)
return;
var values = _pointers.Values.ToList();
_pointers.Clear();
_disposed = true;
foreach (var p in values)
p.Dispose();
}
}
}

7
src/Avalonia.Layout/LayoutHelper.cs

@ -21,8 +21,11 @@ namespace Avalonia.Layout
/// <returns>The control's size.</returns>
public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints)
{
double width = (control.Width > 0) ? control.Width : constraints.Width;
double height = (control.Height > 0) ? control.Height : constraints.Height;
var controlWidth = control.Width;
var controlHeight = control.Height;
double width = (controlWidth > 0) ? controlWidth : constraints.Width;
double height = (controlHeight > 0) ? controlHeight : constraints.Height;
width = Math.Min(width, control.MaxWidth);
width = Math.Max(width, control.MinWidth);
height = Math.Min(height, control.MaxHeight);

8
src/Avalonia.Layout/LayoutManager.cs

@ -15,9 +15,15 @@ namespace Avalonia.Layout
{
private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid);
private readonly Action _executeLayoutPass;
private bool _queued;
private bool _running;
public LayoutManager()
{
_executeLayoutPass = ExecuteLayoutPass;
}
/// <inheritdoc/>
public void InvalidateMeasure(ILayoutable control)
{
@ -215,7 +221,7 @@ namespace Avalonia.Layout
{
if (!_queued && !_running)
{
Dispatcher.UIThread.Post(ExecuteLayoutPass, DispatcherPriority.Layout);
Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout);
_queued = true;
}
}

25
src/Avalonia.Layout/LayoutQueue.cs

@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Layout
{
@ -18,9 +17,11 @@ namespace Avalonia.Layout
_shouldEnqueue = shouldEnqueue;
}
private Func<T, bool> _shouldEnqueue;
private Queue<T> _inner = new Queue<T>();
private Dictionary<T, Info> _loopQueueInfo = new Dictionary<T, Info>();
private readonly Func<T, bool> _shouldEnqueue;
private readonly Queue<T> _inner = new Queue<T>();
private readonly Dictionary<T, Info> _loopQueueInfo = new Dictionary<T, Info>();
private readonly List<KeyValuePair<T, Info>> _notFinalizedBuffer = new List<KeyValuePair<T, Info>>();
private int _maxEnqueueCountPerLoop = 1;
public int Count => _inner.Count;
@ -60,13 +61,19 @@ namespace Avalonia.Layout
public void EndLoop()
{
var notfinalized = _loopQueueInfo.Where(v => v.Value.Count >= _maxEnqueueCountPerLoop).ToArray();
foreach (KeyValuePair<T, Info> info in _loopQueueInfo)
{
if (info.Value.Count >= _maxEnqueueCountPerLoop)
{
_notFinalizedBuffer.Add(info);
}
}
_loopQueueInfo.Clear();
//prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle
//one more time as a final attempt
foreach (var item in notfinalized)
// Prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle
// one more time as a final attempt.
foreach (var item in _notFinalizedBuffer)
{
if (_shouldEnqueue(item.Key))
{
@ -74,6 +81,8 @@ namespace Avalonia.Layout
_inner.Enqueue(item.Key);
}
}
_notFinalizedBuffer.Clear();
}
}
}

51
src/Avalonia.Layout/Layoutable.cs

@ -518,17 +518,25 @@ namespace Avalonia.Layout
var width = measured.Width;
var height = measured.Height;
if (!double.IsNaN(Width))
{
width = Width;
double widthCache = Width;
if (!double.IsNaN(widthCache))
{
width = widthCache;
}
}
width = Math.Min(width, MaxWidth);
width = Math.Max(width, MinWidth);
if (!double.IsNaN(Height))
{
height = Height;
double heightCache = Height;
if (!double.IsNaN(heightCache))
{
height = heightCache;
}
}
height = Math.Min(height, MaxHeight);
@ -562,11 +570,19 @@ namespace Avalonia.Layout
double width = 0;
double height = 0;
foreach (ILayoutable child in this.GetVisualChildren())
var visualChildren = VisualChildren;
var visualCount = visualChildren.Count;
for (var i = 0; i < visualCount; i++)
{
child.Measure(availableSize);
width = Math.Max(width, child.DesiredSize.Width);
height = Math.Max(height, child.DesiredSize.Height);
IVisual visual = visualChildren[i];
if (visual is ILayoutable layoutable)
{
layoutable.Measure(availableSize);
width = Math.Max(width, layoutable.DesiredSize.Width);
height = Math.Max(height, layoutable.DesiredSize.Height);
}
}
return new Size(width, height);
@ -594,6 +610,7 @@ namespace Avalonia.Layout
var verticalAlignment = VerticalAlignment;
var size = availableSizeMinusMargins;
var scale = GetLayoutScale();
var useLayoutRounding = UseLayoutRounding;
if (horizontalAlignment != HorizontalAlignment.Stretch)
{
@ -607,7 +624,7 @@ namespace Avalonia.Layout
size = LayoutHelper.ApplyLayoutConstraints(this, size);
if (UseLayoutRounding)
if (useLayoutRounding)
{
size = new Size(
Math.Ceiling(size.Width * scale) / scale,
@ -641,7 +658,7 @@ namespace Avalonia.Layout
break;
}
if (UseLayoutRounding)
if (useLayoutRounding)
{
originX = Math.Floor(originX * scale) / scale;
originY = Math.Floor(originY * scale) / scale;
@ -658,9 +675,19 @@ namespace Avalonia.Layout
/// <returns>The actual size used.</returns>
protected virtual Size ArrangeOverride(Size finalSize)
{
foreach (ILayoutable child in this.GetVisualChildren().OfType<ILayoutable>())
var arrangeRect = new Rect(finalSize);
var visualChildren = VisualChildren;
var visualCount = visualChildren.Count;
for (var i = 0; i < visualCount; i++)
{
child.Arrange(new Rect(finalSize));
IVisual visual = visualChildren[i];
if (visual is ILayoutable layoutable)
{
layoutable.Arrange(arrangeRect);
}
}
return finalSize;

1
src/Avalonia.Native/Avalonia.Native.csproj

@ -22,5 +22,6 @@
<PackageReference Include="SharpGenTools.Sdk" Version="1.1.2" PrivateAssets="all" />
<PackageReference Include="SharpGen.Runtime.COM" Version="1.1.0" />
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
</ItemGroup>
</Project>

34
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -3,12 +3,15 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Native.Interop;
using Avalonia.Platform.Interop;
using Avalonia.Threading;
using Avalonia.Dialogs;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Native
{
@ -211,6 +214,29 @@ namespace Avalonia.Native
DoLayoutReset();
}
private static NativeMenu CreateDefaultAppMenu()
{
var result = new NativeMenu();
var aboutItem = new NativeMenuItem
{
Header = "About Avalonia",
};
aboutItem.Clicked += async (sender, e) =>
{
var dialog = new AboutAvaloniaDialog();
var mainWindow = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
await dialog.ShowDialog(mainWindow);
};
result.Add(aboutItem);
return result;
}
private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
QueueReset();
@ -241,6 +267,10 @@ namespace Avalonia.Native
{
SetMenu(_menu);
}
else
{
SetMenu(CreateDefaultAppMenu());
}
}
else
{
@ -321,7 +351,7 @@ namespace Avalonia.Native
}), new MenuActionCallback(() => { item.RaiseClick(); }));
menu.AddItem(menuItem);
if (item.Menu?.Items?.Count > 0)
if (item.Menu?.Items?.Count >= 0)
{
var submenu = _factory.CreateMenu();
@ -362,7 +392,7 @@ namespace Avalonia.Native
return false;
}), new MenuActionCallback(() => { item.RaiseClick(); }));
if (item.Menu?.Items.Count > 0 || isMainMenu)
if (item.Menu?.Items.Count >= 0 || isMainMenu)
{
var subMenu = CreateSubmenu(item.Menu?.Items);

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -21,7 +21,6 @@ namespace Avalonia.Native
[DllImport("libAvaloniaNative")]
static extern IntPtr CreateAvaloniaNative();
internal static readonly MouseDevice MouseDevice = new MouseDevice();
internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice();
public Size DoubleClickSize => new Size(4, 4);
@ -95,7 +94,6 @@ namespace Avalonia.Native
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IMouseDevice>().ToConstant(MouseDevice)
.Bind<IPlatformSettings>().ToConstant(this)
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))

7
src/Avalonia.Native/WindowImplBase.cs

@ -24,7 +24,7 @@ namespace Avalonia.Native
private object _syncRoot = new object();
private bool _deferredRendering = false;
private bool _gpu = false;
private readonly IMouseDevice _mouse;
private readonly MouseDevice _mouse;
private readonly IKeyboardDevice _keyboard;
private readonly IStandardCursorFactory _cursorFactory;
private Size _savedLogicalSize;
@ -38,7 +38,7 @@ namespace Avalonia.Native
_deferredRendering = opts.UseDeferredRendering;
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
_mouse = AvaloniaLocator.Current.GetService<IMouseDevice>();
_mouse = new MouseDevice();
_cursorFactory = AvaloniaLocator.Current.GetService<IStandardCursorFactory>();
}
@ -96,7 +96,7 @@ namespace Avalonia.Native
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action Closed { get; set; }
public IMouseDevice MouseDevice => AvaloniaNativePlatform.MouseDevice;
public IMouseDevice MouseDevice => _mouse;
public abstract IPopupImpl CreatePopup();
@ -142,6 +142,7 @@ namespace Avalonia.Native
{
n?.Dispose();
}
_parent._mouse.Dispose();
}
void IAvnWindowBaseEvents.Activated() => _parent.Activated?.Invoke();

58
src/Avalonia.Themes.Default/GridSplitter.xaml

@ -1,51 +1,23 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="GridSplitter:vertical">
<Setter Property="Width" Value="6"/>
<Setter Property="Background" Value="{DynamicResource ThemeControlLowBrush}"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel.Styles>
<Style Selector="Ellipse">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Width" Value="4"/>
<Setter Property="Height" Value="4"/>
<Setter Property="Fill" Value="{DynamicResource ThemeControlMidBrush}"/>
<Setter Property="Margin" Value="1"/>
</Style>
</StackPanel.Styles>
<Ellipse/>
<Ellipse/>
<Ellipse/>
</StackPanel>
</Border>
</ControlTemplate>
<Style Selector="GridSplitter">
<Setter Property="Focusable" Value="True" />
<Setter Property="MinWidth" Value="6" />
<Setter Property="MinHeight" Value="6" />
<Setter Property="Background" Value="{DynamicResource ThemeControlMidBrush}" />
<Setter Property="PreviewContent">
<Template>
<Rectangle Fill="{DynamicResource HighlightBrush}" />
</Template>
</Setter>
</Style>
<Style Selector="GridSplitter:horizontal">
<Setter Property="Height" Value="6"/>
<Setter Property="Background" Value="{DynamicResource ThemeControlLowBrush}"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel.Styles>
<Style Selector="Ellipse">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Width" Value="4"/>
<Setter Property="Height" Value="4"/>
<Setter Property="Fill" Value="{DynamicResource ThemeControlMidBrush}"/>
<Setter Property="Margin" Value="1"/>
</Style>
</StackPanel.Styles>
<Ellipse/>
<Ellipse/>
<Ellipse/>
</StackPanel>
</Border>
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter>
</Style>
</Styles>
</Styles>

28
src/Avalonia.Visuals/Media/FontFamily.cs

@ -5,12 +5,16 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
namespace Avalonia.Media
{
public class FontFamily
public sealed class FontFamily
{
static FontFamily()
{
Default = new FontFamily(FontManager.Default.DefaultFontFamilyName);
}
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:Avalonia.Media.FontFamily" /> class.
@ -30,9 +34,7 @@ namespace Avalonia.Media
{
if (string.IsNullOrEmpty(name))
{
FamilyNames = new FamilyNameCollection(string.Empty);
return;
throw new ArgumentNullException(nameof(name));
}
var fontFamilySegment = GetFontFamilyIdentifier(name);
@ -53,13 +55,16 @@ namespace Avalonia.Media
/// <summary>
/// Represents the default font family
/// </summary>
public static FontFamily Default => new FontFamily(string.Empty);
public static FontFamily Default { get; }
/// <summary>
/// Represents all font families in the system. This can be an expensive call depending on platform implementation.
/// </summary>
/// <remarks>
/// Consider using the new <see cref="FontManager"/> instead.
/// </remarks>
public static IEnumerable<FontFamily> SystemFontFamilies =>
AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().InstalledFontNames.Select(name => new FontFamily(name));
FontManager.Default.GetInstalledFontFamilyNames().Select(name => new FontFamily(name));
/// <summary>
/// Gets the primary family name of the font family.
@ -181,7 +186,14 @@ namespace Avalonia.Media
{
var hash = (int)2186146271;
hash = (hash * 15768619) ^ FamilyNames.GetHashCode();
if (Key != null)
{
hash = (hash * 15768619) ^ Key.GetHashCode();
}
else
{
hash = (hash * 15768619) ^ FamilyNames.GetHashCode();
}
if (Key != null)
{

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

@ -0,0 +1,112 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Platform;
namespace Avalonia.Media
{
/// <summary>
/// The font manager is used to query the system's installed fonts and is responsible for caching loaded fonts.
/// It is also responsible for the font fallback.
/// </summary>
public abstract class FontManager
{
public static readonly FontManager Default = CreateDefault();
/// <summary>
/// Gets the system's default font family's name.
/// </summary>
public string DefaultFontFamilyName
{
get;
protected set;
}
/// <summary>
/// Get all installed fonts in the system.
/// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
/// </summary>
public abstract IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false);
/// <summary>
/// Get a cached typeface from specified parameters.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStyle">The font style.</param>
/// <returns>
/// The cached typeface.
/// </returns>
public abstract Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle);
/// <summary>
/// Tries to match a specified character to a typeface that supports specified font properties.
/// Returns <c>null</c> if no fallback was found.
/// </summary>
/// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param>
/// <returns>
/// The matched typeface.
/// </returns>
public abstract Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default,
FontStyle fontStyle = default,
FontFamily fontFamily = null, CultureInfo culture = null);
public static FontManager CreateDefault()
{
var platformImpl = AvaloniaLocator.Current.GetService<IFontManagerImpl>();
if (platformImpl != null)
{
return new PlatformFontManager(platformImpl);
}
return new EmptyFontManager();
}
private class PlatformFontManager : FontManager
{
private readonly IFontManagerImpl _platformImpl;
public PlatformFontManager(IFontManagerImpl platformImpl)
{
_platformImpl = platformImpl;
DefaultFontFamilyName = _platformImpl.DefaultFontFamilyName;
}
public override IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) =>
_platformImpl.GetInstalledFontFamilyNames(checkForUpdates);
public override Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) =>
_platformImpl.GetTypeface(fontFamily, fontWeight, fontStyle);
public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default,
FontStyle fontStyle = default,
FontFamily fontFamily = null, CultureInfo culture = null) =>
_platformImpl.MatchCharacter(codepoint, fontWeight, fontStyle, fontFamily, culture);
}
private class EmptyFontManager : FontManager
{
public EmptyFontManager()
{
DefaultFontFamilyName = "Empty";
}
public override IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) =>
new[] { DefaultFontFamilyName };
public override Typeface GetCachedTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle) => new Typeface(fontFamily, fontWeight, fontStyle);
public override Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default,
FontStyle fontStyle = default,
FontFamily fontFamily = null, CultureInfo culture = null) => null;
}
}
}

6
src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs

@ -9,7 +9,7 @@ using System.Text;
namespace Avalonia.Media.Fonts
{
public class FamilyNameCollection : IEnumerable<string>
public sealed class FamilyNameCollection : IReadOnlyList<string>
{
/// <summary>
/// Initializes a new instance of the <see cref="FamilyNameCollection"/> class.
@ -130,5 +130,9 @@ namespace Avalonia.Media.Fonts
return other.ToString().Equals(ToString());
}
public int Count => Names.Count;
public string this[int index] => Names[index];
}
}

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

@ -16,9 +16,10 @@ namespace Avalonia.Media
private IFormattedTextImpl _platformImpl;
private IReadOnlyList<FormattedTextStyleSpan> _spans;
private Typeface _typeface;
private double _fontSize;
private string _text;
private TextAlignment _textAlignment;
private TextWrapping _wrapping;
private TextWrapping _textWrapping;
/// <summary>
/// Initializes a new instance of the <see cref="FormattedText"/> class.
@ -37,6 +38,31 @@ namespace Avalonia.Media
_platform = platform;
}
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="typeface"></param>
/// <param name="fontSize"></param>
/// <param name="textAlignment"></param>
/// <param name="textWrapping"></param>
/// <param name="constraint"></param>
public FormattedText(string text, Typeface typeface, double fontSize, TextAlignment textAlignment,
TextWrapping textWrapping, Size constraint)
{
_text = text;
_typeface = typeface;
_fontSize = fontSize;
_textAlignment = textAlignment;
_textWrapping = textWrapping;
_constraint = constraint;
}
/// <summary>
/// Gets the bounds of the text within the <see cref="Constraint"/>.
/// </summary>
@ -61,6 +87,16 @@ namespace Avalonia.Media
set => Set(ref _typeface, value);
}
/// <summary>
/// Gets or sets the font size.
/// </summary>
public double FontSize
{
get => _fontSize;
set => Set(ref _fontSize, value);
}
/// <summary>
/// Gets or sets a collection of spans that describe the formatting of subsections of the
/// text.
@ -92,10 +128,10 @@ namespace Avalonia.Media
/// <summary>
/// Gets or sets the text wrapping.
/// </summary>
public TextWrapping Wrapping
public TextWrapping TextWrapping
{
get => _wrapping;
set => Set(ref _wrapping, value);
get => _textWrapping;
set => Set(ref _textWrapping, value);
}
/// <summary>
@ -110,8 +146,9 @@ namespace Avalonia.Media
_platformImpl = _platform.CreateFormattedText(
_text,
_typeface,
_fontSize,
_textAlignment,
_wrapping,
_textWrapping,
_constraint,
_spans);
}

111
src/Avalonia.Visuals/Media/GlyphTypeface.cs

@ -0,0 +1,111 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Platform;
namespace Avalonia.Media
{
public sealed class GlyphTypeface : IDisposable
{
private static readonly IPlatformRenderInterface s_platformRenderInterface =
AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
public GlyphTypeface(Typeface typeface) : this(s_platformRenderInterface.CreateGlyphTypeface(typeface))
{
}
public GlyphTypeface(IGlyphTypefaceImpl platformImpl)
{
PlatformImpl = platformImpl;
}
public IGlyphTypefaceImpl PlatformImpl { get; }
/// <summary>
/// Gets the font design units per em.
/// </summary>
public short DesignEmHeight => PlatformImpl.DesignEmHeight;
/// <summary>
/// Gets the recommended distance above the baseline in design em size.
/// </summary>
public int Ascent => PlatformImpl.Ascent;
/// <summary>
/// Gets the recommended distance under the baseline in design em size.
/// </summary>
public int Descent => PlatformImpl.Descent;
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
public int LineGap => PlatformImpl.LineGap;
/// <summary>
/// Gets the recommended line height.
/// </summary>
public int LineHeight => Descent - Ascent + LineGap;
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
public int UnderlinePosition => PlatformImpl.UnderlinePosition;
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int UnderlineThickness => PlatformImpl.UnderlineThickness;
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
public int StrikethroughPosition => PlatformImpl.StrikethroughPosition;
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
public int StrikethroughThickness => PlatformImpl.StrikethroughThickness;
/// <summary>
/// Returns an glyph index for the specified codepoint.
/// </summary>
/// <remarks>
/// Returns <c>0</c> if a glyph isn't found.
/// </remarks>
/// <param name="codepoint">The codepoint.</param>
/// <returns>
/// A glyph index.
/// </returns>
public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint);
/// <summary>
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
/// </summary>
/// <param name="codepoints">The codepoints to map.</param>
/// <returns></returns>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints) => PlatformImpl.GetGlyphs(codepoints);
/// <summary>
/// Returns the glyph advance for the specified glyph.
/// </summary>
/// <param name="glyph">The glyph.</param>
/// <returns>
/// The advance.
/// </returns>
public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph);
/// <summary>
/// Returns an array of glyph advances in design em size.
/// </summary>
/// <param name="glyphs">The glyph indices.</param>
/// <returns></returns>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs) => PlatformImpl.GetGlyphAdvances(glyphs);
void IDisposable.Dispose()
{
PlatformImpl?.Dispose();
}
}
}

99
src/Avalonia.Visuals/Media/Typeface.cs

@ -1,39 +1,38 @@
using System;
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Diagnostics;
using JetBrains.Annotations;
namespace Avalonia.Media
{
/// <summary>
/// Represents a typeface.
/// </summary>
public class Typeface
[DebuggerDisplay("Name = {FontFamily.Name}, Weight = {Weight}, Style = {Style}")]
public class Typeface : IEquatable<Typeface>
{
public static readonly Typeface Default = new Typeface(FontFamily.Default);
private GlyphTypeface _glyphTypeface;
/// <summary>
/// Initializes a new instance of the <see cref="Typeface"/> class.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <param name="fontSize">The font size, in DIPs.</param>
/// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param>
public Typeface(
FontFamily fontFamily,
double fontSize = 12,
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal)
/// <param name="style">The font style.</param>
public Typeface([NotNull]FontFamily fontFamily,
FontWeight weight = FontWeight.Normal,
FontStyle style = FontStyle.Normal)
{
if (fontSize <= 0)
{
throw new ArgumentException("Font size must be > 0.");
}
if (weight <= 0)
{
throw new ArgumentException("Font weight must be > 0.");
}
FontFamily = fontFamily;
FontSize = fontSize;
Style = style;
Weight = weight;
}
@ -42,15 +41,12 @@ namespace Avalonia.Media
/// Initializes a new instance of the <see cref="Typeface"/> class.
/// </summary>
/// <param name="fontFamilyName">The name of the font family.</param>
/// <param name="fontSize">The font size, in DIPs.</param>
/// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param>
public Typeface(
string fontFamilyName,
double fontSize = 12,
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal)
: this(new FontFamily(fontFamilyName), fontSize, style, weight)
public Typeface(string fontFamilyName,
FontWeight weight = FontWeight.Normal,
FontStyle style = FontStyle.Normal)
: this(new FontFamily(fontFamilyName), weight, style)
{
}
@ -59,11 +55,6 @@ namespace Avalonia.Media
/// </summary>
public FontFamily FontFamily { get; }
/// <summary>
/// Gets the size of the font in DIPs.
/// </summary>
public double FontSize { get; }
/// <summary>
/// Gets the font style.
/// </summary>
@ -73,5 +64,59 @@ namespace Avalonia.Media
/// Gets the font weight.
/// </summary>
public FontWeight Weight { get; }
/// <summary>
/// Gets the glyph typeface.
/// </summary>
/// <value>
/// The glyph typeface.
/// </value>
public GlyphTypeface GlyphTypeface => _glyphTypeface ?? (_glyphTypeface = new GlyphTypeface(this));
public static bool operator !=(Typeface a, Typeface b)
{
return !(a == b);
}
public static bool operator ==(Typeface a, Typeface b)
{
if (ReferenceEquals(a, b))
{
return true;
}
return !(a is null) && a.Equals(b);
}
public override bool Equals(object obj)
{
if (obj is Typeface typeface)
{
return Equals(typeface);
}
return false;
}
public bool Equals(Typeface other)
{
if (other is null)
{
return false;
}
return FontFamily.Equals(other.FontFamily) && Style == other.Style && Weight == other.Weight;
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (FontFamily != null ? FontFamily.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int)Style;
hashCode = (hashCode * 397) ^ (int)Weight;
return hashCode;
}
}
}
}

48
src/Avalonia.Visuals/Platform/IFontManagerImpl.cs

@ -0,0 +1,48 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
namespace Avalonia.Platform
{
public interface IFontManagerImpl
{
/// <summary>
/// Gets the system's default font family's name.
/// </summary>
string DefaultFontFamilyName { get; }
/// <summary>
/// Get all installed fonts in the system.
/// <param name="checkForUpdates">If <c>true</c> the font collection is updated.</param>
/// </summary>
IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false);
/// <summary>
/// Get a typeface from specified parameters.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStyle">The font style.</param>
/// <returns>
/// The typeface.
/// </returns>
Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle);
/// <summary>
/// Tries to match a specified character to a typeface that supports specified font properties.
/// </summary>
/// <param name="codepoint">The codepoint to match against.</param>
/// <param name="fontWeight">The font weight.</param>
/// <param name="fontStyle">The font style.</param>
/// <param name="fontFamily">The font family. This is optional and used for fallback lookup.</param>
/// <param name="culture">The culture.</param>
/// <returns>
/// The typeface.
/// </returns>
Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default,
FontFamily fontFamily = null, CultureInfo culture = null);
}
}

89
src/Avalonia.Visuals/Platform/IGlyphTypefaceImpl.cs

@ -0,0 +1,89 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Platform
{
public interface IGlyphTypefaceImpl : IDisposable
{
/// <summary>
/// Gets the font design units per em.
/// </summary>
short DesignEmHeight { get; }
/// <summary>
/// Gets the recommended distance above the baseline in design em size.
/// </summary>
int Ascent { get; }
/// <summary>
/// Gets the recommended distance under the baseline in design em size.
/// </summary>
int Descent { get; }
/// <summary>
/// Gets the recommended additional space between two lines of text in design em size.
/// </summary>
int LineGap { get; }
/// <summary>
/// Gets a value that indicates the distance of the underline from the baseline in design em size.
/// </summary>
int UnderlinePosition { get; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
int UnderlineThickness { get; }
/// <summary>
/// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
/// </summary>
int StrikethroughPosition { get; }
/// <summary>
/// Gets a value that indicates the thickness of the underline in design em size.
/// </summary>
int StrikethroughThickness { get; }
/// <summary>
/// Returns an glyph index for the specified codepoint.
/// </summary>
/// <remarks>
/// Returns <c>0</c> if a glyph isn't found.
/// </remarks>
/// <param name="codepoint">The codepoint.</param>
/// <returns>
/// A glyph index.
/// </returns>
ushort GetGlyph(uint codepoint);
/// <summary>
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as <code>0</code>.
/// </summary>
/// <param name="codepoints">The codepoints to map.</param>
/// <returns>
/// An array of glyph indices.
/// </returns>
ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints);
/// <summary>
/// Returns the glyph advance for the specified glyph.
/// </summary>
/// <param name="glyph">The glyph.</param>
/// <returns>
/// The advance.
/// </returns>
int GetGlyphAdvance(ushort glyph);
/// <summary>
/// Returns an array of glyph advances in design em size.
/// </summary>
/// <param name="glyphs">The glyph indices.</param>
/// <returns>
/// An array of glyph advances.
/// </returns>
int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs);
}
}

16
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -13,16 +13,12 @@ namespace Avalonia.Platform
/// </summary>
public interface IPlatformRenderInterface
{
/// <summary>
/// Get all installed fonts in the system
/// </summary>
IEnumerable<string> InstalledFontNames { get; }
/// <summary>
/// Creates a formatted text implementation.
/// </summary>
/// <param name="text">The text.</param>
/// <param name="typeface">The base typeface.</param>
/// <param name="fontSize">The font size.</param>
/// <param name="textAlignment">The text alignment.</param>
/// <param name="wrapping">The text wrapping mode.</param>
/// <param name="constraint">The text layout constraints.</param>
@ -31,6 +27,7 @@ namespace Avalonia.Platform
IFormattedTextImpl CreateFormattedText(
string text,
Typeface typeface,
double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
@ -114,5 +111,14 @@ namespace Avalonia.Platform
/// <param name="stride">The number of bytes per row.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride);
/// <summary>
/// Creates a glyph typeface for specified typeface.
/// </summary>
/// <param name="typeface">The typeface.</param>
/// <returns>
/// The glyph typeface implementation.
/// </returns>
IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface);
}
}

6
src/Avalonia.Visuals/Rendering/RendererBase.cs

@ -7,7 +7,8 @@ namespace Avalonia.Rendering
{
public class RendererBase
{
private static readonly Typeface s_fpsTypeface = new Typeface("Arial", 18);
private static readonly Typeface s_fpsTypeface = new Typeface("Arial");
private static int s_fontSize = 18;
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
private int _framesThisSecond;
private int _fps;
@ -18,7 +19,8 @@ namespace Avalonia.Rendering
{
_fpsText = new FormattedText
{
Typeface = s_fpsTypeface
Typeface = s_fpsTypeface,
FontSize = s_fontSize
};
}

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

@ -51,7 +51,7 @@ namespace Avalonia.Rendering.SceneGraph
UpdateSize(scene);
}
if (visual.VisualRoot != null)
if (visual.VisualRoot == scene.Root.Visual)
{
if (node?.Parent != null &&
visual.VisualParent != null &&

2
src/Avalonia.X11/X11Platform.cs

@ -19,9 +19,7 @@ namespace Avalonia.X11
class AvaloniaX11Platform : IWindowingPlatform
{
private Lazy<KeyboardDevice> _keyboardDevice = new Lazy<KeyboardDevice>(() => new KeyboardDevice());
private Lazy<MouseDevice> _mouseDevice = new Lazy<MouseDevice>(() => new MouseDevice());
public KeyboardDevice KeyboardDevice => _keyboardDevice.Value;
public MouseDevice MouseDevice => _mouseDevice.Value;
public Dictionary<IntPtr, Action<XEvent>> Windows = new Dictionary<IntPtr, Action<XEvent>>();
public XI2Manager XI2;
public X11Info Info { get; private set; }

10
src/Avalonia.X11/X11Window.cs

@ -32,7 +32,8 @@ namespace Avalonia.X11
private PixelPoint? _configurePoint;
private bool _triggeredExpose;
private IInputRoot _inputRoot;
private readonly IMouseDevice _mouse;
private readonly MouseDevice _mouse;
private readonly TouchDevice _touch;
private readonly IKeyboardDevice _keyboard;
private PixelPoint? _position;
private PixelSize _realSize;
@ -57,7 +58,8 @@ namespace Avalonia.X11
_platform = platform;
_popup = popupParent != null;
_x11 = platform.Info;
_mouse = platform.MouseDevice;
_mouse = new MouseDevice();
_touch = new TouchDevice();
_keyboard = platform.KeyboardDevice;
var glfeature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
@ -702,6 +704,8 @@ namespace Avalonia.X11
_platform.XI2?.OnWindowDestroyed(_handle);
_handle = IntPtr.Zero;
Closed?.Invoke();
_mouse.Dispose();
_touch.Dispose();
}
if (_useRenderWindow && _renderHandle != IntPtr.Zero)
@ -830,6 +834,8 @@ namespace Avalonia.X11
}
public IMouseDevice MouseDevice => _mouse;
public TouchDevice TouchDevice => _touch;
public IPopupImpl CreatePopup()
=> _platform.Options.OverlayPopups ? null : new X11Window(_platform, this);

12
src/Avalonia.X11/XI2Manager.cs

@ -92,8 +92,6 @@ namespace Avalonia.X11
private PointerDeviceInfo _pointerDevice;
private AvaloniaX11Platform _platform;
private readonly TouchDevice _touchDevice = new TouchDevice();
public bool Init(AvaloniaX11Platform platform)
{
@ -198,7 +196,7 @@ namespace Avalonia.X11
(ev.Type == XiEventType.XI_TouchUpdate ?
RawPointerEventType.TouchUpdate :
RawPointerEventType.TouchEnd);
client.ScheduleInput(new RawTouchEventArgs(_touchDevice,
client.ScheduleInput(new RawTouchEventArgs(client.TouchDevice,
ev.Timestamp, client.InputRoot, type, ev.Position, ev.Modifiers, ev.Detail));
return;
}
@ -232,10 +230,10 @@ namespace Avalonia.X11
}
if (scrollDelta != default)
client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp,
client.ScheduleInput(new RawMouseWheelEventArgs(client.MouseDevice, ev.Timestamp,
client.InputRoot, ev.Position, scrollDelta, ev.Modifiers));
if (_pointerDevice.HasMotion(ev))
client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
client.ScheduleInput(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
RawPointerEventType.Move, ev.Position, ev.Modifiers));
}
@ -248,7 +246,7 @@ namespace Avalonia.X11
: ev.Button == 3 ? (down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp)
: (RawPointerEventType?)null;
if (type.HasValue)
client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
client.ScheduleInput(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
type.Value, ev.Position, ev.Modifiers));
}
@ -310,5 +308,7 @@ namespace Avalonia.X11
{
IInputRoot InputRoot { get; }
void ScheduleInput(RawInputEventArgs args);
IMouseDevice MouseDevice { get; }
TouchDevice TouchDevice { get; }
}
}

1
src/Skia/Avalonia.Skia/Avalonia.Skia.csproj

@ -12,5 +12,6 @@
</ItemGroup>
<Import Project="..\..\..\build\SkiaSharp.props" />
<Import Project="..\..\..\build\HarfBuzzSharp.props" />
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
</Project>

40
src/Skia/Avalonia.Skia/FontKey.cs

@ -0,0 +1,40 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Media;
namespace Avalonia.Skia
{
internal readonly struct FontKey : IEquatable<FontKey>
{
public readonly FontStyle Style;
public readonly FontWeight Weight;
public FontKey(FontWeight weight, FontStyle style)
{
Style = style;
Weight = weight;
}
public override int GetHashCode()
{
var hash = 17;
hash = hash * 31 + (int)Style;
hash = hash * 31 + (int)Weight;
return hash;
}
public override bool Equals(object other)
{
return other is FontKey key && Equals(key);
}
public bool Equals(FontKey other)
{
return Style == other.Style &&
Weight == other.Weight;
}
}
}

82
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -0,0 +1,82 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia
{
internal class FontManagerImpl : IFontManagerImpl
{
private SKFontManager _skFontManager = SKFontManager.Default;
public FontManagerImpl()
{
DefaultFontFamilyName = SKTypeface.Default.FamilyName;
}
public string DefaultFontFamilyName { get; }
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false)
{
if (checkForUpdates)
{
_skFontManager = SKFontManager.CreateDefault();
}
return _skFontManager.FontFamilies;
}
public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle)
{
return TypefaceCache.Get(fontFamily.Name, fontWeight, fontStyle).Typeface;
}
public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default,
FontFamily fontFamily = null, CultureInfo culture = null)
{
var fontFamilyName = FontFamily.Default.Name;
if (culture == null)
{
culture = CultureInfo.CurrentUICulture;
}
if (fontFamily != null)
{
foreach (var familyName in fontFamily.FamilyNames)
{
var skTypeface = _skFontManager.MatchCharacter(familyName, (SKFontStyleWeight)fontWeight,
SKFontStyleWidth.Normal,
(SKFontStyleSlant)fontStyle,
new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint);
if (skTypeface == null)
{
continue;
}
fontFamilyName = familyName;
break;
}
}
else
{
var skTypeface = _skFontManager.MatchCharacter(null, (SKFontStyleWeight)fontWeight, SKFontStyleWidth.Normal,
(SKFontStyleSlant)fontStyle,
new[] { culture.TwoLetterISOLanguageName, culture.ThreeLetterISOLanguageName }, codepoint);
if (skTypeface != null)
{
fontFamilyName = skTypeface.FamilyName;
}
}
return GetTypeface(fontFamilyName, fontWeight, fontStyle);
}
}
}

80
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -18,6 +18,7 @@ namespace Avalonia.Skia
public FormattedTextImpl(
string text,
Typeface typeface,
double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
@ -28,47 +29,22 @@ namespace Avalonia.Skia
// Replace 0 characters with zero-width spaces (200B)
Text = Text.Replace((char)0, (char)0x200B);
SKTypeface skiaTypeface = null;
var entry = TypefaceCache.Get(typeface.FontFamily, typeface.Weight, typeface.Style);
if (typeface.FontFamily.Key != null)
_paint = new SKPaint
{
var typefaces = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(typeface.FontFamily);
skiaTypeface = typefaces.GetTypeFace(typeface);
}
else
{
if (typeface.FontFamily.FamilyNames.HasFallbacks)
{
foreach (var familyName in typeface.FontFamily.FamilyNames)
{
skiaTypeface = TypefaceCache.GetTypeface(
familyName,
typeface.Style,
typeface.Weight);
if (skiaTypeface.FamilyName != TypefaceCache.DefaultFamilyName) break;
}
}
else
{
skiaTypeface = TypefaceCache.GetTypeface(
typeface.FontFamily.Name,
typeface.Style,
typeface.Weight);
}
}
_paint = new SKPaint();
TextEncoding = SKTextEncoding.Utf16,
IsStroke = false,
IsAntialias = true,
LcdRenderText = true,
SubpixelText = true,
Typeface = entry.SKTypeface,
TextSize = (float)fontSize,
TextAlign = textAlignment.ToSKTextAlign()
};
//currently Skia does not measure properly with Utf8 !!!
//Paint.TextEncoding = SKTextEncoding.Utf8;
_paint.TextEncoding = SKTextEncoding.Utf16;
_paint.IsStroke = false;
_paint.IsAntialias = true;
_paint.LcdRenderText = true;
_paint.SubpixelText = true;
_paint.Typeface = skiaTypeface;
_paint.TextSize = (float)typeface.FontSize;
_paint.TextAlign = textAlignment.ToSKTextAlign();
_wrapping = wrapping;
_constraint = constraint;
@ -99,7 +75,24 @@ namespace Avalonia.Skia
public TextHitTestResult HitTestPoint(Point point)
{
float y = (float)point.Y;
var line = _skiaLines.Find(l => l.Top <= y && (l.Top + l.Height) > y);
AvaloniaFormattedTextLine line = default;
float nextTop = 0;
foreach(var currentLine in _skiaLines)
{
if(currentLine.Top <= y)
{
line = currentLine;
nextTop = currentLine.Top + currentLine.Height;
}
else
{
nextTop = currentLine.Top;
break;
}
}
if (!line.Equals(default(AvaloniaFormattedTextLine)))
{
@ -127,12 +120,15 @@ namespace Avalonia.Skia
line.Length : (line.Length - 1);
}
return new TextHitTestResult
if (y < nextTop)
{
IsInside = false,
TextPosition = line.Start + offset,
IsTrailing = Text.Length == (line.Start + offset + 1)
};
return new TextHitTestResult
{
IsInside = false,
TextPosition = line.Start + offset,
IsTrailing = Text.Length == (line.Start + offset + 1)
};
}
}
bool end = point.X > _bounds.Width || point.Y > _lines.Sum(l => l.Height);

179
src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs

@ -0,0 +1,179 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Runtime.InteropServices;
using Avalonia.Media;
using Avalonia.Platform;
using HarfBuzzSharp;
using SkiaSharp;
namespace Avalonia.Skia
{
public class GlyphTypefaceImpl : IGlyphTypefaceImpl
{
private bool _isDisposed;
public GlyphTypefaceImpl(Typeface typeface)
{
Typeface = TypefaceCache.Get(typeface.FontFamily, typeface.Weight, typeface.Style).SKTypeface;
Face = new Face(GetTable)
{
UnitsPerEm = Typeface.UnitsPerEm
};
Font = new Font(Face);
Font.SetFunctionsOpenType();
Font.GetScale(out var xScale, out _);
DesignEmHeight = (short)xScale;
if (!Font.TryGetHorizontalFontExtents(out var fontExtents))
{
Font.TryGetVerticalFontExtents(out fontExtents);
}
Ascent = -fontExtents.Ascender;
Descent = -fontExtents.Descender;
LineGap = fontExtents.LineGap;
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition))
{
UnderlinePosition = underlinePosition;
}
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness))
{
UnderlineThickness = underlineThickness;
}
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition))
{
StrikethroughPosition = strikethroughPosition;
}
if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness))
{
StrikethroughThickness = strikethroughThickness;
}
}
public Face Face { get; }
public Font Font { get; }
public SKTypeface Typeface { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public short DesignEmHeight { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Ascent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int Descent { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int LineGap { get; }
//ToDo: Get these values from HarfBuzz
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlinePosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int UnderlineThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughPosition { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int StrikethroughThickness { get; }
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public ushort GetGlyph(uint codepoint)
{
if (Font.TryGetGlyph(codepoint, out var glyph))
{
return (ushort)glyph;
}
return 0;
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public ushort[] GetGlyphs(ReadOnlySpan<uint> codepoints)
{
var glyphs = new ushort[codepoints.Length];
for (var i = 0; i < codepoints.Length; i++)
{
if (Font.TryGetGlyph(codepoints[i], out var glyph))
{
glyphs[i] = (ushort)glyph;
}
}
return glyphs;
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int GetGlyphAdvance(ushort glyph)
{
return Font.GetHorizontalGlyphAdvance(glyph);
}
/// <inheritdoc cref="IGlyphTypefaceImpl"/>
public int[] GetGlyphAdvances(ReadOnlySpan<ushort> glyphs)
{
var glyphIndices = new uint[glyphs.Length];
for (var i = 0; i < glyphs.Length; i++)
{
glyphIndices[i] = glyphs[i];
}
return Font.GetHorizontalGlyphAdvances(glyphIndices);
}
private Blob GetTable(Face face, Tag tag)
{
var size = Typeface.GetTableSize(tag);
var data = Marshal.AllocCoTaskMem(size);
var releaseDelegate = new ReleaseDelegate(() => Marshal.FreeCoTaskMem(data));
return Typeface.TryGetTableData(tag, 0, size, data) ?
new Blob(data, size, MemoryMode.ReadOnly, releaseDelegate) : null;
}
private void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
if (!disposing)
{
return;
}
Font?.Dispose();
Face?.Dispose();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

14
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using Avalonia.Controls.Platform.Surfaces;
@ -17,12 +18,13 @@ namespace Avalonia.Skia
/// </summary>
internal class PlatformRenderInterface : IPlatformRenderInterface
{
private readonly ConcurrentDictionary<Typeface, GlyphTypefaceImpl> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, GlyphTypefaceImpl>();
private readonly ICustomSkiaGpu _customSkiaGpu;
private GRContext GrContext { get; }
public IEnumerable<string> InstalledFontNames => SKFontManager.Default.FontFamilies;
public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu)
{
if (customSkiaGpu != null)
@ -52,12 +54,13 @@ namespace Avalonia.Skia
public IFormattedTextImpl CreateFormattedText(
string text,
Typeface typeface,
double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
IReadOnlyList<FormattedTextStyleSpan> spans)
{
return new FormattedTextImpl(text, typeface, textAlignment, wrapping, constraint, spans);
return new FormattedTextImpl(text, typeface,fontSize, textAlignment, wrapping, constraint, spans);
}
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
@ -151,5 +154,10 @@ namespace Avalonia.Skia
{
return new WriteableBitmapImpl(size, dpi, format);
}
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
{
return _glyphTypefaceCache.GetOrAdd(typeface, new GlyphTypefaceImpl(typeface));
}
}
}

91
src/Skia/Avalonia.Skia/SKTypefaceCollection.cs

@ -4,114 +4,59 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Media;
using SkiaSharp;
namespace Avalonia.Skia
{
internal class SKTypefaceCollection
{
private readonly ConcurrentDictionary<string, ConcurrentDictionary<FontKey, SKTypeface>> _fontFamilies =
new ConcurrentDictionary<string, ConcurrentDictionary<FontKey, SKTypeface>>();
private readonly ConcurrentDictionary<string, ConcurrentDictionary<FontKey, TypefaceCollectionEntry>> _fontFamilies =
new ConcurrentDictionary<string, ConcurrentDictionary<FontKey, TypefaceCollectionEntry>>();
public void AddTypeFace(SKTypeface typeface)
public void AddEntry(string familyName, FontKey key, TypefaceCollectionEntry entry)
{
var key = new FontKey((SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant);
if (!_fontFamilies.TryGetValue(typeface.FamilyName, out var fontFamily))
if (!_fontFamilies.TryGetValue(familyName, out var fontFamily))
{
fontFamily = new ConcurrentDictionary<FontKey, SKTypeface>();
fontFamily = new ConcurrentDictionary<FontKey, TypefaceCollectionEntry>();
_fontFamilies.TryAdd(typeface.FamilyName, fontFamily);
_fontFamilies.TryAdd(familyName, fontFamily);
}
fontFamily.TryAdd(key, typeface);
fontFamily.TryAdd(key, entry);
}
public SKTypeface GetTypeFace(Typeface typeface)
public TypefaceCollectionEntry Get(string familyName, FontWeight fontWeight, FontStyle fontStyle)
{
var styleSlant = SKFontStyleSlant.Upright;
switch (typeface.Style)
{
case FontStyle.Italic:
styleSlant = SKFontStyleSlant.Italic;
break;
case FontStyle.Oblique:
styleSlant = SKFontStyleSlant.Oblique;
break;
}
var key = new FontKey(fontWeight, fontStyle);
if (!_fontFamilies.TryGetValue(typeface.FontFamily.Name, out var fontFamily))
{
return TypefaceCache.GetTypeface(TypefaceCache.DefaultFamilyName, typeface.Style, typeface.Weight);
}
var weight = (SKFontStyleWeight)typeface.Weight;
var key = new FontKey(weight, styleSlant);
return fontFamily.GetOrAdd(key, GetFallback(fontFamily, key));
return _fontFamilies.TryGetValue(familyName, out var fontFamily) ?
fontFamily.GetOrAdd(key, GetFallback(fontFamily, key)) :
new TypefaceCollectionEntry(Typeface.Default, SkiaSharp.SKTypeface.Default);
}
private static SKTypeface GetFallback(IDictionary<FontKey, SKTypeface> fontFamily, FontKey key)
private static TypefaceCollectionEntry GetFallback(IDictionary<FontKey, TypefaceCollectionEntry> fontFamily, FontKey key)
{
var keys = fontFamily.Keys.Where(
x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && x.Slant == key.Slant).ToArray();
x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && x.Style == key.Style).ToArray();
if (!keys.Any())
{
keys = fontFamily.Keys.Where(
x => x.Weight == key.Weight && (x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray();
x => x.Weight == key.Weight && (x.Style >= key.Style || x.Style < key.Style)).ToArray();
if (!keys.Any())
{
keys = fontFamily.Keys.Where(
x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) &&
(x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray();
(x.Style >= key.Style || x.Style < key.Style)).ToArray();
}
}
key = keys.FirstOrDefault();
fontFamily.TryGetValue(key, out var typeface);
return typeface;
}
private struct FontKey
{
public readonly SKFontStyleSlant Slant;
public readonly SKFontStyleWeight Weight;
public FontKey(SKFontStyleWeight weight, SKFontStyleSlant slant)
{
Slant = slant;
Weight = weight;
}
public override int GetHashCode()
{
var hash = 17;
hash = (hash * 31) + (int)Slant;
hash = (hash * 31) + (int)Weight;
return hash;
}
fontFamily.TryGetValue(key, out var entry);
public override bool Equals(object other)
{
return other is FontKey key && this.Equals(key);
}
private bool Equals(FontKey other)
{
return Slant == other.Slant &&
Weight == other.Weight;
}
return entry;
}
}
}

8
src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs

@ -45,9 +45,13 @@ namespace Avalonia.Skia
{
var assetStream = assetLoader.Open(asset);
var typeface = SKTypeface.FromStream(assetStream);
var skTypeface = SKTypeface.FromStream(assetStream);
typeFaceCollection.AddTypeFace(typeface);
var typeface = new Typeface(fontFamily, (FontWeight)skTypeface.FontWeight, (FontStyle)skTypeface.FontSlant);
var entry = new TypefaceCollectionEntry(typeface, skTypeface);
typeFaceCollection.AddEntry(skTypeface.FamilyName, new FontKey(typeface.Weight, typeface.Style), entry);
}
return typeFaceCollection;

5
src/Skia/Avalonia.Skia/SkiaPlatform.cs

@ -25,6 +25,11 @@ namespace Avalonia.Skia
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface);
var fontManager = new FontManagerImpl();
AvaloniaLocator.CurrentMutable
.Bind<IFontManagerImpl>().ToConstant(fontManager);
}
/// <summary>

86
src/Skia/Avalonia.Skia/TypefaceCache.cs

@ -1,7 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Collections.Concurrent;
using Avalonia.Media;
using SkiaSharp;
@ -12,88 +12,36 @@ namespace Avalonia.Skia
/// </summary>
internal static class TypefaceCache
{
public static readonly string DefaultFamilyName = CreateDefaultFamilyName();
private static readonly ConcurrentDictionary<string, ConcurrentDictionary<FontKey, TypefaceCollectionEntry>> s_cache =
new ConcurrentDictionary<string, ConcurrentDictionary<FontKey, TypefaceCollectionEntry>>();
private static readonly Dictionary<string, Dictionary<FontKey, SKTypeface>> s_cache =
new Dictionary<string, Dictionary<FontKey, SKTypeface>>();
struct FontKey
public static TypefaceCollectionEntry Get(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle)
{
public readonly SKFontStyleSlant Slant;
public readonly SKFontStyleWeight Weight;
public FontKey(SKFontStyleWeight weight, SKFontStyleSlant slant)
if (fontFamily.Key != null)
{
Slant = slant;
Weight = weight;
return SKTypefaceCollectionCache.GetOrAddTypefaceCollection(fontFamily)
.Get(fontFamily.Name, fontWeight, fontStyle);
}
public override int GetHashCode()
{
int hash = 17;
hash = hash * 31 + (int)Slant;
hash = hash * 31 + (int)Weight;
return hash;
}
public override bool Equals(object other)
{
return other is FontKey ? Equals((FontKey)other) : false;
}
public bool Equals(FontKey other)
{
return Slant == other.Slant &&
Weight == other.Weight;
}
// Equals and GetHashCode ommitted
}
private static string CreateDefaultFamilyName()
{
var defaultTypeface = SKTypeface.CreateDefault();
var typefaceCollection = s_cache.GetOrAdd(fontFamily.Name, new ConcurrentDictionary<FontKey, TypefaceCollectionEntry>());
return defaultTypeface.FamilyName;
}
var key = new FontKey(fontWeight, fontStyle);
private static SKTypeface GetTypeface(string name, FontKey key)
{
var familyKey = name;
if (!s_cache.TryGetValue(familyKey, out var entry))
if (typefaceCollection.TryGetValue(key, out var entry))
{
s_cache[familyKey] = entry = new Dictionary<FontKey, SKTypeface>();
return entry;
}
if (!entry.TryGetValue(key, out var typeface))
{
typeface = SKTypeface.FromFamilyName(familyKey, key.Weight, SKFontStyleWidth.Normal, key.Slant) ??
GetTypeface(DefaultFamilyName, key);
var skTypeface = SKTypeface.FromFamilyName(fontFamily.Name, (SKFontStyleWeight)fontWeight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle) ?? SKTypeface.Default;
entry[key] = typeface;
}
var typeface = new Typeface(fontFamily.Name, fontWeight, fontStyle);
return typeface;
}
public static SKTypeface GetTypeface(string name, FontStyle style, FontWeight weight)
{
var skStyle = SKFontStyleSlant.Upright;
entry = new TypefaceCollectionEntry(typeface, skTypeface);
switch (style)
{
case FontStyle.Italic:
skStyle = SKFontStyleSlant.Italic;
break;
case FontStyle.Oblique:
skStyle = SKFontStyleSlant.Oblique;
break;
}
typefaceCollection[key] = entry;
return GetTypeface(name, new FontKey((SKFontStyleWeight)weight, skStyle));
return entry;
}
}
}

19
src/Skia/Avalonia.Skia/TypefaceCollectionEntry.cs

@ -0,0 +1,19 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Media;
using SkiaSharp;
namespace Avalonia.Skia
{
internal class TypefaceCollectionEntry
{
public TypefaceCollectionEntry(Typeface typeface, SKTypeface skTypeface)
{
Typeface = typeface;
SKTypeface = skTypeface;
}
public Typeface Typeface { get; }
public SKTypeface SKTypeface { get; }
}
}

1
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -14,6 +14,7 @@
</ItemGroup>
<Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\SharpDX.props" />
<Import Project="..\..\..\build\HarfBuzzSharp.props" />
<Import Project="..\..\Shared\RenderHelpers\RenderHelpers.projitems" Label="Shared" />
<Import Project="..\..\..\build\JetBrains.Annotations.props" />
</Project>

25
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using Avalonia.Controls;
@ -27,6 +28,8 @@ namespace Avalonia.Direct2D1
{
public class Direct2D1Platform : IPlatformRenderInterface
{
private readonly ConcurrentDictionary<Typeface, GlyphTypefaceImpl> _glyphTypefaceCache =
new ConcurrentDictionary<Typeface, GlyphTypefaceImpl>();
private static readonly Direct2D1Platform s_instance = new Direct2D1Platform();
public static SharpDX.Direct3D11.Device Direct3D11Device { get; private set; }
@ -41,20 +44,6 @@ namespace Avalonia.Direct2D1
public static SharpDX.DXGI.Device1 DxgiDevice { get; private set; }
public IEnumerable<string> InstalledFontNames
{
get
{
var cache = Direct2D1FontCollectionCache.s_installedFontCollection;
var length = cache.FontFamilyCount;
for (int i = 0; i < length; i++)
{
var names = cache.GetFontFamily(i).FamilyNames;
yield return names.GetString(0);
}
}
}
private static readonly object s_initLock = new object();
private static bool s_initialized = false;
@ -120,6 +109,7 @@ namespace Avalonia.Direct2D1
{
InitializeDirect2D();
AvaloniaLocator.CurrentMutable.Bind<IPlatformRenderInterface>().ToConstant(s_instance);
AvaloniaLocator.CurrentMutable.Bind<IFontManagerImpl>().ToConstant(new FontManagerImpl());
SharpDX.Configuration.EnableReleaseOnFinalizer = true;
}
@ -131,6 +121,7 @@ namespace Avalonia.Direct2D1
public IFormattedTextImpl CreateFormattedText(
string text,
Typeface typeface,
double fontSize,
TextAlignment textAlignment,
TextWrapping wrapping,
Size constraint,
@ -139,6 +130,7 @@ namespace Avalonia.Direct2D1
return new FormattedTextImpl(
text,
typeface,
fontSize,
textAlignment,
wrapping,
constraint,
@ -201,5 +193,10 @@ namespace Avalonia.Direct2D1
{
return new WicBitmapImpl(format, data, size, dpi, stride);
}
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
{
return _glyphTypefaceCache.GetOrAdd(typeface, new GlyphTypefaceImpl(typeface));
}
}
}

49
src/Windows/Avalonia.Direct2D1/Media/Direct2D1FontCollectionCache.cs

@ -1,62 +1,61 @@
using System.Collections.Concurrent;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using SharpDX.DirectWrite;
using FontFamily = Avalonia.Media.FontFamily;
using FontStyle = SharpDX.DirectWrite.FontStyle;
using FontWeight = SharpDX.DirectWrite.FontWeight;
namespace Avalonia.Direct2D1.Media
{
internal static class Direct2D1FontCollectionCache
{
private static readonly ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection> s_cachedCollections;
internal static readonly SharpDX.DirectWrite.FontCollection s_installedFontCollection;
private static readonly ConcurrentDictionary<FontFamilyKey, FontCollection> s_cachedCollections;
internal static readonly FontCollection InstalledFontCollection;
static Direct2D1FontCollectionCache()
{
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, SharpDX.DirectWrite.FontCollection>();
s_cachedCollections = new ConcurrentDictionary<FontFamilyKey, FontCollection>();
s_installedFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false);
InstalledFontCollection = Direct2D1Platform.DirectWriteFactory.GetSystemFontCollection(false);
}
public static SharpDX.DirectWrite.TextFormat GetTextFormat(Typeface typeface)
public static Font GetFont(Typeface typeface)
{
var fontFamily = typeface.FontFamily;
var fontCollection = GetOrAddFontCollection(fontFamily);
var fontFamilyName = FontFamily.Default.Name;
// Should this be cached?
foreach (var familyName in fontFamily.FamilyNames)
{
if (!fontCollection.FindFamilyName(familyName, out _))
if (fontCollection.FindFamilyName(familyName, out var index))
{
continue;
return fontCollection.GetFontFamily(index).GetFirstMatchingFont(
(FontWeight)typeface.Weight,
FontStretch.Normal,
(FontStyle)typeface.Style);
}
fontFamilyName = familyName;
break;
}
return new SharpDX.DirectWrite.TextFormat(
Direct2D1Platform.DirectWriteFactory,
fontFamilyName,
fontCollection,
(SharpDX.DirectWrite.FontWeight)typeface.Weight,
(SharpDX.DirectWrite.FontStyle)typeface.Style,
SharpDX.DirectWrite.FontStretch.Normal,
(float)typeface.FontSize);
InstalledFontCollection.FindFamilyName(FontFamily.Default.Name, out var i);
return InstalledFontCollection.GetFontFamily(i).GetFirstMatchingFont(
(FontWeight)typeface.Weight,
FontStretch.Normal,
(FontStyle)typeface.Style);
}
private static SharpDX.DirectWrite.FontCollection GetOrAddFontCollection(FontFamily fontFamily)
private static FontCollection GetOrAddFontCollection(FontFamily fontFamily)
{
return fontFamily.Key == null ? s_installedFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection);
return fontFamily.Key == null ? InstalledFontCollection : s_cachedCollections.GetOrAdd(fontFamily.Key, CreateFontCollection);
}
private static SharpDX.DirectWrite.FontCollection CreateFontCollection(FontFamilyKey key)
private static FontCollection CreateFontCollection(FontFamilyKey key)
{
var assets = FontFamilyLoader.LoadFontAssets(key);
var fontLoader = new DWriteResourceFontLoader(Direct2D1Platform.DirectWriteFactory, assets);
return new SharpDX.DirectWrite.FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key);
return new FontCollection(Direct2D1Platform.DirectWriteFactory, fontLoader, fontLoader.Key);
}
}
}

71
src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs

@ -0,0 +1,71 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Media;
using Avalonia.Platform;
using SharpDX.DirectWrite;
using FontFamily = Avalonia.Media.FontFamily;
using FontStyle = Avalonia.Media.FontStyle;
using FontWeight = Avalonia.Media.FontWeight;
namespace Avalonia.Direct2D1.Media
{
internal class FontManagerImpl : IFontManagerImpl
{
public FontManagerImpl()
{
//ToDo: Implement a real lookup of the system's default font.
DefaultFontFamilyName = "segoe ui";
}
public string DefaultFontFamilyName { get; }
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false)
{
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
var fontFamilies = new string[familyCount];
for (var i = 0; i < familyCount; i++)
{
fontFamilies[i] = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i).FamilyNames.GetString(0);
}
return fontFamilies;
}
public Typeface GetTypeface(FontFamily fontFamily, FontWeight fontWeight, FontStyle fontStyle)
{
//ToDo: Implement caching.
return new Typeface(fontFamily, fontWeight, fontStyle);
}
public Typeface MatchCharacter(int codepoint, FontWeight fontWeight = default, FontStyle fontStyle = default,
FontFamily fontFamily = null, CultureInfo culture = null)
{
var fontFamilyName = FontFamily.Default.Name;
var familyCount = Direct2D1FontCollectionCache.InstalledFontCollection.FontFamilyCount;
for (var i = 0; i < familyCount; i++)
{
var font = Direct2D1FontCollectionCache.InstalledFontCollection.GetFontFamily(i)
.GetMatchingFonts((SharpDX.DirectWrite.FontWeight)fontWeight, FontStretch.Normal,
(SharpDX.DirectWrite.FontStyle)fontStyle).GetFont(0);
if (!font.HasCharacter(codepoint))
{
continue;
}
fontFamilyName = font.FontFamily.FamilyNames.GetString(0);
break;
}
return GetTypeface(new FontFamily(fontFamilyName), fontWeight, fontStyle);
}
}
}

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

Loading…
Cancel
Save