reckfellENGINE

Focus: Tab, arrows and returning from a menu

A complete C# menu with explicit focus routes, a nested screen, Escape and restoring the selected button.

On this page

Run navigation

Add UiSampleStyle.cs, attach FocusMenuDemo to a Control root, build and run with F6. Start is initially selected. Tab/Shift+Tab and up/down cycle through three buttons. Enter on Settings opens the nested screen and selects Back. Escape or Back restores focus to the original button. Close menu hides the example; ShowMenu reveals it and assigns focus again.

FocusMenuDemo.cs
C#
using Reckfell;

public partial class FocusMenuDemo : Control
{
    public VBoxContainer Main { get; private set; } = null!;
    public VBoxContainer Details { get; private set; } = null!;
    public Button Start { get; private set; } = null!;
    public Button Options { get; private set; } = null!;
    public Button Close { get; private set; } = null!;
    public Button Back { get; private set; } = null!;
    public int StartCount { get; private set; }
    private Control? _returnFocus;

    public override void _Ready()
    {
        UiSampleStyle.Prepare(this);
        var panel = new PanelContainer(); AddChild(panel);
        panel.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect, margin: 24);
        Main = new VBoxContainer(); Details = new VBoxContainer { Visible = false };
        panel.AddChild(Main); panel.AddChild(Details);
        Start = Button(Main, UiSampleStyle.Text("Начать", "Start"));
        Options = Button(Main, UiSampleStyle.Text("Настройки", "Settings"));
        Close = Button(Main, UiSampleStyle.Text("Закрыть меню", "Close menu"));
        Button[] buttons = [Start, Options, Close];
        for (int i = 0; i < buttons.Length; i++)
        {
            Button current = buttons[i], next = buttons[(i + 1) % buttons.Length], previous = buttons[(i + buttons.Length - 1) % buttons.Length];
            current.FocusNext = current.GetPathTo(next); current.FocusPrevious = current.GetPathTo(previous);
            current.FocusNeighborBottom = current.GetPathTo(next); current.FocusNeighborTop = current.GetPathTo(previous);
        }
        Details.AddChild(new Label { Text = UiSampleStyle.Text("Экран настроек", "Settings screen") });
        Back = Button(Details, UiSampleStyle.Text("Назад", "Back"));
        Start.Pressed += () => StartCount++; Options.Pressed += OpenDetails;
        Close.Pressed += Hide; Back.Pressed += CloseDetails;
        Callable.From(() => Start.GrabFocus()).CallDeferred();
    }

    private static Button Button(VBoxContainer parent, string text)
    {
        var button = new Button { Text = text, FocusMode = FocusModeEnum.All };
        parent.AddChild(button); return button;
    }

    public void OpenDetails()
    {
        _returnFocus = GetViewport().GuiGetFocusOwner();
        Main.Hide(); Details.Show(); Back.GrabFocus();
    }

    public void CloseDetails()
    {
        Details.Hide(); Main.Show();
        Control target = _returnFocus is Button b && ReckfellObject.IsInstanceValid(b) && b.IsVisibleInTree() && !b.Disabled ? b : Start;
        target.GrabFocus();
    }

    public void ShowMenu() { Show(); if (Details.Visible) Back.GrabFocus(); else Start.GrabFocus(); }

    public override void _UnhandledInput(InputEvent @event)
    {
        if (IsVisibleInTree() && Details.Visible && @event.IsActionPressed("ui_cancel"))
        {
            CloseDetails(); GetViewport().SetInputAsHandled();
        }
        else if (IsVisibleInTree() && @event.IsAction("ui_accept")) GetViewport().SetInputAsHandled();
    }
}

Define the route

SettingPurpose
FocusMode.NoneExclude from ordinary focus
FocusMode.ClickPointer focus, without ordinary All-style traversal
FocusMode.AllParticipate in keyboard navigation
FocusNext / FocusPreviousTab / Shift+Tab route
FocusNeighborTop/Bottom/Left/Rightui_up/down/left/right directions

Paths are relative to the button itself; GetPathTo supplies that path. Without an explicit path, the engine finds a candidate from hierarchy or geometry. This helps a simple screen but can change after rearranging elements. Initial GrabFocus is deferred until _Ready finishes; merely having buttons does not select one.

A hidden Control loses focus. When opening a screen, assign focus to a visible eligible element; when closing, validate the saved reference and provide a fallback. Disabled prevents button activation but does not automatically mean FocusMode.None. Set None and update neighbors if a disabled button must be skipped. Do not leave focus in a hidden tab.

Inspect effective state rather than only assigned fields:

SymptomCheck
FocusMode.All is set but focus failsGetFocusModeWithOverride and ancestors’ FocusBehaviorRecursive; the local value can be overridden
No focus outlineGetViewport().GuiGetFocusOwner: a missing drawing does not always mean no owner; check the focus style
Tab works but arrows do notFocusNext/Previous and FocusNeighbor are separate routes; verify relative paths
Return targets a closed screenThe stored reference must be alive and visible, and the fallback button focusable

Pause and gameplay input

Set the menu root's ProcessMode.Always if it must work while GetTree().Paused; children can inherit it. Keep gameplay in its appropriate pause mode. This example does not toggle global pause itself.

ui_* actions belong to the interface; Input Map defines keyboard/controller bindings. Do not also use them for jumping/shooting without game-state checks. In this build, keyboard activation of a standard button does not guarantee ui_accept disappears from _UnhandledInput(): the example explicitly consumes it while the menu is active. AcceptEvent in _GuiInput() cannot undo an earlier _Input() or direct Input polling.

Test Tab and directional navigation separately: they are different routes. Physical controllers, device switching and assistive technologies need their own checks. FocusMode.All alone does not certify accessibility.

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.