Browse Source

Merge branch 'master' into feature/4886-expose-skia-currentopacity

Max Katz 3 years ago
parent
commit
9ff0317613

+ 10 - 0
samples/ControlCatalog/Pages/ComboBoxPage.xaml

@@ -86,6 +86,16 @@
                         <sys:Exception />
                         <sys:Exception />
                     </DataValidationErrors.Error>
                     </DataValidationErrors.Error>
                 </ComboBox>
                 </ComboBox>
+
+                <ComboBox PlaceholderText="Scaled" Width="166" RenderTransformOrigin="0,0">
+                    <ComboBox.RenderTransform>
+                      <ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
+                    </ComboBox.RenderTransform>
+                    <ComboBoxItem>Inline Items</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 2</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 3</ComboBoxItem>
+                    <ComboBoxItem>Inline Item 4</ComboBoxItem>
+                </ComboBox>
             </WrapPanel>
             </WrapPanel>
 
 
             <CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>
             <CheckBox IsChecked="{Binding WrapSelection}">WrapSelection</CheckBox>

+ 44 - 12
src/Avalonia.Controls/Primitives/IPopupHost.cs

@@ -2,6 +2,7 @@ using System;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Input;
 using Avalonia.Input;
+using Avalonia.Media;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
 
 
 namespace Avalonia.Controls.Primitives
 namespace Avalonia.Controls.Primitives
@@ -17,16 +18,50 @@ namespace Avalonia.Controls.Primitives
     public interface IPopupHost : IDisposable, IFocusScope
     public interface IPopupHost : IDisposable, IFocusScope
     {
     {
         /// <summary>
         /// <summary>
-        /// Sets the control to display in the popup.
+        /// Gets or sets the fixed width of the popup.
         /// </summary>
         /// </summary>
-        /// <param name="control"></param>
-        void SetChild(IControl? control);
+        double Width { get; set; }
+
+        /// <summary>
+        /// Gets or sets the minimum width of the popup.
+        /// </summary>
+        double MinWidth { get; set; }
+
+        /// <summary>
+        /// Gets or sets the maximum width of the popup.
+        /// </summary>
+        double MaxWidth { get; set; }
+
+        /// <summary>
+        /// Gets or sets the fixed height of the popup.
+        /// </summary>
+        double Height { get; set; }
+
+        /// <summary>
+        /// Gets or sets the minimum height of the popup.
+        /// </summary>
+        double MinHeight { get; set; }
+
+        /// <summary>
+        /// Gets or sets the maximum height of the popup.
+        /// </summary>
+        double MaxHeight { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the presenter from the control's template.
         /// Gets the presenter from the control's template.
         /// </summary>
         /// </summary>
         IContentPresenter? Presenter { get; }
         IContentPresenter? Presenter { get; }
 
 
+        /// <summary>
+        /// Gets or sets whether the popup appears on top of all other windows.
+        /// </summary>
+        bool Topmost { get; set; }
+
+        /// <summary>
+        /// Gets or sets a transform that will be applied to the popup.
+        /// </summary>
+        Transform? Transform { get; set; }
+
         /// <summary>
         /// <summary>
         /// Gets the root of the visual tree in the case where the popup is presented using a
         /// Gets the root of the visual tree in the case where the popup is presented using a
         /// separate visual tree.
         /// separate visual tree.
@@ -57,6 +92,12 @@ namespace Avalonia.Controls.Primitives
             PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,
             PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,
             Rect? rect = null);
             Rect? rect = null);
 
 
+        /// <summary>
+        /// Sets the control to display in the popup.
+        /// </summary>
+        /// <param name="control"></param>
+        void SetChild(IControl? control);
+
         /// <summary>
         /// <summary>
         /// Shows the popup.
         /// Shows the popup.
         /// </summary>
         /// </summary>
@@ -66,14 +107,5 @@ namespace Avalonia.Controls.Primitives
         /// Hides the popup.
         /// Hides the popup.
         /// </summary>
         /// </summary>
         void Hide();
         void Hide();
-
-        /// <summary>
-        /// Binds the constraints of the popup host to a set of properties, usally those present on
-        /// <see cref="Popup"/>.
-        /// </summary>
-        IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty,
-            StyledProperty<double> minWidthProperty, StyledProperty<double> maxWidthProperty,
-            StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
-            StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty);
     }
     }
 }
 }

