diff --git a/.ncrunch/BindingDemo.v3.ncrunchproject b/.ncrunch/BindingDemo.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/BindingDemo.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/RenderDemo.v3.ncrunchproject b/.ncrunch/RenderDemo.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/RenderDemo.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/VirtualizationDemo.v3.ncrunchproject b/.ncrunch/VirtualizationDemo.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/VirtualizationDemo.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 6a9f72039a..b70e0bf77f 100644
--- a/azure-pipelines.yml
+++ b/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'
diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h
index 4a960d47a1..ee57f54e59 100644
--- a/native/Avalonia.Native/inc/avalonia-native.h
+++ b/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;
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index b6ce172ffa..2c03407732 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/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;
}
};
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index b95e01c5ae..f3f70719e3 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -59,10 +59,17 @@
-
+
+
+ No Decorations
+ Border Only
+ Full Decorations
+
+
Light
Dark
-
+
+
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index acb9bc5bc6..5f71b2ecd9 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -56,6 +56,20 @@ namespace ControlCatalog
}
};
Styles.Add(light);
+
+ var decorations = this.Find("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("Decorations");
+ decorations.SelectedIndex = (int)((Window)VisualRoot).SystemDecorations;
}
}
}
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml
index 9b8f8af765..f61931ed72 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml
+++ b/samples/ControlCatalog/Pages/ImagePage.xaml
@@ -7,7 +7,7 @@
Displays an image
-
+
Bitmap
@@ -22,6 +22,23 @@
+ Crop
+
+ None
+ Center
+ TopLeft
+ TopRight
+ BottomLeft
+ BottomRight
+
+
+
+
+
+
+
+
+
Drawing
None
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs
index bbe89d1dfd..d8f4d6d5a2 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs
+++ b/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("bitmapImage");
_drawingImage = this.FindControl("drawingImage");
+ _croppedImage = this.FindControl("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
+ };
+
+ }
}
}
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 88b99cd99a..51062f1568 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/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.
diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
index 29ab10278b..7d3d5492b6 100644
--- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
+++ b/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>();
private readonly Dictionary> _directCache =
new Dictionary>();
- private readonly Dictionary> _initializedCache =
- new Dictionary>();
private readonly Dictionary> _inheritedCache =
new Dictionary>();
@@ -49,7 +45,7 @@ namespace Avalonia
///
/// The type.
/// A collection of definitions.
- public IEnumerable GetRegistered(Type type)
+ public IReadOnlyList GetRegistered(Type type)
{
Contract.Requires(type != null);
@@ -83,7 +79,7 @@ namespace Avalonia
///
/// The type.
/// A collection of definitions.
- public IEnumerable GetRegisteredAttached(Type type)
+ public IReadOnlyList GetRegisteredAttached(Type type)
{
Contract.Requires(type != null);
@@ -114,7 +110,7 @@ namespace Avalonia
///
/// The type.
/// A collection of definitions.
- public IEnumerable GetRegisteredDirect(Type type)
+ public IReadOnlyList GetRegisteredDirect(Type type)
{
Contract.Requires(type != null);
@@ -145,7 +141,7 @@ namespace Avalonia
///
/// The type.
/// A collection of definitions.
- public IEnumerable GetRegisteredInherited(Type type)
+ public IReadOnlyList GetRegisteredInherited(Type type)
{
Contract.Requires(type != null);
@@ -157,16 +153,27 @@ namespace Avalonia
result = new List();
var visited = new HashSet();
- 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
///
/// The object.
/// A collection of definitions.
- public IEnumerable GetRegistered(IAvaloniaObject o)
+ public IReadOnlyList GetRegistered(IAvaloniaObject o)
{
Contract.Requires(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)p;
@@ -308,8 +325,23 @@ namespace Avalonia
Contract.Requires(type != null);
Contract.Requires(property != null);
- return Instance.GetRegistered(type).Any(x => x == property) ||
- Instance.GetRegisteredAttached(type).Any(x => x == property);
+ static bool ContainsProperty(IReadOnlyList 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);
}
///
@@ -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;
- }
- }
}
}
diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs
index ea06bdb394..07e42c64e4 100644
--- a/src/Avalonia.Controls/Calendar/DatePicker.cs
+++ b/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)
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index 9d471a0fc0..d1f54da23a 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/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)
diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs
index 91b895f38a..238070bbef 100644
--- a/src/Avalonia.Controls/Platform/IWindowImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs
@@ -36,7 +36,7 @@ namespace Avalonia.Platform
///
/// Enables or disables system window decorations (title bar, buttons, etc)
///
- void SetSystemDecorations(bool enabled);
+ void SetSystemDecorations(SystemDecorations enabled);
///
/// Sets the icon of this window.
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 9dd481cf40..1a12b53b9d 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/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));
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index 1655e22331..0278360ba5 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -20,6 +20,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty BackgroundProperty =
Border.BackgroundProperty.AddOwner();
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PaddingProperty =
+ Decorator.PaddingProperty.AddOwner();
+
// 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 FontFamilyProperty =
AvaloniaProperty.RegisterAttached(
nameof(FontFamily),
- defaultValue: FontFamily.Default,
+ defaultValue: FontFamily.Default,
inherits: true);
///
@@ -110,20 +116,31 @@ namespace Avalonia.Controls
static TextBlock()
{
ClipToBoundsProperty.OverrideDefaultValue(true);
+
AffectsRender(
- BackgroundProperty,
- ForegroundProperty,
- FontWeightProperty,
- FontSizeProperty,
- FontStyleProperty);
+ BackgroundProperty, ForegroundProperty, FontSizeProperty,
+ FontWeightProperty, FontStyleProperty, TextWrappingProperty,
+ TextTrimmingProperty, TextAlignmentProperty, FontFamilyProperty,
+ TextDecorationsProperty, TextProperty, PaddingProperty);
+
+ AffectsMeasure(
+ 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((x, _) => x.OnTextPropertiesChanged());
+ FontWeightProperty.Changed,
+ FontFamilyProperty.Changed,
+ TextDecorationsProperty.Changed,
+ PaddingProperty.Changed
+ ).AddClassHandler((x, _) => x.InvalidateTextLayout());
}
///
@@ -145,6 +162,15 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Gets or sets the padding to place around the .
+ ///
+ public Thickness Padding
+ {
+ get { return GetValue(PaddingProperty); }
+ set { SetValue(PaddingProperty, value); }
+ }
+
///
/// Gets or sets a brush used to paint the control's background.
///
@@ -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));
}
///
@@ -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();
}
}
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index a438d7380b..4acfb75cb9 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/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();
+ }
+ }
+
///
/// Gets or sets the horizontal alignment of the content within the control.
///
@@ -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);
}
}
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 6554237b3a..f4a3f05f03 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/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,
}
+ ///
+ /// Determines system decorations (title bar, border, etc) for a
+ ///
+ public enum SystemDecorations
+ {
+ ///
+ /// No decorations
+ ///
+ None = 0,
+
+ ///
+ /// Window border without titlebar
+ ///
+ BorderOnly = 1,
+
+ ///
+ /// Fully decorated (default)
+ ///
+ Full = 2
+ }
+
///
/// A top-level window.
///
@@ -59,8 +80,18 @@ namespace Avalonia.Controls
///
/// Enables or disables system window decorations (title bar, buttons, etc)
///
- public static readonly StyledProperty HasSystemDecorationsProperty =
- AvaloniaProperty.Register(nameof(HasSystemDecorations), true);
+ [Obsolete("Use SystemDecorationsProperty instead")]
+ public static readonly DirectProperty HasSystemDecorationsProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(HasSystemDecorations),
+ o => o.HasSystemDecorations,
+ (o, v) => o.HasSystemDecorations = v);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty SystemDecorationsProperty =
+ AvaloniaProperty.Register(nameof(SystemDecorations), SystemDecorations.Full);
///
/// Enables or disables the taskbar icon
@@ -124,9 +155,6 @@ namespace Avalonia.Controls
{
BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue));
- HasSystemDecorationsProperty.Changed.AddClassHandler(
- (s, e) => s.PlatformImpl?.SetSystemDecorations((bool)e.NewValue));
-
ShowInTaskbarProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue));
IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue)?.PlatformImpl));
@@ -135,12 +163,11 @@ namespace Avalonia.Controls
WindowStateProperty.Changed.AddClassHandler(
(w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; });
-
+
MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
MaxHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
-
}
///
@@ -191,11 +218,30 @@ namespace Avalonia.Controls
///
/// Enables or disables system window decorations (title bar, buttons, etc)
///
- ///
+ [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);
+ }
+ }
+ }
+
+ ///
+ /// Sets the system decorations (title bar, border, etc)
+ ///
+ ///
+ public SystemDecorations SystemDecorations
+ {
+ get { return GetValue(SystemDecorationsProperty); }
+ set { SetValue(SystemDecorationsProperty, value); }
}
///
@@ -584,5 +630,27 @@ namespace Avalonia.Controls
/// event needs to be raised.
///
protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e);
+
+ protected override void OnPropertyChanged(
+ AvaloniaProperty property,
+ Optional oldValue,
+ BindingValue newValue,
+ BindingPriority priority)
+ {
+ if (property == SystemDecorationsProperty)
+ {
+ var typedNewValue = newValue.GetValueOrDefault();
+
+ PlatformImpl?.SetSystemDecorations(typedNewValue);
+
+ var o = oldValue.GetValueOrDefault() == SystemDecorations.Full;
+ var n = typedNewValue == SystemDecorations.Full;
+
+ if (o != n)
+ {
+ RaisePropertyChanged(HasSystemDecorationsProperty, o, n);
+ }
+ }
+ }
}
}
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index 86e34ca6d4..7480b3519c 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
@@ -96,7 +96,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
- public void SetSystemDecorations(bool enabled)
+ public void SetSystemDecorations(SystemDecorations enabled)
{
}
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index 4bba5ef41b..7bf1d236bd 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
@@ -110,7 +110,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
- public void SetSystemDecorations(bool enabled)
+ public void SetSystemDecorations(SystemDecorations enabled)
{
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
index 0464047273..b3a8f4745e 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Diagnostics
}
}
- return root.AddHandler(
+ return root.AddDisposableHandler(
InputElement.KeyDownEvent,
PreviewKeyDown,
RoutingStrategies.Tunnel);
diff --git a/src/Avalonia.Interactivity/IInteractive.cs b/src/Avalonia.Interactivity/IInteractive.cs
index 33baa9453a..51aee78988 100644
--- a/src/Avalonia.Interactivity/IInteractive.cs
+++ b/src/Avalonia.Interactivity/IInteractive.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Interactivity
/// The routing strategies to listen to.
/// Whether handled events should also be listened for.
/// A disposable that terminates the event subscription.
- IDisposable AddHandler(
+ void AddHandler(
RoutedEvent routedEvent,
Delegate handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
@@ -38,7 +38,7 @@ namespace Avalonia.Interactivity
/// The routing strategies to listen to.
/// Whether handled events should also be listened for.
/// A disposable that terminates the event subscription.
- IDisposable AddHandler(
+ void AddHandler(
RoutedEvent routedEvent,
EventHandler handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs
index 9493d86885..321ecbc516 100644
--- a/src/Avalonia.Interactivity/Interactive.cs
+++ b/src/Avalonia.Interactivity/Interactive.cs
@@ -27,8 +27,7 @@ namespace Avalonia.Interactivity
/// The handler.
/// The routing strategies to listen to.
/// Whether handled events should also be listened for.
- /// A disposable that terminates the event subscription.
- 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);
}
///
@@ -49,8 +49,7 @@ namespace Avalonia.Interactivity
/// The handler.
/// The routing strategies to listen to.
/// Whether handled events should also be listened for.
- /// A disposable that terminates the event subscription.
- public IDisposable AddHandler(
+ public void AddHandler(
RoutedEvent routedEvent,
EventHandler 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);
}
///
@@ -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>();
@@ -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 _subscriptions;
- private readonly EventSubscription _subscription;
-
- public UnsubscribeDisposable(List subscriptions, EventSubscription subscription)
- {
- _subscriptions = subscriptions;
- _subscription = subscription;
- }
-
- public void Dispose()
- {
- _subscriptions.Remove(_subscription);
- }
- }
}
}
diff --git a/src/Avalonia.Interactivity/InteractiveExtensions.cs b/src/Avalonia.Interactivity/InteractiveExtensions.cs
index 414c408080..e6c93e26b2 100644
--- a/src/Avalonia.Interactivity/InteractiveExtensions.cs
+++ b/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
///
public static class InteractiveExtensions
{
+ ///
+ /// Adds a handler for the specified routed event and returns a disposable that can terminate the event subscription.
+ ///
+ /// The type of the event's args.
+ /// Target for adding given event handler.
+ /// The routed event.
+ /// The handler.
+ /// The routing strategies to listen to.
+ /// Whether handled events should also be listened for.
+ /// A disposable that terminates the event subscription.
+ public static IDisposable AddDisposableHandler(this IInteractive o, RoutedEvent routedEvent,
+ EventHandler 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));
+ }
+
///
/// Gets an observable for a .
///
@@ -31,7 +54,7 @@ namespace Avalonia.Interactivity
o = o ?? throw new ArgumentNullException(nameof(o));
routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
- return Observable.Create(x => o.AddHandler(
+ return Observable.Create(x => o.AddDisposableHandler(
routedEvent,
(_, e) => x.OnNext(e),
routes,
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index c757576017..73ec81ce57 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/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)
diff --git a/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs b/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
index c884b6ac43..7906a29cb5 100644
--- a/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
+++ b/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
@@ -14,6 +14,7 @@ namespace Avalonia.Styling.Activators
{
private readonly IList _match;
private readonly IAvaloniaReadOnlyList _classes;
+ private NotifyCollectionChangedEventHandler? _classesChangedHandler;
public StyleClassActivator(IAvaloniaReadOnlyList classes, IList match)
{
@@ -21,6 +22,9 @@ namespace Avalonia.Styling.Activators
_match = match;
}
+ private NotifyCollectionChangedEventHandler ClassesChangedHandler =>
+ _classesChangedHandler ??= ClassesChanged;
+
public static bool AreClassesMatching(IReadOnlyList classes, IList 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)
diff --git a/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs
new file mode 100644
index 0000000000..70823e114c
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs
@@ -0,0 +1,96 @@
+using System;
+using Avalonia.Visuals.Media.Imaging;
+
+namespace Avalonia.Media.Imaging
+{
+ ///
+ /// Crops a Bitmap.
+ ///
+ public class CroppedBitmap : AvaloniaObject, IImage, IAffectsRender, IDisposable
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty SourceProperty =
+ AvaloniaProperty.Register(nameof(Source));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty SourceRectProperty =
+ AvaloniaProperty.Register(nameof(SourceRect));
+
+ public event EventHandler Invalidated;
+
+ static CroppedBitmap()
+ {
+ SourceRectProperty.Changed.AddClassHandler((x, e) => x.SourceRectChanged(e));
+ SourceProperty.Changed.AddClassHandler((x, e) => x.SourceChanged(e));
+ }
+
+ ///
+ /// Gets or sets the source for the bitmap.
+ ///
+ public IImage Source
+ {
+ get => GetValue(SourceProperty);
+ set => SetValue(SourceProperty, value);
+ }
+
+ ///
+ /// Gets or sets the rectangular area that the bitmap is cropped to.
+ ///
+ 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);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs b/src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs
index e84242c628..30d513386e 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/SimpleTextFormatter.cs
+++ b/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];
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
index 0c9013e6f7..3f0cf7c680 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
+++ b/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);
diff --git a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs
index 10fe74f9b9..29cc365251 100644
--- a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs
+++ b/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")]
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index b065079564..f11bd76a7b 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/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);
}
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 94f1dbc8d1..2492f5a43a 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/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);
}
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 904e122382..50b568cab2 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/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
{
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index cea9f88799..6900d73c93 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/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,
diff --git a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs
index 65a6c15971..838bf49846 100644
--- a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs
+++ b/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)
{
}
diff --git a/tests/Avalonia.Benchmarks/NullGlyphRun.cs b/tests/Avalonia.Benchmarks/NullGlyphRun.cs
new file mode 100644
index 0000000000..5ba0822649
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/NullGlyphRun.cs
@@ -0,0 +1,11 @@
+using Avalonia.Platform;
+
+namespace Avalonia.Benchmarks
+{
+ internal class NullGlyphRun : IGlyphRunImpl
+ {
+ public void Dispose()
+ {
+ }
+ }
+}
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 101d40f00a..1f983069c2 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/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();
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
index 225eca17b2..14251454ea 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
+++ b/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()
{