Browse Source

Merge branch 'master' into fix/DataGridCopyClearsContent

pull/7786/head
Max Katz 5 years ago
committed by GitHub
parent
commit
c6b996ce0e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      samples/ControlCatalog/Converter/MathSubtractConverter.cs
  2. 3
      samples/ControlCatalog/MainView.xaml
  3. 82
      samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
  4. 19
      samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml.cs
  5. 309
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  6. 2
      samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
  7. 5
      src/Avalonia.Controls/ApiCompatBaseline.txt
  8. 6
      src/Avalonia.Controls/ContextMenu.cs
  9. 28
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  10. 8
      src/Avalonia.Controls/Platform/IScreenImpl.cs
  11. 54
      src/Avalonia.Controls/Platform/ScreenHelper.cs
  12. 4
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  13. 39
      src/Avalonia.Controls/Screens.cs
  14. 2
      src/Avalonia.Controls/TextBlock.cs
  15. 14
      src/Avalonia.Controls/Window.cs
  16. 2
      src/Avalonia.Controls/WindowBase.cs
  17. 5
      src/Avalonia.Controls/WindowTransparencyLevel.cs
  18. 15
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  19. 15
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  20. 2
      src/Avalonia.Input/MouseDevice.cs
  21. 15
      src/Avalonia.Native/ScreenImpl.cs
  22. 43
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  23. 8
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  24. 17
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  25. 19
      src/Avalonia.Visuals/Media/FormattedText.cs
  26. 12
      src/Avalonia.Visuals/Media/PixelRect.cs
  27. 16
      src/Avalonia.Visuals/Media/TextCollapsingCreateInfo.cs
  28. 23
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  29. 15
      src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs
  30. 18
      src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs
  31. 95
      src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs
  32. 13
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  33. 146
      src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs
  34. 79
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  35. 26
      src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs
  36. 26
      src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs
  37. 31
      src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs
  38. 18
      src/Avalonia.Visuals/Media/TextNoneTrimming.cs
  39. 36
      src/Avalonia.Visuals/Media/TextTrailingTrimming.cs
  40. 63
      src/Avalonia.Visuals/Media/TextTrimming.cs
  41. 1
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  42. 12
      src/Avalonia.Visuals/Rect.cs
  43. 15
      src/Avalonia.X11/X11Screens.cs
  44. 26
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  45. 10
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  46. 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  47. 15
      src/Web/Avalonia.Web.Blazor/WinStubs.cs
  48. 39
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  49. 6
      src/Windows/Avalonia.Win32/WinScreen.cs
  50. 8
      src/Windows/Avalonia.Win32/WindowImpl.cs
  51. 2
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  52. 31
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  53. 31
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs
  54. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
  55. 38
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

18
samples/ControlCatalog/Converter/MathSubtractConverter.cs

@ -0,0 +1,18 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace ControlCatalog.Converter;
public class MathSubtractConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value - (double)parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

3
samples/ControlCatalog/MainView.xaml

@ -145,6 +145,9 @@
<TabItem Header="ToolTip">
<pages:ToolTipPage />
</TabItem>
<TabItem Header="TransitioningContentControl">
<pages:TransitioningContentControlPage />
</TabItem>
<TabItem Header="TreeView">
<pages:TreeViewPage />
</TabItem>

82
samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml

@ -0,0 +1,82 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ControlCatalog.ViewModels"
xmlns:converter="clr-namespace:ControlCatalog.Converter"
xmlns:system="using:System"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="vm:TransitioningContentControlPageViewModel"
x:CompileBindings="True"
x:Class="ControlCatalog.Pages.TransitioningContentControlPage">
<UserControl.DataContext>
<vm:TransitioningContentControlPageViewModel />
</UserControl.DataContext>
<UserControl.Styles>
<Style Selector="HeaderedContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="HeaderCol" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{TemplateBinding Header}"
Grid.Column="0"
VerticalAlignment="Center" />
<ContentPresenter Content="{TemplateBinding Content}"
Grid.Column="1"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Styles>
<UserControl.Resources>
<converter:MathSubtractConverter x:Key="MathSubtractConverter" />
<system:Double x:Key="TopMargin">8</system:Double>
</UserControl.Resources>
<DockPanel LastChildFill="True"
Height="{Binding Path=Bounds.Height, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}, Converter={StaticResource MathSubtractConverter},ConverterParameter={StaticResource TopMargin}}">
<TextBlock DockPanel.Dock="Top" Classes="h2">The TransitioningContentControl control allows you to show a page transition whenever the Content changes.</TextBlock>
<ExperimentalAcrylicBorder DockPanel.Dock="Bottom" Margin="10" CornerRadius="5" >
<ExperimentalAcrylicBorder.Material>
<ExperimentalAcrylicMaterial BackgroundSource="Digger" TintColor="White" />
</ExperimentalAcrylicBorder.Material>
<StackPanel Margin="5" Spacing="5" Grid.IsSharedSizeScope="True">
<HeaderedContentControl Header="Select a transition">
<ComboBox Items="{Binding PageTransitions}" SelectedItem="{Binding SelectedTransition}" />
</HeaderedContentControl>
<HeaderedContentControl Header="Duration">
<NumericUpDown Value="{Binding Duration}" Increment="250" Minimum="100" />
</HeaderedContentControl>
<HeaderedContentControl Header="Clip to Bounds">
<ToggleSwitch IsChecked="{Binding ClipToBounds}" />
</HeaderedContentControl>
</StackPanel>
</ExperimentalAcrylicBorder>
<Button DockPanel.Dock="Left" Command="{Binding PrevImage}" Content="&lt;" />
<Button DockPanel.Dock="Right" Command="{Binding NextImage}" Content=">" />
<Border ClipToBounds="{Binding ClipToBounds}" Margin="5">
<TransitioningContentControl Content="{Binding SelectedImage}"
PageTransition="{Binding SelectedTransition.Transition}" >
<TransitioningContentControl.ContentTemplate>
<DataTemplate DataType="Bitmap">
<Image Source="{Binding}" />
</DataTemplate>
</TransitioningContentControl.ContentTemplate>
</TransitioningContentControl>
</Border>
</DockPanel>
</UserControl>