+ 19 - 23
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@@ -11,6 +11,12 @@ namespace Avalonia.Controls.Primitives
 {
 {
     public class OverlayPopupHost : ContentControl, IPopupHost, IInteractive, IManagedPopupPositionerPopup
     public class OverlayPopupHost : ContentControl, IPopupHost, IInteractive, IManagedPopupPositionerPopup
     {
     {
+        /// <summary>
+        /// Defines the <see cref="Transform"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Transform?> TransformProperty =
+            PopupRoot.TransformProperty.AddOwner<OverlayPopupHost>();
+
         private readonly OverlayLayer _overlayLayer;
         private readonly OverlayLayer _overlayLayer;
         private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters();
         private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters();
         private ManagedPopupPositioner _positioner;
         private ManagedPopupPositioner _positioner;
@@ -29,10 +35,22 @@ namespace Avalonia.Controls.Primitives
         }
         }
 
 
         public IVisual? HostedVisualTreeRoot => null;
         public IVisual? HostedVisualTreeRoot => null;
-        
+
+        public Transform? Transform
+        {
+            get => GetValue(TransformProperty);
+            set => SetValue(TransformProperty, value);
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         IInteractive? IInteractive.InteractiveParent => Parent;
         IInteractive? IInteractive.InteractiveParent => Parent;
 
 
+        bool IPopupHost.Topmost
+        {
+            get => false;
+            set { /* Not currently supported in overlay popups */ }
+        }
+
         public void Dispose() => Hide();
         public void Dispose() => Hide();
 
 
 
 
@@ -48,28 +66,6 @@ namespace Avalonia.Controls.Primitives
             _shown = false;
             _shown = false;
         }
         }
 
 
-        public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty,
-            StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
-            StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
-        {
-            // Topmost property is not supported
-            var bindings = new List<IDisposable>();
-
-            void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
-            Bind(WidthProperty, widthProperty);
-            Bind(MinWidthProperty, minWidthProperty);
-            Bind(MaxWidthProperty, maxWidthProperty);
-            Bind(HeightProperty, heightProperty);
-            Bind(MinHeightProperty, minHeightProperty);
-            Bind(MaxHeightProperty, maxHeightProperty);
-            
-            return Disposable.Create(() =>
-            {
-                foreach (var x in bindings)
-                    x.Dispose();
-            });
-        }
-
         public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
         public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
             PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None,
             PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None,
             PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,
             PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,

+ 134 - 16
src/Avalonia.Controls/Primitives/Popup.cs

@@ -14,6 +14,8 @@ using Avalonia.LogicalTree;
 using Avalonia.Metadata;
 using Avalonia.Metadata;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
+using Avalonia.Media;
+using Avalonia.Utilities;
 
 
 namespace Avalonia.Controls.Primitives
 namespace Avalonia.Controls.Primitives
 {
 {
@@ -33,6 +35,12 @@ namespace Avalonia.Controls.Primitives
         public static readonly StyledProperty<Control?> ChildProperty =
         public static readonly StyledProperty<Control?> ChildProperty =
             AvaloniaProperty.Register<Popup, Control?>(nameof(Child));
             AvaloniaProperty.Register<Popup, Control?>(nameof(Child));
 
 
+        /// <summary>
+        /// Defines the <see cref="InheritsTransform"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> InheritsTransformProperty =
+            AvaloniaProperty.Register<Popup, bool>(nameof(InheritsTransform));
+
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsOpen"/> property.
         /// Defines the <see cref="IsOpen"/> property.
         /// </summary>
         /// </summary>
@@ -196,6 +204,16 @@ namespace Avalonia.Controls.Primitives
             set;
             set;
         }
         }
 
 
+        /// <summary>
+        /// Gets or sets a value that determines whether the popup inherits the render transform
+        /// from its <see cref="PlacementTarget"/>. Defaults to false.
+        /// </summary>
+        public bool InheritsTransform
+        {
+            get => GetValue(InheritsTransformProperty);
+            set => SetValue(InheritsTransformProperty, value);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets or sets a value that determines how the <see cref="Popup"/> can be dismissed.
         /// Gets or sets a value that determines how the <see cref="Popup"/> can be dismissed.
         /// </summary>
         /// </summary>
@@ -395,24 +413,29 @@ namespace Avalonia.Controls.Primitives
             }
             }
 
 
             _isOpenRequested = false;
             _isOpenRequested = false;
