Browse Source

Merge branch 'master' into SplitViewPort

pull/4174/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
8b5eeadbb1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      Avalonia.sln
  2. 5
      native/Avalonia.Native/inc/avalonia-native.h
  3. 21
      native/Avalonia.Native/src/OSX/controlhost.mm
  4. 33
      native/Avalonia.Native/src/OSX/window.mm
  5. 26
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  6. 9
      samples/interop/NativeEmbedSample/MainWindow.xaml
  7. 1
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  8. 89
      src/Avalonia.Controls/NativeControlHost.cs
  9. 4
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  10. 164
      src/Avalonia.Controls/ProgressBar.cs
  11. 2
      src/Avalonia.Controls/SelectionModel.cs
  12. 6
      src/Avalonia.Controls/TopLevel.cs
  13. 9
      src/Avalonia.Controls/Utils/IEnumerableUtils.cs
  14. 10
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  15. 29
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
  16. 23
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  17. 14
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  18. 78
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
  19. 10
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  20. 37
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  21. 22
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  22. 40
      src/Avalonia.Layout/LayoutManager.cs
  23. 9
      src/Avalonia.Native/NativeControlHostImpl.cs
  24. 12
      src/Avalonia.Native/PopupImpl.cs
  25. 2
      src/Avalonia.Native/WindowImplBase.cs
  26. 12
      src/Avalonia.Themes.Default/ProgressBar.xaml
  27. 43
      src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
  28. 45
      src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
  29. 9
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  30. 8
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  31. 44
      src/Avalonia.Themes.Fluent/FocusAdorner.xaml
  32. 191
      src/Avalonia.Themes.Fluent/ProgressBar.xaml
  33. 16
      src/Avalonia.Themes.Fluent/ToolTip.xaml
  34. BIN
      src/Avalonia.Visuals/Assets/GraphemeBreak.trie
  35. BIN
      src/Avalonia.Visuals/Assets/UnicodeData.trie
  36. 2
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs
  37. 65
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs
  38. 34
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs
  39. 4
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs
  40. 4
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
  41. 178
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs
  42. 10
      src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs
  43. 15
      src/Avalonia.Visuals/Rect.cs
  44. 22
      src/Avalonia.X11/X11NativeControlHost.cs
  45. 12
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
  46. 67
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt
  47. 61
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs
  48. 83
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs
  49. 2
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs
  50. 85
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs
  51. 61
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs
  52. 25
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs
  53. 181
      tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs

8
Avalonia.sln

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

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

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

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

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

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

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

26
samples/ControlCatalog/Pages/ProgressBarPage.xaml

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

9
samples/interop/NativeEmbedSample/MainWindow.xaml

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

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

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

89
src/Avalonia.Controls/NativeControlHost.cs

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

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

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

164
src/Avalonia.Controls/ProgressBar.cs

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

2
src/Avalonia.Controls/SelectionModel.cs

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

6
src/Avalonia.Controls/TopLevel.cs

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

40
src/Avalonia.Layout/LayoutManager.cs

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

9
src/Avalonia.Native/NativeControlHostImpl.cs

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

12
src/Avalonia.Native/PopupImpl.cs

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

2
src/Avalonia.Native/WindowImplBase.cs

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

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

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

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

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

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

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

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

@ -336,6 +336,15 @@
<StaticResource x:Key="MenuFlyoutSubItemRevealBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />--> <StaticResource x:Key="MenuFlyoutSubItemRevealBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />-->
<!--<Thickness x:Key="LanguageSwitcherMenuFlyoutItemPlaceholderThemeThickness">44,0,0,0</Thickness>--> <!--<Thickness x:Key="LanguageSwitcherMenuFlyoutItemPlaceholderThemeThickness">44,0,0,0</Thickness>-->
<!-- Resources for ProgressBar.xaml -->
<x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
<x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#59FFFFFF" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF5B2EC5" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF8A57FF" />
<!-- Resources for TextBox.xaml --> <!-- Resources for TextBox.xaml -->
<SolidColorBrush x:Key="TextBoxForegroundHeaderThemeBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="TextBoxForegroundHeaderThemeBrush" Color="#FFFFFFFF" />
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#AB000000" /> <SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#AB000000" />

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

@ -248,6 +248,14 @@
<SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99FFFFFF" /> <SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99FFFFFF" />
<SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" /> <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
<SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" /> <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
<!-- Resources for ProgressBar.xaml -->
<x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
<x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
<Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
<SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#30000000" />
<SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
<SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF4617B4" />
<SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF4617B4" />
<!-- Resources for MenuFlyout.xaml (Menu, ContextMenu, etc) --> <!-- Resources for MenuFlyout.xaml (Menu, ContextMenu, etc) -->
<x:Double x:Key="MenuFlyoutSeparatorThemeHeight">1</x:Double> <x:Double x:Key="MenuFlyoutSeparatorThemeHeight">1</x:Double>

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

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

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

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

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

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

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

Binary file not shown.

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

Binary file not shown.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

15
src/Avalonia.Visuals/Rect.cs

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

22
src/Avalonia.X11/X11NativeControlHost.cs

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save