19
samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml.cs

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public partial class TransitioningContentControlPage : UserControl
{
public TransitioningContentControlPage()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

309
samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs

@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.VisualTree;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class TransitioningContentControlPageViewModel : ViewModelBase
{
public TransitioningContentControlPageViewModel()
{
var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
var images = new string[]
{
"delicate-arch-896885_640.jpg",
"hirsch-899118_640.jpg",
"maple-leaf-888807_640.jpg"
};
foreach (var image in images)
{
var path = $"avares://ControlCatalog/Assets/{image}";
Images.Add(new Bitmap(assetLoader.Open(new Uri(path))));
}
SetupTransitions();
SelectedTransition = PageTransitions[1];
SelectedImage = Images[0];
}
public List<PageTransition> PageTransitions { get; } = new List<PageTransition>();
public List<Bitmap> Images { get; } = new List<Bitmap>();
private Bitmap? _SelectedImage;
/// <summary>
/// Gets or Sets the selected image
/// </summary>
public Bitmap? SelectedImage
{
get { return _SelectedImage; }
set { this.RaiseAndSetIfChanged(ref _SelectedImage, value); }
}
private PageTransition _SelectedTransition;
/// <summary>
/// Gets or sets the transition to play
/// </summary>
public PageTransition SelectedTransition
{
get { return _SelectedTransition; }
set { this.RaiseAndSetIfChanged(ref _SelectedTransition, value); }
}
private bool _ClipToBounds;
/// <summary>
/// Gets or sets if the content should be clipped to bounds
/// </summary>
public bool ClipToBounds
{
get { return _ClipToBounds; }
set { this.RaiseAndSetIfChanged(ref _ClipToBounds, value); }
}
private int _Duration = 500;
/// <summary>
/// Gets or Sets the duration
/// </summary>
public int Duration
{
get { return _Duration; }
set
{
this.RaiseAndSetIfChanged(ref _Duration , value);
SetupTransitions();
}
}
private void SetupTransitions()
{
if (PageTransitions.Count == 0)
{
PageTransitions.AddRange(new[]
{
new PageTransition("None"),
new PageTransition("CrossFade"),
new PageTransition("Slide horizontally"),
new PageTransition("Slide vertically"),
new PageTransition("Composite"),
new PageTransition("Custom")
});
}
PageTransitions[1].Transition = new CrossFade(TimeSpan.FromMilliseconds(Duration));
PageTransitions[2].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlide.SlideAxis.Horizontal);
PageTransitions[3].Transition = new PageSlide(TimeSpan.FromMilliseconds(Duration), PageSlide.SlideAxis.Vertical);
var compositeTransition = new CompositePageTransition();
compositeTransition.PageTransitions.Add(PageTransitions[1].Transition);
compositeTransition.PageTransitions.Add(PageTransitions[2].Transition);
compositeTransition.PageTransitions.Add(PageTransitions[3].Transition);
PageTransitions[4].Transition = compositeTransition;
PageTransitions[5].Transition = new CustomTransition(TimeSpan.FromMilliseconds(Duration));
}
public void NextImage()
{
var index = Images.IndexOf(SelectedImage) + 1;
if (index >= Images.Count)
{
index = 0;
}
SelectedImage = Images[index];
}
public void PrevImage()
{
var index = Images.IndexOf(SelectedImage) - 1;
if (index < 0)
{
index = Images.Count-1;
}
SelectedImage = Images[index];
}
}
public class PageTransition : ViewModelBase
{
public PageTransition(string displayTitle)
{
DisplayTitle = displayTitle;
}
public string DisplayTitle { get; }
private IPageTransition _Transition;
/// <summary>
/// Gets or sets the transition
/// </summary>
public IPageTransition Transition
{
get { return _Transition; }
set { this.RaiseAndSetIfChanged(ref _Transition, value); }
}
public override string ToString()
{
return DisplayTitle;
}
}
public class CustomTransition : IPageTransition
{
/// <summary>
/// Initializes a new instance of the <see cref="CustomTransition"/> class.
/// </summary>
public CustomTransition()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CustomTransition"/> class.
/// </summary>
/// <param name="duration">The duration of the animation.</param>
public CustomTransition(TimeSpan duration)
{
Duration = duration;
}
/// <summary>
/// Gets the duration of the animation.
/// </summary>
public TimeSpan Duration { get; set; }
public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
var tasks = new List<Task>();
var parent = GetVisualParent(from, to);
var scaleProperty = ScaleTransform.ScaleYProperty;
if (from != null)
{
var animation = new Animation
{
FillMode = FillMode.Forward,
Children =
{
new KeyFrame
{
Setters = { new Setter { Property = scaleProperty, Value = 1d } },
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter
{
Property = scaleProperty,
Value = 0d
}
},
Cue = new Cue(1d)
}
},
Duration = Duration
};
tasks.Add(animation.RunAsync(from, null, cancellationToken));
}
if (to != null)
{
to.IsVisible = true;
var animation = new Animation
{
FillMode = FillMode.Forward,
Children =
{
new KeyFrame
{
Setters =
{
new Setter
{
Property = scaleProperty,
Value = 0d
}
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters = { new Setter { Property = scaleProperty, Value = 1d } },
Cue = new Cue(1d)
}
},
Duration = Duration
};
tasks.Add(animation.RunAsync(to, null, cancellationToken));
}
await Task.WhenAll(tasks);
if (from != null && !cancellationToken.IsCancellationRequested)
{
from.IsVisible = false;
}
}
/// <summary>
/// Gets the common visual parent of the two control.
/// </summary>
/// <param name="from">The from control.</param>
/// <param name="to">The to control.</param>
/// <returns>The common parent.</returns>
/// <exception cref="ArgumentException">
/// The two controls do not share a common parent.
/// </exception>
/// <remarks>
/// Any one of the parameters may be null, but not both.
/// </remarks>
private static IVisual GetVisualParent(IVisual? from, IVisual? to)
{
var p1 = (from ?? to)!.VisualParent;
var p2 = (to ?? from)!.VisualParent;
if (p1 != null && p2 != null && p1 != p2)
{
throw new ArgumentException("Controls for PageSlide must have same parent.");
}
return p1 ?? throw new InvalidOperationException("Cannot determine visual parent.");
}
}
}

2
samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml

@ -22,7 +22,7 @@
<Styles.Resources>
<x:Double x:Key="PaneCompactWidth">40</x:Double>
<x:Double x:Key="PaneExpandWidth">200</x:Double>
<x:Double x:Key="PaneExpandWidth">220</x:Double>
<x:Double x:Key="HeaderHeight">36</x:Double>
<x:Double x:Key="NavigationItemHeight">36</x:Double>
<x:Double x:Key="HamburgerMenuButtonHeight">32</x:Double>

5
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -51,6 +51,9 @@ MembersMustExist : Member 'protected void Avalonia.Controls.Presenters.TextPrese
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
TypesMustExist : Type 'Avalonia.Platform.ExportWindowingSubsystemAttribute' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromPoint(Avalonia.PixelPoint)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromRect(Avalonia.PixelRect)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Action<Avalonia.Size, Avalonia.Platform.PlatformResizeReason> Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the implementation but not in the contract.
@ -69,4 +72,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 70
Total Issues: 73

6
src/Avalonia.Controls/ContextMenu.cs

@ -351,6 +351,11 @@ namespace Avalonia.Controls
? PlacementMode.Bottom
: PlacementMode;
//Position of the line below is really important.
//All styles are being applied only when control has logical parent.
//Line below will add ContextMenu as child to the Popup and this will trigger styles and they would be applied.
//If you will move line below somewhere else it may cause that ContextMenu will behave differently from what you are expecting.
_popup.Child = this;
_popup.PlacementTarget = placementTarget;
_popup.HorizontalOffset = HorizontalOffset;
_popup.VerticalOffset = VerticalOffset;
@ -359,7 +364,6 @@ namespace Avalonia.Controls
_popup.PlacementGravity = PlacementGravity;
_popup.PlacementRect = PlacementRect;
_popup.WindowManagerAddShadowHint = WindowManagerAddShadowHint;
_popup.Child = this;
IsOpen = true;
_popup.IsOpen = true;

28
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@ -2,6 +2,8 @@
using System.Globalization;
using System.Linq;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
@ -58,18 +60,17 @@ namespace Avalonia.Controls.Primitives
private Vector _offset;
private bool _hasInit;
private bool _suppressUpdateOffset;
private ListBoxItem? _pressedItem;
public DateTimePickerPanel()
{
FormatDate = DateTime.Now;
AddHandler(ListBoxItem.PointerPressedEvent, OnItemPointerDown, Avalonia.Interactivity.RoutingStrategies.Bubble);
AddHandler(ListBoxItem.PointerReleasedEvent, OnItemPointerUp, Avalonia.Interactivity.RoutingStrategies.Bubble);
AddHandler(TappedEvent, OnItemTapped, RoutingStrategies.Bubble);
}
static DateTimePickerPanel()
{
FocusableProperty.OverrideDefaultValue<DateTimePickerPanel>(true);
BackgroundProperty.OverrideDefaultValue<DateTimePickerPanel>(Brushes.Transparent);
AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty);
}
@ -523,26 +524,13 @@ namespace Avalonia.Controls.Primitives
return newValue;
}
private void OnItemPointerDown(object? sender, PointerPressedEventArgs e)
private void OnItemTapped(object? sender, TappedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed &&
e.Source is IVisual source)
{
_pressedItem = GetItemFromSource(source);
e.Handled = true;
}
}
private void OnItemPointerUp(object? sender, PointerReleasedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased &&
_pressedItem != null &&
e.Source is IVisual source &&
GetItemFromSource(source) is ListBoxItem item &&
item.Tag is int tag)
if (e.Source is IVisual source &&
GetItemFromSource(source) is ListBoxItem listBoxItem &&
listBoxItem.Tag is int tag)
{
SelectedValue = tag;
_pressedItem = null;
e.Handled = true;
}
}

