Browse Source

Merge branch 'master' into fixes/LineBreakCharacterTests

pull/3644/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
e383bd46b2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .ncrunch/BindingDemo.v3.ncrunchproject
  2. 5
      .ncrunch/RenderDemo.v3.ncrunchproject
  3. 5
      .ncrunch/VirtualizationDemo.v3.ncrunchproject
  4. 8
      azure-pipelines.yml
  5. 8
      native/Avalonia.Native/inc/avalonia-native.h
  6. 81
      native/Avalonia.Native/src/OSX/window.mm
  7. 11
      samples/ControlCatalog/MainView.xaml
  8. 14
      samples/ControlCatalog/MainView.xaml.cs
  9. 19
      samples/ControlCatalog/Pages/ImagePage.xaml
  10. 33
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  11. 6
      src/Avalonia.Base/AvaloniaObject.cs
  12. 88
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  13. 2
      src/Avalonia.Controls/Calendar/DatePicker.cs
  14. 3
      src/Avalonia.Controls/ComboBox.cs
  15. 2
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  16. 2
      src/Avalonia.Controls/Primitives/Popup.cs
  17. 60
      src/Avalonia.Controls/TextBlock.cs
  18. 41
      src/Avalonia.Controls/TextBox.cs
  19. 96
      src/Avalonia.Controls/Window.cs
  20. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  21. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  22. 2
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  23. 4
      src/Avalonia.Interactivity/IInteractive.cs
  24. 32
      src/Avalonia.Interactivity/Interactive.cs
  25. 25
      src/Avalonia.Interactivity/InteractiveExtensions.cs
  26. 4
      src/Avalonia.Native/WindowImpl.cs
  27. 9
      src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
  28. 96
      src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs
  29. 27
      src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs
  30. 16
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  31. 1
      src/Avalonia.Visuals/Properties/AssemblyInfo.cs
  32. 10
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  33. 16
      src/Avalonia.X11/X11Window.cs
  34. 10
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  35. 20
      src/Windows/Avalonia.Win32/WindowImpl.cs
  36. 2
      src/iOS/Avalonia.iOS/EmbeddableImpl.cs
  37. 11
      tests/Avalonia.Benchmarks/NullGlyphRun.cs
  38. 4
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  39. 43
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

5
.ncrunch/BindingDemo.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/RenderDemo.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/VirtualizationDemo.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

8
azure-pipelines.yml

@ -35,16 +35,16 @@ jobs:
vmImage: 'macOS-10.14'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.x'
displayName: 'Use .NET Core SDK 3.1.101'
inputs:
packageType: sdk
version: 3.1.x
version: 3.1.101
- task: UseDotNet@2
displayName: 'Use .NET Core Runtime 3.1.x'
displayName: 'Use .NET Core Runtime 3.1.1'
inputs:
packageType: runtime
version: 3.1.x
version: 3.1.1
- task: CmdLine@2
displayName: 'Install Mono 5.18'

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

@ -25,6 +25,12 @@ struct IAvnGlSurfaceRenderingSession;
struct IAvnAppMenu;
struct IAvnAppMenuItem;
enum SystemDecorations {
SystemDecorationsNone = 0,
SystemDecorationsBorderOnly = 1,
SystemDecorationsFull = 2,
};
struct AvnSize
{
double Width, Height;
@ -236,7 +242,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
{
virtual HRESULT ShowDialog (IAvnWindow* parent) = 0;
virtual HRESULT SetCanResize(bool value) = 0;
virtual HRESULT SetHasDecorations(bool value) = 0;
virtual HRESULT SetHasDecorations(SystemDecorations value) = 0;
virtual HRESULT SetTitle (void* utf8Title) = 0;
virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
virtual HRESULT SetWindowState(AvnWindowState state) = 0;

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

@ -115,7 +115,6 @@ public:
[NSApp activateIgnoringOtherApps:YES];
[Window setTitle:_lastTitle];
[Window setTitleVisibility:NSWindowTitleVisible];
return S_OK;
}
@ -411,7 +410,7 @@ class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, pub
{
private:
bool _canResize = true;
bool _hasDecorations = true;
SystemDecorations _hasDecorations = SystemDecorationsFull;
CGRect _lastUndecoratedFrame;
AvnWindowState _lastWindowState;
@ -476,23 +475,26 @@ private:
bool IsZoomed ()
{
return _hasDecorations ? [Window isZoomed] : UndecoratedIsMaximized();
return _hasDecorations != SystemDecorationsNone ? [Window isZoomed] : UndecoratedIsMaximized();
}
void DoZoom()
{
if (_hasDecorations)
switch (_hasDecorations)
{
[Window performZoom:Window];
}
else
{
if (!UndecoratedIsMaximized())
{
_lastUndecoratedFrame = [Window frame];
}
[Window zoom:Window];
case SystemDecorationsNone:
if (!UndecoratedIsMaximized())
{
_lastUndecoratedFrame = [Window frame];
}
[Window zoom:Window];
break;
case SystemDecorationsBorderOnly:
case SystemDecorationsFull:
[Window performZoom:Window];
break;
}
}
@ -506,13 +508,35 @@ private:
}
}
virtual HRESULT SetHasDecorations(bool value) override
virtual HRESULT SetHasDecorations(SystemDecorations value) override
{
@autoreleasepool
{
_hasDecorations = value;
UpdateStyle();
switch (_hasDecorations)
{
case SystemDecorationsNone:
[Window setHasShadow:NO];
[Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES];
break;
case SystemDecorationsBorderOnly:
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES];
break;
case SystemDecorationsFull:
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
break;
}
return S_OK;
}
}
@ -523,7 +547,6 @@ private:
{
_lastTitle = [NSString stringWithUTF8String:(const char*)utf8title];
[Window setTitle:_lastTitle];
[Window setTitleVisibility:NSWindowTitleVisible];
return S_OK;
}
@ -645,10 +668,26 @@ protected:
virtual NSWindowStyleMask GetStyle() override
{
unsigned long s = NSWindowStyleMaskBorderless;
if(_hasDecorations)
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
if(_canResize)
s = s | NSWindowStyleMaskResizable;
switch (_hasDecorations)
{
case SystemDecorationsNone:
break;
case SystemDecorationsBorderOnly:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView;
break;
case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless;
if(_canResize)
{
s = s | NSWindowStyleMaskResizable;
}
break;
}
return s;
}
};

