diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index b6adbde738..8d56086470 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -95,6 +95,10 @@ namespace Avalonia.Android _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset); } + else + { + _imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.ImplicitOnly); + } } private void SurroundingTextChanged(object sender, EventArgs e) diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs index de8d02f188..b24581fb8b 100644 --- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -4,9 +4,12 @@ using Android.Content; using Android.Content.Res; using Android.OS; using Android.Runtime; +using Android.Views; using AndroidX.AppCompat.App; using AndroidX.Lifecycle; +using AndroidRect = Android.Graphics.Rect; + namespace Avalonia.Android { public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler @@ -15,6 +18,7 @@ namespace Avalonia.Android public Action ActivityResult { get; set; } internal AvaloniaView View; + private GlobalLayoutListener _listener; protected override void OnCreate(Bundle savedInstanceState) { @@ -32,6 +36,10 @@ namespace Avalonia.Android base.OnCreate(savedInstanceState); SetContentView(View); + + _listener = new GlobalLayoutListener(View); + + View.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener); } public object Content @@ -57,6 +65,8 @@ namespace Avalonia.Android { View.Content = null; + View.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener); + base.OnDestroy(); } @@ -66,5 +76,20 @@ namespace Avalonia.Android ActivityResult?.Invoke(requestCode, resultCode, data); } + + class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener + { + private AvaloniaView _view; + + public GlobalLayoutListener(AvaloniaView view) + { + _view = view; + } + + public void OnGlobalLayout() + { + _view.TopLevelImpl?.Resize(_view.TopLevelImpl.ClientSize); + } + } } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 5f53eb36c8..3b29dbc726 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -26,6 +26,7 @@ using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Java.Lang; using Math = System.Math; +using AndroidRect = Android.Graphics.Rect; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -63,7 +64,21 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IInputRoot InputRoot { get; private set; } - public virtual Size ClientSize => Size.ToSize(RenderScaling); + public virtual Size ClientSize + { + get + { + AndroidRect rect = new AndroidRect(); + AndroidRect intersection = new AndroidRect(); + + _view.GetWindowVisibleDisplayFrame(intersection); + _view.GetGlobalVisibleRect(rect); + + intersection.Intersect(rect); + + return new PixelSize(intersection.Right - intersection.Left, intersection.Bottom - intersection.Top).ToSize(RenderScaling); + } + } public Size? FrameSize => null; @@ -149,6 +164,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform Resized?.Invoke(size, PlatformResizeReason.Unspecified); } + internal void Resize(Size size) + { + Resized?.Invoke(size, PlatformResizeReason.Layout); + } + class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo { private readonly TopLevelImpl _tl; diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 59a61e3424..f2e90bb6d7 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -177,7 +177,7 @@ namespace Avalonia { return new IndexerDescriptor { - Priority = BindingPriority.TemplatedParent, + Priority = BindingPriority.Template, Property = property, }; } diff --git a/src/Avalonia.Base/Data/BindingPriority.cs b/src/Avalonia.Base/Data/BindingPriority.cs index dd1654f53c..5fd5aae43b 100644 --- a/src/Avalonia.Base/Data/BindingPriority.cs +++ b/src/Avalonia.Base/Data/BindingPriority.cs @@ -1,7 +1,9 @@ +using System; + namespace Avalonia.Data { /// - /// The priority of a binding. + /// The priority of a value or binding. /// public enum BindingPriority { @@ -16,23 +18,22 @@ namespace Avalonia.Data LocalValue = 0, /// - /// A triggered style binding. + /// A triggered style value. /// /// /// A style trigger is a selector such as .class which overrides a - /// binding. In this way, a basic control can have - /// for example a Background from the templated parent which changes when the - /// control has the :pointerover class. + /// value. In this way, a control can have, e.g. a Background from + /// the template which changes when the control has the :pointerover class. /// StyleTrigger, /// - /// A binding to a property on the templated parent. + /// A value from the control's template. /// - TemplatedParent, + Template, /// - /// A style binding. + /// A style value. /// Style, @@ -42,8 +43,11 @@ namespace Avalonia.Data Inherited, /// - /// The binding is uninitialized. + /// The value is uninitialized. /// Unset = int.MaxValue, + + [Obsolete("Use Template priority")] + TemplatedParent = Template, } } diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index 527b63292d..39edab3fc6 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Logging; +using Avalonia.Styling; using Avalonia.VisualTree; #nullable enable @@ -719,9 +720,9 @@ namespace Avalonia.Layout return finalSize; } - protected sealed override void InvalidateStyles() + internal sealed override void InvalidateStyles(bool recurse) { - base.InvalidateStyles(); + base.InvalidateStyles(recurse); InvalidateMeasure(); } @@ -795,6 +796,12 @@ namespace Avalonia.Layout base.OnVisualParentChanged(oldParent, newParent); } + private protected override void OnControlThemeChanged() + { + base.OnControlThemeChanged(); + InvalidateMeasure(); + } + /// /// Called when the layout manager raises a LayoutUpdated event. /// diff --git a/src/Avalonia.Base/PropertyStore/FramePriority.cs b/src/Avalonia.Base/PropertyStore/FramePriority.cs new file mode 100644 index 0000000000..950a8375f2 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/FramePriority.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + internal enum FramePriority : sbyte + { + Animation, + AnimationTemplatedParentTheme, + AnimationTheme, + StyleTrigger, + StyleTriggerTemplatedParentTheme, + StyleTriggerTheme, + Template, + TemplateTemplatedParentTheme, + TemplateTheme, + Style, + StyleTemplatedParentTheme, + StyleTheme, + } + + internal static class FramePriorityExtensions + { + public static FramePriority ToFramePriority(this BindingPriority priority, FrameType type = FrameType.Style) + { + Debug.Assert(priority != BindingPriority.LocalValue); + var p = (int)(priority > 0 ? priority : priority + 1); + return (FramePriority)(p * 3 + (int)type); + } + + public static bool IsType(this FramePriority priority, FrameType type) + { + return (FrameType)((int)priority % 3) == type; + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs index 1d886e7501..50d5333b9f 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs @@ -7,11 +7,11 @@ namespace Avalonia.PropertyStore /// Holds values in a set by one of the SetValue or AddBinding /// overloads with non-LocalValue priority. /// - internal class ImmediateValueFrame : ValueFrame + internal sealed class ImmediateValueFrame : ValueFrame { public ImmediateValueFrame(BindingPriority priority) + : base(priority, FrameType.Style) { - Priority = priority; } public TypedBindingEntry AddBinding( diff --git a/src/Avalonia.Base/PropertyStore/ValueFrame.cs b/src/Avalonia.Base/PropertyStore/ValueFrame.cs index 5ada4b3c84..7a9d1bb13a 100644 --- a/src/Avalonia.Base/PropertyStore/ValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ValueFrame.cs @@ -1,13 +1,18 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; using Avalonia.Utilities; -using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { + internal enum FrameType + { + Style, + TemplatedParentTheme, + Theme, + } + internal abstract class ValueFrame { private List? _entries; @@ -15,11 +20,18 @@ namespace Avalonia.PropertyStore private ValueStore? _owner; private bool _isShared; + protected ValueFrame(BindingPriority priority, FrameType type) + { + Priority = priority; + FramePriority = priority.ToFramePriority(type); + } + public int EntryCount => _index.Count; public bool IsActive => GetIsActive(out _); public ValueStore? Owner => !_isShared ? _owner : throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame"); - public BindingPriority Priority { get; protected set; } + public BindingPriority Priority { get; } + public FramePriority FramePriority { get; } public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property); diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index d858e30212..92e5288255 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Avalonia.Data; using Avalonia.Diagnostics; -using Avalonia.Logging; +using Avalonia.Styling; using Avalonia.Utilities; +using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { @@ -580,6 +582,53 @@ namespace Avalonia.PropertyStore return false; } + public void RemoveFrames(FrameType type) + { + var removed = false; + + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame is not ImmediateValueFrame && frame.FramePriority.IsType(type)) + { + _frames.RemoveAt(i); + frame.Dispose(); + removed = true; + } + } + + if (removed) + { + ++_frameGeneration; + ReevaluateEffectiveValues(); + } + } + + + public void RemoveFrames(IReadOnlyList styles) + { + var removed = false; + + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame is StyleInstance style && styles.Contains(style.Source)) + { + _frames.RemoveAt(i); + frame.Dispose(); + removed = true; + } + } + + if (removed) + { + ++_frameGeneration; + ReevaluateEffectiveValues(); + } + } + public AvaloniaPropertyValue GetDiagnostic(AvaloniaProperty property) { object? value; @@ -612,7 +661,7 @@ namespace Avalonia.PropertyStore { Debug.Assert(!_frames.Contains(frame)); - var index = BinarySearchFrame(frame.Priority); + var index = BinarySearchFrame(frame.FramePriority); _frames.Insert(index, frame); ++_frameGeneration; frame.SetOwner(this); @@ -626,7 +675,7 @@ namespace Avalonia.PropertyStore { Debug.Assert(priority != BindingPriority.LocalValue); - var index = BinarySearchFrame(priority); + var index = BinarySearchFrame(priority.ToFramePriority()); if (index > 0 && _frames[index - 1] is ImmediateValueFrame f && f.Priority == priority && @@ -914,7 +963,7 @@ namespace Avalonia.PropertyStore } } - private int BinarySearchFrame(BindingPriority priority) + private int BinarySearchFrame(FramePriority priority) { var lo = 0; var hi = _frames.Count - 1; @@ -923,7 +972,7 @@ namespace Avalonia.PropertyStore while (lo <= hi) { var i = lo + ((hi - lo) >> 1); - var order = priority - _frames[i].Priority; + var order = priority - _frames[i].FramePriority; if (order <= 0) { diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index c72f398fd9..187edd8335 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using Avalonia.Animation; using Avalonia.Collections; @@ -11,6 +12,7 @@ using Avalonia.Data; using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.LogicalTree; +using Avalonia.PropertyStore; using Avalonia.Styling; namespace Avalonia @@ -69,10 +71,10 @@ namespace Avalonia private IAvaloniaList? _logicalChildren; private IResourceDictionary? _resources; private Styles? _styles; - private bool _styled; + private bool _stylesApplied; + private bool _themeApplied; private ITemplatedControl? _templatedParent; private bool _dataContextUpdating; - private bool _hasPromotedTheme; private ControlTheme? _implicitTheme; /// @@ -141,7 +143,7 @@ namespace Avalonia set { - if (_styled) + if (_stylesApplied) { throw new InvalidOperationException("Cannot set Name : styled element already styled."); } @@ -353,38 +355,33 @@ namespace Avalonia /// public bool ApplyStyling() { - if (_initCount == 0 && !_styled) + if (_initCount == 0 && (!_stylesApplied || !_themeApplied)) { - var hasPromotedTheme = _hasPromotedTheme; - GetValueStore().BeginStyling(); try { - ApplyControlTheme(); - ApplyStyles(this); + if (!_themeApplied) + { + ApplyControlTheme(); + _themeApplied = true; + } + + if (!_stylesApplied) + { + ApplyStyles(this); + _stylesApplied = true; + } } finally { - _styled = true; GetValueStore().EndStyling(); } - - if (hasPromotedTheme) - { - _hasPromotedTheme = false; - ClearValue(ThemeProperty); - } } - return _styled; + return _stylesApplied; } - /// - /// Detaches all styles from the element and queues a restyle. - /// - protected virtual void InvalidateStyles() => DetachStyles(); - protected void InitializeIfNeeded() { if (_initCount == 0 && !IsInitialized) @@ -506,17 +503,16 @@ namespace Avalonia }; } - void IStyleable.DetachStyles() => DetachStyles(); - void IStyleHost.StylesAdded(IReadOnlyList styles) { - InvalidateStylesOnThisAndDescendents(); + if (HasSettersOrAnimations(styles)) + InvalidateStyles(recurse: true); } void IStyleHost.StylesRemoved(IReadOnlyList styles) { - var allStyles = RecurseStyles(styles); - DetachStylesFromThisAndDescendents(allStyles); + if (FlattenStyles(styles) is { } allStyles) + DetachStyles(allStyles); } protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -615,31 +611,25 @@ namespace Avalonia if (change.Property == ThemeProperty) { - var (oldValue, newValue) = change.GetOldAndNewValue(); - - // Changing the theme detaches all styles, meaning that if the theme property was - // set via a style, it will get cleared! To work around this, if the value was - // applied at less than local value priority then promote the value to local value - // priority until styling is re-applied. - if (change.Priority > BindingPriority.LocalValue) - { - Theme = newValue; - _hasPromotedTheme = true; - } - else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue) - { - _hasPromotedTheme = false; - } - - InvalidateStyles(); - - if (oldValue is not null) - DetachControlThemeFromTemplateChildren(oldValue); + OnControlThemeChanged(); + _themeApplied = false; } } - internal virtual void DetachControlThemeFromTemplateChildren(ControlTheme theme) + private protected virtual void OnControlThemeChanged() { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.Theme); } + finally { values.EndStyling(); } + } + + internal virtual void OnTemplatedParentControlThemeChanged() + { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.TemplatedParentTheme); } + finally { values.EndStyling(); } } internal ControlTheme? GetEffectiveTheme() @@ -667,6 +657,23 @@ namespace Avalonia return null; } + internal virtual void InvalidateStyles(bool recurse) + { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.Style); } + finally { values.EndStyling(); } + + _stylesApplied = false; + + if (recurse && GetInheritanceChildren() is { } children) + { + var childCount = children.Count; + for (var i = 0; i < childCount; ++i) + (children[i] as StyledElement)?.InvalidateStyles(recurse); + } + } + private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) { if (o is StyledElement element) @@ -736,26 +743,28 @@ namespace Avalonia var theme = GetEffectiveTheme(); if (theme is not null) - ApplyControlTheme(theme); + ApplyControlTheme(theme, FrameType.Theme); if (TemplatedParent is StyledElement styleableParent && styleableParent.GetEffectiveTheme() is { } parentTheme) { - ApplyControlTheme(parentTheme); + ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme); } } - private void ApplyControlTheme(ControlTheme theme) + private void ApplyControlTheme(ControlTheme theme, FrameType type) { + Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme); + if (theme.BasedOn is ControlTheme basedOn) - ApplyControlTheme(basedOn); + ApplyControlTheme(basedOn, type); - theme.TryAttach(this, null); + theme.TryAttach(this, type); if (theme.HasChildren) { foreach (var child in theme.Children) - ApplyStyle(child, null); + ApplyStyle(child, null, type); } } @@ -768,17 +777,17 @@ namespace Avalonia if (host.IsStylesInitialized) { foreach (var style in host.Styles) - ApplyStyle(style, host); + ApplyStyle(style, host, FrameType.Style); } } - private void ApplyStyle(IStyle style, IStyleHost? host) + private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type) { if (style is Style s) - s.TryAttach(this, host); + s.TryAttach(this, host, type); foreach (var child in style.Children) - ApplyStyle(child, host); + ApplyStyle(child, host, type); } private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) @@ -824,7 +833,7 @@ namespace Avalonia { _logicalRoot = null; _implicitTheme = null; - DetachStyles(); + InvalidateStyles(recurse: false); OnDetachedFromLogicalTree(e); DetachedFromLogicalTree?.Invoke(this, e); @@ -886,70 +895,81 @@ namespace Avalonia } } - private void DetachStyles(IReadOnlyList? styles = null) + private void DetachStyles(IReadOnlyList