8
src/Avalonia.Controls/Platform/IScreenImpl.cs

@ -1,5 +1,7 @@
using System.Collections.Generic;
#nullable enable
namespace Avalonia.Platform
{
public interface IScreenImpl
@ -7,5 +9,11 @@ namespace Avalonia.Platform
int ScreenCount { get; }
IReadOnlyList<Screen> AllScreens { get; }
Screen? ScreenFromWindow(IWindowBaseImpl window);
Screen? ScreenFromPoint(PixelPoint point);
Screen? ScreenFromRect(PixelRect rect);
}
}

54
src/Avalonia.Controls/Platform/ScreenHelper.cs

@ -0,0 +1,54 @@
using System.Collections.Generic;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.Platform
{
public static class ScreenHelper
{
public static Screen? ScreenFromPoint(PixelPoint point, IReadOnlyList<Screen> screens)
{
foreach (Screen screen in screens)
{
if (screen.Bounds.ContainsExclusive(point))
{
return screen;
}
}
return null;
}
public static Screen? ScreenFromRect(PixelRect bounds, IReadOnlyList<Screen> screens)
{
Screen? currMaxScreen = null;
double maxAreaSize = 0;
foreach (Screen screen in screens)
{
double left = MathUtilities.Clamp(bounds.X, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
double top = MathUtilities.Clamp(bounds.Y, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
double right = MathUtilities.Clamp(bounds.X + bounds.Width, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
double bottom = MathUtilities.Clamp(bounds.Y + bounds.Height, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
double area = (right - left) * (bottom - top);
if (area > maxAreaSize)
{
maxAreaSize = area;
currMaxScreen = screen;
}
}
return currMaxScreen;
}
public static Screen? ScreenFromWindow(IWindowBaseImpl window, IReadOnlyList<Screen> screens)
{
var rect = new PixelRect(
window.Position,
PixelSize.FromSize(window.FrameSize ?? window.ClientSize, window.DesktopScaling));
return ScreenFromRect(rect, screens);
}
}
}

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

@ -106,9 +106,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
{
var screens = _popup.Screens;
var targetScreen = screens.FirstOrDefault(s => s.Bounds.Contains(anchorRect.TopLeft))
var targetScreen = screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(anchorRect.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect))
?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(parentGeometry.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
?? screens.FirstOrDefault();

39
src/Avalonia.Controls/Screens.cs

@ -2,54 +2,45 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Controls
{
public class Screens
{
private readonly IScreenImpl? _iScreenImpl;
private readonly IScreenImpl _iScreenImpl;
public int ScreenCount => _iScreenImpl?.ScreenCount ?? 0;
public IReadOnlyList<Screen> All => _iScreenImpl?.AllScreens ?? Array.Empty<Screen>();
public Screen? Primary => All.FirstOrDefault(x => x.Primary);
public Screens(IScreenImpl? iScreenImpl)
public Screens(IScreenImpl iScreenImpl)
{
_iScreenImpl = iScreenImpl;
}
public Screen? ScreenFromBounds(PixelRect bounds){
Screen? currMaxScreen = null;
double maxAreaSize = 0;
foreach (Screen screen in All)
{
double left = MathUtilities.Clamp(bounds.X, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
double top = MathUtilities.Clamp(bounds.Y, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
double right = MathUtilities.Clamp(bounds.X + bounds.Width, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
double bottom = MathUtilities.Clamp(bounds.Y + bounds.Height, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
double area = (right - left) * (bottom - top);
if (area > maxAreaSize)
{
maxAreaSize = area;
currMaxScreen = screen;
}
}
return currMaxScreen;
public Screen? ScreenFromBounds(PixelRect bounds)
{
return _iScreenImpl.ScreenFromRect(bounds);
}
public Screen? ScreenFromPoint(PixelPoint point)
public Screen? ScreenFromWindow(IWindowBaseImpl window)
{
return All.FirstOrDefault(x => x.Bounds.Contains(point));
return _iScreenImpl.ScreenFromWindow(window);
}
public Screen? ScreenFromPoint(PixelPoint point)
{
return _iScreenImpl.ScreenFromPoint(point);
}
public Screen? ScreenFromVisual(IVisual visual)
{
var tl = visual.PointToScreen(visual.Bounds.TopLeft);
var br = visual.PointToScreen(visual.Bounds.BottomRight);
return ScreenFromBounds(new PixelRect(tl, br));
}
}

2
src/Avalonia.Controls/TextBlock.cs

@ -134,7 +134,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="TextTrimming"/> property.
/// </summary>
public static readonly StyledProperty<TextTrimming> TextTrimmingProperty =
AvaloniaProperty.Register<TextBlock, TextTrimming>(nameof(TextTrimming));
AvaloniaProperty.Register<TextBlock, TextTrimming>(nameof(TextTrimming), defaultValue: TextTrimming.None);
/// <summary>
/// Defines the <see cref="TextDecorations"/> property.

14
src/Avalonia.Controls/Window.cs

@ -879,7 +879,19 @@ namespace Avalonia.Controls
if (startupLocation == WindowStartupLocation.CenterScreen)
{
var screen = Screens.ScreenFromPoint(owner?.Position ?? Position);
Screen? screen = null;
if (owner is not null)
{
screen = Screens.ScreenFromWindow(owner);
screen ??= Screens.ScreenFromPoint(owner.Position);
}
if (screen is null)
{
screen = Screens.ScreenFromPoint(Position);
}
if (screen != null)
{

2
src/Avalonia.Controls/WindowBase.cs

@ -60,7 +60,7 @@ namespace Avalonia.Controls
public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver? dependencyResolver) : base(impl, dependencyResolver)
{
Screens = new Screens(PlatformImpl?.Screen);
Screens = new Screens(impl.Screen);
impl.Activated = HandleActivated;
impl.Deactivated = HandleDeactivated;
impl.PositionChanged = HandlePositionChanged;

5
src/Avalonia.Controls/WindowTransparencyLevel.cs

@ -22,6 +22,11 @@
/// </summary>
AcrylicBlur,
/// <summary>
/// Force acrylic on some incompatible versions of Windows 10.
/// </summary>
ForceAcrylicBlur,
/// <summary>
/// The window background is based on desktop wallpaper tint with a blur. This will only work on Windows 11
/// </summary>

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

@ -237,5 +237,20 @@ namespace Avalonia.DesignerSupport.Remote
public IReadOnlyList<Screen> AllScreens { get; } =
new Screen[] { new Screen(1, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
public Screen ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
}
}

15
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -200,5 +200,20 @@ namespace Avalonia.Headless
new Screen(1, new PixelRect(0, 0, 1920, 1280),
new PixelRect(0, 0, 1920, 1280), true),
};
public Screen ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
}
}

2
src/Avalonia.Input/MouseDevice.cs

@ -147,6 +147,8 @@ namespace Avalonia.Input
if(mouse._disposed)
return;
if (e.Type == RawPointerEventType.NonClientLeftButtonDown) return;
_position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e);
var keyModifiers = KeyModifiersUtils.ConvertToKey(e.InputModifiers);

15
src/Avalonia.Native/ScreenImpl.cs

@ -48,5 +48,20 @@ namespace Avalonia.Native
_native?.Dispose();
_native = null;
}
public Screen ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
}
}

43
src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml

@ -101,23 +101,17 @@
IsVisible="False"
HorizontalAlignment="Right" />
<Viewbox UseLayoutRounding="False"
MinHeight="{DynamicResource ComboBoxMinHeight}"
Grid.Row="1"
Grid.Column="1"
IsHitTestVisible="False"
Margin="0,0,10,0"
Height="12"
Width="12"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<Panel>
<Panel Height="12"
Width="12" />
<Path x:Name="DropDownGlyph"
<PathIcon x:Name="DropDownGlyph"
Grid.Row="1"
Grid.Column="1"
UseLayoutRounding="False"
IsHitTestVisible="False"
Height="12"
Width="12"
Margin="0,0,10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center" />
</Panel>
</Viewbox>
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="False"
IsOpen="{TemplateBinding IsDropDownOpen, Mode=TwoWay}"
@ -159,10 +153,9 @@
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="ComboBox /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForeground}" />
<Style Selector="ComboBox /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ComboBoxDropDownGlyphForeground}" />
<Setter Property="Data" Value="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" />
<Setter Property="Stretch" Value="Uniform" />
</Style>
<!-- PointerOver State -->
@ -195,8 +188,8 @@
<Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
</Style>
<Style Selector="ComboBox:disabled /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundDisabled}" />
<Style Selector="ComboBox:disabled /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ComboBoxDropDownGlyphForegroundDisabled}" />
</Style>
<!-- Focused State -->
@ -213,8 +206,8 @@
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxForegroundFocused}" />
</Style>
<Style Selector="ComboBox:focus-visible /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocused}" />
<Style Selector="ComboBox:focus-visible /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocused}" />
</Style>
<!-- Focus Pressed State -->
@ -226,8 +219,8 @@
<Setter Property="TextBlock.Foreground" Value="{DynamicResource ComboBoxPlaceHolderForegroundFocusedPressed}" />
</Style>
<Style Selector="ComboBox:focused:pressed /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocusedPressed}" />
<Style Selector="ComboBox:focused:pressed /template/ PathIcon#DropDownGlyph">
<Setter Property="Foreground" Value="{DynamicResource ComboBoxDropDownGlyphForegroundFocusedPressed}" />
</Style>
<!-- Error State -->

8
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -1,4 +1,6 @@
<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"
x:CompileBindings="True">
<Design.PreviewWith>
<Border Padding="20">
<TextBox Text="Sample"
@ -121,7 +123,7 @@
</Style>
<!-- PointerOver State-->
<Style Selector="TextBox:disabled">
<Style Selector="TextBox:pointerover">
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundPointerOver}" />
</Style>
@ -168,7 +170,7 @@
<Setter Property="InnerRightContent">
<Template>
<ToggleButton Classes="passwordBoxRevealButton"
IsChecked="{CompiledBinding $parent[TextBox].RevealPassword, Mode=TwoWay}" />
IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}" />
</Template>
</Setter>
</Style>

17
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -52,6 +52,12 @@ MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult..ctor()'
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsInside.set(System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsTrailing.set(System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.TextPosition.set(System.Int32)' does not exist in the implementation but it does exist in the contract.
CannotMakeTypeAbstract : Type 'Avalonia.Media.TextTrimming' is abstract in the implementation but is not abstract in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.TextTrimming' is a 'class' in the implementation but is a 'struct' in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming.CharacterEllipsis' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming.None' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming Avalonia.Media.TextTrimming.WordEllipsis' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Int32 System.Int32 Avalonia.Media.TextTrimming.value__' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(System.String, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableConicGradientBrush..ctor(System.Collections.Generic.IReadOnlyList<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable<Avalonia.RelativePoint>, System.Double)' does not exist in the implementation but it does exist in the contract.
@ -79,6 +85,9 @@ MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.ShapedTextC
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.ShapedTextCharacters.SplitTextCharactersResult Avalonia.Media.TextFormatting.ShapedTextCharacters.Split(System.Int32)' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Media.TextFormatting.ShapedTextCharacters.SplitTextCharactersResult' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Boolean Avalonia.Media.TextFormatting.TextCharacters.TryGetRunProperties(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, Avalonia.Media.Typeface, System.Int32)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public System.Collections.Generic.IReadOnlyList<Avalonia.Media.TextFormatting.TextRun> Avalonia.Media.TextFormatting.TextCollapsingProperties.Collapse(Avalonia.Media.TextFormatting.TextLine)' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextCollapsingProperties.Style.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Media.TextFormatting.TextCollapsingStyle' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextEndOfLine..ctor()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout..ctor(System.String, Avalonia.Media.Typeface, System.Double, Avalonia.Media.IBrush, Avalonia.Media.TextAlignment, Avalonia.Media.TextWrapping, Avalonia.Media.TextTrimming, Avalonia.Media.TextDecorationCollection, System.Double, System.Double, System.Double, System.Int32, System.Collections.Generic.IReadOnlyList<Avalonia.Utilities.ValueSpan<Avalonia.Media.TextFormatting.TextRunProperties>>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
@ -124,6 +133,12 @@ CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextForma
CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalonia.Media.TextFormatting.TextRunProperties.BaselineAlignment' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalonia.Media.TextFormatting.TextRunProperties.BaselineAlignment.get()' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Media.TextFormatting.TextShaper.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
CannotSealType : Type 'Avalonia.Media.TextFormatting.TextTrailingCharacterEllipsis' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextTrailingCharacterEllipsis..ctor(System.Double, Avalonia.Media.TextFormatting.TextRunProperties)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextTrailingCharacterEllipsis.Style.get()' does not exist in the implementation but it does exist in the contract.
CannotSealType : Type 'Avalonia.Media.TextFormatting.TextTrailingWordEllipsis' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextTrailingWordEllipsis..ctor(System.Double, Avalonia.Media.TextFormatting.TextRunProperties)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextCollapsingStyle Avalonia.Media.TextFormatting.TextTrailingWordEllipsis.Style.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Media.TextFormatting.Unicode.BiDiClass' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.Unicode.BiDiClass Avalonia.Media.TextFormatting.Unicode.Codepoint.BiDiClass.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Platform.ExportRenderingSubsystemAttribute' does not exist in the implementation but it does exist in the contract.
@ -161,4 +176,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphR
MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable<System.Int32>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice<T>..ctor(System.ReadOnlyMemory<T>, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract.
Total Issues: 162
Total Issues: 177

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

@ -854,19 +854,9 @@ namespace Avalonia.Media
var lastRunProps = (GenericTextRunProperties)thatFormatRider.CurrentElement!;
TextCollapsingProperties trailingEllipsis;
TextCollapsingProperties collapsingProperties = _that._trimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(maxLineLength, lastRunProps));
if (_that._trimming == TextTrimming.CharacterEllipsis)
{
trailingEllipsis = new TextTrailingCharacterEllipsis(maxLineLength, lastRunProps);
}
else
{
Debug.Assert(_that._trimming == TextTrimming.WordEllipsis);
trailingEllipsis = new TextTrailingWordEllipsis(maxLineLength, lastRunProps);
}
var collapsedLine = line.Collapse(trailingEllipsis);
var collapsedLine = line.Collapse(collapsingProperties);
line = collapsedLine;
}
@ -1121,11 +1111,6 @@ namespace Avalonia.Media
{
set
{
if ((int)value < 0 || (int)value > (int)TextTrimming.WordEllipsis)
{
throw new InvalidEnumArgumentException(nameof(value), (int)value, typeof(TextTrimming));
}
_trimming = value;
_defaultParaProps.SetTextWrapping(_trimming == TextTrimming.None ?

12
src/Avalonia.Visuals/Media/PixelRect.cs

@ -168,6 +168,18 @@ namespace Avalonia
{
return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom;
}
/// <summary>
/// Determines whether a point is in the bounds of the rectangle, exclusive of the
/// rectangle's bottom/right edge.
/// </summary>
/// <param name="p">The point.</param>
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>
public bool ContainsExclusive(PixelPoint p)
{
return p.X >= X && p.X < X + Width &&
p.Y >= Y && p.Y < Y + Height;
}
/// <summary>
/// Determines whether the rectangle fully contains another rectangle.

16
src/Avalonia.Visuals/Media/TextCollapsingCreateInfo.cs

@ -0,0 +1,16 @@
using Avalonia.Media.TextFormatting;
namespace Avalonia.Media
{
public readonly struct TextCollapsingCreateInfo
{
public readonly double Width;
public readonly TextRunProperties TextRunProperties;
public TextCollapsingCreateInfo(double width, TextRunProperties textRunProperties)
{
Width = width;
TextRunProperties = textRunProperties;
}
}
}

23
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -132,6 +132,29 @@ namespace Avalonia.Media.TextFormatting
return length > 0;
}
internal bool TryMeasureCharactersBackwards(double availableWidth, out int length, out double width)
{
length = 0;
width = 0;
for (var i = ShapedBuffer.Length - 1; i >= 0; i--)
{
var advance = ShapedBuffer.GlyphAdvances[i];
if (width + advance > availableWidth)
{
break;
}
Codepoint.ReadAt(GlyphRun.Characters, length, out var count);
length += count;
width += advance;
}
return length > 0;
}
internal SplitResult<ShapedTextCharacters> Split(int length)
{
if (IsReversed)

15
src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingProperties.cs

@ -1,23 +1,26 @@
namespace Avalonia.Media.TextFormatting
using System.Collections.Generic;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Properties of text collapsing
/// Properties of text collapsing.
/// </summary>
public abstract class TextCollapsingProperties
{
/// <summary>
/// Gets the width in which the collapsible range is constrained to
/// Gets the width in which the collapsible range is constrained to.
/// </summary>
public abstract double Width { get; }
/// <summary>
/// Gets the text run that is used as collapsing symbol
/// Gets the text run that is used as collapsing symbol.
/// </summary>
public abstract TextRun Symbol { get; }
/// <summary>
/// Gets the style of collapsing
/// Collapses given text line.
/// </summary>
public abstract TextCollapsingStyle Style { get; }
/// <param name="textLine">Text line to collapse.</param>
public abstract IReadOnlyList<TextRun>? Collapse(TextLine textLine);
}
}

18
src/Avalonia.Visuals/Media/TextFormatting/TextCollapsingStyle.cs

@ -1,18 +0,0 @@
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Text collapsing style
/// </summary>
public enum TextCollapsingStyle
{
/// <summary>
/// Collapse trailing characters
/// </summary>
TrailingCharacter,
/// <summary>
/// Collapse trailing words
/// </summary>
TrailingWord,
}
}

95
src/Avalonia.Visuals/Media/TextFormatting/TextEllipsisHelper.cs

@ -0,0 +1,95 @@
using System.Collections.Generic;
using Avalonia.Media.TextFormatting.Unicode;
namespace Avalonia.Media.TextFormatting
{
internal class TextEllipsisHelper
{
public static List<ShapedTextCharacters>? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis)
{
var shapedTextRuns = textLine.TextRuns as List<ShapedTextCharacters>;
if (shapedTextRuns is null)
{
return null;
}
var runIndex = 0;
var currentWidth = 0.0;
var collapsedLength = 0;
var textRange = textLine.TextRange;
var shapedSymbol = TextFormatterImpl.CreateSymbol(properties.Symbol, FlowDirection.LeftToRight);
if (properties.Width < shapedSymbol.GlyphRun.Size.Width)
{
return new List<ShapedTextCharacters>(0);
}
var availableWidth = properties.Width - shapedSymbol.Size.Width;
while (runIndex < shapedTextRuns.Count)
{
var currentRun = shapedTextRuns[runIndex];
currentWidth += currentRun.Size.Width;
if (currentWidth > availableWidth)
{
if (currentRun.TryMeasureCharacters(availableWidth, out var measuredLength))
{
if (isWordEllipsis && measuredLength < textRange.End)
{
var currentBreakPosition = 0;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionMeasure;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition >= measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
measuredLength = currentBreakPosition;
}
}
collapsedLength += measuredLength;
var shapedTextCharacters = new List<ShapedTextCharacters>(shapedTextRuns.Count);
if (collapsedLength > 0)
{
var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, collapsedLength);
shapedTextCharacters.AddRange(splitResult.First);
TextLineImpl.SortRuns(shapedTextCharacters);
}
shapedTextCharacters.Add(shapedSymbol);
return shapedTextCharacters;
}
availableWidth -= currentRun.Size.Width;
collapsedLength += currentRun.GlyphRun.Characters.Length;
runIndex++;
}
return null;
}
}
}

13
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@ -41,7 +41,7 @@ namespace Avalonia.Media.TextFormatting
IBrush? foreground,
TextAlignment textAlignment = TextAlignment.Left,
TextWrapping textWrapping = TextWrapping.NoWrap,
TextTrimming textTrimming = TextTrimming.None,
TextTrimming? textTrimming = null,
TextDecorationCollection? textDecorations = null,
FlowDirection flowDirection = FlowDirection.LeftToRight,
double maxWidth = double.PositiveInfinity,
@ -58,7 +58,7 @@ namespace Avalonia.Media.TextFormatting
CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping,
textDecorations, flowDirection, lineHeight);
_textTrimming = textTrimming;
_textTrimming = textTrimming ?? TextTrimming.None;
_textStyleOverrides = textStyleOverrides;
@ -641,14 +641,7 @@ namespace Avalonia.Media.TextFormatting
/// <returns>The <see cref="TextCollapsingProperties"/>.</returns>
private TextCollapsingProperties GetCollapsingProperties(double width)
{
return _textTrimming switch
{
TextTrimming.CharacterEllipsis => new TextTrailingCharacterEllipsis(width,
_paragraphProperties.DefaultTextRunProperties),
TextTrimming.WordEllipsis => new TextTrailingWordEllipsis(width,
_paragraphProperties.DefaultTextRunProperties),
_ => throw new ArgumentOutOfRangeException(),
};
return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties));
}
}
}