11
samples/ControlCatalog/MainView.xaml

@ -59,10 +59,17 @@
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
<TabControl.Tag>
<ComboBox x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<StackPanel Width="115" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<ComboBox x:Name="Decorations" SelectedIndex="0" Margin="0,0,0,8">
<ComboBoxItem>No Decorations</ComboBoxItem>
<ComboBoxItem>Border Only</ComboBoxItem>
<ComboBoxItem>Full Decorations</ComboBoxItem>
</ComboBox>
<ComboBox x:Name="Themes" SelectedIndex="0">
<ComboBoxItem>Light</ComboBoxItem>
<ComboBoxItem>Dark</ComboBoxItem>
</ComboBox>
</ComboBox>
</StackPanel>
</TabControl.Tag>
</TabControl>
</Grid>

14
samples/ControlCatalog/MainView.xaml.cs

@ -56,6 +56,20 @@ namespace ControlCatalog
}
};
Styles.Add(light);
var decorations = this.Find<ComboBox>("Decorations");
decorations.SelectionChanged += (sender, e) =>
{
Window window = (Window)VisualRoot;
window.SystemDecorations = (SystemDecorations)decorations.SelectedIndex;
};
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
var decorations = this.Find<ComboBox>("Decorations");
decorations.SelectedIndex = (int)((Window)VisualRoot).SystemDecorations;
}
}
}

19
samples/ControlCatalog/Pages/ImagePage.xaml

@ -7,7 +7,7 @@
<TextBlock Classes="h2">Displays an image</TextBlock>
</StackPanel>
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*" Margin="64">
<Grid ColumnDefinitions="*,*,*" RowDefinitions="Auto,*" Margin="64">
<DockPanel Grid.Column="0" Grid.Row="1" Margin="16">
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Bitmap</TextBlock>
@ -22,6 +22,23 @@
</DockPanel>
<DockPanel Grid.Column="1" Grid.Row="1" Margin="16">
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Crop</TextBlock>
<ComboBox Name="bitmapCrop" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="BitmapCropChanged">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Center</ComboBoxItem>
<ComboBoxItem>TopLeft</ComboBoxItem>
<ComboBoxItem>TopRight</ComboBoxItem>
<ComboBoxItem>BottomLeft</ComboBoxItem>
<ComboBoxItem>BottomRight</ComboBoxItem>
</ComboBox>
<Image Name="croppedImage">
<Image.Source>
<CroppedBitmap Source="/Assets/delicate-arch-896885_640.jpg" SourceRect="0 0 320 240"/>
</Image.Source>
</Image>
</DockPanel>
<DockPanel Grid.Column="2" Grid.Row="1" Margin="16">
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Drawing</TextBlock>
<ComboBox Name="drawingStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="DrawingStretchChanged">
<ComboBoxItem>None</ComboBoxItem>

33
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@ -1,6 +1,10 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
namespace ControlCatalog.Pages
{
@ -8,12 +12,14 @@ namespace ControlCatalog.Pages
{
private readonly Image _bitmapImage;
private readonly Image _drawingImage;
private readonly Image _croppedImage;
public ImagePage()
{
InitializeComponent();
_bitmapImage = this.FindControl<Image>("bitmapImage");
_drawingImage = this.FindControl<Image>("drawingImage");
_croppedImage = this.FindControl<Image>("croppedImage");
}
private void InitializeComponent()
@ -38,5 +44,32 @@ namespace ControlCatalog.Pages
_drawingImage.Stretch = (Stretch)comboxBox.SelectedIndex;
}
}
public void BitmapCropChanged(object sender, SelectionChangedEventArgs e)
{
if (_croppedImage != null)
{
var comboxBox = (ComboBox)sender;
var croppedBitmap = _croppedImage.Source as CroppedBitmap;
croppedBitmap.SourceRect = GetCropRect(comboxBox.SelectedIndex);
}
}
private PixelRect GetCropRect(int index)
{
var bitmapWidth = 640;
var bitmapHeight = 426;
var cropSize = new PixelSize(320, 240);
return index switch
{
1 => new PixelRect(new PixelPoint((bitmapWidth - cropSize.Width) / 2, (bitmapHeight - cropSize.Width) / 2), cropSize),
2 => new PixelRect(new PixelPoint(0, 0), cropSize),
3 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, 0), cropSize),
4 => new PixelRect(new PixelPoint(0, bitmapHeight - cropSize.Height), cropSize),
5 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, bitmapHeight - cropSize.Height), cropSize),
_ => PixelRect.Empty
};
}
}
}

6
src/Avalonia.Base/AvaloniaObject.cs

