1 //  SuperTuxKart - a fun racing game with go-kart
2 //  Copyright (C) 2010-2015 Marianne Gagnon
3 //
4 //  This program is free software; you can redistribute it and/or
5 //  modify it under the terms of the GNU General Public License
6 //  as published by the Free Software Foundation; either version 3
7 //  of the License, or (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program; if not, write to the Free Software
16 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17 
18 namespace GUIEngine
19 {
20 /**
21 
22 
23  \page gui_overview GUI Module Overview
24 
25  In XML files, widgets are declared in the following fashion :
26 
27  \code
28  <widget_name property1="value1" property2="value2" />
29  \endcode
30 
31  or, for widgets of "spawn" type, with children :
32 
33  \code
34  <widget_name property1="value1" property2="value2" >
35  <child1 />
36  <child2 />
37  </widget_name>
38  \endcode
39 
40  The first section of this document describes the widgets you can use; the
41  second describes the properties widgets can take. Not all properties can be
42  applied to all widgets, see the docs for a given widget and a given property
43  for full information.
44 
45  \n
46  <HR>
47  \section toc Table of Contents
48  <HR>
49 
50  \ref widgets
51  \li \ref widget1
52  \li \ref widget2
53  \li \ref widget3
54  \li \ref widget4
55  \li \ref widget5
56  \li \ref widget6
57  \li \ref widget7
58  \li \ref widget8
59  \li \ref widget9
60  \li \ref widget10
61  \li \ref widget11
62  \li \ref widget12
63  \li \ref widget13
64 
65  \ref props
66  \li \ref prop1
67  \li \ref prop2
68  \li \ref prop3
69  \li \ref prop3.1
70  \li \ref prop4
71  \li \ref prop5
72  \li \ref prop6
73  \li \ref prop7
74  \li \ref prop8
75  \li \ref prop9
76  \li \ref prop10
77  \li \ref prop11
78  \li \ref prop12
79  \li \ref prop13
80  \li \ref prop14
81  \li \ref prop15
82  \li \ref prop16
83  \li \ref prop17
84  \li \ref prop18
85 
86  \ref code
87 
88  \ref internals
89 
90  \n
91  \n
92  <HR>
93  \section widgets Widgets
94  <HR>
95 
96  This section describes the widgets you can use in STK's GUI XML files. The
97  upper-case name starting with WTYPE_* is the internal name of the widget
98  (see the WidgetType enum).
99 
100  \n
101  \subsection widget1 WTYPE_RIBBON
102  <em> Names in XML files: </em> \c "ribbon", \c "buttonbar", \c "tabs",
103  \c"vertical-tabs"
104 
105  Appears as an horizontal bar containing elements laid in a row, each being
106  and icon and/or a label
107 
108  \li The "ribbon" subcategory will behave a bit like a radio button group,
109      i.e. one element must selected. Events are triggered as soon as a choice
110      is selected (can be simply by hovering).
111  \li The "buttonbar" subcategory treats children buttons as action buttons,
112      which means they can't have a 'selected' state, only focused or not (i.e.
113      there is no selection that remains if you leave this area). Events are
114      triggered only on enter/fire.
115  \li The "tabs" subcategory will show a tab bar. behaviour is same as normal
116      ribbon, only looks are different. Orientation of tabs (up or down) is
117      automatically inferred from on-screen position
118 
119  \note Ribbon widgets are of spawn type (\<ribbon\> ... \</ribbon\>) and may
120        contain icon-buttons or buttons as children.
121  \note Property PROP_SQUARE can be set to tell the engine if the ribbon's
122        contents are rectangular or not (this will affect the type of
123        highlighting used)
124  \note All elements within a ribbon must have an 'ID' property
125  \note Ribbons (e.g. tabs) can have their elements dynamically added at
126        runtime, too. Just add no children to the ribbon in the XML file, and
127        add them at runtime through the method for this.
128  \note The layout algorithm will reserve space for at most one line of text
129        (if needed) for ribbon elements. If you have ribbon elements with
130        long texts that spawn many lines, 1. give the word_wrap="true" property
131        to the icon button widget in the XML file; 2. expect that the extra
132        lines will not be accounted for in the sizing algorithms (i.e. extra
133        lines will just expand over whatever is located under the ribbon)
134 
135  \n
136  \subsection widget2 WTYPE_SPINNER
137  Names in XML files: </em> \c "spinner", \c "gauge"
138 
139  A spinner component (lets you choose numbers).
140 
141  Specify PROP_MIN_VALUE and PROP_MAX_VALUE to have control over values
142  (default will be from 0 to 99). You can specify an icon; then, include a
143  sprintf format string like %i in the name, and at runtime the current number
144  will be inserted into the given name to find the right file for each
145  possible value the spinner can take. It may also display arbitrary text
146  instead of numbers, though this cannot be achieve in the XML file; use
147  the -\>addLabel(...) method in code to do this.
148  It can also display arbitrary text containing the value; just define the
149  PROP_TEXT property to contain the text you want, including a format string %i
150  where the value should appear (a string that does not contain %i will result
151  in the same text being displayed in the spinner no matter the current value).
152  \note The "gauge" variant behaves similarly, but a fill band shows how close
153        to the max the value is.
154 
155  \n
156  \subsection widget3 WTYPE_BUTTON
157  <em> Name in XML files: </em> \c "button"
158 
159  A plain text button.
160 
161  \n
162  \subsection widget4 WTYPE_ICON_BUTTON
163  <em> Names in XML files: </em> \c "icon-button", \c "icon"
164 
165  A component with an image, and optional text to go under it.
166 
167  \note The "icon" variant will have no border and will not be clickable.
168        PROP_ICON is mandatory for this component. There are three ways to
169        place the texture within the allocated space; the default (and only
170        way currently accessible through xml files) is to scale the texture to
171        fit, while preserving its aspect ratio; other methods, currently only
172        accessible through C++ code, are to stretch the texture to fill the
173        area without caring for aspect ratio, and another to respect an aspect
174        ratio other than the texture's (useful for track screenshots, which
175        are 4:3 compressed to fit in a power-of-two 256x256 texture)
176  \note Supports property PROP_FOCUS_ICON
177 
178  \n
179  \subsection widget5 WTYPE_CHECKBOX
180  <em> Name in XML files: </em> \c "checkbox"
181 
182  A checkbox.
183 
184  \n
185  \subsection widget6 WTYPE_LABEL
186  <em> Names in XML files: </em> \c "label", \c "header" , \c "bright"
187 
188  A plain label.
189 
190  Supports properties PROP_WORD_WRAP and PROP_TEXT_ALIGN.
191  \note The "header" variant uses a bigger and more colourful font.
192  \note The "bright" variant uses a more colourful font but is not bigger.
193 
194 
195  \c WTYPE_BUBBLE is a variation of the plain label; the difference with a
196  bubble widget is that it can be focused, and when focused it will expand
197  to show more text, if the label is too long to be displayed in the allocated
198  space.
199 
200  \n
201  \subsection widget7 WTYPE_SPACER
202  <em> Name in XML files: </em> \c "spacer"
203 
204  Some blank space; not visible on screen.
205 
206  \n
207  \subsection widget8 WTYPE_DIV
208  <em> Name sin XML files: </em> \c "div", \c "box"
209 
210  An invisible container.
211 
212  \li Divs do not do much on themselves, but are useful to lay out children
213      automatically (Supports property PROP_LAYOUT)
214  \li Divs can be nested.
215  \li Of spawn type (\<div\>...\</div\>, place children within)
216  \note "box" is a variant that acts exactly the same but is visible on-screen
217 
218  \n
219  \subsection widget9 WTYPE_DYNAMIC_RIBBON
220  Names in XML files: </em> \c  "ribbon_grid", \c "scrollable_ribbon",
221  \c "scrollable_toolbar"
222 
223  Builds upon the basic Ribbon to be more dynamic (dynamics contents, possibly
224  with scrolling, possibly multi-line)
225 
226  \li NOT of spawn type (\<ribbon_grid .../\>), i.e. children are not specified
227      in the XML file but programmatically at runtime.
228  \li PROP_CHILD_WIDTH and PROP_CHILD_HEIGHT are mandatory (so at least aspect
229      ratio of elements that will later be added is known) An interesting
230      aspect of PROP_CHILD_WIDTH and PROP_CHILD_HEIGHT is that you can use them
231      to show textures to any aspect ratio you want (so you can e.g. save
232      textures to a power-of-two size like 256x256, but then show it in 4:3
233      ratio).
234  \li Property PROP_SQUARE can be set to tell the engine if the ribbon's
235      contents are rectangular or icons (this will  affect the type of
236      highlighting used).
237  \li Supports an optional label at the bottom if PROP_LABELS_LOCATION is set
238      (see more on PROP_LABELS_LOCATION below).
239  \note The "scrollable_ribbon" and "scrollable_toolbar" subtypes are
240        single-line scrollable ribbons. The difference between both is that
241        'scrollable_ribbon' always has a value selected (like in a combo box,
242        or radio buttons), while 'scrollable_toolbar' is a scrollable list of
243        buttons that can be pressed to trigger actions.
244 
245  \n
246  \subsection widget10 WTYPE_MODEL_VIEW
247  <em> Name in XML files: </em> \c "model"
248 
249  Displays a 3D model.
250 
251  \note Contents must be set programmatically.
252 
253  \n
254  \subsection widget11 WTYPE_LIST
255  <em> Name in XML files: </em> \c "list"
256 
257  Displays a list.
258 
259  \note Contents must be set programmatically.
260 
261 
262  \n
263  \subsection widget12 WTYPE_PROGRESS
264  <em> Name in XML files: </em> \c "progressbar"
265 
266  Display a progress bar (e.g. for downloads).
267 
268  \note The value must be set programmatically.
269 
270  \n
271 
272  \subsection widget13 WTYPE_TEXTBOX
273  <em> Name in XML files: </em> \c "textbox"
274 
275  A text field where the user can type text
276 
277  \n
278  \n
279  <HR>
280  \section props Properties
281  <HR>
282 
283  \subsection prop1 PROP_ID
284  <em> Name in XML files: </em> \c "id"
285 
286  Gives a unique internal name to each object using this property. It will be
287  used in events callbacks to determine what action occurred. Can be omitted
288  on components that do not trigger events (e.g. labels)
289 
290  \n
291  \subsection prop2 PROP_TEXT
292  <em> Name in XML files: </em> \c "text" or "raw_text" ("text" is translated, "raw_text" is not)
293 
294  gives text (a label) to the widget where supported. Ribbon-grids give a
295  special meaning to this parameter, see ribbon-grid docs above.
296 
297  \n
298  \subsection prop3 PROP_ICON
299  <em> Name in XML files: </em> \c "icon"
300 
301  give an icon to the widget. Property contents is the path to the file, by
302  default relative to the /data directory of STK (several methods of
303  IconButtonWidget and DynamicRibbon can enable you to use absolute paths if
304  you wish, however).
305 
306  \n
307  \subsection prop3.1 PROP_FOCUS_ICON
308  <em> Name in XML files: </em> \c "focus_icon"
309 
310  For icon buttons. A different icon to show when the item is focused.
311 
312  \n
313  \subsection prop4 PROP_TEXT_ALIGN, PROP_TEXT_VALIGN
314  <em> Name in XML files: </em> \c "text_align", "text_valign"
315 
316  used exclusively by label components. Value can be "right" or "center" (left
317  used if not specified) for "text_align", or "top"/"center"/"bottom" for
318  valign.
319 
320  \n
321  \subsection prop5 PROP_WORD_WRAP
322  <em> Name in XML files: </em> \c "word_wrap"
323 
324  used by label components and icon buttons. Value can be "true" to indicate
325  that long text should spawn on multiple lines. Warning, in icon buttons,
326  the space under the button's text may be rendered unclickable by the label
327  widget overlapping other widgets under.
328 
329  Line breaks are done on space characters; if one word is too long to fit
330  on one line, then SHY (soft hyphen) characters are searched and breaks
331  can be added there.
332 
333  Note that for multiline labels, the layout engine is unable to guess their
334  width and height on their own so you should explicitely give a width and
335  height for labels that use this flag.
336 
337  \n
338  \subsection prop6 PROP_MIN_VALUE, PROP_MAX_VALUE
339  <em> Name in XML files: </em> \c "min_value", \c "max_value"
340 
341  used to specify a minimum and maximum value for numeric widgets
342  (c.f. spinner)
343 
344  \n
345  \subsection prop7 PROP_X, PROP_Y
346  <em> Name in XML files: </em> \c "x", "y"
347 
348  sets the position (location) of a widget, relative to its parent (container
349  \<div\> or screen if none). A plain number will be interpreted as an
350  aabsolute position in pixels. A '%' sign may be added to the given number
351  to mean that the location is specified in terms of a percentage of parent
352  size (parent size means the parent \<div\> or the whole screen if none). A
353  negative value can also be passed to start coordinate from right and/or
354  bottom, instead of starting from top-left corner as usual.
355  Note that in many cases, it is not necessary to manually set a position. Div
356  layouts will often manage that for you (see PROP_LAYOUT). Other widgets will
357  also automatically manage the position and size of their children, for
358  instance ribbons.
359 
360  \n
361  \subsection prop8 PROP_WIDTH, PROP_HEIGHT
362  <em> Name in XML files: </em> \c "width", \c "height"
363 
364  give dimensions to the widget. A plain number will be interpreted as an
365  absolute position in pixels. A '%' sign may be added to the given number to
366  mean that the size is specified in terms of a percentage of parent size
367  (parent size means the parent \<div\> or the whole screen if none).
368  Note that in many cases, it is not necessary to manually a size. Div layouts
369  will often manage that for you (see PROP_LAYOUT). In addition, sizes are
370  automatically calculated for widgets made of icons and/or text like labels
371  and plain icons. Other widgets will also automatically manage the position
372  and size of their children, for instance ribbons.
373 
374  Another possible value is "fit", which will make a \<div\> fit to its
375  contents.
376 
377  Another possible value is "font", which will use the size of the font
378  (useful to insert widgets inside text)
379 
380  \n
381  \subsection prop9 PROP_MAX_WIDTH, PROP_MAX_HEIGHT
382  <em> Names in XML files: </em> \c "max_width", \c "max_height"
383 
384  The maximum size a widget can take; especially useful when using percentages
385  and proportions.
386 
387  \n
388  \subsection prop10 PROP_CHILD_WIDTH, PROP_CHILD_HEIGHT
389  <em> Names in XML files: </em> \c "child_width", \c "child_height"
390 
391  Used exclusively by the ribbon grid widget. See docs for this widget above.
392 
393  \n
394  \subsection prop11 PROP_LAYOUT
395  <em> Name in XML files: </em> \c "layout"
396 
397  Valid on 'div' containers. Value can be "horizontal-row" or "vertical-row".
398  This means x and y coordinates of all children will automatically be
399  calculated at runtime, so they are laid in a row. Width and height can be set
400  absolutely as usual, but can also be determined dynamically according to
401  available screen space. Also see PROP_ALIGN and PROP_PROPORTION to known
402  more about controlling layouts. Note that all components within a layed-out
403  div will ignore all x/y coordinates you may give them as parameter.
404 
405  \n
406  \subsection prop12 PROP_ALIGN
407  <em> Name in XML files: </em> \c "align"
408 
409  For widgets located inside a vertical-row layout div : Changes how the x
410  coord of the widget is determined. Value can be \c "left", \c "center" or
411  \c "right".
412 
413  For widgets located inside a horizontal-row layout div : Changes how the y
414  coord of the widget is determined. Value can be \c "top", \c "center" or
415  \c "bottom".
416 
417  \note If you want to horizontally center widgets in a horizontal-row layout,
418        or vertically center widgets in a vertical-row layout, this property
419        is not what you're looking for; instead, add a stretching spacer before
420        and after the widget(s) you want to center.
421 
422  \note When applied to a label widget, this property will center the text
423        widget within its parent. To align the text inside the label widget,
424        see \ref prop4
425 
426  \n
427  \subsection prop13 PROP_PROPORTION
428  <em> Name in XML files: </em> \c "proportion"
429 
430  Helps  determining widget size dynamically (according to available screen
431  space) in layed-out divs. In a vertical row layout, proportion sets the
432  height of the item. In an horizontal row, it sets the width of the item.
433  Proportions are always evaluated relative to the proportions of other widgets
434  in the same div. If one div contains 4 widgets, and their proportions are
435  1-2-1-1, it means the second must take twice as much space as the 3 others.
436  In this case, 10-20-10-10 would do the exact same effect. 1-1-1-1 would mean
437  all take 1/4 of the available space. Note that it is allowed to mix absolute
438  widget sizes and proportions; in this case, widgets with absolute size are
439  evaluated first, and the dynamically-sized ones split the remaining space
440  according to their proportions.
441 
442  \n
443  \subsection prop14 PROP_SQUARE
444  <em> Name in XML files: </em> \c "square_items"
445 
446  Valid on Ribbons or RibbonGrids. Can be "true" (omitting it means "false").
447  Indicates whether the contents use rectangular icons as opposed to "round"
448  icons (this will affect the type of focus/highlighting used)
449 
450  \n
451  \subsection prop15 PROP_EXTEND_LABEL
452  <em> Name in XML files: </em> \c "extend_label"
453 
454  How many pixels the label is allowed to expand beyond the boundaries of the
455  widget itself. Currently only allowed on icon widgets.
456 
457  \n
458  \subsection prop16 PROP_LABELS_LOCATION
459  <em> Name in XML files: </em> \c "label_location"
460 
461  In dynamic ribbons : Decides where the label is. Value
462  can be "each", "bottom", or "none" (if ommitted, "none" is the default).
463  "each" means that every item has its own label. "bottom" means there is a
464  single label for all at the bottom, that displays the name of the current
465  item.
466 
467  In non-dynamic ribbons, you can also use value "hover" which will make the
468  label only visible when the icon is hovered with the mouse.
469 
470  \n
471  \subsection prop17 PROP_MAX_ROWS
472  <em> Name in XML files: </em> \c "max_rows"
473 
474  Currently used for ribbon grids only. Indicates the maximum amount of rows
475  this ribbon can have.
476 
477  \n
478  \subsection prop18 PROP_WRAP_AROUND
479  <em> Name in XML files: </em> \c "wrap_around"
480 
481  Currently used for spinners only. Value can be "true" or "false"
482 
483 
484  \n
485  \subsection prop19 PROP_DIV_PADDING
486  <em> Name in XML files: </em> \c "padding"
487 
488  Used on divs, indicate by how many pixels to pad contents
489 
490 
491  \n
492  \subsection prop20 PROP_KEEP_SELECTION
493  <em> Name in XML files: </em> \c "keep_selection"
494 
495  Used on lists, indicates that the list should keep showing the selected item
496  even when it doesn't have the focus
497 
498 
499  \n
500  <HR>
501  \section code Using the engine in code
502  <HR>
503 
504  The first thing to do is to derive a class of your own from
505  AbstractStateManager. There are a few callbacks you will need to override.
506  Once it's done, you have all AbstractStateManager methods ready to be used to
507  push/pop/set menus on the screen stack. Once you have instanciated your state
508  manager class, call GUIEngine::init and pass it as argument. One of the most
509  important callbacks is 'eventCallback', which will be called everytime
510  something happens. Events are generally a widget state change. In this case,
511  a pointer to the said widget is passed along its name, so you get its new
512  state and/or act.
513 
514  When you have described the general layout of a Screen in a XML file, as
515  described above, you may use it in the code by creating a class deriving
516  from GUIEngine::Screen, passing the name of the XML file to the constructor
517  of the base class. The derived class will most notably be used for event
518  callbacks, to allowcreating interactive menus. The derived class must also
519  implement the Screen::init and Screen::tearDown methods,
520  that will be called, respectively, when a menu is entered/left. For simple
521  menus, it is not unexpected that those methods do nothing. For init and
522  tearDown the corresponding function in Screen must be called. Note that init
523  is called after the irrlicht elements have been added on screen; if you wish
524  to alter elements BEFORE they are actually added, use either
525  Screen::loadedFromFile or Screen::beforeAddingWidget ; the
526  difference is that the first is called once only upon loading, whereas the
527  second is called every time the menu is visited.
528 
529  \n
530  Summary of callbacks, in order :
531 
532  \li (Load the Screen from file : Screen::loadFromFile is called automatically
533       the first time you reference this screen through the StateManager)
534  \li Screen::loadedFromFile is called (implement it if you need it)
535  \li (Ask to visit the screen through the StateManager)
536  \li Screen::beforeAddingWidget (implement it if you need it)
537  \li Widget::add is called automatically on each Widget of the screen
538  \li Screen::init (implement it if you need it)
539  \li (Ask to leave the Screen through the StateManager)
540  \li Screen::tearDown (implement it if you need it)
541  \li Widget::elementRemoved is called automatically on each Widget of the
542      screen
543 
544  Widget::m_properties contains all the widget properties as loaded
545  from the XML file. They are generally only read from the Widget::add
546  method, so if you alter a property after 'add()' was called it will not
547  appear on screen.
548 
549  Note that the same instance of your object may be entered/left more than
550  once, so make sure that one instance of your object can be used several
551  times if the same screen is visited several times.
552 
553  Note that the same instance of your object may be unloaded then loaded back
554  later. It is thus important to do set-up in the Screen::loadedFromFile
555  callback rather than in the constructor (after the creation of Screen object,
556  it may be unloaded then loaded back at will, this is why it's important to
557  not rely on the constructor to perform set-up).
558 
559  Do not delete a Screen manually, since the GUIEngine caches them; deleting
560  a Screen will only result in dangling pointers in the GUIEngine. Instead, let
561  the GUIEngine do the cleanup itself on shutdown, or on e.g. resolution
562  change.
563 
564  You can also explore the various methods in Screen to discover
565  more optional callbacks you can use.
566 
567  You can also create dialogs by deriving from ModalDialog in a very
568  similar way.
569 
570  \n
571  <HR>
572  \section internals Inside the GUI Engine
573  <HR>
574 
575  \subsection Widget Widget
576 
577  SuperTuxKart's GUIEngine::Widget class is a wrapper for the underlying
578  irrlicht classes. This is needed for a couple reasons :
579  - irrlicht widgets do not do everything we want; so many STK widgets act as
580    composite widgets (create multiple irrlicht widgets and adds logic so they
581    behave as a whole to the end-user)
582  - STK widgets have a longer life-span than their underlying irrlicht
583    counterparts. This is simply an optimisation measure to prevent having to
584    seek the file to disk everytime a screen switch occurs.
585 
586  Each widget contains one (or several) \c irr::gui::IGUIElement instances
587  that represent the irrlicht widget that is added to the \c IGUIEnvironment
588  if the widget is currently shown; if a widget is not currently shown on
589  screen (in irrlicht's \c IGUIEnvironment), then its underlying
590  \c IGUIElement pointer will be \c NULL but the widget continues to exist
591  and remains ready to re-create its underlying irrlicht widget when the screen
592  it is part of is added again. The method \c add() is used to tell a widget
593  to create its irrlicht counterpart in the \c IGUIEnvironment - but note that
594  unless you start handling stuff manually you do NOT need to invoke \c add()
595  on each widget manually, since the parent GUIEngine::Screen object will do
596  it automatically when it is shown. When the irrlicht \c IGUIEnvironment
597  is cleared (when irrlicht widgets are removed), it is very important to tell
598  the Widgets that their pointer to their \cIGUIElement counterpart is no more
599  valid; this is done by calling \c elementRemoved() - but again unless you do
600  manual manipulation of the widget tree, the GUIEngine::Screen object will
601  take care of this for you.
602 
603  So, before trying to access the underlying irrlicht element of a
604  GUIEngine::Widget, it is thus important to check if the GUIEngine::Widget
605  is currently added to the irr \c IGUIEnvironment. This can be done by
606  calling \c ->getIrrlichtElement() and checking if the result is \c NULL (if
607  non-null, the widget is currently added to the screen). Of course, in some
608  circumstances, the check can be skipped because the widget is known to be
609  currently visible.
610 
611  VERY IMPORTANT: some methods should only be called before Screen::init, and
612  some methods should only be called after Screen::init. Unfortunately the
613  documentation does not always make this clear at this point :(
614  A good hint is that methods that make calls on a IGUIElement* need to be
615  called after init(), the others needs to be called before.
616 
617 
618  \subsection Screen Screen
619 
620  This class holds a tree of GUIEngine::Widget instances. It takes care of
621  creating the tree from a XML file upon loading (with the help of others,
622  for instane the GUIEngine::LayoutManager); it handles calling \c add() on
623  each of its GUIEngine::Widget children when being added - so that the
624  corresponding \c IGUIElement irrlicht widgets are added to the irrlicht
625  scene. It also takes care of telling its GUIEngine::Widget children when
626  their irrlicht \c IGUIElement counterpart was removed from the
627  \c IGUIEnvironment so that they don't carry dangling pointers.
628 
629 
630  The default behavior of the GUIEngine::Screen object will be just fine for
631  most basic purposes, but if you want to build highly dynamic screens, you
632  may need to get your hands dirty. Take a look at
633  GUIEngine::Screen::manualRemoveWidget() and
634  GUIEngine::Screen::manualAddWidget() if you wish to dynamically modify the
635  STK widget tree at runtime. If you get into this, be very careful about the
636  relationship
637  between the STK widget tree and the irrlicht widget tree. If you
638  \c manualRemoveWidget() a STK widget that is currently visible on screen,
639  this does not remove its associated irrlicht widget; call
640  \c widget->getIrrlichtElement()->remove() for that. When you removed a
641  widget from a Screen you are also responsible to call
642  \c Widget::elementRemoved() on them to avoid dangling pointers.
643  Similarly, a GUIEngine::Widget that is not inside a GUIEngine::Screen
644  when the screen is added will not have its \c add() method be called
645  automatically (so, for instance, if you \c manualAddWidget() a widget
646  after a Screen was shown, you will also need to call \c ->add() on the
647  widget so that it is added to the irrlicht GUI environment).
648 
649  As a final note, note that the GUIEngine::Skin depends on both the irrlicht
650  widget and the STK widget to render widgets properly. So adding an irrlicht
651  IGUIElement without having its SuperTuxKart GUIEngine::Widget accessible
652  through the current GUIEngine::Screen (or a modal dialog) may result in
653  rendering glitches.
654 
655 
656  */
657 }
658 
659 #include "guiengine/engine.hpp"
660 
661 #include "challenges/story_mode_timer.hpp"
662 #include "config/user_config.hpp"
663 #include "font/bold_face.hpp"
664 #include "font/digit_face.hpp"
665 #include "font/font_manager.hpp"
666 #include "font/font_settings.hpp"
667 #include "font/regular_face.hpp"
668 #include "input/input_manager.hpp"
669 #include "io/file_manager.hpp"
670 #ifndef SERVER_ONLY
671 #include "graphics/2dutils.hpp"
672 #endif
673 #include "graphics/irr_driver.hpp"
674 #include "guiengine/event_handler.hpp"
675 #include "guiengine/modaldialog.hpp"
676 #include "guiengine/message_queue.hpp"
677 #include "guiengine/scalable_font.hpp"
678 #include "guiengine/screen.hpp"
679 #include "guiengine/screen_keyboard.hpp"
680 #include "guiengine/skin.hpp"
681 #include "guiengine/widget.hpp"
682 #include "guiengine/dialog_queue.hpp"
683 #include "modes/demo_world.hpp"
684 #include "modes/cutscene_world.hpp"
685 #include "modes/world.hpp"
686 #include "states_screens/race_gui_base.hpp"
687 #include "tips/tips_manager.hpp"
688 #include "utils/debug.hpp"
689 #include "utils/string_utils.hpp"
690 #include "utils/stk_process.hpp"
691 #include "utils/translation.hpp"
692 
693 #include <algorithm>
694 #include <iostream>
695 #include <assert.h>
696 #include <irrlicht.h>
697 #include <mutex>
698 
699 using namespace irr::gui;
700 using namespace irr::video;
701 
702 namespace GUIEngine
703 {
704 
705     namespace Private
706     {
707         IGUIEnvironment* g_env;
708         Skin* g_skin = NULL;
709         ScalableFont *g_font;
710         ScalableFont *g_outline_font;
711         ScalableFont *g_large_font;
712         ScalableFont *g_title_font;
713         ScalableFont* g_small_title_font;
714         ScalableFont* g_tiny_title_font;
715         ScalableFont *g_small_font;
716         ScalableFont *g_digit_font;
717 
718         IrrlichtDevice* g_device;
719         IVideoDriver* g_driver;
720         Screen* g_current_screen = NULL;
721         AbstractStateManager* g_state_manager = NULL;
722         Widget* g_focus_for_player[MAX_PLAYER_COUNT];
723 
724         int font_height;
725         int large_font_height;
726         int small_font_height;
727         int title_font_height;
728         int small_title_font_height;
729         int tiny_title_font_height;
730 #ifdef ANDROID
731         std::mutex m_gui_functions_mutex;
732         std::vector<std::function<void()> > m_gui_functions;
733 #endif
734     }
735     using namespace Private;
736 
737     PtrVector<Widget, REF> needsUpdate;
738 
739     PtrVector<Screen, REF> g_loaded_screens;
740 
741     float dt = 0;
742 
743     // -----------------------------------------------------------------------
getLatestDt()744     float getLatestDt()
745     {
746         return dt;
747     }   // getLatestDt
748 
749     // -----------------------------------------------------------------------
750     struct MenuMessage
751     {
752         irr::core::stringw m_message;
753         float m_time;
754 
MenuMessageGUIEngine::MenuMessage755         MenuMessage(const core::stringw& message, const float time)
756                    : m_message(message), m_time(time)
757         {
758         }
759     };   // MenuMessage
760 
761     std::vector<MenuMessage> gui_messages;
762 
763     bool g_is_no_graphics[PT_COUNT];
764     // ------------------------------------------------------------------------
showMessage(const core::stringw & message,const float time)765     void showMessage(const core::stringw& message, const float time)
766     {
767         // check for duplicates
768         const int count = (int) gui_messages.size();
769         for (int n=0; n<count; n++)
770         {
771             if (gui_messages[n].m_message == message) return;
772         }
773 
774         // add message
775         gui_messages.push_back( MenuMessage(message, time) );
776 
777     }   // showMessage
778 
779     // ------------------------------------------------------------------------
getFocusForPlayer(const unsigned int playerID)780     Widget* getFocusForPlayer(const unsigned int playerID)
781     {
782         assert(playerID < MAX_PLAYER_COUNT);
783 
784         return g_focus_for_player[playerID];
785     }   // getFocusForPlayer
786 
787     // ------------------------------------------------------------------------
focusNothingForPlayer(const unsigned int playerID)788     void focusNothingForPlayer(const unsigned int playerID)
789     {
790         Widget* focus = getFocusForPlayer(playerID);
791         if (focus != NULL) focus->unsetFocusForPlayer(playerID);
792 
793         g_focus_for_player[playerID] = NULL;
794     }   // focusNothingForPlayer
795 
796     // ------------------------------------------------------------------------
isFocusedForPlayer(const Widget * w,const unsigned int playerID)797     bool isFocusedForPlayer(const Widget* w, const unsigned int playerID)
798     {
799         assert(w != NULL);
800         assert(playerID < MAX_PLAYER_COUNT);
801 
802         // If no focus
803         if (g_focus_for_player[playerID] == NULL) return false;
804 
805         // otherwise check if the focus is the given widget
806         return g_focus_for_player[playerID]->isSameIrrlichtWidgetAs(w);
807     }   // isFocusedForPlayer
808 
809     // ------------------------------------------------------------------------
getTitleFontHeight()810     int getTitleFontHeight()
811     {
812         return Private::title_font_height;
813     }   // getTitleFontHeight
814         // ------------------------------------------------------------------------
getSmallTitleFontHeight()815     int getSmallTitleFontHeight()
816     {
817         return Private::small_title_font_height;
818     }   // getTitleFontHeight
819         // ------------------------------------------------------------------------
getTinyTitleFontHeight()820     int getTinyTitleFontHeight()
821     {
822         return Private::tiny_title_font_height;
823     }   // getTitleFontHeight
824 
825 
826     // ------------------------------------------------------------------------
getFontHeight()827     int getFontHeight()
828     {
829         return Private::font_height;
830     }   // getFontHeight
831 
832     // ------------------------------------------------------------------------
getSmallFontHeight()833     int getSmallFontHeight()
834     {
835         return Private::small_font_height;
836     }   // getSmallFontHeight
837 
838     // ------------------------------------------------------------------------
getLargeFontHeight()839     int getLargeFontHeight()
840    {
841 
842         return Private::large_font_height;
843     }   // getSmallFontHeight
844 
845     // ------------------------------------------------------------------------
clear()846     void clear()
847     {
848         g_env->clear();
849         if (g_current_screen != NULL) g_current_screen->elementsWereDeleted();
850         g_current_screen = NULL;
851 
852         needsUpdate.clearWithoutDeleting();
853 
854         gui_messages.clear();
855     }   // clear
856 
857     // ------------------------------------------------------------------------
858     /** Updates all widgets that need to be updated.
859      *  \param dt Time step size.
860      */
update(float dt)861     void update(float dt)
862     {
863         // Just to mark the begin/end scene block
864         GUIEngine::GameState state = StateManager::get()->getGameState();
865         if (state != GUIEngine::GAME)
866         {
867             // This code needs to go outside beginScene() / endScene() since
868             // the model view widget will do off-screen rendering there
869             for_var_in(GUIEngine::Widget*, widget, GUIEngine::needsUpdate)
870             {
871                 widget->update(dt);
872             }
873             if (state == GUIEngine::MENU) DialogQueue::get()->update();
874         }
875 
876         // Hack : on the first frame, irrlicht processes all events that have been queued
877         // during the loading screen. So way until the second frame to start processing events.
878         // (Events queues during the loading screens are likely the user clicking on the
879         // frame to focus it, or similar, and should not be used as a game event)
880         static int frame = 0;
881         if (frame < 2)
882         {
883             frame++;
884             if (frame == 2)
885                 GUIEngine::EventHandler::get()->setAcceptEvents(true);
886         }
887     }
888     // ------------------------------------------------------------------------
889 
cleanForGame()890     void cleanForGame()
891     {
892         clear();
893 
894         gui_messages.clear();
895     }   // cleanForGame
896 
897     // ------------------------------------------------------------------------
898 
clearScreenCache()899     void clearScreenCache()
900     {
901         StateManager::get()->clearMenuStack();
902         Screen* screen;
903         for_in (screen, g_loaded_screens)
904         {
905             screen->unload();
906         }
907 
908         g_loaded_screens.clearAndDeleteAll();
909         g_current_screen = NULL;
910     }
911 
912     // ------------------------------------------------------------------------
913 
switchToScreen(Screen * screen)914     void switchToScreen(Screen* screen)
915     {
916         needsUpdate.clearWithoutDeleting();
917 
918         // clean what was left by the previous screen
919         g_env->clear();
920         if (g_current_screen != NULL) g_current_screen->elementsWereDeleted();
921         g_current_screen = NULL;
922         Widget::resetIDCounters();
923 
924         // check if we already loaded this screen
925         Screen* loaded_screen;
926         for_in (loaded_screen, g_loaded_screens)
927         {
928             if (loaded_screen == screen)
929             {
930                 g_current_screen = loaded_screen;
931                 break;
932             }
933         }
934 
935         // screen not found in list of existing ones
936         if (g_current_screen == NULL)
937         {
938             assert(false);
939             return;
940         }
941 
942         Debug::closeDebugMenu();
943         g_current_screen->beforeAddingWidget();
944 
945         // show screen
946         g_current_screen->addWidgets();
947     }   // switchToScreen
948 
949     // ------------------------------------------------------------------------
950 
addScreenToList(Screen * cutscene)951     void addScreenToList(Screen* cutscene)
952     {
953         g_loaded_screens.push_back(cutscene);
954     }   // addScreenToList
955 
956     // ------------------------------------------------------------------------
957 
removeScreen(Screen * screen)958     void removeScreen(Screen* screen)
959     {
960         int n = 0;
961         Screen* loaded_screen;
962         for_in (loaded_screen, g_loaded_screens)
963         {
964             if (loaded_screen == screen)
965             {
966                 screen->unload();
967                 delete screen;
968                 g_current_screen = NULL;
969                 g_loaded_screens.remove(n);
970                 break;
971             }
972             n++;
973         }
974     }
975 
976     // ------------------------------------------------------------------------
reshowCurrentScreen()977     void reshowCurrentScreen()
978     {
979         needsUpdate.clearWithoutDeleting();
980         g_state_manager->reshowTopMostMenu();
981     }   // reshowCurrentScreen
982 
983     // ------------------------------------------------------------------------
984 
985     /**
986      * Clean some of the cached data, either for a shutdown or a reload.
987      * If this is a shutdown then you also need to call free().
988      */
cleanUp()989     void cleanUp()
990     {
991         // There is no need to delete the skin, the gui environment holds it
992         //if (g_skin != NULL) delete g_skin;
993         g_skin = NULL;
994 
995         for (unsigned int i=0; i<g_loaded_screens.size(); i++)
996         {
997             g_loaded_screens[i].unload();
998         }
999 
1000         g_current_screen = NULL;
1001         needsUpdate.clearWithoutDeleting();
1002 
1003         if (ScreenKeyboard::isActive()) ScreenKeyboard::dismiss();
1004         if (ModalDialog::isADialogActive()) ModalDialog::dismiss();
1005 
1006         if (g_font)
1007         {
1008             //delete g_font;
1009             g_font->drop();
1010             g_font = NULL;
1011         }
1012         if (g_title_font)
1013         {
1014             //delete g_title_font;
1015             g_title_font->drop();
1016             g_title_font = NULL;
1017         }
1018         if (g_small_title_font)
1019         {
1020             //delete g_small_title_font;
1021             g_small_title_font->drop();
1022             g_small_title_font = NULL;
1023         }
1024         if (g_tiny_title_font)
1025         {
1026             //delete g_tiny_title_font;
1027             g_tiny_title_font->drop();
1028             g_tiny_title_font = NULL;
1029         }
1030         if (g_small_font)
1031         {
1032             //delete g_small_font;
1033             g_small_font->drop();
1034             g_small_font = NULL;
1035         }
1036         if (g_large_font)
1037         {
1038             g_large_font->drop();
1039             g_large_font = NULL;
1040         }
1041         if (g_digit_font)
1042         {
1043             g_digit_font->drop();
1044             g_digit_font = NULL;
1045         }
1046         if (g_outline_font)
1047         {
1048             g_outline_font->drop();
1049             g_outline_font = NULL;
1050         }
1051 
1052         // nothing else to delete for now AFAIK, irrlicht will automatically
1053         // kill everything along the device
1054     }   // cleanUp
1055 
1056     // -----------------------------------------------------------------------
1057 
1058     /**
1059       * To be called after cleanup().
1060       * The difference between cleanup() and free() is that cleanUp() just
1061       * removes some cached data but does not actually uninitialize the gui
1062       * engine. This does.
1063       */
deallocate()1064     void deallocate()
1065     {
1066         g_loaded_screens.clearAndDeleteAll();
1067     }   // deallocate
1068 
1069     // -----------------------------------------------------------------------
resetGlobalVariables()1070     void resetGlobalVariables()
1071     {
1072         // Try to clear global variable for android to avoid crashes
1073         needsUpdate.m_contents_vector.clear();
1074         g_loaded_screens.m_contents_vector.clear();
1075         g_current_screen = NULL;
1076         gui_messages.clear();
1077         MessageQueue::resetGlobalVariables();
1078 #ifdef ANDROID
1079         m_gui_functions.clear();
1080 #endif
1081         g_is_no_graphics[PT_MAIN] = false;
1082         g_is_no_graphics[PT_CHILD] = false;
1083     }   // resetGlobalVariables
1084 
1085     // -----------------------------------------------------------------------
init(IrrlichtDevice * device_a,IVideoDriver * driver_a,AbstractStateManager * state_manager,bool loading)1086     void init(IrrlichtDevice* device_a, IVideoDriver* driver_a,
1087               AbstractStateManager* state_manager, bool loading)
1088     {
1089         g_env = device_a->getGUIEnvironment();
1090         g_device = device_a;
1091         g_driver = driver_a;
1092         g_state_manager = state_manager;
1093 
1094         for (unsigned int n=0; n<MAX_PLAYER_COUNT; n++)
1095         {
1096             g_focus_for_player[n] = NULL;
1097         }
1098 
1099         /*
1100          To make the g_font a little bit nicer, we load an external g_font
1101          and set it as the new default g_font in the g_skin.
1102          To keep the standard g_font for tool tip text, we set it to
1103          the built-in g_font.
1104          */
1105         try
1106         {
1107             g_skin = new Skin(g_env->getSkin());
1108             g_env->setSkin(g_skin);
1109             g_skin->drop(); // GUI env grabbed it
1110             assert(g_skin->getReferenceCount() == 1);
1111         }
1112         catch (std::runtime_error& /*err*/)
1113         {
1114             Log::error("Engine::init", "Cannot load skin specified in user config. "
1115                 "Falling back to defaults.");
1116             UserConfigParams::m_skin_file.revertToDefaults();
1117 
1118             try
1119             {
1120                 g_skin = new Skin(g_env->getSkin());
1121                 g_env->setSkin(g_skin);
1122                 g_skin->drop(); // GUI env grabbed it
1123                 assert(g_skin->getReferenceCount() == 1);
1124             }
1125             catch (std::runtime_error& err)
1126             {
1127                 (void)err;
1128                 Log::fatal("Engine::init", "Canot load default GUI skin");
1129             }
1130         }
1131 
1132         RegularFace* regular = font_manager->getFont<RegularFace>();
1133         BoldFace* bold = font_manager->getFont<BoldFace>();
1134         DigitFace* digit = font_manager->getFont<DigitFace>();
1135 
1136         ScalableFont* digit_font = new ScalableFont(digit);
1137         g_digit_font = digit_font;
1138 
1139         ScalableFont* sfont = new ScalableFont(regular);
1140         g_font = sfont;
1141         Private::font_height = g_font->getDimension( L"X" ).Height;
1142 
1143         ScalableFont* sfont_larger = new ScalableFont(regular);
1144         sfont_larger->setScale(1.4f);
1145         g_large_font = sfont_larger;
1146         Private::large_font_height = g_large_font->getDimension( L"X" ).Height;
1147 
1148         g_outline_font = new ScalableFont(regular);
1149         g_outline_font->getFontSettings()->setBlackBorder(true);
1150 
1151         ScalableFont* sfont_smaller = new ScalableFont(regular);
1152         sfont_smaller->setScale(0.8f);
1153         g_small_font = sfont_smaller;
1154         Private::small_font_height = g_small_font->getDimension( L"X" ).Height;
1155 
1156         ScalableFont* sfont2 = new ScalableFont(bold);
1157         g_title_font = sfont2;
1158         ScalableFont* sfont3 = new ScalableFont(bold);
1159         sfont3->setScale(0.8f);
1160         g_small_title_font = sfont3;
1161         ScalableFont* sfont4 = new ScalableFont(bold);
1162         sfont4->setScale(0.6f);
1163         g_tiny_title_font = sfont4;
1164         Private::title_font_height =
1165             g_title_font->getDimension( L"X" ).Height;
1166         Private::small_title_font_height =
1167             g_small_title_font->getDimension( L"X" ).Height;
1168         Private::tiny_title_font_height =
1169             g_tiny_title_font->getDimension( L"X" ).Height;
1170 
1171         if (g_font != NULL) g_skin->setFont(g_font);
1172 
1173         // set event receiver
1174         g_device->setEventReceiver(EventHandler::get());
1175 
1176         if (loading)
1177         {
1178             g_device->getVideoDriver()
1179                     ->beginScene(true, true, video::SColor(255,100,101,140));
1180             renderLoading(true, true);
1181             g_device->getVideoDriver()->endScene();
1182         }
1183     }   // init
1184 
1185     // -----------------------------------------------------------------------
reloadSkin()1186     void reloadSkin()
1187     {
1188         assert(g_skin != NULL);
1189 
1190         irr::gui::IGUISkin* fallbackSkin = g_skin->getFallbackSkin();
1191 
1192         Skin* newSkin;
1193         try
1194         {
1195             // it's important to create the new skin before deleting the old
1196             // one so that the fallback skin is not dropped
1197             newSkin = new Skin(fallbackSkin);
1198         }
1199         catch (std::runtime_error& /*err*/)
1200         {
1201             Log::error("Engine::reloadSkin", "Canot load newly specified skin");
1202             return;
1203         }
1204 
1205         assert(g_skin->getReferenceCount() == 1);
1206 
1207         g_skin = newSkin;
1208 
1209         // will also drop (and thus delete) the previous skin
1210         g_env->setSkin(g_skin);
1211         g_skin->drop(); // g_env grabbed it
1212         assert(g_skin->getReferenceCount() == 1);
1213     }   // reloadSkin
1214 
1215     // -----------------------------------------------------------------------
reloadForNewSize()1216     void reloadForNewSize()
1217     {
1218         g_skin->resetBackgroundImage();
1219         Private::font_height = g_font->getDimension( L"X" ).Height;
1220         Private::large_font_height = g_large_font->getDimension( L"X" ).Height;
1221         Private::small_font_height = g_small_font->getDimension( L"X" ).Height;
1222         Private::title_font_height =
1223             g_title_font->getDimension( L"X" ).Height;
1224         Private::small_title_font_height =
1225             g_small_title_font->getDimension( L"X" ).Height;
1226         Private::tiny_title_font_height =
1227             g_tiny_title_font->getDimension( L"X" ).Height;
1228         StateManager::get()->onResize();
1229     }   // reloadForNewSize
1230 
1231     // -----------------------------------------------------------------------
addGUIFunctionBeforeRendering(std::function<void ()> func)1232     void addGUIFunctionBeforeRendering(std::function<void()> func)
1233     {
1234 #ifdef ANDROID
1235         std::lock_guard<std::mutex> lock(m_gui_functions_mutex);
1236         m_gui_functions.push_back(func);
1237 #endif
1238     }   // addGUIFunctionBeforeRendering
1239 
1240     // -----------------------------------------------------------------------
1241     /** \brief called on every frame to trigger the rendering of the GUI.
1242      *  \param elapsed_time Time since last rendering calls (in seconds).
1243      *  \param is_loading True if the rendering is called during loading of world,
1244      *         in which case world, physics etc must not be accessed.
1245      */
render(float elapsed_time,bool is_loading)1246     void render(float elapsed_time, bool is_loading)
1247     {
1248 #ifndef SERVER_ONLY
1249         GUIEngine::dt = elapsed_time;
1250 
1251         // Not yet initialized, or already cleaned up
1252         if (g_skin == NULL) return;
1253 
1254 #ifdef ANDROID
1255         // Run all GUI functions first (if any)
1256         std::unique_lock<std::mutex> ul(m_gui_functions_mutex);
1257         if (!m_gui_functions.empty())
1258         {
1259             std::vector<std::function<void()> > functions;
1260             std::swap(functions, m_gui_functions);
1261             ul.unlock();
1262             for (auto& f : functions)
1263                 f();
1264         }
1265         else
1266             ul.unlock();
1267 #endif
1268 
1269         GameState gamestate = g_state_manager->getGameState();
1270 
1271         // ---- some menus may need updating
1272         bool dialog_opened = false;
1273 
1274         if (ScreenKeyboard::isActive())
1275         {
1276             ScreenKeyboard::getCurrent()->onUpdate(dt);
1277             dialog_opened = true;
1278         }
1279         else if (ModalDialog::isADialogActive())
1280         {
1281             ModalDialog::getCurrent()->onUpdate(dt);
1282             dialog_opened = true;
1283         }
1284 
1285         if (gamestate != GAME || is_loading)
1286         {
1287             Screen* screen = getCurrentScreen();
1288 
1289             if (screen != NULL &&
1290                 (!dialog_opened || screen->getUpdateInBackground()))
1291             {
1292                 screen->onUpdate(elapsed_time);
1293             }
1294         }
1295 
1296         gamestate = g_state_manager->getGameState();
1297 
1298         // ---- menu drawing
1299 
1300         // draw background image and sections
1301 
1302         if ( (gamestate == MENU &&
1303               GUIEngine::getCurrentScreen() != NULL &&
1304              !GUIEngine::getCurrentScreen()->needs3D()  ) || is_loading)
1305         {
1306             g_skin->drawBgImage();
1307         }
1308         else if (gamestate == INGAME_MENU)
1309         {
1310             g_skin->drawBGFadeColor();
1311         }
1312 
1313         g_driver->enableMaterial2D();
1314 
1315         if (gamestate == MENU || gamestate == INGAME_MENU)
1316         {
1317             g_skin->renderSections();
1318         }
1319 
1320         // let irrLicht do the rest (the Skin object will be called for
1321         // further render)
1322         g_env->drawAll();
1323 
1324         if (gamestate == GAME && !is_loading && !dialog_opened)
1325         {
1326             RaceGUIBase* rg = World::getWorld()->getRaceGUI();
1327             if (rg != NULL) rg->renderGlobal(elapsed_time);
1328         }
1329 
1330         MessageQueue::update(elapsed_time);
1331 
1332         if (gamestate == INGAME_MENU && dynamic_cast<CutsceneWorld*>(World::getWorld()) != NULL)
1333         {
1334             RaceGUIBase* rg = World::getWorld()->getRaceGUI();
1335             if (rg != NULL) rg->renderGlobal(elapsed_time);
1336         }
1337 
1338         if (gamestate != GAME || is_loading)
1339         {
1340             Screen* screen = getCurrentScreen();
1341 
1342             if (screen != NULL &&
1343                 (!dialog_opened || screen->getUpdateInBackground()))
1344             {
1345                 screen->onDraw(elapsed_time);
1346             }
1347         }
1348 
1349         g_skin->drawTooltips();
1350 
1351         if (gamestate != GAME && !gui_messages.empty())
1352         {
1353             core::dimension2d<u32> screen_size = irr_driver->getFrameSize();
1354             const int text_height = getFontHeight() + 20;
1355             const int y_from = screen_size.Height - text_height;
1356 
1357             int count = 0;
1358 
1359             std::vector<MenuMessage>::iterator it;
1360             for (it=gui_messages.begin(); it != gui_messages.end();)
1361             {
1362                 if ((*it).m_time > 0.0f)
1363                 {
1364                     (*it).m_time -= dt;
1365 
1366                     core::rect<s32>
1367                         msgRect(core::position2d<s32>(0,
1368                                                   y_from - count*text_height),
1369                                 core::dimension2d<s32>(screen_size.Width,
1370                                                        text_height) );
1371                     GL32_draw2DRectangle(SColor(255,252,248,230),
1372                                                        msgRect);
1373                     Private::g_font->draw((*it).m_message.c_str(),
1374                                           msgRect,
1375                                           video::SColor(255, 255, 0, 0),
1376                                           true /* hcenter */,
1377                                           true /* vcenter */);
1378                     count++;
1379                     it++;
1380                 }
1381                 else
1382                 {
1383                     it = gui_messages.erase(it);
1384                 }
1385             }
1386         }
1387 
1388         // draw FPS if enabled
1389         if ( UserConfigParams::m_display_fps ) irr_driver->displayFPS();
1390 
1391         // draw speedrun timer if enabled
1392         if ( UserConfigParams::m_speedrun_mode ) irr_driver->displayStoryModeTimer();
1393         // Update the story mode and speedrun timer (even if not enabled)
1394         story_mode_timer->unpauseTimer(/* exit loading pause */ true);
1395         story_mode_timer->updateTimer();
1396 
1397         g_driver->enableMaterial2D(false);
1398 
1399 
1400         if (gamestate == MENU)
1401         {
1402             if (DemoWorld::updateIdleTimeAndStartDemo(elapsed_time))
1403             {
1404                 return;
1405             }
1406         }
1407         else
1408         {
1409             DemoWorld::resetIdleTime();
1410         }
1411 
1412 #endif
1413     }   // render
1414 
1415     // -----------------------------------------------------------------------
1416     std::vector<irr::video::ITexture*> g_loading_icons;
1417     core::stringw g_tips_string;
1418 
clearLoadingTips()1419     void clearLoadingTips()
1420     {
1421         g_tips_string = L"";
1422     }
1423 
1424     // -----------------------------------------------------------------------
renderLoading(bool clearIcons,bool launching,bool update_tips)1425     void renderLoading(bool clearIcons, bool launching, bool update_tips)
1426     {
1427 #ifndef SERVER_ONLY
1428         if (update_tips)
1429         {
1430             core::stringw tip = TipsManager::get()->getTip("general");
1431             //I18N: Tip shown in gui for giving player hints
1432             g_tips_string = _("Tip: %s", tip);
1433         }
1434 
1435         if (clearIcons) g_loading_icons.clear();
1436 
1437         g_skin->drawBgImage();
1438         ITexture* loading =
1439             irr_driver->getTexture(file_manager->getAsset(FileManager::GUI_ICON,
1440                                                           "loading.png"));
1441 
1442         if(!loading)
1443         {
1444             Log::fatal("Engine", "Can not find loading.png texture, aborting.");
1445             exit(-1);
1446         }
1447         const int texture_w = loading->getSize().Width;
1448         const int texture_h = loading->getSize().Height;
1449         const int stretched_size = getTitleFontHeight() * 2.5f;
1450 
1451         core::dimension2d<u32> frame_size =
1452             GUIEngine::getDriver()->getCurrentRenderTargetSize();
1453         const int screen_w = frame_size.Width;
1454         const int screen_h = frame_size.Height;
1455 
1456         // used in drawing tips
1457         const int text_height = getFontHeight() * 1.2f;
1458         const int y_from = screen_h - text_height * 0.3f;
1459 
1460         const core::rect< s32 > dest_area =
1461             core::rect< s32 >(screen_w/2 - stretched_size/2,
1462                               screen_h/2 - stretched_size/2 - getTitleFontHeight()/2,
1463                               screen_w/2 + stretched_size/2,
1464                               screen_h/2 + stretched_size/2 - getTitleFontHeight()/2);
1465 
1466         const core::rect< s32 > source_area =
1467             core::rect< s32 >(0, 0, texture_w, texture_h);
1468 
1469         draw2DImage( loading, dest_area, source_area,
1470                                             0 /* no clipping */, 0,
1471                                             true /* alpha */);
1472 
1473         // seems like we need to remind irrlicht from time to time to use
1474         // the Material2D
1475         irr_driver->getVideoDriver()->enableMaterial2D();
1476         g_title_font->draw(_("Loading"),
1477                            core::rect< s32 >( 0, screen_h/2 + stretched_size/2 - getTitleFontHeight()/2,
1478                                               screen_w, screen_h ),
1479                            SColor(255,255,255,255),
1480                            true/* center h */, false /* center v */ );
1481 
1482         // Draw a tip during loading
1483         if (!g_tips_string.empty())
1484         {
1485             core::rect<s32> tipRect(core::position2d<s32>(0, y_from - text_height),
1486                                     core::dimension2d<s32>(screen_w, text_height));
1487             GL32_draw2DRectangle(Skin::getColor("tips_background::neutral"), tipRect);
1488             Private::g_font->draw(g_tips_string, tipRect,
1489                 Skin::getColor("brighttext::neutral"),
1490                 true /* hcenter */, true /* vcenter */);
1491         }
1492 
1493         const int icon_count = (int)g_loading_icons.size();
1494         const int icon_size = (int)(std::min(screen_w, screen_h) / 12.0f);
1495         const int ICON_MARGIN = 6;
1496         int x = ICON_MARGIN;
1497         int y = y_from - icon_size - ICON_MARGIN - text_height * 1.2f;
1498         for (int n=0; n<icon_count; n++)
1499         {
1500             draw2DImage(g_loading_icons[n],
1501                               core::rect<s32>(x, y, x+icon_size, y+icon_size),
1502                               core::rect<s32>(core::position2d<s32>(0, 0),
1503                                               g_loading_icons[n]->getSize()),
1504                               NULL, NULL, true
1505                                   );
1506 
1507             x += ICON_MARGIN + icon_size;
1508             if (x + icon_size + ICON_MARGIN/2 > screen_w)
1509             {
1510                 y = y - ICON_MARGIN - icon_size;
1511                 x = ICON_MARGIN;
1512             }
1513         }
1514         // This will avoid no response in windows, also allow showing loading
1515         // icon in apple device, because apple device only update render
1516         // buffer if you poll the mainloop
1517         if (!GUIEngine::isNoGraphics())
1518         {
1519             g_device->setEventReceiver(NULL);
1520             g_device->run();
1521             g_device->setEventReceiver(EventHandler::get());
1522         }
1523 
1524         // If launch is finished, pause & display the story mode timers
1525         if ( !launching)
1526         {
1527             // For speedruns only, display the timer on loading screens
1528             if (UserConfigParams::m_speedrun_mode)
1529                 irr_driver->displayStoryModeTimer();
1530 
1531             //pause the timer during loading
1532             story_mode_timer->pauseTimer(true);
1533         }
1534         else
1535         {
1536             // The screen size may change when loading
1537             irr_driver->handleWindowResize();
1538         }
1539 #endif
1540     } // renderLoading
1541 
1542     // -----------------------------------------------------------------------
1543 
addLoadingIcon(irr::video::ITexture * icon)1544     void addLoadingIcon(irr::video::ITexture* icon)
1545     {
1546         if (icon != NULL)
1547         {
1548             g_loading_icons.push_back(icon);
1549 
1550             g_device->getVideoDriver()
1551                     ->beginScene(true, true, video::SColor(255,100,101,140));
1552             renderLoading(false, true, false);
1553             g_device->getVideoDriver()->endScene();
1554         }
1555         else
1556         {
1557             Log::warn("Engine::addLoadingIcon", "Given "
1558                 "NULL icon");
1559         }
1560     } // addLoadingIcon
1561 
1562     // -----------------------------------------------------------------------
1563 
getWidget(const char * name)1564     Widget* getWidget(const char* name)
1565     {
1566         if (ScreenKeyboard::isActive())
1567         {
1568             Widget* widget = ScreenKeyboard::getCurrent()->getWidget(name);
1569             if (widget != NULL)
1570                 return widget;
1571         }
1572 
1573         // if a modal dialog is shown, search within it too
1574         if (ModalDialog::isADialogActive())
1575         {
1576             Widget* widget = ModalDialog::getCurrent()->getWidget(name);
1577             if (widget != NULL)
1578                 return widget;
1579         }
1580 
1581         Screen* screen = getCurrentScreen();
1582 
1583         if (screen == NULL) return NULL;
1584 
1585         return screen->getWidget(name);
1586     }   // getWidget
1587 
1588     // -----------------------------------------------------------------------
getWidget(const int id)1589     Widget* getWidget(const int id)
1590     {
1591         if (ScreenKeyboard::isActive())
1592         {
1593             Widget* widget = ScreenKeyboard::getCurrent()->getWidget(id);
1594             if (widget != NULL)
1595                 return widget;
1596         }
1597 
1598         // if a modal dialog is shown, search within it too
1599         if (ModalDialog::isADialogActive())
1600         {
1601             Widget* widget = ModalDialog::getCurrent()->getWidget(id);
1602             if (widget != NULL)
1603                 return widget;
1604         }
1605 
1606         Screen* screen = getCurrentScreen();
1607 
1608         if (screen == NULL) return NULL;
1609 
1610         return screen->getWidget(id);
1611     }   // getWidget
1612 
1613 #ifndef SERVER_ONLY
1614     // -----------------------------------------------------------------------
disableGraphics()1615     void disableGraphics()
1616     {
1617         g_is_no_graphics[STKProcess::getType()] = true;
1618     }   // disableGraphics
1619 
1620     // -----------------------------------------------------------------------
isNoGraphics()1621     bool isNoGraphics()
1622     {
1623         return g_is_no_graphics[STKProcess::getType()];
1624     }   // isNoGraphics
1625 #endif
1626 
1627 }   // namespace GUIEngine
1628