146
src/Avalonia.Visuals/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// Ellipsis based on a fixed length leading prefix and suffix growing from the end at character granularity.
/// </summary>
public sealed class TextLeadingPrefixCharacterEllipsis : TextCollapsingProperties
{
private readonly int _prefixLength;
/// <summary>
/// Construct a text trailing word ellipsis collapsing properties.
/// </summary>
/// <param name="ellipsis">Text used as collapsing symbol.</param>
/// <param name="prefixLength">Length of leading prefix.</param>
/// <param name="width">width in which collapsing is constrained to</param>
/// <param name="textRunProperties">text run properties of ellispis symbol</param>
public TextLeadingPrefixCharacterEllipsis(
ReadOnlySlice<char> ellipsis,
int prefixLength,
double width,
TextRunProperties textRunProperties)
{
if (_prefixLength < 0)
{
throw new ArgumentOutOfRangeException(nameof(prefixLength));
}
_prefixLength = prefixLength;
Width = width;
Symbol = new TextCharacters(ellipsis, textRunProperties);
}
/// <inheritdoc/>
public sealed override double Width { get; }
/// <inheritdoc/>
public sealed override TextRun Symbol { get; }
public override IReadOnlyList<TextRun>? Collapse(TextLine textLine)
{
var shapedTextRuns = textLine.TextRuns as List<ShapedTextCharacters>;
if (shapedTextRuns is null)
{
return null;
}
var runIndex = 0;
var currentWidth = 0.0;
var shapedSymbol = TextFormatterImpl.CreateSymbol(Symbol, FlowDirection.LeftToRight);
if (Width < shapedSymbol.GlyphRun.Size.Width)
{
return new List<ShapedTextCharacters>(0);
}
// Overview of ellipsis structure
// Prefix length run | Ellipsis symbol | Post split run growing from the end |
var availableWidth = Width - shapedSymbol.Size.Width;
while (runIndex < shapedTextRuns.Count)
{
var currentRun = shapedTextRuns[runIndex];
currentWidth += currentRun.Size.Width;
if (currentWidth > availableWidth)
{
currentRun.TryMeasureCharacters(availableWidth, out var measuredLength);
var shapedTextCharacters = new List<ShapedTextCharacters>(shapedTextRuns.Count);
if (measuredLength > 0)
{
List<ShapedTextCharacters>? preSplitRuns = null;
List<ShapedTextCharacters>? postSplitRuns = null;
if (_prefixLength > 0)
{
var splitResult = TextFormatterImpl.SplitShapedRuns(shapedTextRuns, Math.Min(_prefixLength, measuredLength));
shapedTextCharacters.AddRange(splitResult.First);
TextLineImpl.SortRuns(shapedTextCharacters);
preSplitRuns = splitResult.First;
postSplitRuns = splitResult.Second;
}
else
{
postSplitRuns = shapedTextRuns;
}
shapedTextCharacters.Add(shapedSymbol);
if (measuredLength > _prefixLength && postSplitRuns is not null)
{
var availableSuffixWidth = availableWidth;
if (preSplitRuns is not null)
{
foreach (var run in preSplitRuns)
{
availableSuffixWidth -= run.Size.Width;
}
}
for (int i = postSplitRuns.Count - 1; i >= 0; i--)
{
var run = postSplitRuns[i];
if (run.TryMeasureCharactersBackwards(availableSuffixWidth, out int suffixCount, out double suffixWidth))
{
availableSuffixWidth -= suffixWidth;
if (suffixCount > 0)
{
var splitSuffix = run.Split(run.TextSourceLength - suffixCount);
shapedTextCharacters.Add(splitSuffix.Second!);
}
}
}
}
}
else
{
shapedTextCharacters.Add(shapedSymbol);
}
return shapedTextCharacters;
}
availableWidth -= currentRun.Size.Width;
runIndex++;
}
return null;
}
}
}

