diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index 1b60bb4df9..5838519596 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -8,7 +8,7 @@
https://github.com/AvaloniaUI/Avalonia/
true
CS1591
- latest
+ preview
MIT
Icon.png
Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.
diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml b/samples/ControlCatalog/Pages/ExpanderPage.xaml
index f9ae1c7a3c..8c8702c665 100644
--- a/samples/ControlCatalog/Pages/ExpanderPage.xaml
+++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml
@@ -32,6 +32,23 @@
Expanded content
+
+
+
+
+
+ Expanded content
+
+
+
+
+ Expanded content
+
+
Rounded
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index 80b5259a53..86118d7b00 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -139,7 +139,8 @@ namespace Avalonia.Controls
e.Source,
true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
- e.KeyModifiers.HasAllFlags(KeyModifiers.Control));
+ e.KeyModifiers.HasAllFlags(KeyModifiers.Control),
+ fromFocus: true);
}
}
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index ea20247b4b..e03b02a479 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -586,6 +586,14 @@ namespace Avalonia.Controls.Primitives
Selection.SelectAll();
e.Handled = true;
}
+ else if (e.Key == Key.Space || e.Key == Key.Enter)
+ {
+ e.Handled = UpdateSelectionFromEventSource(
+ e.Source,
+ true,
+ e.KeyModifiers.HasFlag(KeyModifiers.Shift),
+ e.KeyModifiers.HasFlag(KeyModifiers.Control));
+ }
}
}
@@ -662,12 +670,14 @@ namespace Avalonia.Controls.Primitives
/// Whether the range modifier is enabled (i.e. shift key).
/// Whether the toggle modifier is enabled (i.e. ctrl key).
/// Whether the event is a right-click.
+ /// Wheter the event is a focus event
protected void UpdateSelection(
int index,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false,
- bool rightButton = false)
+ bool rightButton = false,
+ bool fromFocus = false)
{
if (index < 0 || index >= ItemCount)
{
@@ -696,22 +706,25 @@ namespace Avalonia.Controls.Primitives
Selection.Clear();
Selection.SelectRange(Selection.AnchorIndex, index);
}
- else if (multi && toggle)
+ else if (!fromFocus && toggle)
{
- if (Selection.IsSelected(index) == true)
+ if (multi)
{
- Selection.Deselect(index);
+ if (Selection.IsSelected(index) == true)
+ {
+ Selection.Deselect(index);
+ }
+ else
+ {
+ Selection.Select(index);
+ }
}
else
{
- Selection.Select(index);
+ SelectedIndex = (SelectedIndex == index) ? -1 : index;
}
}
- else if (toggle)
- {
- SelectedIndex = (SelectedIndex == index) ? -1 : index;
- }
- else
+ else if (!toggle)
{
using var operation = Selection.BatchUpdate();
Selection.Clear();
@@ -735,18 +748,20 @@ namespace Avalonia.Controls.Primitives
/// Whether the range modifier is enabled (i.e. shift key).
/// Whether the toggle modifier is enabled (i.e. ctrl key).
/// Whether the event is a right-click.
+ /// Wheter the event is a focus event
protected void UpdateSelection(
IControl container,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false,
- bool rightButton = false)
+ bool rightButton = false,
+ bool fromFocus = false)
{
var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1;
if (index != -1)
{
- UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton);
+ UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton, fromFocus);
}
}
@@ -759,6 +774,7 @@ namespace Avalonia.Controls.Primitives
/// Whether the range modifier is enabled (i.e. shift key).
/// Whether the toggle modifier is enabled (i.e. ctrl key).
/// Whether the event is a right-click.
+ /// Wheter the event is a focus event
///
/// True if the event originated from a container that belongs to the control; otherwise
/// false.
@@ -768,13 +784,14 @@ namespace Avalonia.Controls.Primitives
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false,
- bool rightButton = false)
+ bool rightButton = false,
+ bool fromFocus = false)
{
var container = GetContainerFromEventSource(eventSource);
if (container != null)
{
- UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton);
+ UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton, fromFocus);
return true;
}
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index ada081b808..2f96e6911f 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.LogicalTree;
+using Avalonia.Threading;
namespace Avalonia.Controls
{
@@ -45,6 +46,8 @@ namespace Avalonia.Controls
private IControl? _header;
private bool _isExpanded;
private int _level;
+ private bool _templateApplied;
+ private bool _deferredBringIntoViewFlag;
///
/// Initializes static members of the class.
@@ -136,15 +139,24 @@ namespace Avalonia.Controls
protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e)
{
- if (e.TargetObject == this && _header != null)
+ if (e.TargetObject == this)
{
- var m = _header.TransformToVisual(this);
+ if (!_templateApplied)
+ {
+ _deferredBringIntoViewFlag = true;
+ return;
+ }
- if (m.HasValue)
+ if (_header != null)
{
- var bounds = new Rect(_header.Bounds.Size);
- var rect = bounds.TransformToAABB(m.Value);
- e.TargetRect = rect;
+ var m = _header.TransformToVisual(this);
+
+ if (m.HasValue)
+ {
+ var bounds = new Rect(_header.Bounds.Size);
+ var rect = bounds.TransformToAABB(m.Value);
+ e.TargetRect = rect;
+ }
}
}
}
@@ -187,6 +199,12 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_header = e.NameScope.Find("PART_Header");
+ _templateApplied = true;
+ if (_deferredBringIntoViewFlag)
+ {
+ _deferredBringIntoViewFlag = false;
+ Dispatcher.UIThread.Post(this.BringIntoView); // must use the Dispatcher, otherwise the TreeView doesn't scroll
+ }
}
private static int CalculateDistanceFromLogicalParent(ILogical? logical, int @default = -1) where T : class
diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs
index aabfd3ef18..07d877142e 100644
--- a/src/Avalonia.Controls/Viewbox.cs
+++ b/src/Avalonia.Controls/Viewbox.cs
@@ -42,6 +42,7 @@ namespace Avalonia.Controls
// can be applied independently of the Viewbox and Child transforms.
_containerVisual = new ViewboxContainer();
_containerVisual.RenderTransformOrigin = RelativePoint.TopLeft;
+ ((ISetLogicalParent)_containerVisual).SetParent(this);
VisualChildren.Add(_containerVisual);
}
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
index e480fb1670..7e3c8673f5 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
@@ -281,7 +281,6 @@
-
@@ -305,7 +304,39 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
index 4f421f64a7..7917315e19 100644
--- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
+++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
@@ -278,7 +278,7 @@
-
+
@@ -301,7 +301,39 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml
index 7d5bb48333..df9de331ee 100644
--- a/src/Avalonia.Themes.Fluent/Controls/Expander.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/Expander.xaml
@@ -1,6 +1,7 @@
+
@@ -44,45 +45,59 @@
- 16
+
+ 48
+
+
+ Stretch
+ Center
+ 16,0,0,0
+ 1
+ 0
+ 20,0,8,0
+ 32
+
+
16
- 1
- 1,1,0,1
- 1,1,1,0
- 0,1,1,1
- 1,0,1,1
-
-
-
-
-
-
+ 1,1,0,1
+ 1,1,1,0
+ 0,1,1,1
+ 1,0,1,1
+
+
+
+
+
+
+
+
-
+
+ HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+ VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+ Foreground="{TemplateBinding Foreground}"
+ Margin="{TemplateBinding Padding}"/>
+ Width="{DynamicResource ExpanderChevronButtonSize}"
+ Height="{DynamicResource ExpanderChevronButtonSize}"
+ Margin="{DynamicResource ExpanderChevronMargin}"
+ CornerRadius="{DynamicResource ControlCornerRadius}"
+ BorderBrush="{DynamicResource ExpanderChevronBorderBrush}"
+ BorderThickness="{DynamicResource ExpanderChevronBorderThickness}"
+ Background="{DynamicResource ExpanderChevronBackground}">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+ IsChecked="{TemplateBinding IsExpanded, Mode=TwoWay}" />
+ IsVisible="{TemplateBinding IsExpanded, Mode=TwoWay}"
+ Background="{TemplateBinding Background}"
+ BorderBrush="{TemplateBinding BorderBrush}"
+ BorderThickness="{TemplateBinding BorderThickness}"
+ MinHeight="{TemplateBinding MinHeight}"
+ HorizontalAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ Padding="{TemplateBinding Padding}">
+ ContentTemplate="{TemplateBinding ContentTemplate}"
+ Foreground="{TemplateBinding Foreground}"
+ HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+ VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
@@ -241,16 +316,16 @@
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
index f9be3fd62a..1dc7ce5e99 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
@@ -5,7 +5,6 @@
true
Avalonia.Markup.Xaml.Loader
$(DefineConstants);XAMLX_INTERNAL
- 11
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index f325e6e2d6..4ece433530 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
using XamlX.Ast;
@@ -51,6 +52,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlPropertyPathTransformer(),
+ new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
new file mode 100644
index 0000000000..ebc6c01ba8
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
@@ -0,0 +1,34 @@
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+
+internal class AvaloniaXamlIlSetterTargetTypeMetadataTransformer : IXamlAstTransformer
+{
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlAstObjectNode on
+ && on.Children.FirstOrDefault(c => c is XamlAstXmlDirective
+ {
+ Namespace: XamlNamespaces.Xaml2006,
+ Name: "SetterTargetType"
+ }) is { } typeDirective)
+ {
+ var value = ((XamlAstXmlDirective)typeDirective).Values.Single();
+ var type = value is XamlTypeExtensionNode typeNode ? typeNode.Value
+ : value is XamlAstTextNode tn ? TypeReferenceResolver.ResolveType(context, tn.Text, false, tn, true)
+ : null;
+ on.Children.Remove(typeDirective);
+
+ if (type is null)
+ {
+ throw new XamlParseException("Unable to resolve SetterTargetType type", typeDirective);
+ }
+ return new AvaloniaXamlIlTargetTypeMetadataNode(on, type, AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
+ }
+ return node;
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
index ceaec972f6..5a6fd8246d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
@@ -8,7 +9,6 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- using XamlParseException = XamlX.XamlParseException;
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -17,10 +17,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node;
- var targetTypeNode = context.ParentNodes()
+ IXamlType targetType = null;
+ IXamlLineInfo lineInfo = null;
+
+ var styleParent = context.ParentNodes()
.OfType()
- .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) ??
- throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType", node);
+ .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
+
+ if (styleParent != null)
+ {
+ targetType = styleParent.TargetType.GetClrType()
+ ?? throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node);
+ lineInfo = on;
+ }
+
+ if (targetType == null)
+ {
+ throw new XamlParseException("Could not determine target type of Setter", node);
+ }
IXamlType propType = null;
var property = @on.Children.OfType()
@@ -31,9 +45,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (propertyName == null)
throw new XamlParseException("Setter.Property must be a string", node);
-
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
- new XamlAstClrTypeReference(targetTypeNode, targetTypeNode.TargetType.GetClrType(), false), property.Values[0]);
+ new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
property.Values = new List {avaloniaPropertyNode};
propType = avaloniaPropertyNode.AvaloniaPropertyType;
}
diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
index 6b25cbbeab..6711c3dd3d 100644
--- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
+++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
@@ -2,7 +2,6 @@
net6.0;netstandard2.0
Avalonia
- 11
diff --git a/src/Web/Avalonia.Web/Avalonia.Web.csproj b/src/Web/Avalonia.Web/Avalonia.Web.csproj
index cdfa095865..88b23cdad2 100644
--- a/src/Web/Avalonia.Web/Avalonia.Web.csproj
+++ b/src/Web/Avalonia.Web/Avalonia.Web.csproj
@@ -7,6 +7,7 @@
+
diff --git a/src/Web/Avalonia.Web/Avalonia.Web.props b/src/Web/Avalonia.Web/Avalonia.Web.props
index 6c975cd284..668dd20789 100644
--- a/src/Web/Avalonia.Web/Avalonia.Web.props
+++ b/src/Web/Avalonia.Web/Avalonia.Web.props
@@ -1,5 +1,5 @@
-
+
- $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"
+ 16384000
diff --git a/src/Web/Avalonia.Web/Avalonia.Web.targets b/src/Web/Avalonia.Web/Avalonia.Web.targets
index d1bec2aa93..b6a09b33ef 100644
--- a/src/Web/Avalonia.Web/Avalonia.Web.targets
+++ b/src/Web/Avalonia.Web/Avalonia.Web.targets
@@ -4,4 +4,34 @@
+
+
+ True
+ $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"
+ $(EmccExtraLDFlags) -sERROR_ON_UNDEFINED_SYMBOLS=0
+ true
+
+
+
+ true
+ full
+ true
+ -Oz
+ -Oz
+ false
+ false
+ 0
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ en
+ false
+
diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs
index 098b06a0a2..37614399ee 100644
--- a/src/Web/Avalonia.Web/AvaloniaView.cs
+++ b/src/Web/Avalonia.Web/AvaloniaView.cs
@@ -1,5 +1,9 @@
using System;
+using System.Collections.Generic;
+using System.Reflection;
using System.Runtime.InteropServices.JavaScript;
+
+using Avalonia.Collections.Pooled;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform;
@@ -18,6 +22,7 @@ namespace Avalonia.Web
[System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
public partial class AvaloniaView : ITextInputMethodImpl
{
+ private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never);
private readonly BrowserTopLevelImpl _topLevelImpl;
private EmbeddableControlRoot _topLevel;
@@ -52,13 +57,13 @@ namespace Avalonia.Web
}
_containerElement = hostContent.GetPropertyAsJSObject("host")
- ?? throw new InvalidOperationException("Host cannot be null");
+ ?? throw new InvalidOperationException("Host cannot be null");
_canvas = hostContent.GetPropertyAsJSObject("canvas")
- ?? throw new InvalidOperationException("Canvas cannot be null");
+ ?? throw new InvalidOperationException("Canvas cannot be null");
_nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost")
- ?? throw new InvalidOperationException("NativeHost cannot be null");
+ ?? throw new InvalidOperationException("NativeHost cannot be null");
_inputElement = hostContent.GetPropertyAsJSObject("inputElement")
- ?? throw new InvalidOperationException("InputElement cannot be null");
+ ?? throw new InvalidOperationException("InputElement cannot be null");
_splash = DomHelper.GetElementById("avalonia-splash");
@@ -96,7 +101,8 @@ namespace Avalonia.Web
OnCompositionUpdate,
OnCompositionEnd);
- InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel);
+ InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp,
+ OnPointerCancel, OnWheel);
var skiaOptions = AvaloniaLocator.Current.GetService();
@@ -117,7 +123,12 @@ namespace Avalonia.Web
_context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
}
- _topLevelImpl.Surfaces = new[] { new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, GRSurfaceOrigin.BottomLeft) };
+ _topLevelImpl.Surfaces = new[]
+ {
+ new BrowserSkiaSurface(_context, _jsGlInfo, ColorType,
+ new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi,
+ GRSurfaceOrigin.BottomLeft)
+ };
}
else
{
@@ -135,7 +146,7 @@ namespace Avalonia.Web
DomHelper.ObserveSize(host, null, OnSizeChanged);
CanvasHelper.RequestAnimationFrame(_canvas, true);
-
+
InputHelper.FocusElement(_containerElement);
}
@@ -155,17 +166,36 @@ namespace Avalonia.Web
private bool OnPointerMove(JSObject args)
{
- var type = args.GetPropertyAsString("pointertype");
-
+ var pointerType = args.GetPropertyAsString("pointerType");
var point = ExtractRawPointerFromJSArgs(args);
+ var type = pointerType switch
+ {
+ "touch" => RawPointerEventType.TouchUpdate,
+ _ => RawPointerEventType.Move
+ };
+
+ var coalescedEvents = new Lazy?>(() =>
+ {
+ var points = InputHelper.GetCoalescedEvents(args);
+ s_intermediatePointsPooledList.Clear();
+ s_intermediatePointsPooledList.Capacity = points.Length - 1;
+
+ // Skip the last one, as it is already processed point.
+ for (var i = 0; i < points.Length - 1; i++)
+ {
+ var point = points[i];
+ s_intermediatePointsPooledList.Add(ExtractRawPointerFromJSArgs(point));
+ }
+
+ return s_intermediatePointsPooledList;
+ });
- return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+ return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"), coalescedEvents);
}
private bool OnPointerDown(JSObject args)
{
- var pointerType = args.GetPropertyAsString("pointerType");
-
+ var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchBegin,
@@ -176,20 +206,18 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonDown,
3 => RawPointerEventType.XButton1Down,
4 => RawPointerEventType.XButton2Down,
- // 5 => Pen eraser button,
+ 5 => RawPointerEventType.XButton1Down, // should be pen eraser button,
_ => RawPointerEventType.Move
}
};
var point = ExtractRawPointerFromJSArgs(args);
-
- return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+ return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
}
private bool OnPointerUp(JSObject args)
{
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
-
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchEnd,
@@ -200,15 +228,27 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonUp,
3 => RawPointerEventType.XButton1Up,
4 => RawPointerEventType.XButton2Up,
- // 5 => Pen eraser button,
+ 5 => RawPointerEventType.XButton1Up, // should be pen eraser button,
_ => RawPointerEventType.Move
}
};
var point = ExtractRawPointerFromJSArgs(args);
-
return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
}
+
+ private bool OnPointerCancel(JSObject args)
+ {
+ var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
+ if (pointerType == "touch")
+ {
+ var point = ExtractRawPointerFromJSArgs(args);
+ _topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, pointerType, point,
+ GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+ }
+
+ return false;
+ }
private bool OnWheel(JSObject args)
{
diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
index b955da6df2..ed8f417870 100644
--- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
+++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
@@ -67,17 +67,22 @@ namespace Avalonia.Web
public bool RawPointerEvent(
RawPointerEventType eventType, string pointerType,
- RawPointerPoint p, RawInputModifiers modifiers, long touchPointId)
+ RawPointerPoint p, RawInputModifiers modifiers, long touchPointId,
+ Lazy?>? intermediatePoints = null)
{
if (_inputRoot is { }
&& Input is { } input)
{
var device = GetPointerDevice(pointerType);
var args = device is TouchDevice ?
- new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) :
+ new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId)
+ {
+ IntermediatePoints = intermediatePoints
+ } :
new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers)
{
- RawPointerId = touchPointId
+ RawPointerId = touchPointId,
+ IntermediatePoints = intermediatePoints
};
input.Invoke(args);
diff --git a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs
index efa94916fa..5bbe503bc1 100644
--- a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs
@@ -33,7 +33,7 @@ internal static partial class CanvasHelper
public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop);
[JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)]
- public static partial void SetCanvasSize(JSObject canvas, int height, int width);
+ public static partial void SetCanvasSize(JSObject canvas, int width, int height);
[JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)]
private static partial JSObject InitGL(
diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs
index cfec9f30dc..904fa915a8 100644
--- a/src/Web/Avalonia.Web/Interop/InputHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
@@ -36,6 +37,8 @@ internal static partial class InputHelper
[JSMarshalAs>]
Func pointerUp,
[JSMarshalAs>]
+ Func pointerCancel,
+ [JSMarshalAs>]
Func wheel);
@@ -45,6 +48,9 @@ internal static partial class InputHelper
[JSMarshalAs>]
Func input);
+ [JSImport("InputHelper.getCoalescedEvents", AvaloniaModule.MainModuleName)]
+ [return: JSMarshalAs>]
+ public static partial JSObject[] GetCoalescedEvents(JSObject pointerEvent);
[JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)]
public static partial void ClearInputElement(JSObject htmlElement);
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
index ddc1f54ae7..83e8ee7f1c 100644
--- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
@@ -95,41 +95,45 @@ export class InputHelper {
pointerMoveCallback: (args: PointerEvent) => boolean,
pointerDownCallback: (args: PointerEvent) => boolean,
pointerUpCallback: (args: PointerEvent) => boolean,
+ pointerCancelCallback: (args: PointerEvent) => boolean,
wheelCallback: (args: WheelEvent) => boolean
) {
const pointerMoveHandler = (args: PointerEvent) => {
- if (pointerMoveCallback(args)) {
- args.preventDefault();
- }
+ pointerMoveCallback(args);
+ args.preventDefault();
};
const pointerDownHandler = (args: PointerEvent) => {
- if (pointerDownCallback(args)) {
- args.preventDefault();
- }
+ pointerDownCallback(args);
+ args.preventDefault();
};
const pointerUpHandler = (args: PointerEvent) => {
- if (pointerUpCallback(args)) {
- args.preventDefault();
- }
+ pointerUpCallback(args);
+ args.preventDefault();
+ };
+
+ const pointerCancelHandler = (args: PointerEvent) => {
+ pointerCancelCallback(args);
+ args.preventDefault();
};
const wheelHandler = (args: WheelEvent) => {
- if (wheelCallback(args)) {
- args.preventDefault();
- }
+ wheelCallback(args);
+ args.preventDefault();
};
element.addEventListener("pointermove", pointerMoveHandler);
element.addEventListener("pointerdown", pointerDownHandler);
element.addEventListener("pointerup", pointerUpHandler);
element.addEventListener("wheel", wheelHandler);
+ element.addEventListener("pointercancel", pointerCancelHandler);
return () => {
element.removeEventListener("pointerover", pointerMoveHandler);
element.removeEventListener("pointerdown", pointerDownHandler);
element.removeEventListener("pointerup", pointerUpHandler);
+ element.removeEventListener("pointercancel", pointerCancelHandler);
element.removeEventListener("wheel", wheelHandler);
};
}
@@ -150,6 +154,10 @@ export class InputHelper {
};
}
+ public static getCoalescedEvents(pointerEvent: PointerEvent): PointerEvent[] {
+ return pointerEvent.getCoalescedEvents();
+ }
+
public static clearInput(inputElement: HTMLInputElement) {
inputElement.value = "";
}
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
index 7c7cdd08db..556959effb 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
@@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
- public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Add_To_Selection()
+ public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Not_Add_To_Selection()
{
var target = new ListBox
{
@@ -56,11 +56,11 @@ namespace Avalonia.Controls.UnitTests
KeyModifiers = KeyModifiers.Control
});
- Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
+ Assert.Equal(new[] { "Foo" }, target.SelectedItems);
}
[Fact]
- public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Remove_From_Selection()
+ public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Not_Remove_From_Selection()
{
var target = new ListBox
{
@@ -81,7 +81,7 @@ namespace Avalonia.Controls.UnitTests
KeyModifiers = KeyModifiers.Control
});
- Assert.Equal(new[] { "Bar" }, target.SelectedItems);
+ Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
}
private Control CreateListBoxTemplate(ITemplatedControl parent, INameScope scope)
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
index bf516748cc..726a39ac46 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
@@ -59,6 +59,58 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, target.SelectedIndex);
}
+ [Fact]
+ public void Focusing_Item_With_Arrow_Key_And_Ctrl_Pressed_Should_Not_Select_It()
+ {
+ var target = new ListBox
+ {
+ Template = new FuncControlTemplate(CreateListBoxTemplate),
+ Items = new[] { "Foo", "Bar", "Baz " },
+ };
+
+ ApplyTemplate(target);
+
+ target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
+ {
+ RoutedEvent = InputElement.GotFocusEvent,
+ NavigationMethod = NavigationMethod.Directional,
+ KeyModifiers = KeyModifiers.Control
+ });
+
+ Assert.Equal(-1, target.SelectedIndex);
+ }
+
+ [Fact]
+ public void Pressing_Space_On_Focused_Item_With_Ctrl_Pressed_Should_Select_It()
+ {
+ using (UnitTestApplication.Start())
+ {
+ var target = new ListBox
+ {
+ Template = new FuncControlTemplate(CreateListBoxTemplate),
+ Items = new[] { "Foo", "Bar", "Baz " },
+ };
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object);
+ ApplyTemplate(target);
+
+ target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
+ {
+ RoutedEvent = InputElement.GotFocusEvent,
+ NavigationMethod = NavigationMethod.Directional,
+ KeyModifiers = KeyModifiers.Control
+ });
+
+ target.Presenter.Panel.Children[0].RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Space,
+ KeyModifiers = KeyModifiers.Control
+ });
+
+ Assert.Equal(0, target.SelectedIndex);
+ }
+ }
+
[Fact]
public void Clicking_Item_Should_Select_It()
{
diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
index 4ffd314857..629408bcba 100644
--- a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
@@ -1,4 +1,5 @@
using Avalonia.Controls.Shapes;
+using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.UnitTests;
@@ -207,6 +208,26 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(200, 200), target.DesiredSize);
}
+ [Fact]
+ public void Child_DataContext_Binding_Works()
+ {
+ var data = new
+ {
+ Foo = "foo",
+ };
+
+ var target = new Viewbox()
+ {
+ DataContext = data,
+ Child = new Canvas
+ {
+ [!Canvas.DataContextProperty] = new Binding("Foo"),
+ },
+ };
+
+ Assert.Equal("foo", target.Child.DataContext);
+ }
+
private bool TryGetScale(Viewbox viewbox, out Vector scale)
{
if (viewbox.InternalTransform is null)
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs
new file mode 100644
index 0000000000..6fc0f2d91c
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs
@@ -0,0 +1,51 @@
+using Avalonia.Controls;
+using Avalonia.Styling;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests;
+
+public class SetterTests : XamlTestBase
+{
+ [Fact]
+ public void SetterTargetType_Should_Understand_xType_Extensions()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var setter = (Setter)animation.Children[0].Setters[0];
+
+ Assert.Equal(typeof(ContentControl), setter.Property.OwnerType);
+ }
+ }
+
+ [Fact]
+ public void SetterTargetType_Should_Understand_Type_From_Xmlns()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var setter = (Setter)animation.Children[0].Setters[0];
+
+ Assert.Equal(typeof(ContentControl), setter.Property.OwnerType);
+ }
+ }
+}