-            var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
 
 
+            var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
             var handlerCleanup = new CompositeDisposable(7);
             var handlerCleanup = new CompositeDisposable(7);
 
 
-            popupHost.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty,
-                HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty).DisposeWith(handlerCleanup);
-
+            UpdateHostSizing(popupHost, topLevel, placementTarget);
+            popupHost.Topmost = Topmost;
             popupHost.SetChild(Child);
             popupHost.SetChild(Child);
             ((ISetLogicalParent)popupHost).SetParent(this);
             ((ISetLogicalParent)popupHost).SetParent(this);
 
 
-            popupHost.ConfigurePosition(
-                placementTarget,
-                PlacementMode,
-                new Point(HorizontalOffset, VerticalOffset),
-                PlacementAnchor,
-                PlacementGravity,
-                PlacementConstraintAdjustment,
-                PlacementRect);
+            if (InheritsTransform && placementTarget is Control c)
+            {
+                SubscribeToEventHandler<Control, EventHandler<AvaloniaPropertyChangedEventArgs>>(
+                    c,
+                    PlacementTargetPropertyChanged,
+                    (x, handler) => x.PropertyChanged += handler,
+                    (x, handler) => x.PropertyChanged -= handler).DisposeWith(handlerCleanup);
+            }
+            else
+            {
+                popupHost.Transform = null;
+            }
+
+            UpdateHostPosition(popupHost, placementTarget);
 
 
             SubscribeToEventHandler<IPopupHost, EventHandler<TemplateAppliedEventArgs>>(popupHost, RootTemplateApplied,
             SubscribeToEventHandler<IPopupHost, EventHandler<TemplateAppliedEventArgs>>(popupHost, RootTemplateApplied,
                 (x, handler) => x.TemplateApplied += handler,
                 (x, handler) => x.TemplateApplied += handler,
@@ -494,7 +517,7 @@ namespace Avalonia.Controls.Primitives
                 }
                 }
             }
             }
 
 
-            _openState = new PopupOpenState(topLevel, popupHost, cleanupPopup);
+            _openState = new PopupOpenState(placementTarget, topLevel, popupHost, cleanupPopup);
 
 
             WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint);
             WindowManagerAddShadowHintChanged(popupHost, WindowManagerAddShadowHint);
 
 
@@ -542,7 +565,93 @@ namespace Avalonia.Controls.Primitives
             base.OnDetachedFromLogicalTree(e);
             base.OnDetachedFromLogicalTree(e);
             Close();
             Close();
         }
         }