79
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -106,85 +106,18 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0];
var runIndex = 0;
var currentWidth = 0.0;
var textRange = TextRange;
var collapsedLength = 0;
var collapsedRuns = collapsingProperties.Collapse(this);
var shapedSymbol = TextFormatterImpl.CreateSymbol(collapsingProperties.Symbol, _paragraphProperties.FlowDirection);
if (collapsingProperties.Width < shapedSymbol.GlyphRun.Size.Width)
if (collapsedRuns is List<ShapedTextCharacters> shapedRuns)
{
return new TextLineImpl(new List<ShapedTextCharacters>(0), textRange, _paragraphWidth, _paragraphProperties,
_flowDirection, TextLineBreak, true);
}
var availableWidth = collapsingProperties.Width - shapedSymbol.GlyphRun.Size.Width;
while (runIndex < _textRuns.Count)
{
var currentRun = _textRuns[runIndex];
currentWidth += currentRun.Size.Width;
var collapsedLine = new TextLineImpl(shapedRuns, TextRange, _paragraphWidth, _paragraphProperties, _flowDirection, TextLineBreak, true);
if (currentWidth > availableWidth)
if (shapedRuns.Count > 0)
{
if (currentRun.TryMeasureCharacters(availableWidth, out var measuredLength))
{
if (collapsingProperties.Style == TextCollapsingStyle.TrailingWord &&
measuredLength < textRange.End)
{
var currentBreakPosition = 0;
var lineBreaker = new LineBreakEnumerator(currentRun.Text);
while (currentBreakPosition < measuredLength && lineBreaker.MoveNext())
{
var nextBreakPosition = lineBreaker.Current.PositionMeasure;
if (nextBreakPosition == 0)
{
break;
}
if (nextBreakPosition >= measuredLength)
{
break;
}
currentBreakPosition = nextBreakPosition;
}
measuredLength = currentBreakPosition;
}
}
collapsedLength += measuredLength;
var shapedTextCharacters = new List<ShapedTextCharacters>(_textRuns.Count);
if (collapsedLength > 0)
{
var splitResult = TextFormatterImpl.SplitShapedRuns(_textRuns, collapsedLength);
shapedTextCharacters.AddRange(splitResult.First);
SortRuns(shapedTextCharacters);
}
shapedTextCharacters.Add(shapedSymbol);
var textLine = new TextLineImpl(shapedTextCharacters, textRange, _paragraphWidth, _paragraphProperties,
_flowDirection, TextLineBreak, true);
return textLine.FinalizeLine();
collapsedLine.FinalizeLine();
}
availableWidth -= currentRun.Size.Width;
collapsedLength += currentRun.GlyphRun.Characters.Length;
runIndex++;
return collapsedLine;
}
return this;

