Table of Contents

Navigation and panels

IViewManager moves the player between screens. It is available from GetService<IViewManager>(), or as a constructor argument of a view-model.

There are two ways to show a view on top of what is there now:

  • A view stack, where each view replaces the one before it, or is pushed over it and can be popped back off.
  • Panels, which float over the game, several at once, without replacing anything.

Replacing and pushing views

Method What it does
SetView<TViewModel>() Makes the view of a view-model the only view. Everything else is discarded.
PushView<TViewModel>() Shows a view over the active one. The view under it is kept.
PopView() Discards the active view, and brings back the one under it, if there is one.
ClearView() Discards every view.
ReloadActiveView() Builds the views on screen again, keeping their view-models.

The ViewModel overloads, such as SetView(viewModel), take an instance instead of a type. Use them for a view-model that is not registered by its type, such as a DataViewModel. See Views from data.

ActiveView is the view on top, and Views is every view on the stack, bottom first.

A pushed view is drawn dimmed over the view under it. The view under it gets no input and no updates until the pushed view is popped. SetView discards everything on the stack, including the pushed views, so use it to move to a new screen and PushView for anything that returns to the screen beneath it.

A screen that goes back to the one before it, such as an options screen, pops itself. It implements ICancelableViewModel, so Escape also goes back:

using BSGameLib.UI;
using BSGameLib.UI.Builder;

public class MenuViewModel : ViewModel
{
  public MenuViewModel(IViewManager viewManager) : base(viewManager)
  {
    OptionsCommand = new DelegateCommand(() => ViewManager.PushView<OptionsViewModel>());
    PlayCommand = new DelegateCommand(() => ViewManager.SetView<GameViewModel>());
  }

  public ICommand OptionsCommand { get; }

  public ICommand PlayCommand { get; }

  protected override void BuildView(IViewBuilder builder) =>
    builder.PopulateFromFile(Path.Combine(AppContext.BaseDirectory, "Views", "Menu.view.xml"));
}

public class OptionsViewModel : ViewModel, ICancelableViewModel
{
  private bool _fullscreen;

  public OptionsViewModel(IViewManager viewManager) : base(viewManager)
  {
    BackCommand = new DelegateCommand(Cancel);
  }

  public ICommand BackCommand { get; }

  public bool Fullscreen
  {
    get => _fullscreen;
    set => Set(ref _fullscreen, value);
  }

  public void Cancel() => ViewManager.PopView();

  protected override void BuildView(IViewBuilder builder) =>
    builder.PopulateFromFile(Path.Combine(AppContext.BaseDirectory, "Views", "Options.view.xml"));
}

public class GameViewModel : ViewModel
{
  public GameViewModel(IViewManager viewManager) : base(viewManager) { }

  protected override void BuildView(IViewBuilder builder) =>
    builder.PopulateFromFile(Path.Combine(AppContext.BaseDirectory, "Views", "Game.view.xml"));
}

Views/Options.view.xml:

<View>
  <StackLayout Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"
               VerticalSpacing="12">
    <Label Id="title" Size="{240,32}" Text="Options" PanelTitle="true" />
    <CheckBox Id="fullscreen" Text="Fullscreen" Checked="{Binding Fullscreen}" IsInitialFocus="true" />
    <Button Id="back" Size="{240,40}" Text="Back" Command="BackCommand" IsDefault="true" />
  </StackLayout>
</View>

Each visit to a screen builds its view again, so OnViewCreating is the place to reset anything the view-model should forget between visits. See Views and view-models.

Keyboard

The keyboard moves focus and presses buttons:

Key What it does
Tab Moves focus to the next focusable control.
Shift+Tab Moves focus to the previous one.
Enter, Space Presses the focused button.
Enter Presses the view's default button, the one with IsDefault="true". A focused control that needs Enter for itself, such as an open drop-down, keeps it.
Escape Closes an open drop-down or other control that uses Escape. If none does, calls Cancel() on the active view's view-model, if it is an ICancelableViewModel.

When a view opens, focus goes to the control marked IsInitialFocus="true". Without one, it goes to the first focusable control in the file.

Panels

A panel is a view drawn over the game, rather than in place of the current view. A panel does not suspend anything. The game underneath keeps running, and what the panel is drawn over can be a screen, or a game scene with no view on it.

using System.Linq;
using BSGameLib.UI;
using BSGameLib.UI.Builder;

public class HudController
{
  private readonly IViewManager _views;
  private readonly ChatPanelViewModel _chat;

  public HudController(IViewManager views, ChatPanelViewModel chat)
  {
    _views = views;
    _chat = chat;
  }

  public void OpenChat() => _views.ShowPanel(_chat);

  public void CloseChat() => _views.ClosePanel(_chat);

  public bool IsChatOpen => _views.Panels.Any(panel => ReferenceEquals(panel.DataContext, _chat));

  // The game takes the mouse and keyboard only where no panel is using them.
  public bool GameMayUseMouse => !_views.PanelsWantMouse;

  public bool GameMayUseKeyboard => !_views.PanelsWantKeyboard;
}

public class ChatPanelViewModel : ViewModel
{
  public ChatPanelViewModel(IViewManager viewManager) : base(viewManager) { }

  protected override void BuildView(IViewBuilder builder) =>
    builder.PopulateFromFile(Path.Combine(AppContext.BaseDirectory, "Views", "Chat.view.xml"));
}

Several panels can be open at once. A click brings one to the front. A panel takes the mouse only over its own controls and its backgrounds, so the game keeps the mouse everywhere else. It takes the keyboard only while one of its text boxes is being typed in.

Check PanelsWantMouse and PanelsWantKeyboard before the game acts on input, as the example does. A panel stays open until ClosePanel is called with its view-model.

A control marked PanelTitle="true" is the panel's drag handle: pressing it, away from the panel's own controls, and dragging moves the panel.

Panels lists the open panels, back first.

Button sounds

SetButtonClickSound plays an audio event each time a button is pressed. The id is an audio event of the game's audio service:

viewManager.SetButtonClickSound("ui.click");

Pass null to turn the sound off.

Scaling the UI

UIScale makes the UI larger or smaller. It is described in Layout.