@ -80,8 +80,12 @@ namespace Avalonia
_inheritanceParent?.RemoveInheritanceChild(this);
_inheritanceParent = value;
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType()))
var properties = AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType());
var propertiesCount = properties.Count;
for (var i = 0; i < propertiesCount; i++)
{
var property = properties[i];
if (valuestore?.IsSet(property) == true)
{
// If local value set there can be no change.

88
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -3,9 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Data;
namespace Avalonia
{
@ -28,8 +26,6 @@ namespace Avalonia
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _directCache =
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<PropertyInitializationData>> _initializedCache =
new Dictionary<Type, List<PropertyInitializationData>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _inheritedCache =
new Dictionary<Type, List<AvaloniaProperty>>();
@ -49,7 +45,7 @@ namespace Avalonia
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetRegistered(Type type)
public IReadOnlyList<AvaloniaProperty> GetRegistered(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
@ -83,7 +79,7 @@ namespace Avalonia
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetRegisteredAttached(Type type)
public IReadOnlyList<AvaloniaProperty> GetRegisteredAttached(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
@ -114,7 +110,7 @@ namespace Avalonia
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetRegisteredDirect(Type type)
public IReadOnlyList<AvaloniaProperty> GetRegisteredDirect(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
@ -145,7 +141,7 @@ namespace Avalonia
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetRegisteredInherited(Type type)
public IReadOnlyList<AvaloniaProperty> GetRegisteredInherited(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
@ -157,16 +153,27 @@ namespace Avalonia
result = new List<AvaloniaProperty>();
var visited = new HashSet<AvaloniaProperty>();
foreach (var property in GetRegistered(type))
var registered = GetRegistered(type);
var registeredCount = registered.Count;
for (var i = 0; i < registeredCount; i++)
{
var property = registered[i];
if (property.Inherits)
{
result.Add(property);
visited.Add(property);
}
}
foreach (var property in GetRegisteredAttached(type))
var registeredAttached = GetRegisteredAttached(type);
var registeredAttachedCount = registeredAttached.Count;
for (var i = 0; i < registeredAttachedCount; i++)
{
var property = registeredAttached[i];
if (property.Inherits)
{
if (!visited.Contains(property))
@ -185,7 +192,7 @@ namespace Avalonia
/// </summary>
/// <param name="o">The object.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetRegistered(IAvaloniaObject o)
public IReadOnlyList<AvaloniaProperty> GetRegistered(IAvaloniaObject o)
{
Contract.Requires<ArgumentNullException>(o != null);
@ -229,8 +236,13 @@ namespace Avalonia
throw new InvalidOperationException("Attached properties not supported.");
}
foreach (AvaloniaProperty x in GetRegistered(type))
var registered = GetRegistered(type);
var registeredCount = registered.Count;
for (var i = 0; i < registeredCount; i++)
{
AvaloniaProperty x = registered[i];
if (x.Name == name)
{
return x;
@ -276,8 +288,13 @@ namespace Avalonia
return property;
}
foreach (var p in GetRegisteredDirect(o.GetType()))
var registeredDirect = GetRegisteredDirect(o.GetType());
var registeredDirectCount = registeredDirect.Count;
for (var i = 0; i < registeredDirectCount; i++)
{
var p = registeredDirect[i];
if (p == property)
{
return (DirectPropertyBase<T>)p;
@ -308,8 +325,23 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
return Instance.GetRegistered(type).Any(x => x == property) ||
Instance.GetRegisteredAttached(type).Any(x => x == property);
static bool ContainsProperty(IReadOnlyList<AvaloniaProperty> properties, AvaloniaProperty property)
{
var propertiesCount = properties.Count;
for (var i = 0; i < propertiesCount; i++)
{
if (properties[i] == property)
{
return true;
}
}
return false;
}
return ContainsProperty(Instance.GetRegistered(type), property) ||
ContainsProperty(Instance.GetRegisteredAttached(type), property);
}
/// <summary>
@ -374,7 +406,6 @@ namespace Avalonia
}
_registeredCache.Clear();
_initializedCache.Clear();
_inheritedCache.Clear();
}
@ -411,32 +442,7 @@ namespace Avalonia
}
_attachedCache.Clear();
_initializedCache.Clear();
_inheritedCache.Clear();
}
private readonly struct PropertyInitializationData
{
public AvaloniaProperty Property { get; }
public object Value { get; }
public bool IsDirect { get; }
public IDirectPropertyAccessor DirectAccessor { get; }
public PropertyInitializationData(AvaloniaProperty property, IDirectPropertyAccessor directAccessor)
{
Property = property;
Value = null;
IsDirect = true;
DirectAccessor = directAccessor;
}
public PropertyInitializationData(AvaloniaProperty property, IStyledPropertyAccessor styledAccessor, Type type)
{
Property = property;
Value = styledAccessor.GetDefaultValue(type);
IsDirect = false;
DirectAccessor = null;
}
}
}
}

2
src/Avalonia.Controls/Calendar/DatePicker.cs

@ -476,7 +476,7 @@ namespace Avalonia.Controls
{
_dropDownButton.Click += DropDownButton_Click;
_buttonPointerPressedSubscription =
_dropDownButton.AddHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true);
_dropDownButton.AddDisposableHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true);
}
if (_textBox != null)

3
src/Avalonia.Controls/ComboBox.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.VisualTree;
@ -265,7 +266,7 @@ namespace Avalonia.Controls
var toplevel = this.GetVisualRoot() as TopLevel;
if (toplevel != null)
{
_subscriptionsOnOpen = toplevel.AddHandler(PointerWheelChangedEvent, (s, ev) =>
_subscriptionsOnOpen = toplevel.AddDisposableHandler(PointerWheelChangedEvent, (s, ev) =>
{
//eat wheel scroll event outside dropdown popup while it's open
if (IsDropDownOpen && (ev.Source as IVisual).GetVisualRoot() == toplevel)

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

@ -36,7 +36,7 @@ namespace Avalonia.Platform
/// <summary>
/// Enables or disables system window decorations (title bar, buttons, etc)
/// </summary>
void SetSystemDecorations(bool enabled);
void SetSystemDecorations(SystemDecorations enabled);
/// <summary>
/// Sets the icon of this window.

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

@ -279,7 +279,7 @@ namespace Avalonia.Controls.Primitives
}
}
DeferCleanup(topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel));
DeferCleanup(topLevel.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel));
DeferCleanup(InputManager.Instance?.Process.Subscribe(ListenForNonClientClick));

60
src/Avalonia.Controls/TextBlock.cs

@ -20,6 +20,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<TextBlock>();
/// <summary>
/// Defines the <see cref="Padding"/> property.
/// </summary>
public static readonly StyledProperty<Thickness> PaddingProperty =
Decorator.PaddingProperty.AddOwner<TextBlock>();
// TODO: Define these attached properties elsewhere (e.g. on a Text class) and AddOwner
// them into TextBlock.
@ -29,7 +35,7 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, FontFamily>(
nameof(FontFamily),
defaultValue: FontFamily.Default,
defaultValue: FontFamily.Default,
inherits: true);
/// <summary>
@ -110,20 +116,31 @@ namespace Avalonia.Controls
static TextBlock()
{
ClipToBoundsProperty.OverrideDefaultValue<TextBlock>(true);
AffectsRender<TextBlock>(
BackgroundProperty,
ForegroundProperty,
FontWeightProperty,
FontSizeProperty,
FontStyleProperty);
BackgroundProperty, ForegroundProperty, FontSizeProperty,
FontWeightProperty, FontStyleProperty, TextWrappingProperty,
TextTrimmingProperty, TextAlignmentProperty, FontFamilyProperty,
TextDecorationsProperty, TextProperty, PaddingProperty);
AffectsMeasure<TextBlock>(
FontSizeProperty, FontWeightProperty, FontStyleProperty,
FontFamilyProperty, TextTrimmingProperty, TextProperty,
PaddingProperty);
Observable.Merge(
TextProperty.Changed,
ForegroundProperty.Changed,
TextAlignmentProperty.Changed,
TextWrappingProperty.Changed,
TextTrimmingProperty.Changed,
FontSizeProperty.Changed,
FontStyleProperty.Changed,
FontWeightProperty.Changed
).AddClassHandler<TextBlock>((x, _) => x.OnTextPropertiesChanged());
FontWeightProperty.Changed,
FontFamilyProperty.Changed,
TextDecorationsProperty.Changed,
PaddingProperty.Changed
).AddClassHandler<TextBlock>((x, _) => x.InvalidateTextLayout());
}
/// <summary>
@ -145,6 +162,15 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Gets or sets the padding to place around the <see cref="Text"/>.
/// </summary>
public Thickness Padding
{
get { return GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
/// <summary>
/// Gets or sets a brush used to paint the control's background.
/// </summary>
@ -363,7 +389,9 @@ namespace Avalonia.Controls
context.FillRectangle(background, new Rect(Bounds.Size));
}
TextLayout?.Draw(context.PlatformImpl, new Point());
var padding = Padding;
TextLayout?.Draw(context.PlatformImpl, new Point(padding.Left, padding.Top));
}
/// <summary>
@ -412,6 +440,10 @@ namespace Avalonia.Controls
return new Size();
}
var padding = Padding;
availableSize = availableSize.Deflate(padding);
if (_constraint != availableSize)
{
InvalidateTextLayout();
@ -419,19 +451,17 @@ namespace Avalonia.Controls
_constraint = availableSize;
return TextLayout?.Bounds.Size ?? Size.Empty;
var measuredSize = TextLayout?.Bounds.Size ?? Size.Empty;
return measuredSize.Inflate(padding);
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
InvalidateTextLayout();
InvalidateMeasure();
}
private void OnTextPropertiesChanged()
{
InvalidateTextLayout();
InvalidateMeasure();
}
}