-        
+
+        protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+        {
+            if (_openState is not null)
+            {
+                if (change.Property == WidthProperty ||
+                    change.Property == MinWidthProperty ||
+                    change.Property == MaxWidthProperty ||
+                    change.Property == HeightProperty ||
+                    change.Property == MinHeightProperty ||
+                    change.Property == MaxHeightProperty)
+                {
+                    UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget);
+                }
+                else if (change.Property == PlacementTargetProperty ||
+                         change.Property == PlacementModeProperty ||
+                         change.Property == HorizontalOffsetProperty ||
+                         change.Property == VerticalOffsetProperty ||
+                         change.Property == PlacementAnchorProperty ||
+                         change.Property == PlacementConstraintAdjustmentProperty ||
+                         change.Property == PlacementRectProperty)
+                {
+                    if (change.Property == PlacementTargetProperty)
+                    {
+                        var newTarget = change.GetNewValue<Control?>() ?? this.FindLogicalAncestorOfType<IControl>();
+
+                        if (newTarget is null || newTarget.GetVisualRoot() != _openState.TopLevel)
+                        {
+                            Close();
+                            return;
+                        }
+
+                        _openState.PlacementTarget = newTarget;
+                    }
+
+                    UpdateHostPosition(_openState.PopupHost, _openState.PlacementTarget);
+                }
+                else if (change.Property == TopmostProperty)
+                {
+                    _openState.PopupHost.Topmost = change.GetNewValue<bool>();
+                }
+            }
+        }
+
+        private void UpdateHostPosition(IPopupHost popupHost, IControl placementTarget)
+        {
+            popupHost.ConfigurePosition(
+                placementTarget,
+                PlacementMode,
+                new Point(HorizontalOffset, VerticalOffset),
+                PlacementAnchor,
+                PlacementGravity,
+                PlacementConstraintAdjustment,
+                PlacementRect ?? new Rect(default, placementTarget.Bounds.Size));
+        }
+
+        private void UpdateHostSizing(IPopupHost popupHost, TopLevel topLevel, IControl placementTarget)
+        {
+            var scaleX = 1.0;
+            var scaleY = 1.0;
+
+            if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is Matrix m)
+            {
+                scaleX = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12);
+                scaleY = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12);
+
+                // Ideally we'd only assign a ScaleTransform here when the scale != 1, but there's
+                // an issue with LayoutTransformControl in that it sets its LayoutTransform property
+                // with LocalValue priority in ArrangeOverride in certain cases when LayoutTransform
+                // is null, which breaks TemplateBindings to this property. Offending commit/line:
+                //
+                // https://github.com/AvaloniaUI/Avalonia/commit/6fbe1c2180ef45a940e193f1b4637e64eaab80ed#diff-5344e793df13f462126a8153ef46c44194f244b6890f25501709bae51df97f82R54
+                popupHost.Transform = new ScaleTransform(scaleX, scaleY);
+            }
+            else
+            {
+                popupHost.Transform = null;
+            }
+
+            popupHost.Width = Width * scaleX;
+            popupHost.MinWidth = MinWidth * scaleX;
+            popupHost.MaxWidth = MaxWidth * scaleX;
+            popupHost.Height = Height * scaleY;
+            popupHost.MinHeight = MinHeight * scaleY;
+            popupHost.MaxHeight = MaxHeight * scaleY;
+        }
+
         private void HandlePositionChange()
         private void HandlePositionChange()
         {
         {
             if (_openState != null)
             if (_openState != null)
@@ -824,6 +933,14 @@ namespace Avalonia.Controls.Primitives
             }
             }
         }
         }
         
         
