/usr/src/castle-game-engine-4.1.1/ui/opengl/castlenotifications.pas is in castle-game-engine-src 4.1.1-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | {
Copyright 2002-2013 Michalis Kamburelis.
This file is part of "Castle Game Engine".
"Castle Game Engine" is free software; see the file COPYING.txt,
included in this distribution, for details about the copyright.
"Castle Game Engine" is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
----------------------------------------------------------------------------
}
{ Notifications displayed in the OpenGL window (TCastleNotifications). }
unit CastleNotifications;
interface
uses GL, GLU, CastleUIControls, Classes, SysUtils, CastleUtils, CastleGLUtils,
CastleGLBitmapFonts, CastleTimeUtils, CastleVectors, CastleStringUtils,
FGL, CastleColors;
type
THorizontalPosition = (hpLeft, hpMiddle, hpRight);
TVerticalPosition = (vpDown, vpMiddle, vpUp);
{ Internal type. @exclude }
TNotification = class
Text: string;
Time: TMilisecTime; {< appear time }
end;
{ Internal type. @exclude }
TNotificationList = class(specialize TFPGObjectList<TNotification>)
procedure DeleteFirst(DelCount: Integer);
end;
{ Notifications displayed in the OpenGL window.
The idea is to display messages about something happening
at the bottom / top of the screen. These messages disappear by themselves
after some short time.
Similar to older FPS games messages, e.g. DOOM, Quake, Duke Nukem 3D.
Suitable for game messages like "Picked up 20 ammo"
or "Player Foo joined game".
This is a TUIControl descendant, so to use it --- just add it
to TCastleWindowCustom.Controls or TCastleControlCustom.Controls.
Call @link(Show) to display a message. }
TCastleNotifications = class(TUIControl)
private
{ Messages, ordered from oldest (new mesages are added at the end).}
Messages: TNotificationList;
FHorizontalPosition: THorizontalPosition;
FVerticalPosition: TVerticalPosition;
FColor: TVector3Single;
FMaxMessages: integer;
FTimeout: TMilisecTime;
FHorizontalMargin, FVerticalMargin: Integer;
FHistory: TCastleStringList;
FCollectHistory: boolean;
public
const
DefaultMaxMessages = 4;
DefaultMessagesTimeout = 5000;
DefaultHorizontalPosition = hpMiddle;
DefaultVerticalPosition = vpDown;
DefaultHorizontalMargin = 10;
DefaultVerticalMargin = 1;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
{ Show new message. An overloaded version that takes a single string will
detect newlines in the string automatically so a message may be multi-line.
The messages will be automatically broken to fit on the screen width with
given font.
@groupBegin }
procedure Show(const s: string); overload;
procedure Show(s: TStringList); overload;
{ @groupEnd }
{ Clear all messages. }
procedure Clear;
procedure Update(const SecondsPassed: Single;
var HandleInput: boolean); override;
procedure Draw; override;
function DrawStyle: TUIControlDrawStyle; override;
{ Color used to draw messages. Default value is yellow. }
property Color: TVector3Single read FColor write FColor;
{ All the messages passed to @link(Show), collected only if CollectHistory.
May be @nil when not CollectHistory. }
property History: TCastleStringList read FHistory;
published
{ How many message lines should be visible on the screen, at maximum. }
property MaxMessages: integer
read FMaxMessages write FMaxMessages default DefaultMaxMessages;
{ How long a given message should be visible on the screen, in miliseconds.
Message stops being visible when this timeout passed,
or when we need more space for new messages (see MaxMessages). }
property Timeout: TMilisecTime
read FTimeout write FTimeout default DefaultMessagesTimeout;
property HorizontalPosition: THorizontalPosition read FHorizontalPosition
write FHorizontalPosition default DefaultHorizontalPosition;
property VerticalPosition: TVerticalPosition read FVerticalPosition
write FVerticalPosition default DefaultVerticalPosition;
{ Margins, in pixels, from the border of the container (window or such).
@groupBegin }
property HorizontalMargin: Integer read FHorizontalMargin write FHorizontalMargin
default DefaultHorizontalMargin;
property VerticalMargin: Integer read FVerticalMargin write FVerticalMargin
default DefaultVerticalMargin;
{ @groupEnd }
{ Turn this on to have all the messages you pass to @link(Show) be collected
inside @link(History) string list. @link(History) is expanded by @link(Show),
it is cleared by @link(Clear), just like the notifications on screen.
However, unlike the visible messages, it has unlimited size
(messages there are not removed when MaxMessages or @link(Timeout)
take action), and messages inside are not broken to honour screen width.
This is useful if you want to show the player a history of messages
(in case they missed the message in game). }
property CollectHistory: boolean read FCollectHistory write FCollectHistory
default false;
end;
procedure Register;
implementation
uses CastleLog, CastleControls;
procedure Register;
begin
RegisterComponents('Castle', [TCastleNotifications]);
end;
procedure TNotificationList.DeleteFirst(DelCount: Integer);
var
I: Integer;
begin
{ Could be optimized better, but this is simple and works correctly
with TFPGObjectList.FreeObjects = true management.
This is called only for really small DelCount values, so no problem. }
for I := 1 to DelCount do Delete(0);
end;
{ TCastleNotifications ------------------------------------------------------- }
constructor TCastleNotifications.Create(AOwner: TComponent);
begin
inherited;
Messages := TNotificationList.Create;
FHistory := TCastleStringList.Create;
MaxMessages := DefaultMaxMessages;
Timeout := DefaultMessagesTimeout;
FHorizontalPosition := DefaultHorizontalPosition;
FVerticalPosition := DefaultVerticalPosition;
FHorizontalMargin := DefaultHorizontalMargin;
FVerticalMargin := DefaultVerticalMargin;
FColor := Yellow3Single;
end;
destructor TCastleNotifications.Destroy;
begin
FreeAndNil(Messages);
FreeAndNil(FHistory);
inherited;
end;
procedure TCastleNotifications.Show(S: TStringList);
procedure AddStrings(S: TStrings);
var
N: TNotification;
i: integer;
begin
{ Below could be optimized. But we use this only for a small number
of messages, so no need to. }
for i := 0 to S.Count - 1 do
begin
if Messages.Count = MaxMessages then Messages.Delete(0);
N := TNotification.Create;
N.Text := S[i];
N.Time := GetTickCount;
Messages.Add(N);
end;
end;
var
Broken: TStringList;
begin
if Log then
WriteLog('Time message', S.Text);
{ TODO: It's a bummer that we need UIFont created (which means:
OpenGL context must be initialized) to make BreakLines,
while BreakLines only really uses font metrics (doesn't need OpenGL
font resources). }
if ContainerSizeKnown and GLInitialized then
begin
Broken := TStringList.Create;
try
UIFont.BreakLines(s, Broken, ContainerWidth - HorizontalMargin * 2);
AddStrings(Broken);
finally Broken.Free end;
end else
AddStrings(S);
if CollectHistory then
History.AddList(S);
VisibleChange;
end;
procedure TCastleNotifications.Show(const s: string);
var
strs: TStringList;
begin
strs := TStringList.Create;
try
strs.Text := s;
Show(strs);
finally strs.Free end;
end;
procedure TCastleNotifications.Clear;
begin
Messages.Clear;
if CollectHistory then
History.Clear;
VisibleChange;
end;
procedure TCastleNotifications.Draw;
var
i: integer;
x, y: integer;
begin
if DrawStyle = dsNone then Exit;
glLoadIdentity;
glColorv(Color);
for i := 0 to Messages.Count-1 do
begin
{ calculate x relative to 0..ContainerWidth, then convert to 0..GLMaxX }
case HorizontalPosition of
hpLeft : x := HorizontalMargin;
hpRight : x := ContainerWidth-UIFont.TextWidth(messages[i].Text) - HorizontalMargin;
hpMiddle: x := (ContainerWidth-UIFont.TextWidth(messages[i].Text)) div 2;
end;
{ calculate y relative to 0..ContainerHeight, then convert to 0..GLMaxY }
case VerticalPosition of
vpDown : y := (Messages.Count-i-1) * UIFont.RowHeight + UIFont.Descend + VerticalMargin;
vpMiddle: y := (ContainerHeight - Messages.Count * UIFont.RowHeight) div 2 + i*UIFont.RowHeight;
vpUp : y := ContainerHeight-(i+1)*UIFont.RowHeight - VerticalMargin;
end;
SetWindowPos(x, y);
UIFont.PrintAndMove(Messages[i].Text);
end;
end;
function TCastleNotifications.DrawStyle: TUIControlDrawStyle;
begin
if GetExists and (Messages.Count <> 0) then
Result := ds2D else
Result := dsNone;
end;
procedure TCastleNotifications.Update(const SecondsPassed: Single;
var HandleInput: boolean);
{ Check which messages should time out. }
var
gtc: TMilisecTime;
i: integer;
begin
inherited;
gtc := GetTickCount;
for i := Messages.Count - 1 downto 0 do
if TimeTickSecondLater(Messages[i].Time, gtc, Timeout) then
begin { delete messages 0..I }
Messages.DeleteFirst(I + 1);
VisibleChange;
break;
end;
end;
end.
|