41
src/Avalonia.Controls/TextBox.cs

@ -275,6 +275,22 @@ namespace Avalonia.Controls
}
}
public string SelectedText
{
get { return GetSelection(); }
set
{
if (value == null)
{
return;
}
_undoRedoHelper.Snapshot();
HandleTextInput(value);
_undoRedoHelper.Snapshot();
}
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
@ -683,12 +699,12 @@ namespace Avalonia.Controls
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
var point = e.GetPosition(_presenter);
var index = CaretIndex = _presenter.GetCaretIndex(point);
var text = Text;
if (text != null && e.MouseButton == MouseButton.Left)
if (text != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
var point = e.GetPosition(_presenter);
var index = CaretIndex = _presenter.GetCaretIndex(point);
switch (e.ClickCount)
{
case 1:
@ -714,7 +730,8 @@ namespace Avalonia.Controls
protected override void OnPointerMoved(PointerEventArgs e)
{
if (_presenter != null && e.Pointer.Captured == _presenter)
// selection should not change during pointer move if the user right clicks
if (_presenter != null && e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
var point = e.GetPosition(_presenter);
@ -727,6 +744,22 @@ namespace Avalonia.Controls
{
if (_presenter != null && e.Pointer.Captured == _presenter)
{
if (e.InitialPressMouseButton == MouseButton.Right)
{
var point = e.GetPosition(_presenter);
var caretIndex = _presenter.GetCaretIndex(point);
// see if mouse clicked inside current selection
// if it did not, we change the selection to where the user clicked
var firstSelection = Math.Min(SelectionStart, SelectionEnd);
var lastSelection = Math.Max(SelectionStart, SelectionEnd);
var didClickInSelection = SelectionStart != SelectionEnd &&
caretIndex >= firstSelection && caretIndex <= lastSelection;
if (!didClickInSelection)
{
CaretIndex = SelectionEnd = SelectionStart = caretIndex;
}
}
e.Pointer.Capture(null);
}
}

96
src/Avalonia.Controls/Window.cs

@ -2,19 +2,19 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using System.ComponentModel;
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
@ -45,6 +45,27 @@ namespace Avalonia.Controls
WidthAndHeight = 3,
}
/// <summary>
/// Determines system decorations (title bar, border, etc) for a <see cref="Window"/>
/// </summary>
public enum SystemDecorations
{
/// <summary>
/// No decorations
/// </summary>
None = 0,
/// <summary>
/// Window border without titlebar
/// </summary>
BorderOnly = 1,
/// <summary>
/// Fully decorated (default)
/// </summary>
Full = 2
}
/// <summary>
/// A top-level window.
/// </summary>
@ -59,8 +80,18 @@ namespace Avalonia.Controls
/// <summary>
/// Enables or disables system window decorations (title bar, buttons, etc)
/// </summary>
public static readonly StyledProperty<bool> HasSystemDecorationsProperty =
AvaloniaProperty.Register<Window, bool>(nameof(HasSystemDecorations), true);
[Obsolete("Use SystemDecorationsProperty instead")]
public static readonly DirectProperty<Window, bool> HasSystemDecorationsProperty =
AvaloniaProperty.RegisterDirect<Window, bool>(
nameof(HasSystemDecorations),
o => o.HasSystemDecorations,
(o, v) => o.HasSystemDecorations = v);
/// <summary>
/// Defines the <see cref="SystemDecorations"/> property.
/// </summary>
public static readonly StyledProperty<SystemDecorations> SystemDecorationsProperty =
AvaloniaProperty.Register<Window, SystemDecorations>(nameof(SystemDecorations), SystemDecorations.Full);
/// <summary>
/// Enables or disables the taskbar icon
@ -124,9 +155,6 @@ namespace Avalonia.Controls
{
BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue));
HasSystemDecorationsProperty.Changed.AddClassHandler<Window>(
(s, e) => s.PlatformImpl?.SetSystemDecorations((bool)e.NewValue));
ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue));
IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue)?.PlatformImpl));
@ -135,12 +163,11 @@ namespace Avalonia.Controls
WindowStateProperty.Changed.AddClassHandler<Window>(
(w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; });
MinWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
MinHeightProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
MaxWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
MaxHeightProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
}
/// <summary>
@ -191,11 +218,30 @@ namespace Avalonia.Controls
/// <summary>
/// Enables or disables system window decorations (title bar, buttons, etc)
/// </summary>
///
[Obsolete("Use SystemDecorations instead")]
public bool HasSystemDecorations
{
get { return GetValue(HasSystemDecorationsProperty); }
set { SetValue(HasSystemDecorationsProperty, value); }
get => SystemDecorations == SystemDecorations.Full;
set
{
var oldValue = HasSystemDecorations;
if (oldValue != value)
{
SystemDecorations = value ? SystemDecorations.Full : SystemDecorations.None;
RaisePropertyChanged(HasSystemDecorationsProperty, oldValue, value);
}
}
}
/// <summary>
/// Sets the system decorations (title bar, border, etc)
/// </summary>
///
public SystemDecorations SystemDecorations
{
get { return GetValue(SystemDecorationsProperty); }
set { SetValue(SystemDecorationsProperty, value); }
}
/// <summary>
@ -584,5 +630,27 @@ namespace Avalonia.Controls
/// <see cref="Closing"/> event needs to be raised.
/// </remarks>
protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e);
protected override void OnPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
{
if (property == SystemDecorationsProperty)
{
var typedNewValue = newValue.GetValueOrDefault<SystemDecorations>();
PlatformImpl?.SetSystemDecorations(typedNewValue);
var o = oldValue.GetValueOrDefault<SystemDecorations>() == SystemDecorations.Full;
var n = typedNewValue == SystemDecorations.Full;
if (o != n)
{
RaisePropertyChanged(HasSystemDecorationsProperty, o, n);
}
}
}
}
}

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

