00001 // /////////////////////////////////////////////////////////////////////////// 00002 // lesson03_main.cpp by Victor Dods, created 2006/08/06 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 03 - Creating Your Own Customized GUI Widgets 00014 // /////////////////////////////////////////////////////////////////////////// 00015 00016 /* @endcode 00019 This lesson will show you how to create custom GUI elements by subclassing 00020 Widget. It will involve creating a widget capable of responding to mouse 00021 input, and a container widget which facilitates the interaction between its 00022 children and provides other necessary control logic. Also, we will slightly 00023 change the game loop to add framerate control logic. 00024 00025 <ul> 00026 <li>@ref lesson03_main.cpp "This lesson's source code"</li> 00027 <li>@ref lessons "Main lesson index"</li> 00028 </ul> 00029 00030 With the ability to create customized GUI, we can actually start to do something 00031 interesting with the engine. This lesson will be a simulation of heat 00032 exchange on a 2 dimensional surface made up of rectangular grid cells. Each 00033 grid cell will indicate its temperature with colors, making a nice visual 00034 representation of the temperature distribution throughout the material. 00035 00036 The cells will be implemented using a customized subclass of Widget (<tt>HeatButton</tt>), 00037 and will be formed into a grid using the good old Layout class. Furthermore, 00038 we will create a custom container widget (<tt>HeatSimulation</tt>) to hold the grid, 00039 the other widgets, and to facilitate interaction between the GUI elements. 00040 00041 <tt>HeatSimulation</tt> will contain all GUI elements necessary for the operation of 00042 the application, so all that's required in the <tt>main</tt> function (besides 00043 the previously covered initialization/game loop/shutdown stuff) is to create 00044 an instance of HeatSimulation and set it as the main widget of Screen. 00045 00046 It should be noted that implementing this heat exchange simulation using a 00047 massive grid of GUI widgets is inefficient and a retarded way to go, 00048 but it's perfect for the purposes of this lesson. Also note that 00049 creating the dozens (or hundreds, if you modify <tt>GRID_WIDTH</tt> and 00050 <tt>GRID_HEIGHT</tt>) of widgets inside the Layout will take a long time due 00051 to the (as of August 2006) calculation-intensive Layout resizing code. 00052 00053 <strong>Procedural Overview</strong> -- Items in bold are additions/changes to the previous lesson. 00054 00055 <ul> 00056 <li><strong>Global declarations</strong></li> 00057 <ul> 00058 <li><strong>Tuning values</strong></li> 00059 <ul> 00060 <li><strong><tt>g_desired_framerate</tt></strong></li> 00061 <li><strong><tt>g_mouse_temperature_change_rate</tt></strong></li> 00062 <li><strong><tt>g_temperature_retention_rate</tt></strong></li> 00063 </ul> 00064 <li><strong>HeatButton subclass of Button</strong></li> 00065 <ul> 00066 <li><strong>In the constructor, set <tt>m_accepts_mouseover</tt> to 00067 <tt>true</tt>.</strong></li> 00068 <li><strong>Specify accessors/modifiers for current and ambient 00069 temperature member variables.</strong></li> 00070 <li><strong>Override Widget::Draw for customized drawing code -- 00071 fill the widget with a solid color representing its 00072 temperature.</strong></li> 00073 <li><strong>Override Widget::HandleFrame for customized 00074 off-screen, per-frame processing -- update the current 00075 temperature based on the ambient temperature and react to 00076 mouse input.</strong></li> 00077 </ul> 00078 <li><strong>HeatSimulation subclass of Widget</strong></li> 00079 <ul> 00080 <li><strong>In the constructor, create the grid of HeatButton 00081 widgets along with other control widgets, and make necessary 00082 signal connections.</strong></li> 00083 <li><strong>Override ContainerWidget::HandleFrame for customized 00084 off-screen, per-frame processing -- calculate and set the 00085 ambient temperature for each HeatButton.</strong></li> 00086 <li><strong>Define the static member 00087 HeatSimulation::ms_distribution_function for use in 00088 HeatSimulation::HandleFrame.</strong></li> 00089 </ul> 00090 </ul> 00091 <li>Main function</li> 00092 <ul> 00093 <li>Initialize the Pal and game engine singletons. Create the Screen object.</li> 00094 <li>Execute game-specific code.</li> 00095 <ul> 00096 <li>Create application-specific GUI and connect necessary signals.</li> 00097 <ul> 00098 <li><strong>Create an instance of HeatSimulation and set it as the screen's main widget.</strong></li> 00099 </ul> 00100 <li>Run the game loop</li> 00101 <ul> 00102 <li><strong>Calculate the Singleton::Pal().Sleep duration necessary to achieve the desired framerate.</strong></li> 00103 <li>Handle events (user and system-generated).</li> 00104 <li>Perform off-screen processing.</li> 00105 <li>Draw the Screen object's entire widget hierarchy.</li> 00106 </ul> 00107 </ul> 00108 <li>Delete the Screen object. Shutdown the Pal and game engine singletons.</li> 00109 </ul> 00110 </ul> 00111 00112 Comments explaining previously covered material will be made more terse or 00113 deleted entirely in each successive lesson. If something is not explained 00114 well enough, it was probably already explained in 00115 @ref lessons "previous lessons". 00116 00117 <strong>Code Diving!</strong> 00118 00119 @code */ 00120 #include "xrb.hpp" // Must be included in every source/header file. 00121 00122 #include "xrb_containerwidget.hpp" // For use of the ContainerWidget class. 00123 #include "xrb_event.hpp" // For use of the Event classes. 00124 #include "xrb_eventqueue.hpp" // For use of the EventQueue class. 00125 #include "xrb_frameratecalculator.hpp" // For use of the FramerateCalculator class. 00126 #include "xrb_inputstate.hpp" // For use of the InputState class (via Singleton::). 00127 #include "xrb_label.hpp" // For use of the Label class. 00128 #include "xrb_layout.hpp" // For use of the Layout widget class. 00129 #include "xrb_render.hpp" // For use of the Render namespace functions. 00130 #include "xrb_screen.hpp" // For use of the necessary Screen widget class. 00131 #include "xrb_sdlpal.hpp" // For use of the SDLPal platform abstraction layer. 00132 #include "xrb_validator.hpp" // For use of various Validator subclasses. 00133 #include "xrb_valueedit.hpp" // For use of the ValueEdit<T> template class. 00134 #include "xrb_valuelabel.hpp" // For use of the ValueLabel<T> template class. 00135 #include "xrb_widget.hpp" // For use of the Widget class. 00136 00137 using namespace Xrb; // To avoid having to use Xrb:: everywhere. 00138 00139 /* @endcode 00140 Define some global variables to hold the tuning parameters for the simulation. 00141 Normally globals aren't the best way to accomplish this sort of task because it 00142 makes for non-reentrant code, but for this lesson, it works. 00143 <tt>g_desired_framerate</tt> is the target framerate for the game loop. The 00144 actual framerate may not match if the computer is not fast enough. 00145 <tt>g_mouse_temperature_change_rate</tt> is the number of 00146 @code */ 00147 Float g_desired_framerate = 30.0f; 00148 Float g_mouse_temperature_change_rate = 200.0f; 00149 Float g_temperature_retention_rate = 0.001f; 00150 00151 /* @endcode 00152 Here we declare our awesome first customized Widget subclass. It should 00153 inherit Widget publicly. We will override certain virtual methods from 00154 Widget to specify our widget's custom behavior. 00155 00156 Our widget will behave as a single cell in a grid of heat-sensitive material, 00157 so it will have a "temperature" value and an "ambient temperature" value. 00158 The temperature value is the current temperature of this grid cell, and will 00159 determine what color the cell is. The ambient temperature value is the 00160 effective external temperature experienced by this grid cell, and will be used 00161 in per-frame calculations to update the temperature value. 00162 00163 The constructor must accept as an argument a pointer to the parent Widget, 00164 and optionally a name (which we will assign a default value of "HeatButton"). 00165 The constructor can also accept whatever other parameters you want, though no 00166 extra are needed here. The convention is to put additional parameters before 00167 the superclass' parameters (see Label, Button, ValueLabel, etc). 00168 00169 For this widget, there are 2 Widget methods we will override to implement 00170 the custom behavior -- <tt>Draw</tt> and <tt>HandleFrame</tt>. Widget 00171 provides many many overridable virtual methods to facilitate behavior 00172 customization. 00173 @code */ 00174 class HeatButton : public Widget 00175 { 00176 public: 00177 00178 /* @endcode 00179 Here's our lovely constructor with its awesome parameters which we just 00180 pass directly to the superclass constructor. We will initialize the 00181 temperature and ambient temperature values to a nice dumb default of zero. 00182 Note how we set <tt>m_accepts_mouseover</tt> to <tt>true</tt> here. 00183 <tt>m_accepts_mouseover</tt> is a protected member of Widget which 00184 indicates if mouseover events will be caught and processed by this widget. 00185 If we want to use the Widget::IsMouseover accessor, this must be set to 00186 <tt>true</tt>. If <tt>m_accepts_mouseover</tt> remains the default value 00187 of <tt>false</tt>, this widget will allow mouseover events to fall through 00188 to the widget(s) behind it, including its parent widget, which may be 00189 desirable if you're writing custom HUD widgets for a game. 00190 @code */ 00191 HeatButton (ContainerWidget *parent, std::string const &name = "HeatButton") 00192 : 00193 Widget(parent, name) 00194 { 00195 m_temperature = 0.0f; 00196 m_ambient_temperature = 0.0f; 00197 00198 m_accepts_mouseover = true; 00199 } 00200 00201 // Accessor for temperature -- used in HeatSimulation::HandleFrame. 00202 inline Float Temperature () const { return m_temperature; } 00203 // Modifier for ambient temperature -- also used in HeatSimulation::HandleFrame. 00204 inline void SetAmbientTemperature (Float ambient_temperature) 00205 { 00206 m_ambient_temperature = ambient_temperature; 00207 } 00208 00209 /* @endcode 00210 Here is the override of Widget::Draw. This method is predictably used 00211 to specify how to draw the widget. The single parameter is a RenderContext 00212 which is something which all drawing functions require (it is used by the 00213 drawing/rendering primitive functions in Xrb::Render). For HeatButton in 00214 particular, all we want to do is draw a rectangle of a specific color, 00215 filling the entire widget. 00216 @code */ 00217 virtual void Draw (RenderContext const &render_context) const 00218 { 00219 // Normalize the temperature from (-inf, +inf) to [0, 1] so we'll 00220 // have a value that can parameterize linear interpolations with to 00221 // calculate the color. A temperature of 0 will translate into 00222 // a normalized_temperature of 0.5. 00223 Float normalized_temperature = 0.5f * (1.0f + Math::Atan(m_temperature) / 90.0f); 00224 // Calculate the red, green and blue components for the color. Each 00225 // component in the RGBA color value must be within the range [0, 1], 00226 // with an alpha value of 0 being completely transparent and an alpha 00227 // value of 1 being completely opaque. We'll just use a boring old 00228 // greyscale gradient for simplicity. Because of the above 00229 // normalization, a temperature of 0 will be 50% grey, a temperature 00230 // approaching -infinity will approach pure black, and a temperature 00231 // approaching +infinity will approach pure white. 00232 Color button_color( 00233 normalized_temperature, // red component 00234 normalized_temperature, // green component 00235 normalized_temperature, // blue component 00236 1.0f); // alpha component (completely opaque) 00237 // Draw a rectangle which fills this widget's screen rect using the 00238 // calculated color. 00239 Render::DrawScreenRect(render_context, button_color, ScreenRect()); 00240 } 00241 00242 protected: 00243 00244 /* @endcode 00245 Here is the override of Widget::HandleFrame. This method is what 00246 performs all the off-screen, per-frame computation specific to the widget. 00247 For HeatButton specifically, it is to recalculate <tt>m_temperature</tt> using 00248 <tt>m_ambient_temperature</tt> and to modify <tt>m_temperature</tt> based on mouse 00249 input. The HandleFrame method originally comes from FrameHandler 00250 (which is inherited by Widget) and there are a few notable provided methods: 00251 FrameHandler::FrameTime and FrameHandler::FrameDT which are only 00252 available during the execution of HandleFrame (or in a function 00253 called by it), and FrameHandler::MostRecentFrameTime which is available 00254 at any time. 00255 @code */ 00256 virtual void HandleFrame () 00257 { 00258 ASSERT1(g_temperature_retention_rate > 0.0f); 00259 ASSERT1(g_temperature_retention_rate < 1.0f); 00260 // Calculate heat transfer. The amount is proportional to the 00261 // difference between m_temperature and m_ambient_temperature -- the 00262 // fancy looking exponential factor is used to correctly scale the 00263 // amount based on time. g_temperature_retention_rate is the ratio of 00264 // a grid cell's heat relative to the ambient temperature it retains 00265 // over one second. Thus, a high value means the heat spreads slowly, 00266 // while a low value causes heat to spread quickly. 00267 m_temperature += 00268 (m_ambient_temperature - m_temperature) * 00269 (1.0f - Math::Pow(g_temperature_retention_rate, FrameDT())); 00270 00271 // If the mouse cursor is currently over this widget and the left mouse 00272 // button is pressed, increase the temperature. This allows the user 00273 // to manually heat up grid cells. Note that this isn't the primary 00274 // method of facilitating mouse input -- Xrb::Event based mouse input 00275 // will be covered later. 00276 if (IsMouseover() && Singleton::InputState().IsKeyPressed(Key::LEFTMOUSE)) 00277 m_temperature += g_mouse_temperature_change_rate * FrameDT(); 00278 } 00279 00280 private: 00281 00282 Float m_temperature; 00283 Float m_ambient_temperature; 00284 }; // end of class HeatButton 00285 00286 /* @endcode 00287 Here is the declaration of our other super-awesome custom Widget subclass. 00288 It will also inherit Widget publicly (actually I can't think of a reason you'd 00289 ever not inherit Widget publicly). 00290 00291 The constructor will create and initialize all the subordinate GUI elements 00292 and make necessary signal connections. 00293 00294 We will only need to override ContainerWidget::HandleFrame for the desired 00295 custom behavior, since a container widget is only rendered to screen by proxy 00296 through its children's Draw methods. 00297 @code */ 00298 class HeatSimulation : public ContainerWidget 00299 { 00300 public: 00301 00302 /* @endcode 00303 The constructor for <tt>HeatSimulation</tt> is similar to that of <tt>HeatButton</tt> 00304 -- it accepts the parent widget and a widget name as parameters, and passes 00305 them directly to the superclass constructor. 00306 00307 Notice the constructors for the member variables 00308 <tt>m_desired_framerate_validator</tt> and 00309 <tt>m_temperature_retention_range_validator</tt>. They are instances of 00310 implementations of the Validator interface class which is used to constrain 00311 values to preset valid ranges. These are primarily useful in user interface 00312 code where the user may enter any retarded value, but must be constrained 00313 to a particular range. The motivation to use these seemingly unwieldy 00314 objects is to avoid having to add in extra code to manually correct invalid 00315 values. The goal is to have all GUI elements, signal connections, value 00316 validation (and really everything in general) to be as plug-and-forget 00317 (pronounced "hard to fuck up") as possible. The Validator subclasses are 00318 templatized for your convenience. 00319 00320 In the game loop, the calculations to determine the value to pass to 00321 Singleton::Pal().Sleep involves dividing by <tt>g_desired_framerate</tt>, and therefore we must 00322 avoid setting <tt>g_desired_framerate</tt> to zero. 00323 <tt>m_desired_framerate_validator</tt> is an instance of 00324 GreaterOrEqualValidator<Uint32> -- we will limit the desired framerate to 00325 be greater or equal to 1 (the parameter passed into the constructor for 00326 <tt>m_desired_framerate_validator</tt>). 00327 00328 Note that unlike the constructor for <tt>HeatButton</tt>, we do <i>not</i> 00329 set <tt>m_accepts_mouseover</tt> to <tt>true</tt>. This is because 00330 ContainerWidgets by themselves should not accept mouseover -- only their 00331 children should. This is to prevent ContainerWidgets which contain only 00332 <tt>m_accepts_mouseover = false</tt> from accepting mouseover instead of 00333 perhaps some background widget (such as a game view widget) which may want 00334 it. For example, an in-game HUD Layout containing only Labels -- you would 00335 not want it or its children accepting mouseover and blocking the game view 00336 widget from accepting mouseover. 00337 @code */ 00338 HeatSimulation (ContainerWidget *parent, std::string const &name = "HeatSimulation") 00339 : 00340 ContainerWidget(parent, "HeatSimulation"), 00341 /* @endcode 00342 The single argument to this validator's constructor is the lower 00343 bound for GreaterOrEqualValidator<Uint32>. Anything greator or equal 00344 to 1 is valid. 00345 @code */ 00346 m_desired_framerate_validator(1), 00347 /* @endcode 00348 There are two classifications of range validators. Inclusive 00349 (GreaterOrEqualValidator, LessOrEqualValidator and 00350 RangeInclusiveValidator) and Exclusive (GreaterThanValidator, 00351 LessThanValidator and RangeExclusiveValidator). The inclusive range 00352 validators can simply define the lower and/or upper bounds, but the 00353 exclusive range validators must specify both lower and/or upper bounds, 00354 as well as minimum and/or maximum valid values (in order to bring an 00355 invalid value into the valid range). Because the validator subclasses 00356 are templatized, one can't assume that one could just add/subtract 1 00357 (or some other general value) to the lower/upper bound to get the 00358 minimum/maximum valid value. Thus, the necessity for a minimum and/or 00359 maximum valid value parameter. 00360 00361 In the following constructor -- for RangeExclusive<Float> -- the first 00362 parameter is the lower bound of the exclusive range. The second is 00363 the minimum valid value which will be used if a value-to-be-validated 00364 is less than or equal to the lower bound value. The third parameter 00365 is the maximum valid value. It is similarly used when a 00366 value-to-be-validated is greater or equal to the upper bound value. 00367 The final parameter is the upper bound of the exclusive range. See 00368 @ref Xrb::RangeExclusiveValidator for more. 00369 @code */ 00370 m_temperature_retention_range_validator(0.0f, 1e-20f, 0.999999f, 1.0f), 00371 m_internal_receiver_set_desired_framerate(&HeatSimulation::SetDesiredFramerate, this), 00372 m_internal_receiver_set_temperature_retention_rate(&HeatSimulation::SetTemperatureRetentionRate, this) 00373 { 00374 Layout *main_layout = new Layout(VERTICAL, this); 00375 main_layout->SetIsUsingZeroedLayoutSpacingMargins(true); 00376 SetMainWidget(main_layout); 00377 00378 Layout *grid_layout = new Layout(ROW, GRID_WIDTH, main_layout, "grid layout"); 00379 /* @endcode 00380 This next variable declaration is a little mechanism to speed up 00381 creation of complex (many-widget) GUI containers. The 00382 ContainerWidget::ChildResizeBlocker object blocks all children of 00383 the ContainerWidget passed to its constructor from being resized or 00384 otherwise changing size properties (min/max size, etc). The idea is 00385 that it's pointless to resize all the widgets in a ContainerWidget 00386 before they're all added. ContainerWidget::ChildResizeBlocker's 00387 destructor is what unblocks the ContainerWidget and resizes the child 00388 widgets appropriately. 00389 00390 ContainerWidget::ChildResizeBlocker must be created as a stack variable 00391 (no new'ing one up on the heap) so that when it goes out of scope and 00392 its destructor is called, the applicable ContainerWidget is unblocked. 00393 The rationale is again to provide a plug-and-forget mechanism for ease 00394 of use and to reduce the possibility of programmer error. 00395 @code */ 00396 ContainerWidget::ChildResizeBlocker blocker(grid_layout); 00397 grid_layout->SetIsUsingZeroedFrameMargins(true); 00398 grid_layout->SetIsUsingZeroedLayoutSpacingMargins(true); 00399 00400 // Create the HeatButton grid in a doubly-nested loop, saving pointers 00401 // to each grid cell in the 2 dimensional array, m_button_grid for 00402 // use in temperature calculations. 00403 for (Uint32 y = 0; y < GRID_HEIGHT; ++y) 00404 for (Uint32 x = 0; x < GRID_WIDTH; ++x) 00405 m_button_grid[y][x] = new HeatButton(grid_layout); 00406 00407 // Create a layout for the controls below the grid and enable 00408 // the frame margins (which are zeroed-out by default). 00409 Layout *sub_layout = new Layout(HORIZONTAL, main_layout); 00410 sub_layout->SetIsUsingZeroedFrameMargins(false); 00411 00412 // ValueLabel<T> is a templatized subclass of Label which contains a 00413 // value instead of a string. It has Value and SetValue methods, 00414 // and corresponding SignalSenders and SignalReceivers. It is very 00415 // flexible, due to its value-to-text-printf-format and 00416 // text-to-value-function constructor parameters. 00417 new Label("Actual Framerate:", sub_layout); 00418 m_actual_framerate_label = new ValueLabel<Uint32>("%u", Util::TextToUint<Uint32>, sub_layout); 00419 m_actual_framerate_label->SetAlignment(Dim::X, LEFT); 00420 00421 // ValueEdit<T> is a templatized subclass of LineEdit, analogous to 00422 // ValueLabel<T>. You can type into it and it will attempt to use 00423 // the specified text-to-value-function to convert it to a value. 00424 // A validator may also be specified to enforce valid input values. 00425 new Label("Desired Framerate:", sub_layout); 00426 ValueEdit<Uint32> *desired_framerate_edit = 00427 new ValueEdit<Uint32>("%u", Util::TextToUint<Uint32>, sub_layout); 00428 desired_framerate_edit->SetValidator(&m_desired_framerate_validator); 00429 desired_framerate_edit->SetValue(static_cast<Uint32>(Math::Round(g_desired_framerate))); 00430 // We'll connect this one to this HeatSimulation's SetDesiredFramerate 00431 // SignalReceiver. 00432 SignalHandler::Connect1( 00433 desired_framerate_edit->SenderValueUpdated(), 00434 &m_internal_receiver_set_desired_framerate); 00435 00436 // Similarly create a ValueEdit for the temperature retention rate 00437 // and connect it up. 00438 new Label("Temperature Retention Rate:", sub_layout); 00439 ValueEdit<Float> *temperature_retention_rate_edit = 00440 new ValueEdit<Float>("%g", Util::TextToFloat, sub_layout); 00441 temperature_retention_rate_edit->SetValue(g_temperature_retention_rate); 00442 temperature_retention_rate_edit->SetValidator(&m_temperature_retention_range_validator); 00443 SignalHandler::Connect1( 00444 temperature_retention_rate_edit->SenderValueUpdated(), 00445 &m_internal_receiver_set_temperature_retention_rate); 00446 00447 // Ensure DISTRIBUTION_FUNCTION_WIDTH and DISTRIBUTION_FUNCTION_HEIGHT 00448 // are odd, so that there is an exact center in the array. 00449 ASSERT0(DISTRIBUTION_FUNCTION_WIDTH % 2 == 1); 00450 ASSERT0(DISTRIBUTION_FUNCTION_HEIGHT % 2 == 1); 00451 // Make sure the center weight in the distribution function 00452 // (representing the target square) is zero. 00453 ASSERT0(ms_distribution_function[DISTRIBUTION_FUNCTION_HEIGHT/2][DISTRIBUTION_FUNCTION_WIDTH/2] == 0.0f); 00454 // Calculate the total of all weights in the distribution function, 00455 // so that later calculations can divide by this value and be ensured 00456 // that the adjusted total weight of the distribution function is one. 00457 m_distribution_normalization = 0.0f; 00458 for (Uint32 y = 0; y < DISTRIBUTION_FUNCTION_HEIGHT; ++y) 00459 for (Uint32 x = 0; x < DISTRIBUTION_FUNCTION_WIDTH; ++x) 00460 m_distribution_normalization += ms_distribution_function[y][x]; 00461 } 00462 00463 protected: 00464 00465 /* @endcode 00466 Our customized override of ContainerWidget::HandleFrame is where the 00467 ambient temperature for each grid cell is computed. We also call the 00468 superclass' HandleFrame method and keep track of the framerate 00469 in order to display the "Actual Framerate:" value. The reason we have to 00470 call ContainerWidget::HandleFrame is because that's where the child 00471 widgets' ProcessFrameOverrides are recursively called. 00472 @code */ 00473 virtual void HandleFrame () 00474 { 00475 // must call the superclass' HandleFrame -- this is where 00476 // all the child widgets' ProcessFrameOverrides are called. 00477 ContainerWidget::HandleFrame(); 00478 00479 // Keep track of the framerate and update the "Actual Framerate" label. 00480 m_framerate_calculator.AddFrameTime(FrameTime()); 00481 m_actual_framerate_label->SetValue( 00482 static_cast<Uint32>(Math::Round(m_framerate_calculator.Framerate()))); 00483 00484 // Compute and set the ambient temperature for each grid cell, 00485 // using the distribution function. 00486 for (Sint32 gy = 0; gy < GRID_HEIGHT; ++gy) 00487 for (Sint32 gx = 0; gx < GRID_WIDTH; ++gx) 00488 { 00489 // Calculate the ambient temperature by summing the weighted 00490 // temperatures from adjacent grid cells indicated by the 00491 // distribution function. 00492 Float ambient_temperature = 0.0f; 00493 for (Sint32 dy = -DISTRIBUTION_FUNCTION_HEIGHT/2; dy <= DISTRIBUTION_FUNCTION_HEIGHT/2; ++dy) 00494 for (Sint32 dx = -DISTRIBUTION_FUNCTION_WIDTH/2; dx <= DISTRIBUTION_FUNCTION_WIDTH/2; ++dx) 00495 // If the array indices would go out of bounds, don't 00496 // add to the running total ambient temperature. 00497 if (gy+dy >= 0 && gy+dy < GRID_HEIGHT && gx+dx >= 0 && gx+dx < GRID_WIDTH) 00498 ambient_temperature += 00499 ms_distribution_function[dy+DISTRIBUTION_FUNCTION_HEIGHT/2][dx+DISTRIBUTION_FUNCTION_WIDTH/2] / 00500 m_distribution_normalization * 00501 m_button_grid[gy+dy][gx+dx]->Temperature(); 00502 m_button_grid[gy][gx]->SetAmbientTemperature(ambient_temperature); 00503 } 00504 } 00505 00506 private: 00507 00508 // Various enums for named constants (array dimension sizes in this case). 00509 enum 00510 { 00511 GRID_WIDTH = 16, 00512 GRID_HEIGHT = 12, 00513 DISTRIBUTION_FUNCTION_WIDTH = 5, 00514 DISTRIBUTION_FUNCTION_HEIGHT = 5 00515 }; 00516 00517 /* @endcode 00518 This is a method use solely by <tt>m_internal_receiver_set_desired_framerate</tt> 00519 -- when said receiver is signaled, it sets <tt>g_desired_framerate</tt> with the 00520 specified value. Specifically, the receiver will be hooked up to the 00521 desired framerate ValueEdit<Uint32>::SignalValueUpdated signal. 00522 @code */ 00523 void SetDesiredFramerate (Uint32 desired_framerate) 00524 { 00525 ASSERT1(desired_framerate > 0); 00526 g_desired_framerate = static_cast<Float>(desired_framerate); 00527 } 00528 /* @endcode 00529 This method is similarly used solely by 00530 <tt>m_internal_receiver_set_temperature_retention_rate</tt> -- to set 00531 <tt>g_temperature_retention_rate</tt>, and will be hooked up to the 00532 temperature retention rate ValueEdit<Float>::SignalValueUpdated signal. 00533 @code */ 00534 void SetTemperatureRetentionRate (Float temperature_retention_rate) 00535 { 00536 ASSERT1(temperature_retention_rate > 0.0f); 00537 ASSERT1(temperature_retention_rate < 1.0f); 00538 g_temperature_retention_rate = temperature_retention_rate; 00539 } 00540 00541 HeatButton *m_button_grid[GRID_HEIGHT][GRID_WIDTH]; 00542 ValueLabel<Uint32> *m_actual_framerate_label; 00543 FramerateCalculator m_framerate_calculator; 00544 GreaterOrEqualValidator<Uint32> m_desired_framerate_validator; 00545 RangeExclusiveValidator<Float> m_temperature_retention_range_validator; 00546 Float m_distribution_normalization; 00547 00548 SignalReceiver1<Uint32> m_internal_receiver_set_desired_framerate; 00549 SignalReceiver1<Float> m_internal_receiver_set_temperature_retention_rate; 00550 00551 static Float const ms_distribution_function[DISTRIBUTION_FUNCTION_HEIGHT][DISTRIBUTION_FUNCTION_WIDTH]; 00552 }; // end of class HeatSimulation 00553 00554 Float const HeatSimulation::ms_distribution_function[DISTRIBUTION_FUNCTION_HEIGHT][DISTRIBUTION_FUNCTION_WIDTH] = 00555 { 00556 { 0.00f, 0.05f, 0.10f, 0.05f, 0.00f }, 00557 { 0.05f, 0.15f, 0.30f, 0.15f, 0.05f }, 00558 { 0.10f, 0.30f, 0.00f, 0.30f, 0.10f }, 00559 { 0.05f, 0.15f, 0.30f, 0.15f, 0.05f }, 00560 { 0.00f, 0.05f, 0.10f, 0.05f, 0.00f } 00561 }; 00562 00563 void CleanUp () 00564 { 00565 fprintf(stderr, "CleanUp();\n"); 00566 // Shutdown the Pal and singletons. 00567 Singleton::Pal().Shutdown(); 00568 Singleton::Shutdown(); 00569 } 00570 00571 int main (int argc, char **argv) 00572 { 00573 fprintf(stderr, "main();\n"); 00574 00575 // Initialize engine singletons. 00576 Singleton::Initialize(SDLPal::Create, "none"); 00577 // Initialize the Pal. 00578 if (Singleton::Pal().Initialize() != Pal::SUCCESS) 00579 return 1; 00580 // Set the window caption. 00581 Singleton::Pal().SetWindowCaption("XuqRijBuh Lesson 03"); 00582 // Create Screen object and initialize given video mode. 00583 Screen *screen = Screen::Create(800, 600, 32, false); 00584 // If the Screen failed to initialize, print an error message and quit. 00585 if (screen == NULL) 00586 { 00587 CleanUp(); 00588 return 2; 00589 } 00590 00591 // At this point, the singletons and the Pal have been initialized, the 00592 // video mode has been set, and the engine is ready to go. Here is where 00593 // the application-specific code begins. 00594 { 00595 // this is the only child of Screen 00596 HeatSimulation *main_widget = new HeatSimulation(screen); 00597 screen->SetMainWidget(main_widget); 00598 00599 // These values will be used below in the framerate control code. 00600 Float current_real_time = 0.0f; 00601 Float next_real_time = 0.0f; 00602 // Run the game loop until the Screen no longer has the will to live. 00603 while (!screen->IsQuitRequested()) 00604 { 00605 /* @endcode 00606 Here is the newly added framerate control code. We keep track of 00607 the desired next frame time, and use it in combination with the 00608 real time as returned by <tt>Singleton::Pal().CurrentTime</tt> to calculate how 00609 long to sleep to attempt to achieve the exact desired framerate. 00610 @code */ 00611 // Retrieve the current real time in seconds as a Float. 00612 current_real_time = 0.001f * Singleton::Pal().CurrentTime(); 00613 // figure out how much time to sleep before processing the next frame 00614 Sint32 milliseconds_to_sleep = Max(0, static_cast<Sint32>(1000.0f * (next_real_time - current_real_time))); 00615 // Delay for the calculated number of milliseconds. 00616 Singleton::Pal().Sleep(milliseconds_to_sleep); 00617 // Calculate the desired next game loop time (which should never 00618 // fall below current_real_time. 00619 next_real_time = Max(current_real_time, next_real_time + 1.0f / g_desired_framerate); 00620 00621 // Process events until there are no more. 00622 Event *event = NULL; 00623 while ((event = Singleton::Pal().PollEvent(screen, current_real_time)) != NULL) 00624 { 00625 // Let the InputState singleton "have a go" at keyboard/mouse events. 00626 if (event->IsKeyEvent() || event->IsMouseButtonEvent()) 00627 Singleton::InputState().ProcessEvent(event); 00628 // Give the GUI hierarchy a chance at the event and then delete it. 00629 screen->ProcessEvent(event); 00630 Delete(event); 00631 } 00632 00633 // Turn the EventQueue crank, Perform off-screen GUI processing, 00634 // turn the EventQueue crank again, and then draw everything. 00635 screen->OwnerEventQueue()->ProcessFrame(current_real_time); 00636 screen->ProcessFrame(current_real_time); 00637 screen->OwnerEventQueue()->ProcessFrame(current_real_time); 00638 screen->Draw(); 00639 } 00640 } 00641 00642 // Delete the Screen (and GUI hierarchy), "SHUT IT DOWN", and return success. 00643 Delete(screen); 00644 CleanUp(); 00645 return 0; 00646 } 00647 /* @endcode 00648 00649 <strong>Exercises</strong> 00650 00651 <ul> 00652 <li>Screw with <tt>grid_layout</tt>'s SetIsUsingZeroedFrameMargins and 00653 SetIsUsingZeroedLayoutSpacingMargins and observe the visual change.</li> 00654 <li>Change the color gradient code to make a fire-like effect. Let a 00655 normalized temperature of 0.5 be black, 0.7 be red, 0.9 be yellow 00656 and 1.0 be white. Tune these values to your liking.</li> 00657 <li>Make it so the right mouse button decreases a grid cell's temperature.</li> 00658 <li>Modify the color gradient code to make temperatures below zero appear 00659 using ice-like colors, while preserving the previously added fire-like 00660 colors. Let a normalized temperature of 0.3 be dark blue, 0.1 be light 00661 blue and 0.0 be white. Again, tune these values to your liking.</li> 00662 <li>Change <tt>HeatSimulation::m_button_grid</tt> and 00663 <tt>HeatSimulation::ms_distribution_function</tt> to be 1-dimensional 00664 arrays, and perform the row-major array indexing yourself. They are 00665 both currently indexed with the Y component first to make this switch 00666 easier.</li> 00667 <li>Change <tt>HeatSimulation::ms_distribution_function</tt> to cause the 00668 temperature changes to propogate up the screen like fire. Along the 00669 bottom row of the grid, simulate fluctuating embers via random 00670 temperatures. You may need to add additional methods to <tt>HeatButton</tt> 00671 and/or <tt>HeatSimulation</tt>.</li> 00672 <li>Make the heat dissipation wrap top/bottom and left/right on the 00673 grid (you might want to change 00674 <tt>HeatSimulation::ms_distribution_function</tt> back to its original 00675 value and disable the fluctuating ember code before you do this).</li> 00676 <li>Add a reset button to the row of widgets at the bottom of the screen 00677 to reset all the grid cells' temperatures to 0</li> 00678 <li>Add a quit button next to the reset button (recall the 00679 Screen::ReceiverRequestQuit method from @ref lesson02 "lesson02").</li> 00680 </ul> 00681 00682 Thus concludes lesson03. Somehow you're actually dumber now from it. 00683 */