Class TUIState

Unit

Declaration

type TUIState = class(TCastleUserInterface)

Description

UI state, to manage the state of your game UI. See also https://castle-engine.io/states for an overview of using TUIState.

In simple cases, only one state is current at a given time, and it can be get or set using the TUIState.Current property. In more complex cases, you can use TUIState.Push and TUIState.Pop to build a stack of states, and in effect multiple states are active at the same time. All of the states on stack are started, but only the top-most is resumed.

Each state has Start and Stop methods that you can override to perform work when state becomes part of the current state stack, or stops being part of it. You can also override Resume and Pause methods, to perform work when the state becomes the top-most state or is no longer the top-most state. The distinction becomes important once you play around with pushing/popping states.

To define state user interface:

  1. It is simplest to set DesignUrl to the design file you created using CGE editor. Such user interface controls will be created right before Start and destroyed right after Stop (so the state UI always "resets" when state starts).

  2. You can always create more UI controls and add them to the state at any point. The state is a TCastleUserInterface descendant and you can add UI to it just by using TCastleUserInterface.InsertFront.

    UI children can be added anytime you want, e.g. in Start or in overridden constructor.

    UI children can be removed or destroyed anytime you want as well. You can use FreeAtStop as an owner for anything you want to be automatically destroyed at Stop.

Current state is placed on the list of container controls. This way state is notified about UI events, and can react to them. Note that our children will handle events before the state itself is notified about them, following TCastleUserInterface events behavior. This way state can:

See the TCastleUserInterface class for a lot of useful methods that you can override in your state descendants to capture various events.

Hierarchy

Overview

Fields

Public class var Log: boolean;

Methods

Protected function ContainerWidth: Cardinal; override;
Protected function ContainerHeight: Cardinal; override;
Protected function ContainerRect: TRectangle; override;
Protected function ContainerSizeKnown: boolean; override;
Protected function StateContainer: TCastleContainer; virtual;
Protected function InsertAtPosition: Integer; virtual;
Protected function FreeAtStop: TComponent;
Public class procedure Push(const NewState: TUIState);
Public class procedure Pop; overload;
Public class procedure Pop(const CurrentTopMostState: TUIState); overload;
Public class function StateStackCount: Integer;
Public constructor Create(AOwner: TComponent); override;
Public destructor Destroy; override;
Public constructor CreateUntilStopped;
Public procedure Start; virtual;
Public procedure Stop; virtual;
Public procedure Resume; virtual;
Public procedure Pause; virtual;
Public procedure Finish; virtual; deprecated 'use Stop';
Public function Active: boolean;
Public function Press(const Event: TInputPressRelease): boolean; override;
Public function Release(const Event: TInputPressRelease): boolean; override;
Public function Motion(const Event: TInputMotion): boolean; override;
Public procedure Update(const SecondsPassed: Single; var HandleInput: boolean); override;
Public procedure Render; override;
Public function UIScale: Single; override;
Public procedure InsertUserInterface(const ADesignUrl: String; const FinalOwner: TComponent; out Ui: TCastleUserInterface; out UiOwner: TComponent); overload; deprecated 'instead of this, set DesignUrl in constructor';
Public procedure InsertUserInterface(const ADesignUrl: String; const FinalOwner: TComponent; out UiOwner: TComponent); overload; deprecated 'instead of this, set DesignUrl in constructor';
Public procedure WaitForRenderAndCall(const Event: TNotifyEvent);
Public function DesignedComponent(const ComponentName: String): TComponent;

Properties

Public class property Current: TUIState read GetCurrent write SetCurrent;
Public class property CurrentTop: TUIState read GetCurrentTop;
Public class property StateStack [constIndex:Integer]: TUIState read GetStateStack;
Public property InterceptInput: boolean read FInterceptInput write FInterceptInput default false;
Public property DesignUrl: String read FDesignUrl write SetDesignUrl;
Public property DesignPreload: Boolean read FDesignPreload write SetDesignPreload default false;
Published property FullSize default true;

Description

Fields

Public class var Log: boolean;

When True, state operations will send a log to CastleLog.

Methods

Protected function ContainerWidth: Cardinal; override;