@ -96,7 +96,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void SetSystemDecorations(bool enabled)
public void SetSystemDecorations(SystemDecorations enabled)
{
}

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

@ -110,7 +110,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void SetSystemDecorations(bool enabled)
public void SetSystemDecorations(SystemDecorations enabled)
{
}

2
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -22,7 +22,7 @@ namespace Avalonia.Diagnostics
}
}
return root.AddHandler(
return root.AddDisposableHandler(
InputElement.KeyDownEvent,
PreviewKeyDown,
RoutingStrategies.Tunnel);

4
src/Avalonia.Interactivity/IInteractive.cs

@ -23,7 +23,7 @@ namespace Avalonia.Interactivity
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
IDisposable AddHandler(
void AddHandler(
RoutedEvent routedEvent,
Delegate handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
@ -38,7 +38,7 @@ namespace Avalonia.Interactivity
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
IDisposable AddHandler<TEventArgs>(
void AddHandler<TEventArgs>(
RoutedEvent<TEventArgs> routedEvent,
EventHandler<TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,

32
src/Avalonia.Interactivity/Interactive.cs

@ -27,8 +27,7 @@ namespace Avalonia.Interactivity
/// <param name="handler">The handler.</param>
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
public IDisposable AddHandler(
public void AddHandler(
RoutedEvent routedEvent,
Delegate handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
@ -38,7 +37,8 @@ namespace Avalonia.Interactivity
handler = handler ?? throw new ArgumentNullException(nameof(handler));
var subscription = new EventSubscription(handler, routes, handledEventsToo);
return AddEventSubscription(routedEvent, subscription);
AddEventSubscription(routedEvent, subscription);
}
/// <summary>
@ -49,8 +49,7 @@ namespace Avalonia.Interactivity
/// <param name="handler">The handler.</param>
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
public IDisposable AddHandler<TEventArgs>(
public void AddHandler<TEventArgs>(
RoutedEvent<TEventArgs> routedEvent,
EventHandler<TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
@ -69,7 +68,7 @@ namespace Avalonia.Interactivity
var subscription = new EventSubscription(handler, routes, handledEventsToo, (baseHandler, sender, args) => InvokeAdapter(baseHandler, sender, args));
return AddEventSubscription(routedEvent, subscription);
AddEventSubscription(routedEvent, subscription);
}
/// <summary>
@ -188,7 +187,7 @@ namespace Avalonia.Interactivity
return result;
}
private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription)
private void AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription)
{
_eventHandlers ??= new Dictionary<RoutedEvent, List<EventSubscription>>();
@ -199,8 +198,6 @@ namespace Avalonia.Interactivity
}
subscriptions.Add(subscription);
return new UnsubscribeDisposable(subscriptions, subscription);
}
private readonly struct EventSubscription
@ -225,22 +222,5 @@ namespace Avalonia.Interactivity
public bool HandledEventsToo { get; }
}
private sealed class UnsubscribeDisposable : IDisposable
{
private readonly List<EventSubscription> _subscriptions;
private readonly EventSubscription _subscription;
public UnsubscribeDisposable(List<EventSubscription> subscriptions, EventSubscription subscription)
{
_subscriptions = subscriptions;
_subscription = subscription;
}
public void Dispose()
{
_subscriptions.Remove(_subscription);
}
}
}
}

