/// <summary> /// Converts a rectangle from an Avalon Rect to a Win32 RECT /// </summary> /// <remarks> /// Rounds "double" values to the nearest "int" /// </remarks> /// <param name="rect"> /// The rectangle as an Avalon Rect /// </param> /// <returns> /// The rectangle as a Win32 RECT /// </returns> internal static NativeMethods.RECT FromRect(Rect rect) { NativeMethods.RECT rc = new NativeMethods.RECT(); rc.top = DoubleUtil.DoubleToInt(rect.Y); rc.left = DoubleUtil.DoubleToInt(rect.X); rc.bottom = DoubleUtil.DoubleToInt(rect.Bottom); rc.right = DoubleUtil.DoubleToInt(rect.Right); return(rc); }
// To position the popup, we find the InterestPoints of the placement rectangle/point // in the screen coordinate space. We also find the InterestPoints of the child in // the popup's space. Then we attempt all valid combinations of matching InterestPoints // (based on PlacementMode) to find the position that best fits on the screen. // NOTE: any reference to the screen implies the monitor for full trust and // the browser area for partial trust private void UpdatePosition() { if (_popupRoot == null) { return; } PlacementMode placement = PlacementInternal; // Get a list of the corners of the target/child in screen space Point[] placementTargetInterestPoints = GetPlacementTargetInterestPoints(placement); Point[] childInterestPoints = GetChildInterestPoints(placement); // Find bounds of screen and child in screen space Rect targetBounds = GetBounds(placementTargetInterestPoints); Rect screenBounds; Rect childBounds = GetBounds(childInterestPoints); double childArea = childBounds.Width * childBounds.Height; Rect windowRect = _secHelper.GetWindowRect(); _positionInfo ??= new PositionInfo(); _positionInfo.X = (int)windowRect.X; _positionInfo.Y = (int)windowRect.Y; _positionInfo.ChildSize = windowRect.Size; // Rank possible positions int bestIndex = -1; Vector bestTranslation = new Vector(_positionInfo.X, _positionInfo.Y); double bestScore = -1; PopupPrimaryAxis bestAxis = PopupPrimaryAxis.None; int positions; CustomPopupPlacement[] customPlacements = null; // Find the number of possible positions if (placement == PlacementMode.Custom) { CustomPopupPlacementCallback customCallback = CustomPopupPlacementCallback; if (customCallback != null) { customPlacements = customCallback(childBounds.Size, targetBounds.Size, new Point(HorizontalOffset, VerticalOffset)); } positions = customPlacements == null ? 0 : customPlacements.Length; // Return if callback closed the popup if (!IsOpen) { return; } } else { positions = GetNumberOfCombinations(placement); } // Try each position until the best one is found for (int i = 0; i < positions; i++) { Vector popupTranslation; PopupPrimaryAxis axis; // Get the ith Position to rank if (placement == PlacementMode.Custom) { // The custom callback only calculates relative to 0,0 // so the placementTarget's top/left need to be re-applied. popupTranslation = (Vector)placementTargetInterestPoints[(int)InterestPoint.TopLeft] + (Vector)customPlacements[i].Point; // vector from origin axis = customPlacements[i].PrimaryAxis; } else { PointCombination pointCombination = GetPointCombination(placement, i, out axis); InterestPoint targetInterestPoint = pointCombination.TargetInterestPoint; InterestPoint childInterestPoint = pointCombination.ChildInterestPoint; // Compute the vector from the screen origin to the top left corner of the popup // that will cause the the two interest points to overlap popupTranslation = placementTargetInterestPoints[(int)targetInterestPoint] - childInterestPoints[(int)childInterestPoint]; } // Find percent of popup on screen by translating the popup bounds // and calculating the percent of the bounds that is on screen // Note: this score is based on the percent of the popup that is on screen // not the percent of the child that is on screen. For certain // scenarios, this may produce in counter-intuitive results. // If this is a problem, more complex scoring is needed Rect tranlsatedChildBounds = Rect.Offset(childBounds, popupTranslation); screenBounds = GetScreenBounds(targetBounds, placementTargetInterestPoints[(int)InterestPoint.TopLeft]); Rect currentIntersection = Rect.Intersect(screenBounds, tranlsatedChildBounds); // Calculate area of intersection double score = currentIntersection != Rect.Empty ? currentIntersection.Width * currentIntersection.Height : 0; // If current score is better than the best score so far, save the position info if (score - bestScore > Tolerance) { bestIndex = i; bestTranslation = popupTranslation; bestScore = score; bestAxis = axis; // Stop when we find a popup that is completely on screen if (Math.Abs(score - childArea) < Tolerance) { break; } } } // Check to see if the pop needs to be nudged onto the screen. // Popups are not nudged if their axes do not align with the screen axes // Use the size of the popupRoot in case it is clipping the popup content Matrix transformToDevice = _secHelper.GetTransformToDevice(); childBounds = new Rect((Size)transformToDevice.Transform((Point)GetChildSize())); childBounds.Offset(bestTranslation); Vector childTranslation = (Vector)transformToDevice.Transform(GetChildTranslation()); childBounds.Offset(childTranslation); screenBounds = GetScreenBounds(targetBounds, placementTargetInterestPoints[(int)InterestPoint.TopLeft]); Rect intersection = Rect.Intersect(screenBounds, childBounds); // See if width/height of intersection are less than child's if (Math.Abs(intersection.Width - childBounds.Width) > Tolerance || Math.Abs(intersection.Height - childBounds.Height) > Tolerance) { // Nudge Horizontally Point topLeft = placementTargetInterestPoints[(int)InterestPoint.TopLeft]; Point topRight = placementTargetInterestPoints[(int)InterestPoint.TopRight]; // Create a vector pointing from the top of the placement target to the bottom // to determine which direction the popup should be nudged in. // If the vector is zero (NaN's after normalization), nudge horizontally Vector horizontalAxis = topRight - topLeft; horizontalAxis.Normalize(); // See if target's horizontal axis is aligned with screen // (For opaque windows always translate horizontally) if (!IsTransparent || double.IsNaN(horizontalAxis.Y) || Math.Abs(horizontalAxis.Y) < Tolerance) { // Nudge horizontally if (childBounds.Right > screenBounds.Right) { bestTranslation.X = screenBounds.Right - childBounds.Width; bestTranslation.X -= childTranslation.X; } else if (childBounds.Left < screenBounds.Left) { bestTranslation.X = screenBounds.Left; bestTranslation.X -= childTranslation.X; } } else if (IsTransparent && Math.Abs(horizontalAxis.X) < Tolerance) { // Nudge vertically, limit horizontally if (childBounds.Bottom > screenBounds.Bottom) { bestTranslation.Y = screenBounds.Bottom - childBounds.Height; bestTranslation.Y -= childTranslation.Y; } else if (childBounds.Top < screenBounds.Top) { bestTranslation.Y = screenBounds.Top; bestTranslation.Y -= childTranslation.Y; } } // Nudge Vertically Point bottomLeft = placementTargetInterestPoints[(int)InterestPoint.BottomLeft]; // Create a vector pointing from the top of the placement target to the bottom // to determine which direction the popup should be nudged in // If the vector is zero (NaN's after normalization), nudge vertically Vector verticalAxis = topLeft - bottomLeft; verticalAxis.Normalize(); // Axis is aligned with screen, nudge if (!IsTransparent || double.IsNaN(verticalAxis.X) || Math.Abs(verticalAxis.X) < Tolerance) { if (childBounds.Bottom > screenBounds.Bottom) { bestTranslation.Y = screenBounds.Bottom - childBounds.Height; bestTranslation.Y -= childTranslation.Y; } else if (childBounds.Top < screenBounds.Top) { bestTranslation.Y = screenBounds.Top; bestTranslation.Y -= childTranslation.Y; } } else if (IsTransparent && Math.Abs(verticalAxis.Y) < Tolerance) { if (childBounds.Right > screenBounds.Right) { bestTranslation.X = screenBounds.Right - childBounds.Width; bestTranslation.X -= childTranslation.X; } else if (childBounds.Left < screenBounds.Left) { bestTranslation.X = screenBounds.Left; bestTranslation.X -= childTranslation.X; } } } // Finally, take the best position and apply it to the popup int bestX = DoubleUtil.DoubleToInt(bestTranslation.X); int bestY = DoubleUtil.DoubleToInt(bestTranslation.Y); if (bestX != _positionInfo.X || bestY != _positionInfo.Y) { _positionInfo.X = bestX; _positionInfo.Y = bestY; _secHelper.SetPopupPos(true, bestX, bestY, false, 0, 0); } Size GetChildSize() { if (_popup.Child is { } child) { return(child.RenderSize); } return(_popupRoot.RenderSize); } Point GetChildTranslation() { if (_popup.Child is { } child) { return(child.TranslatePoint(new Point(), _popupRoot)); } return(new Point()); } }
private void RaiseInfoCardOpeningEvent() { ResetInfoCardTimer(); var lastMouseOver = LastMouseOverWithInfoCard; if (lastMouseOver != null) { var showInfoCard = true; var inputElement = lastMouseOver as IInputElement; if (inputElement != null) { // Raise the screen tip opening event var e = new RoutedEventArgs(InfoCardOpeningEvent, this); inputElement.RaiseEvent(e); showInfoCard = !e.Handled; } if (showInfoCard) { if ((_currentInfoCard != null) && !_currentInfoCard.IsOpen) { RetireInfoCard(_currentInfoCard); } _currentInfoCard = CreateInfoCard(lastMouseOver); if (_currentInfoCard != null) { var targetElement = lastMouseOver as UIElement; _currentInfoCard.TargetElement = targetElement; var infoCardPosition = Mouse.GetPosition(inputElement); var infoCardSite = _currentInfoCard.RegisteredInfoCardSite ?? lastMouseOver.FindVisualAncestorByType <InfoCardSite>(); if (infoCardSite == null) { var window = Window.GetWindow(lastMouseOver); if (window != null) { if (!_generatedSites.TryGetValue(window, out infoCardSite)) { infoCardSite = new InfoCardSite(); _generatedSites[window] = infoCardSite; } } else { RetireInfoCard(_currentInfoCard); _currentInfoCard = null; return; } } if (!_currentInfoCard.IsOpen) { if (!infoCardSite.InfoCards.Contains(_currentInfoCard)) { SetUnregisterInfoCardOnClose(_currentInfoCard, true); infoCardSite.InfoCards.Add(_currentInfoCard); } } if (infoCardSite.IsLoaded) { var targetVisual = targetElement; if (targetVisual != null) { var transformToVisual = targetVisual.TransformToVisual(infoCardSite); if (transformToVisual != null) { infoCardPosition = transformToVisual.Transform(infoCardPosition); } } } if (targetElement != null) { var customPlacementCallback = _currentInfoCard.CustomPlacementCallback ?? GetCustomInfoCardPlacementCallback(targetElement); if (customPlacementCallback != null) { _currentInfoCard.UpdateLayout(); infoCardPosition = customPlacementCallback( _currentInfoCard.RenderSize, targetElement, infoCardPosition); } } _currentInfoCard.Location = new Point( DoubleUtil.DoubleToInt(infoCardPosition.X), DoubleUtil.DoubleToInt(infoCardPosition.Y)); if (_currentInfoCard.IsOpen) { var infoCardWindow = InfoCardHost.GetInfoCardWindow(_currentInfoCard); if (infoCardWindow != null) { infoCardWindow.Setup(_currentInfoCard.Location); infoCardWindow.Activate(); } return; } _currentInfoCard.Open(); } } } }