reckfellENGINE

Фокус: Tab, стрелки и возврат из меню

Полное меню C# с явным маршрутом фокуса, вложенным экраном, Escape и восстановлением выбранной кнопки.

На этой странице

Запустить навигацию

Добавьте UiSampleStyle.cs, прикрепите FocusMenuDemo к корню Control, соберите и запустите F6. Изначально выбрано Начать. Tab/Shift+Tab и вверх/вниз обходят три кнопки по кругу. Enter на Настройки открывает вложенный экран и выбирает Назад. Escape или Назад возвращают фокус в исходную кнопку. Закрыть меню скрывает пример; ShowMenu показывает его снова и задаёт фокус.

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();
    }
}

Задайте маршрут явно

НастройкаНазначение
FocusMode.NoneНе участвовать в обычном фокусе
FocusMode.ClickФокус от указателя, без обычного обхода как All
FocusMode.AllУчаствовать в клавиатурной навигации
FocusNext / FocusPreviousМаршрут Tab / Shift+Tab
FocusNeighborTop/Bottom/Left/RightНаправления ui_up/down/left/right

Пути отсчитываются от самой кнопки; GetPathTo даёт нужный относительный путь. Если явного пути нет, движок ищет кандидата по иерархии или геометрии. Это удобно для простого экрана, но после перестановки элементов результат может измениться. Начальный GrabFocus отложен до завершения _Ready; одного наличия кнопок недостаточно для начального выбора.

Скрытый Control теряет фокус. После открытия экрана назначайте его видимому доступному элементу; при закрытии проверяйте сохранённую ссылку и запасной вариант. Disabled запрещает действие кнопки, но не означает автоматический FocusMode.None. Если отключённую кнопку нужно пропустить, задайте None и при необходимости обновите соседей. Не оставляйте фокус в скрытой вкладке.

Проверяйте фактическое состояние, а не только введённые поля:

СимптомПроверка
FocusMode.All задан, но фокус не берётсяGetFocusModeWithOverride и FocusBehaviorRecursive у предков; локальное значение может быть переопределено
Рамки фокуса нетGetViewport().GuiGetFocusOwner: отсутствие рисунка не всегда означает отсутствие владельца; проверьте стиль focus
Tab работает, стрелки нетFocusNext/Previous и FocusNeighbor — разные маршруты; проверьте относительные пути
Возврат ведёт в закрытый экранСохранённая ссылка должна быть живой и видимой, а запасная кнопка — доступной для фокуса

Пауза и ввод игры

Чтобы меню работало при GetTree().Paused, задайте его корню ProcessMode.Always; дочерние элементы могут наследовать режим. Игра при этом должна оставаться в подходящем режиме паузы. Пример не переключает глобальную паузу сам.

ui_* — действия интерфейса; привязки клавиш и контроллера задаются в Input Map. Не используйте их одновременно для прыжка/выстрела без проверки состояния игры. В этой сборке нажатие стандартной кнопки клавиатурой не следует считать гарантией, что ui_accept исчезнет из _UnhandledInput(): пример явно поглощает это действие, пока меню активно. AcceptEvent из _GuiInput() не отменяет уже выполненный _Input() и прямой опрос Input.

Проверяйте не только Tab, но и направления: это разные маршруты. Физический контроллер, переключение устройств и средства доступности требуют собственной проверки. Наличие FocusMode.All не является сертификатом доступности интерфейса.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.