Basic user interface control class. All controls derive from this class, overriding chosen methods to react to some events. Various user interface containers (things that directly receive messages from something outside, like operating system, windowing library etc.) implement support for such controls.
Control may handle mouse/keyboard input, see Press and Release methods.
Various methods return boolean saying if input event is handled. The idea is that not handled events are passed to the next control suitable. Handled events are generally not processed more — otherwise the same event could be handled by more than one listener, which is bad. Generally, return ExclusiveEvents if anything (possibly) was done (you changed any field value etc.) as a result of this, and only return False when you're absolutely sure that nothing was done by this control.
All screen (mouse etc.) coordinates passed here should be in the usual window system coordinates, that is (0, 0) is left-top window corner. (Note that this is contrary to the usual OpenGL 2D system, where (0, 0) is left-bottom window corner.)
It TUIControl class, this returns the value of Exists property. May be overridden in descendants, to return something more complicated, but it should always be a logical "and" with the inherited GetExists implementation (so setting the Exists := false will always work), like
Result := (inherited GetExists) and MyComplicatedConditionForExists;
function PositionInside(const Position: TVector2Single): boolean; virtual;
Is given position inside this control. Returns always False in this class. Always treated like False when GetExists returns False, so the implementation of this method only needs to make checks assuming that GetExists = True.
procedure BeforeRender; virtual;
Prepare your resources, right before drawing. Called only when GetExists and GLInitialized.
procedure Render; virtual;
Render a control. Called only when GetExists and GLInitialized, you can depend on it in the implementation of this method.
Do's and don't's when implementing Render:
All controls with RenderStyle = rs3D are drawn first.
The state of projection matrix (GL_PROJECTION for fixed-function pipeline, and global ProjectionMatrix variable) is undefined for rs3D objects. As is the viewport. So you should always set the viewport and projection yourself at the beginning of rs3D rendring, usually by CastleGLUtils.PerspectiveProjection or CastleGLUtils.OrthoProjection. Usually you should just use TCastleSceneManager, which automatically sets projection to something suitable, see TCastleSceneManager.ApplyProjection and TCastleScene.GLProjection.
Then all the controls with RenderStyle = rs2D are drawn. For them, OpenGL projection is guaranteed to be set to standard 2D that fills the whole screen, like by
The only OpenGL state you can change carelessly is:
The modelview matrix value.
rs3D controls can also freely change projection matrix value and viewport.
The raster position and WindowPos. The only place in our engine using WindowPos is the deprecated TCastleFont methods (ones without explicit X, Y).
The color (glColor), material (glMaterial) values.
The line width, point size.
Every other change should be secured to go back to original value. For older OpenGL, you can use glPushAttrib / glPopAttrib. For things that have guaranteed values at the beginning of draw method (e.g. scissor is always off for rs2D controls), you can also just manually set it back to off at the end (e.g. if you use scissor, them remember to disable it back at the end of draw method.)
Things that are guaranteed about OpenGL state when Render is called:
The current matrix is modelview, and it's value is identity.
Only for RenderStyle = rs2D: the WindowPos is at (0, 0). The projection and viewport is suitable as for 2D, see above.
Only for RenderStyle = rs2D: Texturing, depth test, lighting, fog, scissor are turned off.
Render a tooltip of this control. If you want to have tooltip for this control detected, you have to override TooltipExists. Then the TCastleWindowCustom.TooltipVisible will be detected, and your TooltipRender will be called.
The values of rs2D and rs3D are interpreted in the same way as RenderStyle. And TooltipRender is called in the same way as Render, so e.g. you can safely assume that modelview matrix is identity and (for 2D) WindowPos is zero. TooltipRender is always called as a last (front-most) 2D or 3D control.
function TooltipExists: boolean; virtual;
procedure TooltipRender; virtual;
procedure GLContextOpen; virtual;
Initialize your OpenGL resources.
This is called when OpenGL context of the container is created. Also called when the control is added to the already existing context. In other words, this is the moment when you can initialize OpenGL resources, like display lists, VBOs, OpenGL texture names, etc.
As an exception, this is called regardless of the GetExists value. This way a control can prepare it's resources, regardless if it exists now.
procedure GLContextClose; virtual;
Destroy your OpenGL resources.
Called when OpenGL context of the container is destroyed. Also called when controls is removed from the container Controls list. Also called from the destructor.
You should release here any resources that are tied to the OpenGL context. In particular, the ones created in GLContextOpen.
As an exception, this is called regardless of the GetExists value. This way a control can release it's resources, regardless if it exists now.
This can be useful as an optimization, to keep the OpenGL resources created even for controls that are not present on the TUIContainer.Controls list. This must used very, very carefully, as bad things will happen if the actual OpenGL context will be destroyed while the control keeps the OpenGL resources (because it had DisableContextOpenClose > 0). The control will then remain having incorrect OpenGL resource handles, and will try to use them, causing OpenGL errors or at least weird display artifacts.
Most of the time, when you think of using this, you should instead use the TUIControl.Exists property. This allows you to keep the control of the TUIContainer.Controls list, and it will be receive GLContextOpen and GLContextClose events as usual, but will not exist for all other purposes.
Using this mechanism is only sensible if you want to reliably hide a control, but also allow readding it to the TUIContainer.Controls list, and then you want to show it again. This is useful for CastleWindowModes, that must push (and then pop) the controls, but then allows the caller to modify the controls list. And some games, e.g. castle1, add back some (but not all) of the just-hidden controls. For example the TCastleNotifications instance is added back, to be visible even in the menu mode. This means that CastleWindowModes cannot just modify the TUIContainer.Exists value, leaving the control on the TUIContainer.Controls list: it would leave the TUIControl existing many times on the TUIContainer.Controls list, with the undefined TUIContainer.Exists value.
Not existing control is not visible, it doesn't receive input and generally doesn't exist from the point of view of user. You can also remove this from controls list (like TCastleWindowCustom.Controls), but often it's more comfortable to set this property to false.