Trying to force widget to stay on screen

Hey, I have a bit of a head scratcher here. What I’m trying to accomplish is for my right click menu to just not go off screen. So what I’m doing is seeing what the viewport size is and if it’s more than 94% of that size, then don’t go further even if my mouse does. This way there’s a max it can go:

Now this works to some degree, but I have noticed that when I go full screen the position of that widget is not the same as the lower resolution counterpart. Everything scales up with higher resolution, so I figured if I use a screen percent then it should stay consistent. Unfortunately the offset gets smaller / bigger in a different speed that the actual resolution change between the widgets.

Small viewport:
image

Large Viewport:

I just want that when it’s positioned, it doesn’t go off screen, so if there’s much better ways of doing that I’m open to any suggestions. Thanks! :slight_smile:

The Menu Anchor is the UE’s native way of handling context menus. And here’s how you can try it the hard way:

The Menu Anchor is interesting, but in the demo it’s always in the same place, while mine generates based on mouse position, so it doesn’t seem like it’ll work. But I’m only now learning about it so maybe there’s more I need to figure out first.

As for the hard method, I’ve tried to get the Desired Size, but it just returns 0 for me, despite the User Widget being set to Desired in the top right. I’ve even tried to get the desired size from the vertical box inside the widget, still returns 0.

After playing around a bit with the Menu Anchor, it’s doing what I want, but there’s an issue. When I currently create the widget I have a lot of exposed variables that I push through the one that created it. With it being a menu anchor, I can’t seem to find any way to set the variables. Is it possible?

Desired Size returns correct size only after the underlying slate widgets have been created. I have very similar thing in my project where I am showing interaction options in a widget when certain objects are interacted with, so the player can choose an option.

The interaction point is first projected to screen space and the widget is positioned to the projected screen location but is also clamped inside screen.

I am using C++ and UMG-View Models to do this but maybe you can get some help from it.

// Header
UFUNCTION(BlueprintPure, Category = "Widget Utils")
static FVector2D ClampWidgetPositionInsideViewport(APlayerController* PlayerController, const FVector2D& ScreenLocation, const FMargin& Margin);

// Cpp
FVector2D UWidgetUtils::ClampWidgetPositionInsideViewport(APlayerController* PlayerController, const FVector2D& ScreenLocation, const FMargin& Margin)
{
	if (!IsValid(PlayerController))
	{
		return FVector2D::ZeroVector;
	}

	int32 VPSizeX, VPSizeY;
	PlayerController->GetViewportSize(VPSizeX, VPSizeY);
	if (VPSizeX == 0 || VPSizeY == 0)
	{
		return FVector2D::ZeroVector;
	}

	const float MinX = Margin.Left;
	const float MaxX = ((float)VPSizeX) - Margin.Right;
	const float MinY = Margin.Top;
	const float MaxY = ((float)VPSizeY) - Margin.Bottom;

	if (MinX >= MaxX || MinY >= MaxY)
	{
		return FVector2D(VPSizeX / 2.0f, VPSizeY / 2.0f);
	}

	FVector2D Result = ScreenLocation;
	Result.X = FMath::Clamp(ScreenLocation.X, MinX, MaxX);
	Result.Y = FMath::Clamp(ScreenLocation.Y, MinY, MaxY);
	return Result;
}

This very simple function clamps the given screen location inside viewport with additional margin from each screen edge.

Then in view model I am calculating the screen transform for the widget using the following code:

    // Widget screen location is the same as interactable screen location
	FVector2D NewWidgetLocation = InteractableScreenLocation;

	// Center alignment
	NewWidgetLocation = NewWidgetLocation - FVector2D(WidgetSize.X / 2.0f, WidgetSize.Y / 2.0f);

	// Clamp inside screen
	NewWidgetLocation = UWidgetUtils::ClampWidgetPositionInsideViewport(PlayerController, NewWidgetLocation, FMargin(WidgetSize.X, WidgetSize.Y, WidgetSize.X, WidgetSize.Y));
	SetOptionWidgetScreenLocation(NewWidgetLocation);

The code expects that the widget pivot is at the center. WidgetSize is set to the view model by using Desired Size of the widget. SetOptionWidgetScreenLocation just sets a property which is Vector2D with field notify, so the widget can bind into it.

Hope that you get it solved.

1 Like

One rabit hole later I found an old post of yours @Everynone from 2017 that helped me out with getting those variables through:

So thanks for point me in the right direction. A bit of fiddling left to do but the main problem has now been fixed! :smiley:

1 Like

When it comes to Desired Size returning 0, try calling Force Layout Prepass first and then get widget size. This may be needed if you construct and want to use the widget immediately in the very same frame.

Good luck.

WidgetPos.zip (102.4 KB)

Here’s my take. Full BP, respects screen scale and dpi scaling.

Right click to show context menu (can hook update to tick in main menu to have it follow cursor all the time)

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.