+        private void PlacementTargetPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (_openState is not null && e.Property == Visual.TransformedBoundsProperty)
+            {
+                UpdateHostSizing(_openState.PopupHost, _openState.TopLevel, _openState.PlacementTarget);
+            }
+        }
+
         private void WindowLostFocus()
         private void WindowLostFocus()
         {
         {
             if (IsLightDismissEnabled)
             if (IsLightDismissEnabled)
@@ -862,15 +979,16 @@ namespace Avalonia.Controls.Primitives
             private readonly IDisposable _cleanup;
             private readonly IDisposable _cleanup;
             private IDisposable? _presenterCleanup;
             private IDisposable? _presenterCleanup;
 
 
-            public PopupOpenState(TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup)
+            public PopupOpenState(IControl placementTarget, TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup)
             {
             {
+                PlacementTarget = placementTarget;
                 TopLevel = topLevel;
                 TopLevel = topLevel;
                 PopupHost = popupHost;
                 PopupHost = popupHost;
                 _cleanup = cleanup;
                 _cleanup = cleanup;
             }
             }
 
 
             public TopLevel TopLevel { get; }
             public TopLevel TopLevel { get; }
-
+            public IControl PlacementTarget { get; set; }
             public IPopupHost PopupHost { get; }
             public IPopupHost PopupHost { get; }
 
 
             public void SetPresenterSubscription(IDisposable? presenterCleanup)
             public void SetPresenterSubscription(IDisposable? presenterCleanup)

+ 15 - 24
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -1,6 +1,4 @@
 using System;
 using System;
-using System.Collections.Generic;
-using System.Reactive.Disposables;
 using Avalonia.Automation.Peers;
 using Avalonia.Automation.Peers;
 using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
@@ -8,7 +6,6 @@ using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.Styling;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
 using Avalonia.VisualTree;
-using JetBrains.Annotations;
 
 
 namespace Avalonia.Controls.Primitives
 namespace Avalonia.Controls.Primitives
 {
 {
@@ -17,6 +14,12 @@ namespace Avalonia.Controls.Primitives
     /// </summary>
     /// </summary>
     public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost
     public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost
     {
     {
+        /// <summary>
+        /// Defines the <see cref="Transform"/> property.
+        /// </summary>
+        public static readonly StyledProperty<Transform?> TransformProperty =
+            AvaloniaProperty.Register<PopupRoot, Transform?>(nameof(Transform));
+
         private PopupPositionerParameters _positionerParameters;        
         private PopupPositionerParameters _positionerParameters;        
 
 
         /// <summary>
         /// <summary>
@@ -54,6 +57,15 @@ namespace Avalonia.Controls.Primitives
         /// </summary>
         /// </summary>
         public new IPopupImpl? PlatformImpl => (IPopupImpl?)base.PlatformImpl;               
         public new IPopupImpl? PlatformImpl => (IPopupImpl?)base.PlatformImpl;               
 
 
+        /// <summary>
+        /// Gets or sets a transform that will be applied to the popup.
+        /// </summary>
+        public Transform? Transform
+        {
+            get => GetValue(TransformProperty);
+            set => SetValue(TransformProperty, value);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the parent control in the event route.
         /// Gets the parent control in the event route.
         /// </summary>
         /// </summary>
@@ -103,27 +115,6 @@ namespace Avalonia.Controls.Primitives
 
 
         IVisual IPopupHost.HostedVisualTreeRoot => this;
         IVisual IPopupHost.HostedVisualTreeRoot => this;
         
         
-        public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty,
-            StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
-            StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
-        {
-            var bindings = new List<IDisposable>();
-
-            void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
-            Bind(WidthProperty, widthProperty);
-            Bind(MinWidthProperty, minWidthProperty);
-            Bind(MaxWidthProperty, maxWidthProperty);
-            Bind(HeightProperty, heightProperty);
-            Bind(MinHeightProperty, minHeightProperty);
-            Bind(MaxHeightProperty, maxHeightProperty);
-            Bind(TopmostProperty, topmostProperty);
-            return Disposable.Create(() =>
-            {
-                foreach (var x in bindings)
-                    x.Dispose();
-            });
-        }
-
         protected override Size MeasureOverride(Size availableSize)
         protected override Size MeasureOverride(Size availableSize)
         {
         {
             var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity;
             var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity;

+ 10 - 8
src/Avalonia.Themes.Default/Controls/OverlayPopupHost.xaml

@@ -1,4 +1,4 @@
-<Style xmlns="https://github.com/avaloniaui" 
+<Style xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Selector="OverlayPopupHost">
        Selector="OverlayPopupHost">
   <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
   <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
@@ -9,13 +9,15 @@
   <Setter Property="Template">
   <Setter Property="Template">
     <ControlTemplate>
     <ControlTemplate>
       <!--  Do not forget to update Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent test  -->
       <!--  Do not forget to update Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent test  -->
-      <VisualLayerManager IsPopup="True">
-        <ContentPresenter Name="PART_ContentPresenter"
-                          Background="{TemplateBinding Background}"
-                          ContentTemplate="{TemplateBinding ContentTemplate}"
-                          Content="{TemplateBinding Content}"
-                          Padding="{TemplateBinding Padding}"/>
-      </VisualLayerManager>
+      <LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">
+        <VisualLayerManager IsPopup="True">
+          <ContentPresenter Name="PART_ContentPresenter"
+                            Background="{TemplateBinding Background}"
+                            ContentTemplate="{TemplateBinding ContentTemplate}"
+                            Content="{TemplateBinding Content}"
+                            Padding="{TemplateBinding Padding}"/>
+        </VisualLayerManager>
+      </LayoutTransformControl>
     </ControlTemplate>
     </ControlTemplate>
   </Setter>
   </Setter>
 </Style>
 </Style>

+ 12 - 10
src/Avalonia.Themes.Default/Controls/PopupRoot.xaml

@@ -10,16 +10,18 @@
   <Setter Property="FontStyle" Value="Normal" />
   <Setter Property="FontStyle" Value="Normal" />
   <Setter Property="Template">
   <Setter Property="Template">
     <ControlTemplate>
     <ControlTemplate>
-      <Panel>
-        <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
-        <VisualLayerManager IsPopup="True">
-          <ContentPresenter Name="PART_ContentPresenter"
-                            Background="{TemplateBinding Background}"
-                            ContentTemplate="{TemplateBinding ContentTemplate}"
-                            Content="{TemplateBinding Content}"
-                            Padding="{TemplateBinding Padding}"/>
-        </VisualLayerManager>
-      </Panel>
+      <LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">
+        <Panel>
+          <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
+          <VisualLayerManager IsPopup="True">
+            <ContentPresenter Name="PART_ContentPresenter"
+                              Background="{TemplateBinding Background}"
+                              ContentTemplate="{TemplateBinding ContentTemplate}"
+                              Content="{TemplateBinding Content}"
+                              Padding="{TemplateBinding Padding}"/>
+          </VisualLayerManager>
+        </Panel>
+      </LayoutTransformControl>
     </ControlTemplate>
     </ControlTemplate>
   </Setter>
   </Setter>
 </Style>
 </Style>

+ 2 - 1
src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml

@@ -119,7 +119,8 @@
                    MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
                    MinWidth="{Binding Bounds.Width, RelativeSource={RelativeSource TemplatedParent}}"
                    MaxHeight="{TemplateBinding MaxDropDownHeight}"
                    MaxHeight="{TemplateBinding MaxDropDownHeight}"
                    PlacementTarget="Background"
                    PlacementTarget="Background"
-                   IsLightDismissEnabled="True">
+                   IsLightDismissEnabled="True"
+                   InheritsTransform="True">
               <Border x:Name="PopupBorder"
               <Border x:Name="PopupBorder"
                       Background="{DynamicResource ComboBoxDropDownBackground}"
                       Background="{DynamicResource ComboBoxDropDownBackground}"
                       BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}"
                       BorderBrush="{DynamicResource ComboBoxDropDownBorderBrush}"

+ 9 - 7
src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml

@@ -6,13 +6,15 @@
   <Setter Property="FontStyle" Value="Normal" />
   <Setter Property="FontStyle" Value="Normal" />
   <Setter Property="Template">
   <Setter Property="Template">
     <ControlTemplate>
     <ControlTemplate>
-      <VisualLayerManager IsPopup="True">
-        <ContentPresenter Name="PART_ContentPresenter"
-                          Background="{TemplateBinding Background}"
-                          ContentTemplate="{TemplateBinding ContentTemplate}"
-                          Content="{TemplateBinding Content}"
-                          Padding="{TemplateBinding Padding}"/>
-      </VisualLayerManager>
+      <LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">
+        <VisualLayerManager IsPopup="True">
+          <ContentPresenter Name="PART_ContentPresenter"
+                            Background="{TemplateBinding Background}"
+                            ContentTemplate="{TemplateBinding ContentTemplate}"
+                            Content="{TemplateBinding Content}"
+                            Padding="{TemplateBinding Padding}"/>
+        </VisualLayerManager>
+      </LayoutTransformControl>
     </ControlTemplate>
     </ControlTemplate>
   </Setter>
   </Setter>
 </Style>
 </Style>

+ 8 - 6
src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml

@@ -10,16 +10,18 @@
     <Setter Property="FontStyle" Value="Normal" />
     <Setter Property="FontStyle" Value="Normal" />
     <Setter Property="Template">
     <Setter Property="Template">
       <ControlTemplate>
       <ControlTemplate>
-        <Panel>
-          <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
-          <VisualLayerManager IsPopup="True">
-            <ContentPresenter Name="PART_ContentPresenter"
+        <LayoutTransformControl LayoutTransform="{TemplateBinding Transform}">
+          <Panel>
+            <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
+            <VisualLayerManager IsPopup="True">
+              <ContentPresenter Name="PART_ContentPresenter"
                               Background="{TemplateBinding Background}"
                               Background="{TemplateBinding Background}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               ContentTemplate="{TemplateBinding ContentTemplate}"
                               Content="{TemplateBinding Content}"
                               Content="{TemplateBinding Content}"
                               Padding="{TemplateBinding Padding}"/>
                               Padding="{TemplateBinding Padding}"/>
-          </VisualLayerManager>
-        </Panel>
+            </VisualLayerManager>
+          </Panel>
+        </LayoutTransformControl>
       </ControlTemplate>
       </ControlTemplate>
     </Setter>
     </Setter>
   </Style>
   </Style>

+ 6 - 2
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@@ -78,9 +78,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
 
                 var templatedChild = ((Visual)target.Host).GetVisualChildren().Single();
                 var templatedChild = ((Visual)target.Host).GetVisualChildren().Single();
                 
                 
-                Assert.IsType<Panel>(templatedChild);
+                Assert.IsType<LayoutTransformControl>(templatedChild);
 
 
-                var visualLayerManager = templatedChild.GetVisualChildren().Skip(1).Single();
+                var panel = templatedChild.GetVisualChildren().Single();
+
+                Assert.IsType<Panel>(panel);
+
+                var visualLayerManager = panel.GetVisualChildren().Skip(1).Single();
 
 
                 Assert.IsType<VisualLayerManager>(visualLayerManager);
                 Assert.IsType<VisualLayerManager>(visualLayerManager);
 
 

+ 4 - 0
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@@ -325,6 +325,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                     Assert.Equal(
                     Assert.Equal(
                         new[]
                         new[]
                         {
                         {
+                            "LayoutTransformControl",
                             "VisualLayerManager",
                             "VisualLayerManager",
                             "ContentPresenter",
                             "ContentPresenter",
                             "ContentPresenter",
                             "ContentPresenter",
@@ -337,6 +338,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                     Assert.Equal(
                     Assert.Equal(
                         new[]
                         new[]
                         {
                         {
+                            "LayoutTransformControl",
                             "Panel",
                             "Panel",
                             "Border",
                             "Border",
                             "VisualLayerManager",
                             "VisualLayerManager",
@@ -356,6 +358,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                     Assert.Equal(
                     Assert.Equal(
                         new object[]
                         new object[]
                         {
                         {
+                            popupRoot,
                             popupRoot,
                             popupRoot,
                             popupRoot,
                             popupRoot,
                             target,
                             target,
@@ -372,6 +375,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
                             popupRoot,
                             popupRoot,
                             popupRoot,
                             popupRoot,
                             popupRoot,
                             popupRoot,
+                            popupRoot,
                             target,
                             target,
                             null,
                             null,
                         },
                         },