26
src/Avalonia.Visuals/Media/TextFormatting/TextTrailingCharacterEllipsis.cs

@ -1,24 +1,24 @@
using Avalonia.Utilities;
using System.Collections.Generic;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// a collapsing properties to collapse whole line toward the end
/// at character granularity and with ellipsis being the collapsing symbol
/// A collapsing properties to collapse whole line toward the end
/// at character granularity.
/// </summary>
public class TextTrailingCharacterEllipsis : TextCollapsingProperties
public sealed class TextTrailingCharacterEllipsis : TextCollapsingProperties
{
private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
/// <summary>
/// Construct a text trailing character ellipsis collapsing properties
/// </summary>
/// <param name="width">width in which collapsing is constrained to</param>
/// <param name="textRunProperties">text run properties of ellispis symbol</param>
public TextTrailingCharacterEllipsis(double width, TextRunProperties textRunProperties)
/// <param name="ellipsis">Text used as collapsing symbol.</param>
/// <param name="width">Width in which collapsing is constrained to.</param>
/// <param name="textRunProperties">Text run properties of ellispis symbol.</param>
public TextTrailingCharacterEllipsis(ReadOnlySlice<char> ellipsis, double width, TextRunProperties textRunProperties)
{
Width = width;
Symbol = new TextCharacters(s_ellipsis, textRunProperties);
Symbol = new TextCharacters(ellipsis, textRunProperties);
}
/// <inheritdoc/>
@ -27,7 +27,9 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/>
public sealed override TextRun Symbol { get; }
/// <inheritdoc/>
public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingCharacter;
public override IReadOnlyList<TextRun>? Collapse(TextLine textLine)
{
return TextEllipsisHelper.Collapse(textLine, this, false);
}
}
}

26
src/Avalonia.Visuals/Media/TextFormatting/TextTrailingWordEllipsis.cs

