00001 // /////////////////////////////////////////////////////////////////////////// 00002 // lesson01_main.cpp by Victor Dods, created 2006/07/25 00003 // /////////////////////////////////////////////////////////////////////////// 00004 // Unless a different license was explicitly granted in writing by the 00005 // copyright holder (Victor Dods), this software is freely distributable under 00006 // the terms of the GNU General Public License, version 2. Any works deriving 00007 // from this work must also be released under the GNU GPL. See the included 00008 // file LICENSE for details. 00009 // /////////////////////////////////////////////////////////////////////////// 00010 00011 00012 // /////////////////////////////////////////////////////////////////////////// 00013 // Lesson 01 - GUI Widgets And The Game Loop 00014 // /////////////////////////////////////////////////////////////////////////// 00015 00016 /* @endcode 00019 This lesson will show you how to begin creating use graphical user interfaces 00020 and how to design and run a game loop -- the heartbeat of a game engine. 00021 00022 <ul> 00023 <li>@ref lesson01_main.cpp "This lesson's source code"</li> 00024 <li>@ref lessons "Main lesson index"</li> 00025 </ul> 00026 00027 <strong>Procedural Overview</strong> -- Items in bold are additions/changes to the previous lesson. 00028 00029 <ul> 00030 <li>Main function</li> 00031 <ul> 00032 <li>Initialize the platform abstraction layer (Pal) and game engine singletons. 00033 Create the Screen object. This was covered in previous lesson(s).</li> 00034 <li>Execute game-specific code.</li> 00035 <ul> 00036 <li><strong>Create formatted layouts of GUI widgets.</strong></li> 00037 <li><strong>Run the game loop</strong></li> 00038 <ul> 00039 <li><strong>Handle events (user and system-generated).</strong></li> 00040 <li><strong>Perform off-screen processing.</strong></li> 00041 <li><strong>Draw the Screen object's entire widget hierarchy.</strong></li> 00042 </ul> 00043 </ul> 00044 <li>Delete the Screen object. Shutdown the Pal and game engine singletons. 00045 This was covered in previous lesson(s).</li> 00046 </ul> 00047 </ul> 00048 00049 Comments explaining previously covered material will be made more terse or 00050 deleted entirely in each successive lesson. If something is not explained 00051 well enough, it was probably already explained in 00052 @ref lessons "previous lessons". 00053 00054 <strong>Code Diving!</strong> 00055 00056 @code */ 00057 // This header MUST be included in every source/header file. 00058 #include "xrb.hpp" 00059 00060 #include "xrb_button.hpp" // For use of the Button widget class. 00061 #include "xrb_event.hpp" // For use of the Event classes. 00062 #include "xrb_eventqueue.hpp" // For use of the EventQueue class. 00063 #include "xrb_inputstate.hpp" // For use of the InputState class (via Singleton::). 00064 #include "xrb_label.hpp" // For use of the Label widget class. 00065 #include "xrb_layout.hpp" // For use of the Layout widget class. 00066 #include "xrb_lineedit.hpp" // For use of the LineEdit widget class. 00067 #include "xrb_screen.hpp" // For use of the necessary Screen widget class. 00068 #include "xrb_sdlpal.hpp" // For use of the SDLPal platform abstraction layer. 00069 00070 // Used so we don't need to qualify every library type/class/etc with Xrb:: 00071 using namespace Xrb; 00072 00073 // This is just a helper function to group all the shutdown code together. 00074 void CleanUp () 00075 { 00076 fprintf(stderr, "CleanUp();\n"); 00077 00078 // Shutdown the platform abstraction layer. 00079 Singleton::Pal().Shutdown(); 00080 // Shutdown the game engine singletons. This is necessary for the 00081 // game engine to shutdown cleanly. 00082 Singleton::Shutdown(); 00083 } 00084 00085 int main (int argc, char **argv) 00086 { 00087 fprintf(stderr, "main();\n"); 00088 00089 // Initialize the game engine singleton facilities. 00090 Singleton::Initialize(SDLPal::Create, "none"); 00091 // Initialize the platform abstraction layer (Pal). 00092 if (Singleton::Pal().Initialize() != Pal::SUCCESS) 00093 return 1; 00094 // Set the window caption. 00095 Singleton::Pal().SetWindowCaption("XuqRijBuh Lesson 01"); 00096 00097 // This call creates the Screen object and initializes the given video mode. 00098 // The Screen object is the root widget of the GUI widget hierarchy, and 00099 // does a bunch of special handling to draw its child widgets properly. 00100 Screen *screen = Screen::Create( 00101 800, // video mode/screen width 00102 600, // video mode/screen height 00103 32, // video mode pixel bitdepth 00104 false); // not fullscreen -- none for now. 00105 // If the Screen failed to initialize, print an error message and quit. 00106 if (screen == NULL) 00107 { 00108 fprintf(stderr, "failure during Screen creation\n"); 00109 // this shuts down the Pal and singletons. 00110 CleanUp(); 00111 // return with an error value. 00112 return 2; 00113 } 00114 00115 /* @endcode 00116 At this point, the singletons and the Pal have been initialized, the video 00117 mode has been set, and the engine is ready to go. Here is where the 00118 application-specific code begins. 00119 00120 Everything that is visible in this game engine happens inside an instance 00121 of a Widget subclass. Therefore, we need to instantiate some widgets 00122 before anything interesting can happen. 00123 00124 There are custom subclasses of Widget (such as Label, LineEdit, Button, 00125 Layout, etc) to perform various functions. Widgets are organized in a 00126 hierarchy (i.e. parent/child relationship) which also dictates the spatial 00127 organization of widgets onscreen -- a child widget is completely contained 00128 within its parent, and cannot draw anything outside itself. This is the 00129 same widget paradigm as used by the FLTK, MFC, QT (and many other) 00130 toolkits. The GUI system in this game engine is primarily modeled 00131 after Trolltech's excellent QT GUI toolkit. 00132 00133 Instead of having to worry about the exact screen coordinates at which to 00134 place our widgets, there is a Widget subclass called Layout which 00135 automatically handles widget sizing and placement. Layouts can place 00136 widgets in horizontal/vertical lines or in grids, and can be nested 00137 within one another to form complicated formatting of widgets. 00138 @code */ 00139 { 00140 /* @endcode 00141 We will now create a Layout widget as a child of our Screen (which, 00142 not coincidentally, is itself a Widget subclass). this Layout will be 00143 the parent to the widgets we place inside. The first parameter is the 00144 orientation of the Layout (a vertical column of widgets). The second 00145 parameter is the Widget to assign as its parent -- in this case, the 00146 Screen itself. The third parameter, which is present in almost all 00147 Widget subclasses after the parent widget parameter, is the "name" of 00148 the widget. This parameter is optional, but its use is recommended. 00149 It isn't used directly by the engine, but is invaluable in debugging 00150 GUI problems -- the ability to identify which widget you're dealing 00151 with in a mostly meaningless call stack within some event loop. 00152 @code */ 00153 Layout *main_layout = new Layout(VERTICAL, screen, "main layout"); 00154 /* @endcode 00155 This call causes main_layout to fill out the entire space of screen. 00156 SetMainWidget is a Widget method, so can be used on any parent widget 00157 for a child. If the parent widget's size or location change, its 00158 main widget will be resized and/or moved accordingly. 00159 @code */ 00160 screen->SetMainWidget(main_layout); 00161 00162 /* @endcode 00163 Now to create some directly useful widgets. 00164 00165 A Label is a simple, non-interactive widget which draws text or a 00166 picture (we'll get to picture labels later). the default 00167 justification for a Label's contents is centered both horizontally 00168 and vertically, but this, among other properties, can be changed. 00169 @code */ 00170 new Label("I LIKE ZOMBIES.", main_layout, "awesome zombie text label"); 00171 /* @endcode 00172 A Button can be clicked upon to signal some other piece of code to do 00173 something. This button isn't connected to anything, but we'll get to 00174 that later. 00175 @code */ 00176 new Button("This button does nothing", main_layout, "do-nothing button"); 00177 00178 /* @endcode 00179 Layouts, just like any Widget subclass, can be contained within other 00180 Layouts. This layout will be used to place a text Label and a LineEdit 00181 side-by-side. The code block is only being used to indicate creation 00182 of the layout and its child widgets. 00183 00184 A LineEdit is a text-entry box. The first parameter in its 00185 constructor indicates the maximum number of characters that can be 00186 entered into it. It derives from the same baseclass as Label, so it 00187 shares many of the same properties as Label, such as alignment, text 00188 color, and so forth. 00189 @code */ 00190 { 00191 Layout *sub_layout = new Layout(HORIZONTAL, main_layout, "label and line-edit layout"); 00192 00193 // Create a text Label to indicate what to do with the following LineEdit. 00194 new Label("You can enter up to 30 characters in this LineEdit ->", sub_layout, "indicator label"); 00195 // Create the LineEdit after the Label, and it will be placed next 00196 // in the horizontal layout. 00197 new LineEdit(30, sub_layout, "30-char line edit"); 00198 } 00199 00200 /* @endcode 00201 Add another horizontal Layout so we can have a row of text Labels to 00202 demonstrate the word wrapping and alignment properties. Again, the 00203 code block is only being used to indicate creation of the layout and 00204 its child widgets. Make sure to read the text contained in each Label, 00205 as each contains useful information. 00206 @code */ 00207 { 00208 Layout *sub_layout = new Layout(HORIZONTAL, main_layout, "note label layout"); 00209 00210 // Another text Label. It notes that holding down a key will not cause 00211 // the character to repeat. Doing this will be discussed later. This 00212 // Label demonstrates word wrapping and justification. 00213 Label *note_label = 00214 new Label( 00215 "Note that holding a key down while typing into the\n" 00216 "LineEdit does not cause more than one character to\n" 00217 "be entered (i.e. no keyboard repeating). Doing so\n" 00218 "complicates the event-polling loop, and it was\n" 00219 "omitted to keep this example as simple as possible.\n" 00220 "Notice how this text Label is wider than the other\n" 00221 "three adjacent Labels. This is because by default,\n" 00222 "text Labels' minimum width is fixed to the text\n" 00223 "width, so none of the text is cut off. The other\n" 00224 "three Labels can be resized horizontally because\n" 00225 "they each have word wrapping enabled, so the text\n" 00226 "formatting is dictated by the width of the Label.", 00227 sub_layout, 00228 "left-aligned note label"); 00229 // All text will be aligned with the left edge of the Label. By default, 00230 // a Label's alignment is CENTER. 00231 note_label->SetAlignment(Dim::X, LEFT); 00232 00233 // Add another label, this time, with centered alignment. 00234 note_label = 00235 new Label( 00236 "This Label widget is using word wrapping with center alignment. " 00237 "The width of the Label dictates where the words will be wrapped.", 00238 sub_layout, 00239 "center-aligned note label with word wrapping"); 00240 // This call does exactly what it looks like it does. 00241 note_label->SetWordWrap(true); 00242 // The default alignment is already CENTER, so we don't need to do anything. 00243 00244 // Add another label, this time, with right alignment. 00245 note_label = 00246 new Label( 00247 "This Label widget is using word wrapping with right alignment. " 00248 "Any Label (not just word wrapped Labels) can use the alignment " 00249 "property, including aspect-ratio-preserving picture Labels.", 00250 sub_layout, 00251 "right-aligned note label with word wrapping"); 00252 // This call does exactly what it looks like it does. 00253 note_label->SetWordWrap(true); 00254 // All text will be aligned with the right edge of the Label. 00255 note_label->SetAlignment(Dim::X, RIGHT); 00256 00257 // Add another label, this time, with right alignment. 00258 note_label = 00259 new Label( 00260 "This Label widget uses word wrapping with character spacing. " 00261 "Spacing is a special type of alignment that only applies to word " 00262 "wrapped Labels, and will attempt to space the characters out to " 00263 "fill out the entire width of the Label. The extra spacing between " 00264 "characters seems to sometimes screw up the font's kerning (the " 00265 "space between specific glyph pairs to make the text look more " 00266 "natural).", 00267 sub_layout, 00268 "spaced note label with word wrapping"); 00269 // This call does exactly what it looks like it does. 00270 note_label->SetWordWrap(true); 00271 // see note_label's text for a description. 00272 note_label->SetAlignment(Dim::X, SPACED); 00273 } 00274 00275 /* @endcode 00276 Note the apparently dangling pointers that the above calls to "new" 00277 returned. We don't have to ever worry about deleting widgets manually 00278 (except for the Screen), because when a Widget is destroyed, it deletes 00279 all its children (pretty heartless, huh?). Thus, when the Screen is 00280 deleted, its entire widget hierarchy is massacred. 00281 00282 We're done creating widgets for this example. Next up is the game 00283 loop, where all the computation, screen-rendering, event-processing, 00284 user-input-processing, etc takes place. 00285 00286 The screen keeps track of if a quit request was detected (i.e. Alt+F4 00287 or clicking the little X in the corner of the window pane). We want 00288 to loop until the user requests to quit the app. 00289 @code */ 00290 while (!screen->IsQuitRequested()) 00291 { 00292 /* @endcode 00293 Sleep for 33 milliseconds so we don't suck up too much CPU time. 00294 This will result in limiting the game loop framerate to about 30 00295 frames per second (because there is one screen-rendering per game 00296 loop iteration). 00297 @code */ 00298 Singleton::Pal().Sleep(33); 00299 /* @endcode 00300 Certain facilities of the game engine require the current time. 00301 This value should be represented as a Float (notice the 00302 capitalization), and should measure the number of seconds since 00303 the application started. Singleton::Pal().CurrentTime() returns the number of 00304 milliseconds since the app started, so take one thousandth of that. 00305 @code */ 00306 Float time = 0.001f * Singleton::Pal().CurrentTime(); 00307 /* @endcode 00308 This loop sucks up all pending events, produces Xrb::Event objects 00309 out of them, and craps them onto the top of the widget hierarchy (the 00310 Screen object). Singleton::Pal().PollEvent will return non-NULL while 00311 there are still events to process. We will loop until there are no 00312 more events left. 00313 @code */ 00314 Event *event = NULL; 00315 while ((event = Singleton::Pal().PollEvent(screen, time)) != NULL) 00316 { 00317 /* @endcode 00318 If the event is of the keyboard or mouse, let the InputState 00319 singleton process it. This step is necessary so that the 00320 state of said user-input devices is updated. 00321 @code */ 00322 if (event->IsKeyEvent() || event->IsMouseButtonEvent()) 00323 Singleton::InputState().ProcessEvent(event); 00324 /* @endcode 00325 All events are delegated to the proper widgets via the top of 00326 the widget hierarchy (the Screen object). Events must go 00327 through the widget hierarchy because certain events are handled 00328 based on locations of widgets (e.g. mousewheel events always 00329 go to the widget(s) directly under the mouse cursor). 00330 @code */ 00331 screen->ProcessEvent(event); 00332 /* @endcode 00333 We don't want to rely on widgets deleting events themselves, as 00334 it would create a maintenance nightmare, so we insist that 00335 events must be deleted at whatever code scope they were 00336 created. This also allows events that were created on the 00337 stack to be passed in without fear that they will be illegally 00338 deleted. 00339 @code */ 00340 Delete(event); 00341 } 00342 00343 /* @endcode 00344 Events can be enqueued for asynchronous/delayed processing (which 00345 will be discussed later), and so processing them on a time-basis 00346 must be done each game loop iteration. 00347 @code */ 00348 screen->OwnerEventQueue()->ProcessFrame(time); 00349 /* @endcode 00350 This call is where all the off-screen (strictly non-rendering) 00351 computation for the widget hierarchy takes place. Widget 00352 subclasses can override a particular method so that they can do 00353 per-frame computation. The widget hierarchy is traversed in an 00354 undefined order (not strictly prefix, infix or postfix), calling 00355 each widget's "think" method. 00356 @code */ 00357 screen->ProcessFrame(time); 00358 /* @endcode 00359 Turn the crank on the event queue again, since there may have been 00360 new events enqueued during the previous call, and we want to handle 00361 these delayed events at the earliest possible time. 00362 @code */ 00363 screen->OwnerEventQueue()->ProcessFrame(time); 00364 /* @endcode 00365 Here is where the visual magic happens. All the visible widgets 00366 in this hierarchy are drawn in this call. If a widget is invisible 00367 -- if it is hidden, of zero area, or it is not on-screen -- its 00368 Draw method is not called. 00369 @code */ 00370 screen->Draw(); 00371 } 00372 } 00373 00374 // Delete the Screen object, and with it the entire GUI widget hierarchy. 00375 Delete(screen); 00376 // this shuts down the Pal and the singletons. 00377 CleanUp(); 00378 // return with success value. 00379 return 0; 00380 } 00381 /* @endcode 00382 00383 <strong>Exercises</strong> 00384 00385 <ul> 00386 <li>Do NOT set a fullscreen video mode, because Alt+F4 and the window 00387 pane's X button will be unavailable. Fullscreen-able apps must 00388 provide their own facilities for detecting the user's desire to quit 00389 (such as a quit button, or a certain keypress).</li> 00390 <li>Change the value of the @c Singleton::Pal().Sleep call in the game loop, and see 00391 what effect it has on the responsiveness of the Button and 00392 LineEdit.</li> 00393 <li>Add more text Label widgets to @c main_layout after the do-nothing 00394 button. Note that their on-screen position in the vertical 00395 @c main_layout is directly reflected by the order they're created 00396 as children of <tt>main_layout</tt>.</li> 00397 <li>Enable word wrapping for the Label with the name 00398 <tt>"left-aligned note label"</tt>. Notice how each newline in the 00399 Label's text starts a new paragraph.</li> 00400 <li>Change the event-polling from a while-loop to a single statement -- only 00401 polling one event per frame -- and see what effect it has on the 00402 responsiveness of the application.</li> 00403 </ul> 00404 00405 Thus concludes lesson01. Do you feel smarter? Well, you aren't. 00406 */