using System; using System.Windows; using System.Windows.Controls.Primitives; using System.Windows.Media; namespace WPFluent.Controls.Primitives; internal static class CustomPopupPlacementHelper { public static readonly DependencyProperty PlacementProperty = DependencyProperty.RegisterAttached("Placement", typeof(CustomPlacementMode), typeof(CustomPopupPlacementHelper), new PropertyMetadata(CustomPlacementMode.Top)); public static CustomPlacementMode GetPlacement(DependencyObject element) { return (CustomPlacementMode)element.GetValue(PlacementProperty); } public static void SetPlacement(DependencyObject element, CustomPlacementMode value) { element.SetValue(PlacementProperty, value); } internal static CustomPopupPlacement[] PositionPopup( CustomPlacementMode placement, Size popupSize, Size targetSize, Point offset, FrameworkElement child = null!) { Matrix transformToDevice = default; if (child != null) { Helper.TryGetTransformToDevice(child, out transformToDevice); } var preferredPlacement = CalculatePopupPlacement(placement, popupSize, targetSize, offset, child!, transformToDevice); CustomPopupPlacement? alternativePlacement = null; var alternativePlacementMode = GetAlternativePlacementMode(placement); if (alternativePlacementMode.HasValue) { alternativePlacement = CalculatePopupPlacement(alternativePlacementMode.Value, popupSize, targetSize, offset, child!, transformToDevice); } if (alternativePlacement.HasValue) { return [preferredPlacement, alternativePlacement.Value]; } else { return [preferredPlacement]; } } private static CustomPopupPlacement CalculatePopupPlacement( CustomPlacementMode placement, Size popupSize, Size targetSize, Point offset, FrameworkElement child = null!, Matrix transformToDevice = default) { Point point; PopupPrimaryAxis primaryAxis; switch (placement) { case CustomPlacementMode.Top: point = new Point((targetSize.Width - popupSize.Width) / 2, -popupSize.Height); primaryAxis = PopupPrimaryAxis.Horizontal; break; case CustomPlacementMode.Bottom: point = new Point((targetSize.Width - popupSize.Width) / 2, targetSize.Height); primaryAxis = PopupPrimaryAxis.Horizontal; break; case CustomPlacementMode.Left: point = new Point(-popupSize.Width, (targetSize.Height - popupSize.Height) / 2); primaryAxis = PopupPrimaryAxis.Vertical; break; case CustomPlacementMode.Right: point = new Point(targetSize.Width, (targetSize.Height - popupSize.Height) / 2); primaryAxis = PopupPrimaryAxis.Vertical; break; case CustomPlacementMode.Full: point = new Point((targetSize.Width - popupSize.Width) / 2, (targetSize.Height - popupSize.Height) / 2); primaryAxis = PopupPrimaryAxis.None; break; case CustomPlacementMode.TopEdgeAlignedLeft: point = new Point(0, -popupSize.Height); primaryAxis = PopupPrimaryAxis.Horizontal; break; case CustomPlacementMode.TopEdgeAlignedRight: point = new Point(targetSize.Width - popupSize.Width, -popupSize.Height); primaryAxis = PopupPrimaryAxis.Horizontal; break; case CustomPlacementMode.BottomEdgeAlignedLeft: point = new Point(0, targetSize.Height); primaryAxis = PopupPrimaryAxis.Horizontal; break; case CustomPlacementMode.BottomEdgeAlignedRight: point = new Point(targetSize.Width - popupSize.Width, targetSize.Height); primaryAxis = PopupPrimaryAxis.Horizontal; break; case CustomPlacementMode.LeftEdgeAlignedTop: point = new Point(-popupSize.Width, 0); primaryAxis = PopupPrimaryAxis.Vertical; break; case CustomPlacementMode.LeftEdgeAlignedBottom: point = new Point(-popupSize.Width, targetSize.Height - popupSize.Height); primaryAxis = PopupPrimaryAxis.Vertical; break; case CustomPlacementMode.RightEdgeAlignedTop: point = new Point(targetSize.Width, 0); primaryAxis = PopupPrimaryAxis.Vertical; break; case CustomPlacementMode.RightEdgeAlignedBottom: point = new Point(targetSize.Width, targetSize.Height - popupSize.Height); primaryAxis = PopupPrimaryAxis.Vertical; break; //case CustomPopupPlacementMode.Auto: default: throw new ArgumentOutOfRangeException(nameof(placement)); } if (child != null) { var childOffset = VisualTreeHelper.GetOffset(child); if (transformToDevice != default) { childOffset = transformToDevice.Transform(childOffset); } point -= childOffset; } return new CustomPopupPlacement(point, primaryAxis); } private static CustomPlacementMode? GetAlternativePlacementMode(CustomPlacementMode placement) { switch (placement) { case CustomPlacementMode.Top: return CustomPlacementMode.Bottom; case CustomPlacementMode.Bottom: return CustomPlacementMode.Top; case CustomPlacementMode.Left: return CustomPlacementMode.Right; case CustomPlacementMode.Right: return CustomPlacementMode.Left; case CustomPlacementMode.Full: return null; case CustomPlacementMode.TopEdgeAlignedLeft: return CustomPlacementMode.BottomEdgeAlignedLeft; case CustomPlacementMode.TopEdgeAlignedRight: return CustomPlacementMode.BottomEdgeAlignedRight; case CustomPlacementMode.BottomEdgeAlignedLeft: return CustomPlacementMode.TopEdgeAlignedLeft; case CustomPlacementMode.BottomEdgeAlignedRight: return CustomPlacementMode.TopEdgeAlignedRight; case CustomPlacementMode.LeftEdgeAlignedTop: return CustomPlacementMode.RightEdgeAlignedTop; case CustomPlacementMode.LeftEdgeAlignedBottom: return CustomPlacementMode.RightEdgeAlignedBottom; case CustomPlacementMode.RightEdgeAlignedTop: return CustomPlacementMode.RightEdgeAlignedTop; case CustomPlacementMode.RightEdgeAlignedBottom: return CustomPlacementMode.LeftEdgeAlignedBottom; //case CustomPopupPlacementMode.Auto: default: return null; } } } internal enum CustomPlacementMode { Top = 0, Bottom = 1, Left = 2, Right = 3, Full = 4, TopEdgeAlignedLeft = 5, TopEdgeAlignedRight = 6, BottomEdgeAlignedLeft = 7, BottomEdgeAlignedRight = 8, LeftEdgeAlignedTop = 9, LeftEdgeAlignedBottom = 10, RightEdgeAlignedTop = 11, RightEdgeAlignedBottom = 12, //Auto = 13 } file static class Helper { public static bool TryGetTransformToDevice(Visual visual, out Matrix value) { var presentationSource = PresentationSource.FromVisual(visual); if (presentationSource != null) { value = presentationSource.CompositionTarget.TransformToDevice; return true; } value = default; return false; } }