@ -1,37 +1,39 @@
using Avalonia.Utilities;
using System.Collections.Generic;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
{
/// <summary>
/// a collapsing properties to collapse whole line toward the end
/// at word granularity and with ellipsis being the collapsing symbol
/// at word granularity.
/// </summary>
public class TextTrailingWordEllipsis : TextCollapsingProperties
public sealed class TextTrailingWordEllipsis : TextCollapsingProperties
{
private static readonly ReadOnlySlice<char> s_ellipsis = new ReadOnlySlice<char>(new[] { '\u2026' });
/// <summary>
/// Construct a text trailing word ellipsis collapsing properties
/// Construct a text trailing word ellipsis collapsing properties.
/// </summary>
/// <param name="width">width in which collapsing is constrained to</param>
/// <param name="textRunProperties">text run properties of ellispis symbol</param>
/// <param name="ellipsis">Text used as collapsing symbol.</param>
/// <param name="width">width in which collapsing is constrained to.</param>
/// <param name="textRunProperties">text run properties of ellispis symbol.</param>
public TextTrailingWordEllipsis(
ReadOnlySlice<char> ellipsis,
double width,
TextRunProperties textRunProperties
)
{
Width = width;
Symbol = new TextCharacters(s_ellipsis, textRunProperties);
Symbol = new TextCharacters(ellipsis, textRunProperties);
}
/// <inheritdoc/>
public sealed override double Width { get; }
/// <inheritdoc/>
public sealed override TextRun Symbol { get; }
/// <inheritdoc/>
public sealed override TextCollapsingStyle Style { get; } = TextCollapsingStyle.TrailingWord;
public override IReadOnlyList<TextRun>? Collapse(TextLine textLine)
{
return TextEllipsisHelper.Collapse(textLine, this, true);
}
}
}

31
src/Avalonia.Visuals/Media/TextLeadingPrefixTrimming.cs

@ -0,0 +1,31 @@
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Media
{
public sealed class TextLeadingPrefixTrimming : TextTrimming
{
private readonly ReadOnlySlice<char> _ellipsis;
private readonly int _prefixLength;
public TextLeadingPrefixTrimming(char ellipsis, int prefixLength) : this(new[] { ellipsis }, prefixLength)
{
}
public TextLeadingPrefixTrimming(char[] ellipsis, int prefixLength)
{
_prefixLength = prefixLength;
_ellipsis = new ReadOnlySlice<char>(ellipsis);
}
public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo)
{
return new TextLeadingPrefixCharacterEllipsis(_ellipsis, _prefixLength, createInfo.Width, createInfo.TextRunProperties);
}
public override string ToString()
{
return nameof(PrefixCharacterEllipsis);
}
}
}

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

@ -0,0 +1,18 @@
using System;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Media
{
internal sealed class TextNoneTrimming : TextTrimming
{
public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo)
{
throw new NotSupportedException();
}
public override string ToString()
{
return nameof(None);
}
}
}

36
src/Avalonia.Visuals/Media/TextTrailingTrimming.cs

@ -0,0 +1,36 @@
using Avalonia.Media.TextFormatting;
using Avalonia.Utilities;
namespace Avalonia.Media
{
public sealed class TextTrailingTrimming : TextTrimming
{
private readonly ReadOnlySlice<char> _ellipsis;
private readonly bool _isWordBased;
public TextTrailingTrimming(char ellipsis, bool isWordBased) : this(new[] {ellipsis}, isWordBased)
{
}
public TextTrailingTrimming(char[] ellipsis, bool isWordBased)
{
_isWordBased = isWordBased;
_ellipsis = new ReadOnlySlice<char>(ellipsis);
}
public override TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo)
{
if (_isWordBased)
{
return new TextTrailingWordEllipsis(_ellipsis, createInfo.Width, createInfo.TextRunProperties);
}
return new TextTrailingCharacterEllipsis(_ellipsis, createInfo.Width, createInfo.TextRunProperties);
}
public override string ToString()
{
return _isWordBased ? nameof(WordEllipsis) : nameof(CharacterEllipsis);
}
}
}

63
src/Avalonia.Visuals/Media/TextTrimming.cs

@ -1,23 +1,76 @@
namespace Avalonia.Media
using System;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Media
{
/// <summary>
/// Describes how text is trimmed when it overflows.
/// </summary>
public enum TextTrimming
public abstract class TextTrimming
{
public static char s_defaultEllipsisChar = '\u2026';
/// <summary>
/// Text is not trimmed.
/// </summary>
None,
public static TextTrimming None { get; } = new TextNoneTrimming();
/// <summary>
/// Text is trimmed at a character boundary. An ellipsis (...) is drawn in place of remaining text.
/// </summary>
CharacterEllipsis,
public static TextTrimming CharacterEllipsis { get; } = new TextTrailingTrimming(s_defaultEllipsisChar, false);
/// <summary>
/// Text is trimmed at a word boundary. An ellipsis (...) is drawn in place of remaining text.
/// </summary>
WordEllipsis
public static TextTrimming WordEllipsis { get; } = new TextTrailingTrimming(s_defaultEllipsisChar, true);
/// <summary>
/// Text is trimmed after a given prefix length. An ellipsis (...) is drawn in between prefix and suffix and represents remaining text.
/// </summary>
public static TextTrimming PrefixCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 8);
/// <summary>
/// Text is trimmed at a character boundary starting from the beginning. An ellipsis (...) is drawn in place of remaining text.
/// </summary>
public static TextTrimming LeadingCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 0);
/// <summary>
/// Creates properties that will be used for collapsing lines of text.
/// </summary>
/// <param name="createInfo">Contextual info about text that will be collapsed.</param>
public abstract TextCollapsingProperties CreateCollapsingProperties(TextCollapsingCreateInfo createInfo);
/// <summary>
/// Parses a text trimming string. Names must match static properties defined in this class.
/// </summary>
/// <param name="s">The text trimming string.</param>
/// <returns>The <see cref="TextTrimming"/>.</returns>
public static TextTrimming Parse(string s)
{
bool Matches(string name)
{
return name.Equals(s, StringComparison.OrdinalIgnoreCase);
}
if (Matches(nameof(None)))
{
return None;
}
if (Matches(nameof(CharacterEllipsis)))
{
return CharacterEllipsis;
}
else if (Matches(nameof(WordEllipsis)))
{
return WordEllipsis;
}
else if (Matches(nameof(PrefixCharacterEllipsis)))
{
return PrefixCharacterEllipsis;
}
throw new FormatException($"Invalid text trimming string: '{s}'.");
}
}
}

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

@ -11,6 +11,7 @@ namespace Avalonia.Platform
/// </summary>
public interface IPlatformRenderInterface
{
/// <summary>
/// Creates an ellipse geometry implementation.
/// </summary>
/// <param name="rect">The bounds of the ellipse.</param>

12
src/Avalonia.Visuals/Rect.cs

@ -252,6 +252,18 @@ namespace Avalonia
return p.X >= _x && p.X <= _x + _width &&
p.Y >= _y && p.Y <= _y + _height;
}
/// <summary>
/// Determines whether a point is in the bounds of the rectangle, exclusive of the
/// rectangle's bottom/right edge.
/// </summary>
/// <param name="p">The point.</param>
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>
public bool ContainsExclusive(Point p)
{
return p.X >= _x && p.X < _x + _width &&
p.Y >= _y && p.Y < _y + _height;
}
/// <summary>
/// Determines whether the rectangle fully contains another rectangle.

15
src/Avalonia.X11/X11Screens.cs

@ -200,6 +200,21 @@ namespace Avalonia.X11
}
public Screen ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList<Screen> AllScreens =>

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

@ -221,6 +221,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
if (type.Equals(types.TextTrimming))
{
foreach (var property in types.TextTrimming.Properties)
{
if (property.PropertyType == types.TextTrimming && property.Name.Equals(text, StringComparison.OrdinalIgnoreCase))
{
result = new XamlStaticOrTargetedReturnMethodCallNode(node, property.Getter, Enumerable.Empty<IXamlAstValueNode>());
return true;
}
}
}
if (type.Equals(types.TextDecorationCollection))
{
foreach (var property in types.TextDecorations.Properties)
{
if (property.PropertyType == types.TextDecorationCollection && property.Name.Equals(text, StringComparison.OrdinalIgnoreCase))
{
result = new XamlStaticOrTargetedReturnMethodCallNode(node, property.Getter, Enumerable.Empty<IXamlAstValueNode>());
return true;
}
}
}
result = null;
return false;
}