25
src/Avalonia.Interactivity/InteractiveExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
namespace Avalonia.Interactivity
@ -11,6 +12,28 @@ namespace Avalonia.Interactivity
/// </summary>
public static class InteractiveExtensions
{
/// <summary>
/// Adds a handler for the specified routed event and returns a disposable that can terminate the event subscription.
/// </summary>
/// <typeparam name="TEventArgs">The type of the event's args.</typeparam>
/// <param name="o">Target for adding given event handler.</param>
/// <param name="routedEvent">The routed event.</param>
/// <param name="handler">The handler.</param>
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
public static IDisposable AddDisposableHandler<TEventArgs>(this IInteractive o, RoutedEvent<TEventArgs> routedEvent,
EventHandler<TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false) where TEventArgs : RoutedEventArgs
{
o.AddHandler(routedEvent, handler, routes, handledEventsToo);
return Disposable.Create(
(instance: o, handler, routedEvent),
state => state.instance.RemoveHandler(state.routedEvent, state.handler));
}
/// <summary>
/// Gets an observable for a <see cref="RoutedEvent{TEventArgs}"/>.
/// </summary>
@ -31,7 +54,7 @@ namespace Avalonia.Interactivity
o = o ?? throw new ArgumentNullException(nameof(o));
routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
return Observable.Create<TEventArgs>(x => o.AddHandler(
return Observable.Create<TEventArgs>(x => o.AddDisposableHandler(
routedEvent,
(_, e) => x.OnNext(e),
routes,

4
src/Avalonia.Native/WindowImpl.cs

@ -68,9 +68,9 @@ namespace Avalonia.Native
_native.CanResize = value;
}
public void SetSystemDecorations(bool enabled)
public void SetSystemDecorations(Controls.SystemDecorations enabled)
{
_native.HasDecorations = enabled;
_native.HasDecorations = (Interop.SystemDecorations)enabled;
}
public void SetTitleBarColor (Avalonia.Media.Color color)

9
src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs

@ -14,6 +14,7 @@ namespace Avalonia.Styling.Activators
{
private readonly IList<string> _match;
private readonly IAvaloniaReadOnlyList<string> _classes;
private NotifyCollectionChangedEventHandler? _classesChangedHandler;
public StyleClassActivator(IAvaloniaReadOnlyList<string> classes, IList<string> match)
{
@ -21,6 +22,9 @@ namespace Avalonia.Styling.Activators
_match = match;
}
private NotifyCollectionChangedEventHandler ClassesChangedHandler =>
_classesChangedHandler ??= ClassesChanged;
public static bool AreClassesMatching(IReadOnlyList<string> classes, IList<string> toMatch)
{
int remainingMatches = toMatch.Count;
@ -51,16 +55,15 @@ namespace Avalonia.Styling.Activators
return remainingMatches == 0;
}
protected override void Initialize()
{
PublishNext(IsMatching());
_classes.CollectionChanged += ClassesChanged;
_classes.CollectionChanged += ClassesChangedHandler;
}
protected override void Deinitialize()
{
_classes.CollectionChanged -= ClassesChanged;
_classes.CollectionChanged -= ClassesChangedHandler;
}
private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e)

96
src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs

@ -0,0 +1,96 @@
using System;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media.Imaging
{
/// <summary>
/// Crops a Bitmap.
/// </summary>
public class CroppedBitmap : AvaloniaObject, IImage, IAffectsRender, IDisposable
{
/// <summary>
/// Defines the <see cref="Source"/> property.
/// </summary>
public static readonly StyledProperty<IImage> SourceProperty =
AvaloniaProperty.Register<CroppedBitmap, IImage>(nameof(Source));
/// <summary>
/// Defines the <see cref="SourceRect"/> property.
/// </summary>
public static readonly StyledProperty<PixelRect> SourceRectProperty =
AvaloniaProperty.Register<CroppedBitmap, PixelRect>(nameof(SourceRect));
public event EventHandler Invalidated;
static CroppedBitmap()
{
SourceRectProperty.Changed.AddClassHandler<CroppedBitmap>((x, e) => x.SourceRectChanged(e));
SourceProperty.Changed.AddClassHandler<CroppedBitmap>((x, e) => x.SourceChanged(e));
}
/// <summary>
/// Gets or sets the source for the bitmap.
/// </summary>
public IImage Source
{
get => GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
}
/// <summary>
/// Gets or sets the rectangular area that the bitmap is cropped to.
/// </summary>
public PixelRect SourceRect
{
get => GetValue(SourceRectProperty);
set => SetValue(SourceRectProperty, value);
}
public CroppedBitmap()
{
Source = null;
SourceRect = default;
}
public CroppedBitmap(IImage source, PixelRect sourceRect)
{
Source = source;
SourceRect = sourceRect;
}
private void SourceChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.NewValue == null)
return;
if (!(e.NewValue is IBitmap))
throw new ArgumentException("Only IBitmap supported as source");
Invalidated?.Invoke(this, e);
}
private void SourceRectChanged(AvaloniaPropertyChangedEventArgs e) => Invalidated?.Invoke(this, e);
public virtual void Dispose()
{
(Source as IBitmap)?.Dispose();
}
public Size Size {
get
{
if (Source == null)
return Size.Empty;
if (SourceRect.IsEmpty)
return Source.Size;
return SourceRect.Size.ToSizeWithDpi((Source as IBitmap).Dpi);
}
}
public void Draw(DrawingContext context, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
if (Source == null)
return;
var topLeft = SourceRect.TopLeft.ToPointWithDpi((Source as IBitmap).Dpi);
Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect, bitmapInterpolationMode);
}
}
}

27
src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs

@ -201,18 +201,17 @@ namespace Avalonia.Media.TextFormatting
var availableWidth = paragraphWidth;
var currentWidth = 0.0;
var runIndex = 0;
var length = 0;
while (runIndex < textRuns.Count)
{
var currentRun = textRuns[runIndex];
currentWidth += currentRun.GlyphRun.Bounds.Width;
if (currentWidth > availableWidth)
if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth)
{
var measuredLength = MeasureText(currentRun, paragraphWidth);
var measuredLength = MeasureText(currentRun, paragraphWidth - currentWidth);
if (measuredLength < text.End)
if (measuredLength < currentRun.Text.Length)
{
var currentBreakPosition = -1;
@ -241,15 +240,19 @@ namespace Avalonia.Media.TextFormatting
}
}
var splitResult = SplitTextRuns(textRuns, measuredLength);
length += measuredLength;
var splitResult = SplitTextRuns(textRuns, length);
var textLineMetrics =
TextLineMetrics.Create(splitResult.First, paragraphWidth, paragraphProperties.TextAlignment);
return new SimpleTextLine(text.Take(measuredLength), splitResult.First, textLineMetrics);
return new SimpleTextLine(text.Take(length), splitResult.First, textLineMetrics);
}
availableWidth -= currentRun.GlyphRun.Bounds.Width;
currentWidth += currentRun.GlyphRun.Bounds.Width;
length += currentRun.GlyphRun.Characters.Length;
runIndex++;
}
@ -281,12 +284,18 @@ namespace Avalonia.Media.TextFormatting
if (measuredWidth + advance > availableWidth)
{
index--;
break;
}
measuredWidth += advance;
}
if(index < 0)
{
return 0;
}
var cluster = textRun.GlyphRun.GlyphClusters[index];
var characterHit = textRun.GlyphRun.FindNearestCharacterHit(cluster, out _);
@ -355,7 +364,7 @@ namespace Avalonia.Media.TextFormatting
continue;
}
var firstCount = currentRun.GlyphRun.Characters.Length > 1 ? i + 1 : i;
var firstCount = currentRun.GlyphRun.Characters.Length >= 1 ? i + 1 : i;
var first = new ShapedTextRun[firstCount];

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