As the state knows about the container it will be put in (StateContainer), the state size is always known. It is known regardless if we are between Start / Stop, regardless if the state is already added to some Container.Items. This makes all other routines, like ParentRect, EffectiveRect, EffectiveWidth, EffectiveHeight also work.

Protected function ContainerHeight: Cardinal; override;
 
Protected function ContainerRect: TRectangle; override;
 
Protected function ContainerSizeKnown: boolean; override;
 
Protected function StateContainer: TCastleContainer; virtual;

Container on which state works. By default, this is Application.MainWindow if you use CastleWindow or TCastleControl.MainControl if you use CastleControl. When the state is current, then Container property (from ancestor, see TCastleUserInterface.Container) is equal to this.

Protected function InsertAtPosition: Integer; virtual;

Position on StateContainer.Controls where we insert this state. By default, state is inserted as the front-most control, so position is equal to StateContainer.Controls.Count.

Protected function FreeAtStop: TComponent;

Assign this component as owner for your controls, to make them freed during nearest Stop.

Public class procedure Push(const NewState: TUIState);

Pushing the state adds it at the top of the state stack, this makes new state to be displayed on top of previous ones.

The state known as Current is conceptually at the bottom of state stack, always. When it is nil, then pushing new state sets the Current state. Otherwise Current state is left as-it-is, new state is added on top.

Public class procedure Pop; overload;

Pop the current top-most state, reversing the Push operation.

Public class procedure Pop(const CurrentTopMostState: TUIState); overload;

Pop the current top-most state, reversing the Push operation, also checking whether the current top-most state is as expected.

Makes a warning, and does nothing, if the current top-most state is different than indicated. This is usually a safer (more chance to easily catch bugs) version of Pop than the parameter-less version.

Public class function StateStackCount: Integer;

Count of states in the state stack. State stack is managed using Start / Push / Pop.

Public constructor Create(AOwner: TComponent); override;

Create an instance of the state. You willl typically create one instance of each state class (like TStateMain, TStatePlay) at the application initialization (e.g. in Application.OnInitialize callback), like

StateMainMenu := TStateMainMenu.Create(Application);
StatePlay := TStatePlay.Create(Application);

Later you switch between states using Current or Push or Pop, like this:

TUIState.Current := StateMain;

See https://castle-engine.io/states and numerous engine examples.

Public destructor Destroy; override;
 
Public constructor CreateUntilStopped;

Create the instance TUIState that will be automatically freed when the state is stopped. This allows alternative usage of states (as opposed to the ones described in Create docs), where you create short-lived instances of state classes. Use it like this:

TUIState.Current := TStateMain.CreateUntilStopped;

The advantages:

  • You don't need to worry that some state field value will "survive" with an invalid value after Stop. So you don't need to clear everything in Stop or initialize everything in Start, instead you can depend that Start happens only once right after the constructor, so the instance fields are clear.

  • You avoid having global variables, keeping singletons of each state class. So the code is a little safer.

  • You can reintroduce your own constructor to require some parameters, instead of exposing state parameters as public fields/properties.

The disadvantage is that you cannot store in state fields anything that should "survive" the state Stop. You can instead use "class variables" in state class, or any global variable.

Public procedure Start; virtual;

Executed when state becomes active, it's now part of the state stack.

Started state is part of the StateStack, and will soon become running (top-most on the stack). When the state is set to be current, by TUIState.Current := MyState, this happens:

  1. MyState is pushed as the top-most state on state stack.

  2. MyState.Start is called.

  3. MyState is added to the StateContainer.Controls list, so the state methods GLContextOpen and Resize are called (as for all normal TCastleUserInterface instances).

  4. MyStar.Resume is called.

Public procedure Stop; virtual;

Executed when state is no longer active, no longer part of state stack.

When the state stops becoming active, this happens:

  1. MyState.Pause is called.

  2. MyState is removed from the StateContainer.Controls list. So the state method GLContextClose is called (as for all normal TCastleUserInterface instances).

  3. MyState.Stop is called.

  4. MyState is removed from the on state stack.

This is always called to finalize the started state. When the state is destroyed, it's Pause and Stop are called too, so you can use this method to reliably finalize whatever you initialized in Start.

Public procedure Resume; virtual;

