AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology
https://avaloniaui.net
MIT License
25.26k stars 2.19k forks source link

Flyouts inside Viewbox scaled incorrectly #9412

Open nitroxis opened 1 year ago

nitroxis commented 1 year ago

Describe the bug Placing a Flyout inside a Button which is itself inside a Viewbox leads to the flyout not respecting the Viewbox' transform.

To Reproduce Do something like:

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="300"
             x:Class="AvaloniaTest.FlyoutTest">
    <Viewbox>
        <Button Width="100" Height="30">
            test
            <Button.Flyout>
                <Flyout>
                    flyout
                </Flyout>
            </Button.Flyout>
        </Button>
    </Viewbox>
</UserControl>

The button will be scaled up to fit the viewbox, but the flyout is very tiny in comparison.

Expected behavior The flyout should have the same scaling as the button, or at least have an option to enable such behavior.

Screenshots image

Desktop (please complete the following information):

nitroxis commented 1 year ago

I tried setting InheritTransform = true in FlyoutBase.CreatePopup, which seems to work as far as scaling is concered. However, this causes an exception when the user resizes the Viewbox while the popup is opened:

System.InvalidOperationException: Visual was invalidated during a render pass
   at Avalonia.Rendering.DirtyVisuals.Add(IVisual visual) in Avalonia\src\Avalonia.Base\Rendering\DirtyVisuals.cs:line 35
   at Avalonia.Rendering.DeferredRenderer.AddDirty(IVisual visual) in Avalonia\src\Avalonia.Base\Rendering\DeferredRenderer.cs:line 121
   at Avalonia.Visual.InvalidateVisual() in Avalonia\src\Avalonia.Base\Visual.cs:line 329
   at Avalonia.Layout.Layoutable.InvalidateMeasure() in Avalonia\src\Avalonia.Base\Layout\Layoutable.cs:line 428
   at Avalonia.Controls.LayoutTransformControl.ApplyLayoutTransform() in Avalonia\src\Avalonia.Controls\LayoutTransformControl.cs:line 272
   at Avalonia.Controls.LayoutTransformControl.OnLayoutTransformChanged(AvaloniaPropertyChangedEventArgs e) in Avalonia\src\Avalonia.Controls\LayoutTransformControl.cs:line 432
   at Avalonia.Controls.LayoutTransformControl.<>c.<.cctor>b__2_0(LayoutTransformControl x, AvaloniaPropertyChangedEventArgs e) in Avalonia\src\Avalonia.Controls\LayoutTransformControl.cs:line 28
   at Avalonia.AvaloniaObjectExtensions.<>c__DisplayClass16_0`1.<AddClassHandler>b__0(AvaloniaPropertyChangedEventArgs e) in Avalonia\src\Avalonia.Base\AvaloniaObjectExtensions.cs:line 442
   at System.Reactive.AnonymousObserver`1.OnNextCore(T value) in /_/Rx.NET/Source/src/System.Reactive/AnonymousObserver.cs:line 67
   at System.Reactive.ObserverBase`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/ObserverBase.cs:line 34
   at System.Reactive.Subjects.Subject`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Subjects/Subject.cs:line 147
   at Avalonia.AvaloniaProperty`1.NotifyChanged(AvaloniaPropertyChangedEventArgs`1 e) in Avalonia\src\Avalonia.Base\AvaloniaProperty`1.cs:line 65
   at Avalonia.AvaloniaObject.RaisePropertyChanged[T](AvaloniaPropertyChangedEventArgs`1 change) in Avalonia\src\Avalonia.Base\AvaloniaObject.cs:line 779
   at Avalonia.AvaloniaObject.ValueChanged[T](AvaloniaPropertyChangedEventArgs`1 change) in Avalonia\src\Avalonia.Base\AvaloniaObject.cs:line 551
   at Avalonia.ValueStore.ValueChanged[T](AvaloniaPropertyChangedEventArgs`1 change) in Avalonia\src\Avalonia.Base\ValueStore.cs:line 235
   at Avalonia.PropertyStore.ValueOwner`1.ValueChanged(AvaloniaPropertyChangedEventArgs`1 e) in Avalonia\src\Avalonia.Base\PropertyStore\ValueOwner.cs:line 40
   at Avalonia.PropertyStore.BindingEntry`1.UpdateValue(BindingValue`1 value) in Avalonia\src\Avalonia.Base\PropertyStore\BindingEntry.cs:line 151
   at Avalonia.PropertyStore.BindingEntry`1.OnNext(BindingValue`1 value) in Avalonia\src\Avalonia.Base\PropertyStore\BindingEntry.cs:line 87
   at Avalonia.Reactive.SingleSubscriberObservableBase`1.PublishNext(T value) in Avalonia\src\Avalonia.Base\Reactive\SingleSubscriberObservableBase.cs:line 49
   at Avalonia.Reactive.TypedBindingAdapter`1.OnNext(BindingValue`1 value) in Avalonia\src\Avalonia.Base\Reactive\TypedBindingAdapter.cs:line 29
   at Avalonia.Reactive.SingleSubscriberObservableBase`1.PublishNext(T value) in Avalonia\src\Avalonia.Base\Reactive\SingleSubscriberObservableBase.cs:line 49
   at Avalonia.Reactive.BindingValueAdapter`1.OnNext(T value) in Avalonia\src\Avalonia.Base\Reactive\BindingValueAdapter.cs:line 16
   at Avalonia.Reactive.SingleSubscriberObservableBase`1.PublishNext(T value) in Avalonia\src\Avalonia.Base\Reactive\SingleSubscriberObservableBase.cs:line 49
   at Avalonia.Data.TemplateBinding.PublishValue() in Avalonia\src\Markup\Avalonia.Markup\Data\TemplateBinding.cs:line 146
   at Avalonia.Data.TemplateBinding.TemplatedParentPropertyChanged(Object sender, AvaloniaPropertyChangedEventArgs e) in Avalonia\src\Markup\Avalonia.Markup\Data\TemplateBinding.cs:line 183
   at Avalonia.AvaloniaObject.RaisePropertyChanged[T](AvaloniaPropertyChangedEventArgs`1 change) in Avalonia\src\Avalonia.Base\AvaloniaObject.cs:line 780
   at Avalonia.AvaloniaObject.ValueChanged[T](AvaloniaPropertyChangedEventArgs`1 change) in Avalonia\src\Avalonia.Base\AvaloniaObject.cs:line 551
   at Avalonia.ValueStore.NotifyValueChanged[T](AvaloniaProperty`1 property, Optional`1 oldValue, BindingValue`1 newValue, BindingPriority priority) in Avalonia\src\Avalonia.Base\ValueStore.cs:line 354
   at Avalonia.ValueStore.SetExisting[T](Object slot, StyledPropertyBase`1 property, T value, BindingPriority priority) in Avalonia\src\Avalonia.Base\ValueStore.cs:line 264
   at Avalonia.ValueStore.SetValue[T](StyledPropertyBase`1 property, T value, BindingPriority priority) in Avalonia\src\Avalonia.Base\ValueStore.cs:line 96
   at Avalonia.AvaloniaObject.SetValue[T](StyledPropertyBase`1 property, T value, BindingPriority priority) in Avalonia\src\Avalonia.Base\AvaloniaObject.cs:line 368
   at Avalonia.Controls.Primitives.OverlayPopupHost.set_Transform(Transform value) in Avalonia\src\Avalonia.Controls\Primitives\OverlayPopupHost.cs:line 42
   at Avalonia.Controls.Primitives.Popup.UpdateHostSizing(IPopupHost popupHost, TopLevel topLevel, IControl placementTarget) in Avalonia\src\Avalonia.Controls\Primitives\Popup.cs:line 597
   at Avalonia.Controls.Primitives.Popup.PlacementTargetPropertyChanged(Object sender, AvaloniaPropertyChangedEventArgs e) in Avalonia\src\Avalonia.Controls\Primitives\Popup.cs:line 882
   at Avalonia.AvaloniaObject.RaisePropertyChanged[T](AvaloniaPropertyChangedEventArgs`1 change) in Avalonia\src\Avalonia.Base\AvaloniaObject.cs:line 780
   at Avalonia.AvaloniaObject.RaisePropertyChanged[T](AvaloniaProperty`1 property, Optional`1 oldValue, BindingValue`1 newValue, BindingPriority priority) in Avalonia\src\Avalonia.Base\AvaloniaObject.cs:line 691
   at Avalonia.AvaloniaObject.SetAndRaise[T](AvaloniaProperty`1 property, T& field, T value) in Avalonia\src\Avalonia.Base\AvaloniaObject.cs:line 721
   at Avalonia.Visual.Avalonia.VisualTree.IVisual.set_TransformedBounds(Nullable`1 value) in Avalonia\src\Avalonia.Base\Visual.cs:line 321
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 262
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 306
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 299
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 306
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(DrawingContext context, Scene scene, VisualNode node, Rect clip, Boolean forceRecurse) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 273
   at Avalonia.Rendering.SceneGraph.SceneBuilder.Update(Scene scene, IVisual visual) in Avalonia\src\Avalonia.Base\Rendering\SceneGraph\SceneBuilder.cs:line 91
   at Avalonia.Rendering.DeferredRenderer.UpdateScene() in Avalonia\src\Avalonia.Base\Rendering\DeferredRenderer.cs:line 661
   at Avalonia.Rendering.DeferredRenderer.Paint(Rect rect) in Avalonia\src\Avalonia.Base\Rendering\DeferredRenderer.cs:line 222
   at Avalonia.Controls.TopLevel.HandlePaint(Rect rect) in Avalonia\src\Avalonia.Controls\TopLevel.cs:line 374
maxkatz6 commented 1 year ago

I tried setting InheritTransform = true in FlyoutBase.CreatePopup, which seems to work as far as scaling is concered

Yes, that's correct IIRC. @grokys

However, this causes an exception when the user resizes the Viewbox while the popup is opened:

Try to replace line 883 on Popup:

UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget);

with

Dispatcher.UIThread.Post(() => UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget));
nitroxis commented 1 year ago

Changing that line does fix the exception.

There is one other problem: Resizing the Viewbox does not reposition the Popup to stay aligned with the Button. It does scale up/down though.

Also, when using a MenuFlyout, sub-menus don't seem to have InheritTransform set to true. On sub-popups this property should probably be set to the same value as the parent, in general?

nitroxis commented 1 year ago

Another possible solution would be to put a VisualLayerManager inside the Viewbox so popups use this as their container. Then it would be necessary to change PopupPositionerExtensions.ConfigurePosition so that it can compute transforms relative to it (e.g. supply the overlay container as an argument)