@ -233,6 +233,12 @@ namespace Avalonia.Media.TextFormatting
var textLine = TextFormatter.Current.FormatLine(textSource, 0, MaxWidth, _paragraphProperties);
if (!double.IsPositiveInfinity(MaxHeight) && bottom + textLine.LineMetrics.Size.Height > MaxHeight)
{
currentPosition = _text.Length;
break;
}
UpdateBounds(textLine, ref left, ref right, ref bottom);
textLines.Add(textLine);
@ -253,17 +259,17 @@ namespace Avalonia.Media.TextFormatting
{
var emptyTextLine = CreateEmptyTextLine(currentPosition);
if (!double.IsPositiveInfinity(MaxHeight) && bottom + emptyTextLine.LineMetrics.Size.Height > MaxHeight)
{
break;
}
UpdateBounds(emptyTextLine, ref left, ref right, ref bottom);
textLines.Add(emptyTextLine);
break;
}
if (!double.IsPositiveInfinity(MaxHeight) && MaxHeight < Bounds.Height)
{
break;
}
}
Bounds = new Rect(left, 0, right, bottom);

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

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

10
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -355,15 +355,21 @@ namespace Avalonia.Rendering
node.BeginRender(context, isLayerRoot);
foreach (var operation in node.DrawOperations)
var drawOperations = node.DrawOperations;
var drawOperationsCount = drawOperations.Count;
for (int i = 0; i < drawOperationsCount; i++)
{
var operation = drawOperations[i];
_currentDraw = operation;
operation.Item.Render(context);
_currentDraw = null;
}
foreach (var child in node.Children)
var children = node.Children;
var childrenCount = children.Count;
for (int i = 0; i < childrenCount; i++)
{
var child = children[i];
Render(context, (VisualNode)child, layer, clipBounds);
}

16
src/Avalonia.X11/X11Window.cs

@ -173,6 +173,7 @@ namespace Avalonia.X11
Surfaces = surfaces.ToArray();
UpdateMotifHints();
UpdateSizeHints(null);
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
XNames.XNClientWindow, _handle, IntPtr.Zero);
XFlush(_x11.Display);
@ -219,12 +220,16 @@ namespace Avalonia.X11
var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border |
MotifDecorations.Maximize | MotifDecorations.Minimize | MotifDecorations.ResizeH;
if (_popup || !_systemDecorations)
if (_popup || _systemDecorations == SystemDecorations.None)
{
decorations = 0;
}
else if (_systemDecorations == SystemDecorations.BorderOnly)
{
decorations = MotifDecorations.Border;
}
if (!_canResize)
if (!_canResize || _systemDecorations == SystemDecorations.BorderOnly)
{
functions &= ~(MotifFunctions.Resize | MotifFunctions.Maximize);
decorations &= ~(MotifDecorations.Maximize | MotifDecorations.ResizeH);
@ -247,7 +252,7 @@ namespace Avalonia.X11
var min = _minMaxSize.minSize;
var max = _minMaxSize.maxSize;
if (!_canResize)
if (!_canResize || _systemDecorations == SystemDecorations.BorderOnly)
max = min = _realSize;
if (preResize.HasValue)
@ -621,7 +626,7 @@ namespace Avalonia.X11
return rv;
}
private bool _systemDecorations = true;
private SystemDecorations _systemDecorations = SystemDecorations.Full;
private bool _canResize = true;
private const int MaxWindowDimension = 100000;
@ -777,10 +782,11 @@ namespace Avalonia.X11
(int)(point.X * Scaling + Position.X),
(int)(point.Y * Scaling + Position.Y));
public void SetSystemDecorations(bool enabled)
public void SetSystemDecorations(SystemDecorations enabled)
{
_systemDecorations = enabled;
UpdateMotifHints();
UpdateSizeHints(null);
}

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