Executed when state is now the top-most state. See Start and Stop docs about state lifecycle methods. This is called after Start, it is also called when you pop another state, making this state the top-most.

Public procedure Pause; virtual;

Executed when state is no longer the top-most state. See Start and Stop docs about state lifecycle methods. This is called before Stop, it is also called when another state is pushed over this state, so this stops being the the top-most state.

Public procedure Finish; virtual; deprecated 'use Stop';

Warning: this symbol is deprecated: use Stop

 
Public function Active: boolean;

State is right now part of the state stack, which means it's between Start and Stop calls. The state is added to the stack before the Start call, and removed after the Stop call, so this returns True during all the methods — Start, Resume, Pause, Stop.

Public function Press(const Event: TInputPressRelease): boolean; override;
 
Public function Release(const Event: TInputPressRelease): boolean; override;
 
Public function Motion(const Event: TInputMotion): boolean; override;
 
Public procedure Update(const SecondsPassed: Single; var HandleInput: boolean); override;
 
Public procedure Render; override;
 
Public function UIScale: Single; override;
 
Public procedure InsertUserInterface(const ADesignUrl: String; const FinalOwner: TComponent; out Ui: TCastleUserInterface; out UiOwner: TComponent); overload; deprecated 'instead of this, set DesignUrl in constructor';

Warning: this symbol is deprecated: instead of this, set DesignUrl in constructor

Load and show a user interface from a .castle-user-interface file, designed in Castle Game Engine Editor.

This is an utility method, loading a UI in a typical way into the TUIState. It is not the only way to load a .castle-user-interface file, a more general approach is to use UserInterfaceLoad from CastleComponentSerialize unit, and manually call InsertFront to add it to the state UI.

The loaded UI is returned under the Ui parameter, unless you use an overloaded version that omits this parameter.

It is owned by UiOwner, which is useful to find the components by name, like

ButtonQuit := UiOwner.FindRequiredComponent('ButtonQuit') as TCastleButton;

The UiOwner, in turn, is owned by the FinalOwner. You typically use this method inside overridden Start, and as FinalOwner you pass FreeAtStop – this way the user interface is freed when the state is stopped.

Public procedure InsertUserInterface(const ADesignUrl: String; const FinalOwner: TComponent; out UiOwner: TComponent); overload; deprecated 'instead of this, set DesignUrl in constructor';

Warning: this symbol is deprecated: instead of this, set DesignUrl in constructor

 
Public procedure WaitForRenderAndCall(const Event: TNotifyEvent);

Wait until the render event happens (to redraw current state), and then call Event.

The scheduled Event will be called at the Update time.

If the state stopped before the scheduled events fired, then the remaining events are ignored. That is, the scheduled events are cleared every time you start the state.

One use-case of this is to show a loading state, where you load things, but also update the loading progress from time to time. Be careful in this case to not call this too often, as then your loading time will be tied to rendering time. For example, when the monitor refresh rate is 60, and the "vertical sync" is "on", then the render events happen at most 60 times per second. So if you call WaitForRenderAndCall too often during loading, you may spend more time waiting for render event (each WaitForRenderAndCall taking 1/60 of second) than doing actual loading.

Public function DesignedComponent(const ComponentName: String): TComponent;

When the DesignUrl is set, and the state is started, you can use this method to find loaded components. Like this:

MyButton := DesignedComponent('MyButton') as TCastleButton;

See DesignUrl for full usage example.

See also
DesignUrl
Load a designed user interface (from .castle-user-interface file) when this state is started.

Properties

Public class property Current: TUIState read GetCurrent write SetCurrent;

Current state. Simply assign to this property to change the current state.

In case multiple states are active (only possible if you used the Push method), this property returns the bottom state (use CurrentTop to get top state). Setting this property resets whole state stack.

When is it allowed to change the state?

While in theory you can change current state stack (assigning TUIState.Current or using TUIState.Push / TUIState.Pop) at any moment, but remember that stopping the state frees also the state UI. So you should not change the current state stack within events/overriden methods of classes like TCastleUserInterface, TCastleTransform, TCastleBehavior that could be destroyed by the state stop.

The simpler advise is: Assign to TUIState.Current or use TUIState.Push / TUIState.Pop only from the overridden TUIState methods. Like TMyState.Update or TMyState.Press.

