/usr/src/castle-game-engine-5.2.0/window/castlewindow_android.inc is in castle-game-engine-src 5.2.0-2.
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 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 | {
Copyright 2004-2014 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.
----------------------------------------------------------------------------
}
{ Manage OpenGL ES context on Android using EGL,
and manage message loop using Android NDK. }
{$I castlewindow_egl.inc}
{$I castlewindow_dialogs_by_messages.inc}
{$ifdef read_interface_uses}
CastleAndroidNativeWindow, CastleAndroidInput,
CastleAndroidNativeAppGlue, CastleGenericLists,
{$endif}
{$ifdef read_implementation_uses}
CastleAndroidCWString, CastleAndroidLooper,
CastleAndroidLog, CastleAndroidAssetStream, CastleConfig,
CastleOpenAL, CastleVorbisFile, CastleFreeTypeH, CastleZLib,
{$endif}
{$ifdef read_window_interface}
private
NativeWindow: EGLNativeWindowType; //< must be set before Window.Open
{$endif read_window_interface}
{$ifdef read_application_interface}
private
type
{ On Android, we have to track previous touch positions using our
own structure, instead of relying on
MainWindow.FTouches.FingerIndexPosition[FingerIndex].
Reason: DoMotion is not immediately called when
receiving AMOTION_EVENT_ACTION_MOVE. That is bacause we have to do
our own event loop, that is emptied after native_app_glue is emptied,
so we may process many AMOTION_EVENT_ACTION_MOVE before calling
DoMotion for them.
This is a consequence of native_app_glue weirdness, that forces us to process
AMOTION_EVENT_ACTION_MOVE immediately, while calling DoMotion may take a while
(esp. if it calls Application.ProcessMessage). }
TTouchChange = object
OldPosition, Position: TVector2Single;
end;
TQueuedEventType = (aeOpen, aeMotionDown, aeMotionUp, aeMotionMove);
TQueuedEvent = object
EventType: TQueuedEventType;
Touch: TTouchChange;
FingerIndex: TFingerIndex;
end;
TQueuedEventList = specialize TGenericStructList<TQueuedEvent>;
const
FingerCount = 20;
var
FScreenWidth, FScreenHeight: Integer;
LastDoTimerTime: TMilisecTime;
AndroidApp: PAndroid_App;
QEvents: TQueuedEventList;
TouchChanges: array [0..FingerCount - 1] of TTouchChange;
function HandleInput(App: PAndroid_app; Event: PAInputEvent): boolean;
procedure HandleCommand(App: PAndroid_app; Command: Integer);
{$endif read_application_interface}
{$ifdef read_implementation_uses}
{$endif}
{$ifdef read_implementation}
{ Android NDK integration ---------------------------------------------------- }
procedure OpenContext;
var
Width, Height: Integer;
Window: TCastleWindowCustom;
NativeWindow: PANativeWindow;
begin
Window := Application.MainWindow;
NativeWindow := Window.NativeWindow; // NativeWindow was saved here by HandleCommand
{ get window size, use it }
Width := ANativeWindow_getWidth(NativeWindow);
Height := ANativeWindow_getHeight(NativeWindow);
AndroidLog(alInfo, 'OpenContext (size: %d %d)', [Width, Height]);
Application.FScreenWidth := Width;
Application.FScreenHeight := Height;
//Window.FullScreen := true; // TODO: setting fullscreen should work like that 4 lines below. Also, should be default?
Window.Left := 0;
Window.Top := 0;
Window.Width := Width;
Window.Height := Height;
{ create OpenGL context, run OnOpen events and so on }
Window.Open;
end;
{ Whenever the context is lost, this is called.
It's important that we release all OpenGL resources, to recreate them later. }
procedure CloseContext;
var
Window: TCastleWindowCustom;
begin
AndroidLog(alInfo, 'CloseContext');
Window := Application.MainWindow;
if Window <> nil then
begin
Window.Close;
Window.NativeWindow := nil; // make sure to not access the NativeWindow anymore
end;
end;
procedure TCastleApplication.HandleCommand(App: PAndroid_app; Command: Integer);
var
QEvent: TQueuedEvent;
ConfigState: TStringStream;
begin
case Command of
APP_CMD_INIT_WINDOW:
begin
{ We will actually process this event (calling TCastleWindow.Open)
later, to avoid ANRs when OnOpen does something time-consuming
(like SceneManager.LoadLevel). }
MainWindow.NativeWindow := App^.Window;
QEvent.EventType := aeOpen;
QEvents.Add(QEvent);
end;
APP_CMD_TERM_WINDOW:
begin
{ Note that we cannot delay processing this event using
QEvents. After CloseContext, window is really destroyed,
see android_app_post_exec_cmd in native app glue. }
QEvents.Clear;
CloseContext;
end;
APP_CMD_SAVE_STATE:
begin
{ We cannot delay processing this, onSaveInstanceState waits for us
to fill App^.SavedState. }
ConfigState := TStringStream.Create('');
try
{ read Config to ConfigState }
Config.Save(ConfigState);
ConfigState.Position := 0;
{ read ConfigState to App^.SavedState }
App^.SavedState := AllocateSavedState(ConfigState.Size);
ConfigState.ReadBuffer(App^.SavedState^, ConfigState.Size);
App^.SavedStateSize := ConfigState.Size;
{ log }
WritelnLogMultiline('Config', 'Saved state:' + NL + ConfigState.DataString);
finally FreeAndNil(ConfigState) end;
end;
end;
end;
function TCastleApplication.HandleInput(App: PAndroid_app; Event: PAInputEvent): boolean;
function CurrentPosition(const PointerIndex: Integer): TVector2Single;
begin
Result := Vector2Single(
AMotionEvent_getX(Event, PointerIndex),
ScreenHeight - AMotionEvent_getY(Event, PointerIndex));
end;
{ Note that MotionDown etc. do *not* call directly DoMouseDown and similar,
as we *cannot* block HandleInput for indefinite amount of time
(and DoMouseDown may call OnPress which may even do MessageOK
that makes a modal dialog calling Application.ProcessMessage inside).
The API of native_app_glue is somewhat stupid, it allows us to process
messages in a loop, but still the HandleInput and HandleCommand
cannot block making a modal dialog --- the process_input and process_cmd
implementations in native_app_glue depend on that. }
procedure MotionDown(const FingerIndex: TFingerIndex; const PointerIndex: Integer);
var
QEvent: TQueuedEvent;
begin
if FingerIndex < FingerCount then
begin
{ update TouchChanges }
TouchChanges[FingerIndex].OldPosition := TouchChanges[FingerIndex].Position;
TouchChanges[FingerIndex].Position := CurrentPosition(PointerIndex);
QEvent.EventType := aeMotionDown;
QEvent.FingerIndex := FingerIndex;
QEvent.Touch := TouchChanges[FingerIndex];
QEvents.Add(QEvent);
end;
end;
procedure MotionUp(const FingerIndex: TFingerIndex; const PointerIndex: Integer);
var
QEvent: TQueuedEvent;
begin
if FingerIndex < FingerCount then
begin
{ update TouchChanges }
TouchChanges[FingerIndex].OldPosition := TouchChanges[FingerIndex].Position;
TouchChanges[FingerIndex].Position := CurrentPosition(PointerIndex);
QEvent.EventType := aeMotionUp;
QEvent.FingerIndex := FingerIndex;
QEvent.Touch := TouchChanges[FingerIndex];
QEvents.Add(QEvent);
end;
end;
procedure MotionMove(const FingerIndex: TFingerIndex; const PointerIndex: Integer);
var
QEvent: TQueuedEvent;
NewPosition: TVector2Single;
const
MinDistanceToReportMove = 0.5;
begin
if FingerIndex < FingerCount then
begin
NewPosition := CurrentPosition(PointerIndex);
if PointsDistanceSqr(NewPosition, TouchChanges[FingerIndex].Position) >
Sqr(MinDistanceToReportMove) then
begin
{ update TouchChanges }
TouchChanges[FingerIndex].OldPosition := TouchChanges[FingerIndex].Position;
TouchChanges[FingerIndex].Position := NewPosition;
QEvent.EventType := aeMotionMove;
QEvent.FingerIndex := FingerIndex;
QEvent.Touch := TouchChanges[FingerIndex];
QEvents.Add(QEvent);
end;
end;
end;
var
MotionAction, PointerIndex, ActionPointerIndex: Integer;
begin
Result := false;
if AInputEvent_getType(Event) = AINPUT_EVENT_TYPE_MOTION then
begin
MotionAction := AMotionEvent_getAction(Event) and AMOTION_EVENT_ACTION_MASK;
ActionPointerIndex := (AMotionEvent_getAction(Event) and
AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) shr
AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
case MotionAction of
AMOTION_EVENT_ACTION_DOWN:
begin
for PointerIndex := 0 to AMotionEvent_getPointerCount(Event) - 1 do
MotionDown(AMotionEvent_getPointerId(Event, PointerIndex), PointerIndex);
Result := true;
end;
AMOTION_EVENT_ACTION_UP:
begin
for PointerIndex := 0 to AMotionEvent_getPointerCount(Event) - 1 do
MotionUp(AMotionEvent_getPointerId(Event, PointerIndex), PointerIndex);
Result := true;
end;
AMOTION_EVENT_ACTION_CANCEL:
WritelnLog('Android', 'Motion cancel event');
{ TODO: Handle this like AMOTION_EVENT_ACTION_UP? }
AMOTION_EVENT_ACTION_POINTER_DOWN:
MotionDown(AMotionEvent_getPointerId(Event, ActionPointerIndex), ActionPointerIndex);
AMOTION_EVENT_ACTION_POINTER_UP:
MotionUp(AMotionEvent_getPointerId(Event, ActionPointerIndex), ActionPointerIndex);
AMOTION_EVENT_ACTION_MOVE:
begin
for PointerIndex := 0 to AMotionEvent_getPointerCount(Event) - 1 do
MotionMove(AMotionEvent_getPointerId(Event, PointerIndex), PointerIndex);
Result := true;
end;
end;
end;
end;
procedure AndroidMainImplementation(App: PAndroid_App);
var
ConfigState: TStringStream;
begin
Application.AndroidApp := App;
{ Use SavedState as XML contents that should be loaded to global Config.
Note that we directly use AndroidLog below, not WritelnLog or OnWarning,
because user code had no chance to call InitializeLog yet. }
if App^.SavedState <> nil then
begin
ConfigState := TStringStream.Create('');
try
{ load ConfigState from App^.SavedState }
ConfigState.WriteBuffer(App^.SavedState^, App^.SavedStateSize);
ConfigState.Position := 0;
{ load Config from ConfigState }
try
Config.Load(ConfigState);
{ log }
AndroidLog(alInfo, 'Config: Loaded state:' + NL + ConfigState.DataString);
except
on E: TObject do
AndroidLog(alWarn, 'Config: Cannot read XML config from Android state: ' + ExceptMessage(E));
end;
finally FreeAndNil(ConfigState) end;
end else
AndroidLog(alInfo, 'Config: no saved state');
Application.Run;
end;
{ TCastleWindowCustom ---------------------------------------------------------- }
procedure TCastleWindowCustom.CreateBackend;
begin
end;
{ It would be nice to update Android's app menu based on MainMenu contents.
But we cannot do this from NDK, it would require implementing Java code
connected to us. }
procedure TCastleWindowCustom.BackendMenuInitialize;
begin
end;
procedure TCastleWindowCustom.BackendMenuFinalize;
begin
end;
procedure TCastleWindowCustom.MenuUpdateCaption(Entry: TMenuEntryWithCaption);
begin
MenuFinalize;
MenuInitialize;
end;
procedure TCastleWindowCustom.MenuUpdateEnabled(Entry: TMenuEntryWithCaption);
begin
MenuFinalize;
MenuInitialize;
end;
procedure TCastleWindowCustom.MenuUpdateChecked(Entry: TMenuItemChecked);
begin
MenuFinalize;
MenuInitialize;
end;
function TCastleWindowCustom.MenuUpdateCheckedFast: boolean;
begin
Result := false;
end;
procedure TCastleWindowCustom.MenuInsert(const Parent: TMenu;
const ParentPosition: Integer; const Entry: TMenuEntry);
begin
MenuFinalize;
MenuInitialize;
end;
procedure TCastleWindowCustom.MenuDelete(const Parent: TMenu;
const ParentPosition: Integer; const Entry: TMenuEntry);
begin
MenuFinalize;
MenuInitialize;
end;
procedure TCastleWindowCustom.OpenBackend;
begin
ContextCreateEnd(NativeWindow);
Application.OpenWindowsAdd(Self);
end;
procedure TCastleWindowCustom.CloseBackend;
begin
ContextDestroy;
{ Note that we do not clear NativeWindow now, because we may need it
if window is opened again. Although when Android system opens the window,
NativeWindow is set by OpenContext,
but when we manually reopen the window from
code (like "reopen context" button in android_demo), we need to keep
previous NativeWindow value. }
end;
procedure TCastleWindowCustom.SetCaption(const Part: TCaptionPart; const Value: string);
begin
FCaption[Part] := Value;
if not Closed then { TODO: use GetWholeCaption };
end;
procedure TCastleWindowCustom.SetCursor(const Value: TMouseCursor);
begin
if FCursor <> Value then
begin
FCursor := Value;
if not Closed then
{ TODO UpdateCursor };
end;
end;
procedure TCastleWindowCustom.SetCustomCursor(const Value: TRGBAlphaImage);
begin
{ TODO }
FCustomCursor := Value;
end;
function TCastleWindowCustom.RedirectKeyDownToMenuClick: boolean;
begin
{ Call menu shortcuts on key presses.
Since we don't show MainMenu, this is the only way how we can at least
support it's shortcuts. }
Result := true;
end;
procedure TCastleWindowCustom.SetMousePosition(const Value: TVector2Single);
begin
{ There is no cursor position on Android devices.
Just update MousePosition (although we're not really required to
do this, code using SetMousePosition should not depend that
it was always successfull). }
if not Closed then
FMousePosition := Value;
end;
procedure TCastleWindowCustom.SetFullScreen(const Value: boolean);
begin
FFullScreen := Value; //< does nothing on Android
end;
{ TCastleApplication ---------------------------------------------------------- }
procedure TCastleApplication.CreateBackend;
begin
AndroidMain := @AndroidMainImplementation;
end;
procedure TCastleApplication.DestroyBackend;
begin
end;
function TCastleApplication.ProcessAllMessages: boolean;
begin
Result := ProcessMessage(false, false);
end;
function TCastleApplication.ProcessMessage(WaitForMessage, WaitToLimitFPS: boolean): boolean;
{ Call Update on Application and all open Windows, call OnTimer on Application }
procedure WindowsUpdateAndTimer;
begin
DoSelfUpdate;
FOpenWindows.DoUpdate;
MaybeDoTimer(LastDoTimerTime);
end;
procedure Resize(Width, Height: Integer);
begin
AndroidLog(alInfo, 'Resize (size: %d %d)', [Width, Height]);
FScreenWidth := Width;
FScreenHeight := Height;
if not MainWindow.Closed then
MainWindow.DoResize(Width, Height, false);
end;
var
Ident, Events, NewWidth, NewHeight: Integer;
Source: Pandroid_poll_source;
QEvent: TQueuedEvent;
MessageHandlingBegin: TTimerResult;
begin
MessageHandlingBegin := Timer;
repeat
if WaitForMessage and AllowSuspendForInput and
{ Unfortunately, we have to be more conservative than
AllowSuspendForInput to decide when we can suspend.
Right now we cannot suspend on non-closed window.
Otherwise we do not react to resize events soon enough,
since we have to actually do a couple of loop passes until resize
reaches us. Reproducible on drawing_toy: without this,
we will not receive resize before drawing. }
((MainWindow = nil) or MainWindow.Closed) then
begin
WritelnLog('Android', 'Waiting for next event without consuming CPU ticks.');
Ident := ALooper_pollAll(-1 { wait }, nil, @Events, @Source);
end else
Ident := ALooper_pollAll(0, nil, @Events, @Source);
if Ident < 0 then Break;
if Source <> nil then
Source^.Process(AndroidApp, Source);
{ poll our own events quueue.
Yes, we need our own events queue to shield from the native_app_glue
weirdness... }
if MainWindow <> nil then
while QEvents.Count > 0 do
begin
QEvent := QEvents.First;
QEvents.Delete(0);
case QEvent.EventType of
aeOpen: OpenContext;
aeMotionDown:
if not MainWindow.Closed then
MainWindow.DoMouseDown(QEvent.Touch.Position, mbLeft, QEvent.FingerIndex);
aeMotionUp :
if not MainWindow.Closed then
MainWindow.DoMouseUp (QEvent.Touch.Position, mbLeft, QEvent.FingerIndex, false);
aeMotionMove:
if not MainWindow.Closed then
MainWindow.DoMotion (InputMotion(QEvent.Touch.OldPosition, QEvent.Touch.Position, [mbLeft], QEvent.FingerIndex));
end;
end;
// Check if we are exiting.
if AndroidApp^.DestroyRequested = 1 then
begin
CloseContext;
Exit(false);
end;
// loop condition avoids being clogged with motion events
until (Timer - MessageHandlingBegin) / TimerFrequency > 1 / LimitFPS;
if (MainWindow <> nil) and not MainWindow.Closed then
begin
{ check for Resize. As there is no reliable event to capture it
(ANativeWindow_getWidth and ANativeWindow_getheight are immediately
updated, but for some time EGL sizes stay old) so we just watch
for changes, and only fire our "Resize" when really EGL size changed. }
MainWindow.QuerySize(NewWidth, NewHeight);
if (NewWidth <> MainWindow.Width) or
(NewHeight <> MainWindow.Height) then
Resize(NewWidth, NewHeight);
{ we do not check Invalidated here, just redraw every frame when window is open }
MainWindow.DoRender;
WindowsUpdateAndTimer;
end;
{ Note that we ignore WaitToLimitFPS here, right now.
When redrawing (not MainWindow.Closed), it is always "on" in the main loop,
we don't control it, Android throttles render speed anyway.
When not redrawing, then there's not much point in WaitToLimitFPS,
we should rather let WaitForMessage mechanism work (when all windows are
closed, we should usually have WaitForMessage = true). }
Result := true;
end;
procedure TCastleApplication.Run;
begin
if MainWindow = nil then
raise Exception.Create('For Android, you have to assign Application.MainWindow');
if QEvents = nil then
QEvents := TQueuedEventList.Create else
QEvents.Clear;
FillByte(TouchChanges, SizeOf(TouchChanges), 0);
{ since we have AndroidApp now, prepare it }
AndroidApp^.OnAppCmd := @HandleCommand;
AndroidApp^.OnInputEvent := @HandleInput;
AssetManager := AndroidApp^.Activity^.AssetManager;
{ We could not do dlopen on Android before AndroidMain is called.
Load necessary libraries now. Some of the below libraries are not really
available on Android anyway, but we keep the list below for completeness
(engine gracefully handles lack of most libraries, only OpenGLES
is really required). }
{ Note that this may be called many times, because AndroidMainImplementation
may be called many times, because ANativeActivity_onCreate may be called
multple times. So things below must be implemented in a secure way,
to work Ok when executed multiple times. }
GLES20Initialization;
OpenALInitialization;
VorbisFileInitialization;
InitializeAndroidCWString;
LoadFreeTypeLibrary;
{$ifndef CASTLE_PNG_USING_FCL_IMAGE}
{$ifndef CASTLE_PNG_STATIC}
PngInitialization;
{$endif}
{$endif}
{$ifndef CASTLE_ZLIB_USING_PASZLIB}
ZLibInitialization;
{$endif}
while ProcessMessage(true, true) do ;
FreeAndNil(QEvents);
end;
procedure TCastleApplication.QuitWhenNoOpenWindows;
begin
{ TODO }
end;
function TCastleApplication.ScreenWidth: integer;
begin
Result := FScreenWidth;
end;
function TCastleApplication.ScreenHeight: integer;
begin
Result := FScreenHeight;
end;
function TCastleApplication.BackendName: string;
begin
Result := 'Android';
end;
{ TCastleClipboard ----------------------------------------------------------- }
function TCastleClipboard.GetAsText: string;
begin
{ TODO }
Result := '';
end;
procedure TCastleClipboard.SetAsText(const Value: string);
begin
{ TODO }
end;
{ TCastleWindow -------------------------------------------------------------- }
procedure TCastleWindow.NavigationInfoChanged;
begin
end;
{$endif read_implementation}
|