@ -1298,7 +1298,17 @@ namespace Avalonia.Win32.Interop
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
internal static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, out int finalEffect);
[DllImport("dwmapi.dll")]
public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
[StructLayout(LayoutKind.Sequential)]
internal struct MARGINS
{
public int cxLeftWidth;
public int cxRightWidth;
public int cyTopHeight;
public int cyBottomHeight;
}
public enum MONITOR
{

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

@ -34,7 +34,7 @@ namespace Avalonia.Win32
private IInputRoot _owner;
private ManagedDeferredRendererLock _rendererLock = new ManagedDeferredRendererLock();
private bool _trackingMouse;
private bool _decorated = true;
private SystemDecorations _decorated = SystemDecorations.Full;
private bool _resizable = true;
private bool _topmost = false;
private bool _taskbarIcon = true;
@ -97,7 +97,7 @@ namespace Avalonia.Win32
{
get
{
if (_decorated)
if (_decorated == SystemDecorations.Full)
{
var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE);
var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE);
@ -281,7 +281,7 @@ namespace Avalonia.Win32
UnmanagedMethods.ShowWindow(_hwnd, UnmanagedMethods.ShowWindowCommand.Hide);
}
public void SetSystemDecorations(bool value)
public void SetSystemDecorations(SystemDecorations value)
{
if (value == _decorated)
{
@ -464,7 +464,7 @@ namespace Avalonia.Win32
return IntPtr.Zero;
case WindowsMessage.WM_NCCALCSIZE:
if (ToInt32(wParam) == 1 && !_decorated)
if (ToInt32(wParam) == 1 && _decorated != SystemDecorations.Full)
{
return IntPtr.Zero;
}
@ -682,14 +682,14 @@ namespace Avalonia.Win32
break;
case WindowsMessage.WM_NCPAINT:
if (!_decorated)
if (_decorated != SystemDecorations.Full)
{
return IntPtr.Zero;
}
break;
case WindowsMessage.WM_NCACTIVATE:
if (!_decorated)
if (_decorated != SystemDecorations.Full)
{
return new IntPtr(1);
}
@ -1001,7 +1001,7 @@ namespace Avalonia.Win32
style |= WindowStyles.WS_OVERLAPPEDWINDOW;
if (!_decorated)
if (_decorated != SystemDecorations.Full)
{
style ^= (WindowStyles.WS_CAPTION | WindowStyles.WS_SYSMENU);
}
@ -1011,6 +1011,10 @@ namespace Avalonia.Win32
style ^= (WindowStyles.WS_SIZEFRAME);
}
MARGINS margins = new MARGINS();
margins.cyBottomHeight = _decorated == SystemDecorations.BorderOnly ? 1 : 0;
UnmanagedMethods.DwmExtendFrameIntoClientArea(_hwnd, ref margins);
GetClientRect(_hwnd, out var oldClientRect);
var oldClientRectOrigin = new UnmanagedMethods.POINT();
ClientToScreen(_hwnd, ref oldClientRectOrigin);
@ -1024,7 +1028,7 @@ namespace Avalonia.Win32
if (oldDecorated != _decorated)
{
var newRect = oldClientRect;
if (_decorated)
if (_decorated == SystemDecorations.Full)
AdjustWindowRectEx(ref newRect, (uint)style, false,
GetWindowLong(_hwnd, (int)WindowLongParam.GWL_EXSTYLE));
SetWindowPos(_hwnd, IntPtr.Zero, newRect.left, newRect.top, newRect.Width, newRect.Height,

2
src/iOS/Avalonia.iOS/EmbeddableImpl.cs

@ -20,7 +20,7 @@ namespace Avalonia.iOS
return Disposable.Empty;
}
public void SetSystemDecorations(bool enabled)
public void SetSystemDecorations(SystemDecorations enabled)
{
}

11
tests/Avalonia.Benchmarks/NullGlyphRun.cs

@ -0,0 +1,11 @@
using Avalonia.Platform;
namespace Avalonia.Benchmarks
{
internal class NullGlyphRun : IGlyphRunImpl
{
public void Dispose()
{
}
}
}

4
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -72,7 +72,9 @@ namespace Avalonia.Benchmarks
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width)
{
throw new NotImplementedException();
width = default;
return new NullGlyphRun();
}
}
}

43
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -385,6 +385,49 @@ namespace Avalonia.Controls.UnitTests
Assert.True(target.SelectionEnd <= "123".Length);
}
}
[Fact]
public void SelectedText_Changes_OnSelectionChange()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
Text = "0123456789"
};
Assert.True(target.SelectedText == "");
target.SelectionStart = 2;
target.SelectionEnd = 4;
Assert.True(target.SelectedText == "23");
}
}
[Fact]
public void SelectedText_EditsText()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
Text = "0123"
};
target.SelectedText = "AA";
Assert.True(target.Text == "AA0123");
target.SelectionStart = 1;
target.SelectionEnd = 3;
target.SelectedText = "BB";
Assert.True(target.Text == "ABB123");
}
}
[Fact]
public void CoerceCaretIndex_Doesnt_Cause_Exception_with_malformed_line_ending()
{

Loading…
Cancel
Save