Note that you cannot change current state stack when another change is in progress. That is, you cannot change state from within TMyState.Start/Resume/Pause/Stop.

Public class property CurrentTop: TUIState read GetCurrentTop;

The top-most state.

In case you used Push, this returns the top-most (most recently pushed) state.

If there is only one (or none) state, e.g. because you never used Push, then this property returns the same thing as Current.

Public class property StateStack [constIndex:Integer]: TUIState read GetStateStack;

Access any state within the state stack. Use with indexes between 0 and StateStackCount - 1. State stack is managed using Start / Push / Pop.

Public property InterceptInput: boolean read FInterceptInput write FInterceptInput default false;

Prevents passing mouse/keyboard events to the states underneath.

More precisely, when this property is True, then the Press, Release and Motion events are marked as "handled" in this UI state. This means that they will not be processed further, by UI controls under this state, in particular by UI states that are underneath this state in state stack (created by Push method). They will also not be passed to final container (TCastleWindow, TCastleControl) callbacks like TCastleWindow.OnPress (as these callbacks are always used at the end, when nothing else handled the event).

Note that setting this to True means that calling inherited from your Press overridden implementation will always return True, as if the ancestor handled all the items. For this reason, in such case you should not immediately Exit when inherited is True. You should just ignore the ancestor result, like this:

function TMyState.Press(const Event: TInputPressRelease): Boolean;
begin
  Result := inherited;
  // ignore the ancestor result, as we use InterceptInput, so ancestor always returns true
  // if Result the Exit;

  if Event.IsMouseButton(buttonLeft) then
  begin
    ...
    Exit(true);
  end;
end;

Public property DesignUrl: String read FDesignUrl write SetDesignUrl;

Load a designed user interface (from .castle-user-interface file) when this state is started. You typically set this property in overridden constructor, and in effect the given design file will be loaded right before Start and it will be freed right after Stop.

Typical use-case looks like this:

constructor TStatePlay.Create(AOwner: TComponent);
begin
  inherited;
  DesignUrl := 'castle-data:/gamestateplay.castle-user-interface';
  // DesignPreload := true; // to make future "TUIState.Current := StatePlay" faster
end;

procedure TStatePlay.Start;
begin
  inherited;
  MyButton := DesignedComponent('MyButton') as TCastleButton;
end;

You can also modify this property when the state has already started (after Start and before Stop) in which case the previous design will be freed and new one will be loaded immediately.

Set this to empty string (default) to mean "no design should be loaded".

Note that this is not the only way to load a .castle-user-interface file. A more general approach is to use UserInterfaceLoad, and call InsertFront to add it to the state UI. Using this property just adds some comfortable automatic behavior (state is freed at stop, and you can use comfortable DesignedComponent or DesignPreload).

See also
DesignPreload
Preload the design file (specified in DesignUrl) as soon as possible, making starting the state much faster.
DesignedComponent
When the DesignUrl is set, and the state is started, you can use this method to find loaded components.
Public property DesignPreload: Boolean read FDesignPreload write SetDesignPreload default false;

Preload the design file (specified in DesignUrl) as soon as possible, making starting the state much faster. Using this property means that we "preload" the design file, to cache the referenced images / TCastleScene instances etc. as soon as possible, to make the future loading of this design (when state starts) very quick.

Typically you set this property in overridden TUIState constructor, right after (or before, it doesn't matter) setting DesignUrl. It will mean that constructor takes a longer time (which typically means that Application.OnInitialize takes a longer time) but in exchange future starting of the state (when you do e.g. TUIState.Current := StateXxx or TUIState.Push(StateXxx) will be much faster.

No functional difference should be visible, regardless of the DesignPreload value. Internally the designed component is added/removed from state at the same time. So think of this property as pure optimization – you decide whether to slow down the state constructor, or state start.

See also
DesignUrl
Load a designed user interface (from .castle-user-interface file) when this state is started.
Published property FullSize default true;

TUIState control makes most sense when it is FullSize, filling the whole window.

This way it always captures events on the whole container. And the child controls (anchored to this) behave like anchored to the whole container.


Generated by PasDoc 0.16.0.