Repository navigation
Menus and UiElement
Menus gives you a frame of your own on the game's UI thread. It tells you what has the focus and which window is active, and presses the controller's keys in the menus for you. UiElement is one element of a menu page: a page, a panel, a button or a list row, with where it's drawn on screen.
The game's menus never get the mouse. A mouse mod finds the element under the cursor (UiElement.TryGetBounds), gives it the focus (UiElement.Focus) and presses the controller's keys (Menus.Tap). The page then behaves exactly as it would with a controller.
Thread: the UI thread, inside Menus.Frame.
A left click on a menu item selects it, as if you'd moved there with the d-pad and pressed A.
using System.Numerics;
using FH2;
using FH2ModPlatform;
public sealed class ClickMenus : Mod
{
private bool _wasDown;
public override ModInfo Info => new("ClickMenus", "1.0.0");
protected override void OnInit()
{
Menus.Frame += dt =>
{
bool down = GameWindow.IsMouseDown(MouseButton.Left);
if (down && !_wasDown && Menus.ActiveWindow is { } page)
{
var hit = Under(page, page, Menus.ToUi(GameWindow.Cursor));
if (hit != null && hit.Focus()) Menus.Tap(MenuKey.A);
}
_wasDown = down;
};
}
// The last focusable element under the point: later children are drawn over earlier ones.
private static UiElement? Under(UiElement root, UiElement e, Vector2 at)
{
UiElement? hit = null;
if (e.IsFocusable && e.Opacity > 0 && e.TryGetBounds(root, out var r) && r.Contains(at)) hit = e;
foreach (var c in e.Children)
if (Under(root, c, at) is { } h) hit = h;
return hit;
}
}Imports System.Numerics
Imports FH2
Imports FH2ModPlatform
Public Class ClickMenus
Inherits [Mod]
Private _wasDown As Boolean
Public Overrides ReadOnly Property Info As ModInfo
Get
Return New ModInfo("ClickMenus", "1.0.0")
End Get
End Property
Protected Overrides Sub OnInit()
AddHandler Menus.Frame,
Sub(dt)
Dim down = GameWindow.IsMouseDown(MouseButton.Left)
Dim page = Menus.ActiveWindow
If down AndAlso Not _wasDown AndAlso page IsNot Nothing Then
Dim hit = Under(page, page, Menus.ToUi(GameWindow.Cursor))
If hit IsNot Nothing AndAlso hit.Focus() Then Menus.Tap(MenuKey.A)
End If
_wasDown = down
End Sub
End Sub
' The last focusable element under the point: later children are drawn over earlier ones.
Private Shared Function Under(root As UiElement, e As UiElement, at As Vector2) As UiElement
Dim hit As UiElement = Nothing
Dim r As UiRect
If e.IsFocusable AndAlso e.Opacity > 0 AndAlso e.TryGetBounds(root, r) AndAlso r.Contains(at) Then hit = e
For Each c In e.Children
Dim h = Under(root, c, at)
If h IsNot Nothing Then hit = h
Next
Return hit
End Function
End Class| Member | What it does |
|---|---|
IsReady (property, bool) |
True once the menus' code has been checked and the frame hook is in. Reading it starts Menus. Any thread. |
IsUiThread (property, bool) |
True on the UI thread. Any thread. |
IsActive (property, bool) |
True if the UI has run a frame within the last second (the game is up and not minimised). Any thread. |
FrameTime (property, float) |
The last UI frame's length, in seconds. |
Size (property, Vector2) |
The UI's size in its own pixels (the game's render size: 1920 x 1080 at 1080p). |
ActiveWindow (property, UiElement?) |
The active window (the page on screen, or the HUD's while driving), or null. |
Focused (property, UiElement?) |
The element with the keyboard focus (the page itself, a button, a list), or null. |
FocusedTypeName (property, string) |
The focused element's class name, or "" for none: "UIWindow" while driving, "MapScene" on the map... |
QueuedKeys (property, int) |
How many queued key events are still to go. |
| Member | What it does |
|---|---|
ToUi(Vector2 window) (method, returns Vector2) |
Turns a point in the game window's pixels (GameWindow.Cursor) into the UI's pixels. |
SendKey(MenuKey key, bool up) (method) |
Sends a key to the menus now, as the controller sends it: down (up false) or up (up true). |
QueueKey(MenuKey key, bool up, float wait = 0) (method) |
Sends a key in a later frame, one key event per frame, after waiting wait seconds. Some menus look at what's held each frame and miss a press and release sent in the same frame. |
Tap(MenuKey key, float wait = 0) (method) |
A press and release of key (two QueueKey calls), after wait seconds. The usual way to press a menu key. |
PressPadButton(MenuKey key, bool hold = false) (method, returns bool) |
Presses the controller button that sends key, the way the controller does, so pages that check the button itself see it too (the pause menu's Y). Held at least 80 ms, and with hold until ReleasePadButton. False if the input hooks are off or a press is already under way; use Tap then. |
ReleasePadButton() (method) |
Lets go of the button PressPadButton held. |
| Member | What it does |
|---|---|
Frame (event, Action<float>) |
Once a UI frame, on the UI thread, before the game's UI updates. Gets the frame's length in seconds (0.001..0.1). Runs on the title screen, in every menu, and while driving. The place for everything on this page. Keep it short. |
The keys the controller sends the menus: Pause, View, A, B, X, Y, LB, RB, LT, RT, Left, Up, Right, Down, LeftStick, RightStick.
An element of the game's menus. Get one from Menus.ActiveWindow or Menus.Focused, then walk its Children and Parent.
| Member | What it does |
|---|---|
Handle (property, nint) |
The element's address, for code of your own. |
TypeName (property, string) |
Its class name as the game has it ("ListBoxItem", "MapScene", "ItemsSelector"). |
Is(UiType type) (method, returns bool) |
True if it's a type, or derives from one. |
Parent (property, UiElement?) |
The element it's drawn in, or null. |
ChildCount (property, int) |
How many elements are drawn in it. |
Child(int index) (method, returns UiElement?) |
The indexth element drawn in it, or null. Later ones are drawn over earlier ones. |
Children (property, IEnumerable<UiElement>) |
Every element drawn in it, in order. |
Opacity (property, float) |
Its opacity, 0..1 (0 is hidden). |
IsLaidOut (property, bool) |
True once it has a place and a size on the page. |
RenderSize (property, Vector2) |
Its size, as laid out. |
IsFocusable (property, bool) |
True if it can take the focus. |
IsFocused (property, bool) |
True if it has the focus (a tab row marks its current tab this way). |
DataContext (property, nint) |
What it shows. Only compare it, never follow it. 0 for none. |
SelectedItem (property, nint) |
A UiType.Selector's selected item (compare it with an item's DataContext), or 0. |
HelpButtonType (property, int) |
A UiType.HelpButton's button: 0 A, 1 B, 2 X, 3 Y, 4 Menu, 5 View, 6 LB, 7 RB, 8 LT, 9 RT, 10 L3, 11 R3, 12/13 d-pad both ways, 14-17 d-pad up/down/left/right... Or -1. |
Focus() (method, returns bool) |
Gives it the focus, as the d-pad would. True if it has the focus now. |
TryGetLayoutBounds(UiElement root, out UiRect bounds) (method, returns bool) |
Where it's laid out, in root's pixels (the screen's, when root is the active window). False if it isn't laid out. |
TryGetBounds(UiElement root, out UiRect bounds) (method, returns bool) |
Where it's actually drawn, in root's pixels: as laid out, then as the menus' tilt shows it. Use this one to find what's under the cursor. |
==, !=, Equals(UiElement? other)
|
Two UiElements are equal when they're the same element. |
ToString() (method) |
Its type name and address, for logs. |
Kinds of element, for UiElement.Is.
| Value | Meaning |
|---|---|
Visual, UIElement
|
The base kinds, as in WPF: anything drawn is a Visual. |
ButtonBase, RepeatButton, ListBoxItem, ComboBoxItem, TabItem, TreeViewItem, MenuItem, Slider
|
The usual WPF kinds. |
Selector |
A list or row of items that has a selection (ListBox, the game's ItemsSelector...). |
Spinner |
The game's "< value >" spinner. |
HelpButton |
One of the help bar's buttons ("(A) Select"). |
A rectangle in the UI's pixels.
| Member | What it does |
|---|---|
UiRect(float Left, float Top, float Right, float Bottom) (constructor) |
The rectangle's edges. |
Left, Top, Right, Bottom (properties, float) |
The edges. |
Width, Height (properties, float) |
Its size. |
Center (property, Vector2) |
Its middle. |
Contains(Vector2 p) (method, returns bool) |
True if p is inside it. |
DistanceTo(Vector2 p) (method, returns float) |
How far p is outside it (0 inside): the larger of the distance across and the distance down. |
-
UI thread only.
ActiveWindow,Focused,FocusedTypeNameandSendKeythrowInvalidOperationExceptionif used off the UI thread. Do your menu work insideMenus.Frame. You can subscribe toFramefromOnInit, andIsReady,IsActiveandIsUiThreadwork from any thread. -
A
UiElementis only valid during the UI frame you got it in. Don't keep one for later: find it again next frame. - Compare elements with
==in C# and=in VB. Each call gives you a newUiElementobject, so VB'sIs(same object) is false even for the same element. -
Isis a keyword in VB. Writeel.[Is](UiType.Selector). -
Menusstarts the first time a mod readsIsReadyor subscribes toFrame. GameInput and the photo and garage cameras start with it. - Most menus are drawn tilted back a little.
TryGetBoundstakes that into account;TryGetLayoutBoundsdoesn't. - The mouse position from GameWindow is in the window's pixels, but bounds are in the UI's pixels. Turn it with
Menus.ToUi(GameWindow.Cursor)before you compare the two. - An exception in a
Framehandler is written to platform.log and the handler stays on. - At most 64 key events wait in the queue; more are dropped.
- If your mod is a hosted mod and it's reloaded, its
Framehandlers and queued keys are removed. -
Unsupported game build: if the menus' code isn't as expected,
Menusswitches itself off and platform.log says "Menus off: ...".IsReadystays false andFramenever runs.
- GameWindow: the cursor and the mouse buttons.
- GameInput: menu sticks and held controller buttons.
-
MapView, PhotoCamera, GarageCamera: the map, which
Menus.Focusedfinds. - ButtonIcons and Picture
- UI and Hud
- Threads
Playing
Getting started
Writing mods
The FH2 classes
- Game
- Player
- Vehicle
- World
- Garage, CarCatalog, Profile
- UI and Hud
- Camera
- Audio
- ANNA
- Skills
- DebugOverlay
- Controller
- GameInput
- GameWindow
- Menus and UiElement
- SettingsTile
- ButtonIcons and Picture
- MapView, PhotoCamera, GarageCamera
More