10
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@ -117,7 +117,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
return parentDataContextNode.DataContextType;
};
XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startTypeResolver, context.ParentNodes().OfType<XamlAstConstructableObjectNode>().First().Type.GetClrType());
var selfType = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().First().Type.GetClrType();
// When using self bindings with setters we need to change target type to resolved selector type.
if (context.GetAvaloniaTypes().ISetter.IsAssignableFrom(selfType))
{
selfType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().First().TargetType.GetClrType();
}
XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startTypeResolver, selfType);
}
return node;

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

@ -88,6 +88,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ImmutableSolidColorBrush { get; }
public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; }
public IXamlType TypeUtilities { get; }
public IXamlType TextDecorationCollection { get; }
public IXamlType TextDecorations { get; }
public IXamlType TextTrimming { get; }
public IXamlType ISetter { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
@ -193,6 +197,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush");
ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List<IXamlType> { UInt });
TypeUtilities = cfg.TypeSystem.GetType("Avalonia.Utilities.TypeUtilities");
TextDecorationCollection = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorationCollection");
TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations");
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter");
}
}

15
src/Web/Avalonia.Web.Blazor/WinStubs.cs

@ -55,5 +55,20 @@ namespace Avalonia.Web.Blazor
public IReadOnlyList<Screen> AllScreens { get; } =
new[] { new Screen(96, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
public Screen? ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen? ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen? ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
}
}

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

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods;
@ -70,5 +71,43 @@ namespace Avalonia.Win32
{
_allScreens = null;
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
var handle = window.Handle.Handle;
var monitor = MonitorFromWindow(handle, MONITOR.MONITOR_DEFAULTTONULL);
return FindScreenByHandle(monitor);
}
public Screen ScreenFromPoint(PixelPoint point)
{
var monitor = MonitorFromPoint(new POINT
{
X = point.X,
Y = point.Y
}, MONITOR.MONITOR_DEFAULTTONULL);
return FindScreenByHandle(monitor);
}
public Screen ScreenFromRect(PixelRect rect)
{
var monitor = MonitorFromRect(new RECT
{
left = rect.TopLeft.X,
top = rect.TopLeft.Y,
right = rect.TopRight.X,
bottom = rect.BottomRight.Y
}, MONITOR.MONITOR_DEFAULTTONULL);
return FindScreenByHandle(monitor);
}
private Screen FindScreenByHandle(IntPtr handle)
{
return AllScreens.Cast<WinScreen>().FirstOrDefault(m => m.Handle == handle);
}
}
}

6
src/Windows/Avalonia.Win32/WinScreen.cs

@ -9,9 +9,11 @@ namespace Avalonia.Win32
public WinScreen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(pixelDensity, bounds, workingArea, primary)
{
this._hMonitor = hMonitor;
_hMonitor = hMonitor;
}
public IntPtr Handle => _hMonitor;
public override int GetHashCode()
{
return (int)_hMonitor;
@ -19,7 +21,7 @@ namespace Avalonia.Win32
public override bool Equals(object obj)
{
return (obj is WinScreen screen) ? this._hMonitor == screen._hMonitor : base.Equals(obj);
return (obj is WinScreen screen) ? _hMonitor == screen._hMonitor : base.Equals(obj);
}
}
}

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

@ -396,6 +396,11 @@ namespace Avalonia.Win32
}
}
if (Win32Platform.WindowsVersion < WinUICompositorConnection.MinHostBackdropVersion && effect == BlurEffect.Mica)
{
effect = BlurEffect.Acrylic;
}
_blurHost?.SetBlur(effect);
return transparencyLevel;
@ -428,7 +433,8 @@ namespace Avalonia.Win32
break;
case WindowTransparencyLevel.AcrylicBlur:
case (WindowTransparencyLevel.AcrylicBlur + 1): // hack-force acrylic.
case WindowTransparencyLevel.ForceAcrylicBlur: // hack-force acrylic.
case WindowTransparencyLevel.Mica:
accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC;
transparencyLevel = WindowTransparencyLevel.AcrylicBlur;
break;

2
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -516,6 +516,8 @@ namespace Avalonia.Controls.UnitTests
var screens = new Mock<IScreenImpl>();
screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object });
screens.Setup(x => x.ScreenFromPoint(It.IsAny<PixelPoint>())).Returns(screen1.Object);
var windowImpl = MockWindowingPlatform.CreateWindowMock();
windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480));

31
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -1064,6 +1064,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[Fact]
public void Binds_To_Self_In_Style()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Button'>
<Setter Property='IsVisible' Value='{CompiledBinding $self.IsEnabled}' />
</Style>
</Window.Styles>
<Button Name='button' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.True(button.IsVisible);
button.IsEnabled = false;
Assert.False(button.IsVisible);
}
}
[Fact]
public void SupportsMethodBindingAsDelegate()
{

31
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs

@ -183,6 +183,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
[Fact]
public void Binding_To_Self_In_Style_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Button'>
<Setter Property='IsVisible' Value='{Binding $self.IsEnabled}' />
</Style>
</Window.Styles>
<Button Name='button' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.True(button.IsVisible);
button.IsEnabled = false;
Assert.False(button.IsVisible);
}
}
[Fact]
public void Stream_Binding_To_Observable_Works()
{

2
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs

@ -387,7 +387,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
if (textLine.Width > 300 || currentHeight + textLine.Height > 240)
{
textLine = textLine.Collapse(new TextTrailingWordEllipsis(300, defaultProperties));
textLine = textLine.Collapse(new TextTrailingWordEllipsis(new ReadOnlySlice<char>(new[] {TextTrimming.s_defaultEllipsisChar}), 300, defaultProperties));
}
currentHeight += textLine.Height;

38
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -360,12 +360,29 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[InlineData("01234 01234", 58, TextCollapsingStyle.TrailingCharacter, "01234 0\u2026")]
[InlineData("01234 01234", 58, TextCollapsingStyle.TrailingWord, "01234\u2026")]
[InlineData("01234", 9, TextCollapsingStyle.TrailingCharacter, "\u2026")]
[InlineData("01234", 2, TextCollapsingStyle.TrailingCharacter, "")]
public static IEnumerable<object[]> CollapsingData
{
get
{
yield return CreateData("01234 01234 01234", 120, TextTrimming.PrefixCharacterEllipsis, "01234 01\u20264 01234");
yield return CreateData("01234 01234", 58, TextTrimming.CharacterEllipsis, "01234 0\u2026");
yield return CreateData("01234 01234", 58, TextTrimming.WordEllipsis, "01234\u2026");
yield return CreateData("01234", 9, TextTrimming.CharacterEllipsis, "\u2026");
yield return CreateData("01234", 2, TextTrimming.CharacterEllipsis, "");
object[] CreateData(string text, double width, TextTrimming mode, string expected)
{
return new object[]
{
text, width, mode, expected
};
}
}
}
[MemberData(nameof(CollapsingData))]
[Theory]
public void Should_Collapse_Line(string text, double width, TextCollapsingStyle style, string expected)
public void Should_Collapse_Line(string text, double width, TextTrimming trimming, string expected)
{
using (Start())
{
@ -381,16 +398,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.False(textLine.HasCollapsed);
TextCollapsingProperties collapsingProperties;
if (style == TextCollapsingStyle.TrailingCharacter)
{
collapsingProperties = new TextTrailingCharacterEllipsis(width, defaultProperties);
}
else
{
collapsingProperties = new TextTrailingWordEllipsis(width, defaultProperties);
}
TextCollapsingProperties collapsingProperties = trimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, defaultProperties));
var collapsedLine = textLine.Collapse(collapsingProperties);

Loading…
Cancel
Save