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