2 changed files with 156 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
public enum InputPaneAwareBehavior |
||||
|
{ |
||||
|
None = 0, |
||||
|
Pan, |
||||
|
Resize |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,147 @@ |
|||||
|
using Avalonia.Animation; |
||||
|
using Avalonia.Animation.Easings; |
||||
|
using Avalonia.Controls.Platform; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A control that automatically adjusts its child position or height based on the input pane's height to ensure the content is visible.
|
||||
|
/// </summary>
|
||||
|
public class InputPaneAwareView : Decorator |
||||
|
{ |
||||
|
private InputPaneAwareBehavior _behavior; |
||||
|
private IInputPane? _inputPane; |
||||
|
private bool _firstLayoutDone; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Behavior"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<InputPaneAwareView, InputPaneAwareBehavior> BehaviorProperty = AvaloniaProperty.RegisterDirect<InputPaneAwareView, InputPaneAwareBehavior>( |
||||
|
nameof(Behavior), |
||||
|
o => o.Behavior, |
||||
|
(o, x) => o.Behavior = x); |
||||
|
|
||||
|
private static readonly StyledProperty<double> CurrentInputPanePaddingProperty = AvaloniaProperty.Register<InputPaneAwareView, double>( |
||||
|
nameof(CurrentInputPanePadding), coerce: (o, x) => |
||||
|
{ |
||||
|
return x; |
||||
|
}); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets how the view reacts to the input pane state.
|
||||
|
/// </summary>
|
||||
|
public InputPaneAwareBehavior Behavior |
||||
|
{ |
||||
|
get => _behavior; |
||||
|
set => SetAndRaise(BehaviorProperty, ref _behavior, value); |
||||
|
} |
||||
|
|
||||
|
private double CurrentInputPanePadding |
||||
|
{ |
||||
|
get => GetValue(CurrentInputPanePaddingProperty); |
||||
|
set => SetValue(CurrentInputPanePaddingProperty, value); |
||||
|
} |
||||
|
|
||||
|
static InputPaneAwareView() |
||||
|
{ |
||||
|
AffectsMeasure<InputPaneAwareView>(BehaviorProperty, CurrentInputPanePaddingProperty); |
||||
|
AffectsArrange<InputPaneAwareView>(CurrentInputPanePaddingProperty); |
||||
|
} |
||||
|
|
||||
|
protected override Size MeasureOverride(Size availableSize) |
||||
|
{ |
||||
|
if (_inputPane != null && _firstLayoutDone && VisualRoot is { } root && Behavior == InputPaneAwareBehavior.Resize) |
||||
|
{ |
||||
|
SetCurrentValue(PaddingProperty, new Thickness(0, 0, 0, CurrentInputPanePadding)); |
||||
|
} |
||||
|
return base.MeasureOverride(availableSize); |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
_firstLayoutDone = true; |
||||
|
|
||||
|
if (Behavior == InputPaneAwareBehavior.Pan) |
||||
|
{ |
||||
|
Child?.Arrange(new Rect(new Point(0, -CurrentInputPanePadding), finalSize)); |
||||
|
|
||||
|
return finalSize; |
||||
|
} |
||||
|
else |
||||
|
return base.ArrangeOverride(finalSize); |
||||
|
} |
||||
|
|
||||
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
||||
|
{ |
||||
|
base.OnAttachedToVisualTree(e); |
||||
|
|
||||
|
_inputPane = TopLevel.GetTopLevel(this)?.InputPane; |
||||
|
_inputPane?.StateChanged += InputPaneAwareView_StateChanged; |
||||
|
EnsureInputPanePaddingApplied(); |
||||
|
} |
||||
|
|
||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
||||
|
{ |
||||
|
base.OnDetachedFromVisualTree(e); |
||||
|
_inputPane?.StateChanged -= InputPaneAwareView_StateChanged; |
||||
|
_inputPane = null; |
||||
|
SetCurrentValue(PaddingProperty, default); |
||||
|
#pragma warning disable AVP1012 // An AvaloniaObject should use SetCurrentValue when assigning its own StyledProperty or AttachedProperty values
|
||||
|
CurrentInputPanePadding = 0; |
||||
|
#pragma warning restore AVP1012 // An AvaloniaObject should use SetCurrentValue when assigning its own StyledProperty or AttachedProperty values
|
||||
|
} |
||||
|
|
||||
|
private void EnsureInputPanePaddingApplied() |
||||
|
{ |
||||
|
SetCurrentValue(PaddingProperty, default); |
||||
|
|
||||
|
if (_inputPane != null && _firstLayoutDone && this.VisualRoot is { } root && Behavior != InputPaneAwareBehavior.None) |
||||
|
{ |
||||
|
var occludedRect = _inputPane.OccludedRect; |
||||
|
|
||||
|
var transformMatrix = this.TransformToVisual(root) ?? Matrix.Identity; |
||||
|
|
||||
|
var translatedRect = Bounds.TransformToAABB(transformMatrix); |
||||
|
|
||||
|
var intersect = occludedRect.Intersect(translatedRect); |
||||
|
|
||||
|
#pragma warning disable AVP1012 // An AvaloniaObject should use SetCurrentValue when assigning its own StyledProperty or AttachedProperty values
|
||||
|
CurrentInputPanePadding = intersect.Height; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
CurrentInputPanePadding = 0; |
||||
|
} |
||||
|
#pragma warning restore AVP1012 // An AvaloniaObject should use SetCurrentValue when assigning its own StyledProperty or AttachedProperty values
|
||||
|
} |
||||
|
|
||||
|
private void InputPaneAwareView_StateChanged(object? sender, InputPaneStateEventArgs e) |
||||
|
{ |
||||
|
var transition = new DoubleTransition() |
||||
|
{ |
||||
|
Property = CurrentInputPanePaddingProperty, |
||||
|
Duration = e.AnimationDuration, |
||||
|
Easing = e.Easing as Easing ?? new LinearEasing() |
||||
|
}; |
||||
|
|
||||
|
Transitions = |
||||
|
[ |
||||
|
transition |
||||
|
]; |
||||
|
|
||||
|
EnsureInputPanePaddingApplied(); |
||||
|
|
||||
|
InvalidateMeasure(); |
||||
|
} |
||||
|
|
||||
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
||||
|
{ |
||||
|
base.OnPropertyChanged(change); |
||||
|
|
||||
|
if(change.Property == BehaviorProperty) |
||||
|
{ |
||||
|
EnsureInputPanePaddingApplied(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue