]> git.lizzy.rs Git - minetest.git/blob - src/gui/guiEngine.cpp
Add formspec theming using prepended strings
[minetest.git] / src / gui / guiEngine.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 sapier
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "guiEngine.h"
21
22 #include <IGUIStaticText.h>
23 #include <ICameraSceneNode.h>
24 #include "client/renderingengine.h"
25 #include "scripting_mainmenu.h"
26 #include "util/numeric.h"
27 #include "config.h"
28 #include "version.h"
29 #include "porting.h"
30 #include "filesys.h"
31 #include "settings.h"
32 #include "guiMainMenu.h"
33 #include "sound.h"
34 #include "client/sound_openal.h"
35 #include "clouds.h"
36 #include "httpfetch.h"
37 #include "log.h"
38 #include "fontengine.h"
39 #include "guiscalingfilter.h"
40 #include "irrlicht_changes/static_text.h"
41
42 #ifdef __ANDROID__
43 #include "client/tile.h"
44 #include <GLES/gl.h>
45 #endif
46
47
48 /******************************************************************************/
49 void TextDestGuiEngine::gotText(const StringMap &fields)
50 {
51         m_engine->getScriptIface()->handleMainMenuButtons(fields);
52 }
53
54 /******************************************************************************/
55 void TextDestGuiEngine::gotText(const std::wstring &text)
56 {
57         m_engine->getScriptIface()->handleMainMenuEvent(wide_to_utf8(text));
58 }
59
60 /******************************************************************************/
61 MenuTextureSource::~MenuTextureSource()
62 {
63         for (const std::string &texture_to_delete : m_to_delete) {
64                 const char *tname = texture_to_delete.c_str();
65                 video::ITexture *texture = m_driver->getTexture(tname);
66                 m_driver->removeTexture(texture);
67         }
68 }
69
70 /******************************************************************************/
71 video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
72 {
73         if(id)
74                 *id = 0;
75         if(name.empty())
76                 return NULL;
77         m_to_delete.insert(name);
78
79 #ifdef __ANDROID__
80         video::IImage *image = m_driver->createImageFromFile(name.c_str());
81         if (image) {
82                 image = Align2Npot2(image, m_driver);
83                 video::ITexture* retval = m_driver->addTexture(name.c_str(), image);
84                 image->drop();
85                 return retval;
86         }
87 #endif
88         return m_driver->getTexture(name.c_str());
89 }
90
91 /******************************************************************************/
92 /** MenuMusicFetcher                                                          */
93 /******************************************************************************/
94 void MenuMusicFetcher::fetchSounds(const std::string &name,
95                         std::set<std::string> &dst_paths,
96                         std::set<std::string> &dst_datas)
97 {
98         if(m_fetched.count(name))
99                 return;
100         m_fetched.insert(name);
101         std::string base;
102         base = porting::path_share + DIR_DELIM + "sounds";
103         dst_paths.insert(base + DIR_DELIM + name + ".ogg");
104         int i;
105         for(i=0; i<10; i++)
106                 dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg");
107         base = porting::path_user + DIR_DELIM + "sounds";
108         dst_paths.insert(base + DIR_DELIM + name + ".ogg");
109         for(i=0; i<10; i++)
110                 dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg");
111 }
112
113 /******************************************************************************/
114 /** GUIEngine                                                                 */
115 /******************************************************************************/
116 GUIEngine::GUIEngine(JoystickController *joystick,
117                 gui::IGUIElement *parent,
118                 IMenuManager *menumgr,
119                 MainMenuData *data,
120                 bool &kill) :
121         m_parent(parent),
122         m_menumanager(menumgr),
123         m_smgr(RenderingEngine::get_scene_manager()),
124         m_data(data),
125         m_kill(kill)
126 {
127         //initialize texture pointers
128         for (image_definition &texture : m_textures) {
129                 texture.texture = NULL;
130         }
131         // is deleted by guiformspec!
132         m_buttonhandler = new TextDestGuiEngine(this);
133
134         //create texture source
135         m_texture_source = new MenuTextureSource(RenderingEngine::get_video_driver());
136
137         //create soundmanager
138         MenuMusicFetcher soundfetcher;
139 #if USE_SOUND
140         if (g_settings->getBool("enable_sound"))
141                 m_sound_manager = createOpenALSoundManager(g_sound_manager_singleton.get(), &soundfetcher);
142 #endif
143         if(!m_sound_manager)
144                 m_sound_manager = &dummySoundManager;
145
146         //create topleft header
147         m_toplefttext = L"";
148
149         core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
150                 g_fontengine->getTextHeight());
151         rect += v2s32(4, 0);
152
153         m_irr_toplefttext = gui::StaticText::add(RenderingEngine::get_gui_env(),
154                         m_toplefttext, rect, false, true, 0, -1);
155
156         //create formspecsource
157         m_formspecgui = new FormspecFormSource("");
158
159         /* Create menu */
160         m_menu = new GUIFormSpecMenu(joystick,
161                         m_parent,
162                         -1,
163                         m_menumanager,
164                         NULL /* &client */,
165                         m_texture_source,
166                         m_formspecgui,
167                         m_buttonhandler,
168                         "",
169                         false);
170
171         m_menu->allowClose(false);
172         m_menu->lockSize(true,v2u32(800,600));
173
174         // Initialize scripting
175
176         infostream << "GUIEngine: Initializing Lua" << std::endl;
177
178         m_script = new MainMenuScripting(this);
179
180         try {
181                 m_script->setMainMenuData(&m_data->script_data);
182                 m_data->script_data.errormessage = "";
183
184                 if (!loadMainMenuScript()) {
185                         errorstream << "No future without main menu!" << std::endl;
186                         abort();
187                 }
188
189                 run();
190         } catch (LuaError &e) {
191                 errorstream << "Main menu error: " << e.what() << std::endl;
192                 m_data->script_data.errormessage = e.what();
193         }
194
195         m_menu->quitMenu();
196         m_menu->drop();
197         m_menu = NULL;
198 }
199
200 /******************************************************************************/
201 bool GUIEngine::loadMainMenuScript()
202 {
203         // Set main menu path (for core.get_mainmenu_path())
204         m_scriptdir = g_settings->get("main_menu_path");
205         if (m_scriptdir.empty()) {
206                 m_scriptdir = porting::path_share + DIR_DELIM + "builtin" + DIR_DELIM + "mainmenu";
207         }
208
209         // Load builtin (which will load the main menu script)
210         std::string script = porting::path_share + DIR_DELIM "builtin" + DIR_DELIM "init.lua";
211         try {
212                 m_script->loadScript(script);
213                 // Menu script loaded
214                 return true;
215         } catch (const ModError &e) {
216                 errorstream << "GUIEngine: execution of menu script failed: "
217                         << e.what() << std::endl;
218         }
219
220         return false;
221 }
222
223 /******************************************************************************/
224 void GUIEngine::run()
225 {
226         // Always create clouds because they may or may not be
227         // needed based on the game selected
228         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
229
230         cloudInit();
231
232         unsigned int text_height = g_fontengine->getTextHeight();
233
234         irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
235                 g_settings->getU16("screen_h"));
236
237         while (RenderingEngine::run() && (!m_startgame) && (!m_kill)) {
238
239                 const irr::core::dimension2d<u32> &current_screen_size =
240                         RenderingEngine::get_video_driver()->getScreenSize();
241                 // Verify if window size has changed and save it if it's the case
242                 // Ensure evaluating settings->getBool after verifying screensize
243                 // First condition is cheaper
244                 if (previous_screen_size != current_screen_size &&
245                                 current_screen_size != irr::core::dimension2d<u32>(0,0) &&
246                                 g_settings->getBool("autosave_screensize")) {
247                         g_settings->setU16("screen_w", current_screen_size.Width);
248                         g_settings->setU16("screen_h", current_screen_size.Height);
249                         previous_screen_size = current_screen_size;
250                 }
251
252                 //check if we need to update the "upper left corner"-text
253                 if (text_height != g_fontengine->getTextHeight()) {
254                         updateTopLeftTextSize();
255                         text_height = g_fontengine->getTextHeight();
256                 }
257
258                 driver->beginScene(true, true, video::SColor(255,140,186,250));
259
260                 if (m_clouds_enabled)
261                 {
262                         cloudPreProcess();
263                         drawOverlay(driver);
264                 }
265                 else
266                         drawBackground(driver);
267
268                 drawHeader(driver);
269                 drawFooter(driver);
270
271                 RenderingEngine::get_gui_env()->drawAll();
272
273                 driver->endScene();
274
275                 if (m_clouds_enabled)
276                         cloudPostProcess();
277                 else
278                         sleep_ms(25);
279
280                 m_script->step();
281
282 #ifdef __ANDROID__
283                 m_menu->getAndroidUIInput();
284 #endif
285         }
286 }
287
288 /******************************************************************************/
289 GUIEngine::~GUIEngine()
290 {
291         if (m_sound_manager != &dummySoundManager){
292                 delete m_sound_manager;
293                 m_sound_manager = NULL;
294         }
295
296         infostream<<"GUIEngine: Deinitializing scripting"<<std::endl;
297         delete m_script;
298
299         m_irr_toplefttext->setText(L"");
300
301         //clean up texture pointers
302         for (image_definition &texture : m_textures) {
303                 if (texture.texture)
304                         RenderingEngine::get_video_driver()->removeTexture(texture.texture);
305         }
306
307         delete m_texture_source;
308
309         if (m_cloud.clouds)
310                 m_cloud.clouds->drop();
311 }
312
313 /******************************************************************************/
314 void GUIEngine::cloudInit()
315 {
316         m_cloud.clouds = new Clouds(m_smgr, -1, rand());
317         m_cloud.clouds->setHeight(100.0f);
318         m_cloud.clouds->update(v3f(0, 0, 0), video::SColor(255,200,200,255));
319
320         m_cloud.camera = m_smgr->addCameraSceneNode(0,
321                                 v3f(0,0,0), v3f(0, 60, 100));
322         m_cloud.camera->setFarValue(10000);
323
324         m_cloud.lasttime = RenderingEngine::get_timer_time();
325 }
326
327 /******************************************************************************/
328 void GUIEngine::cloudPreProcess()
329 {
330         u32 time = RenderingEngine::get_timer_time();
331
332         if(time > m_cloud.lasttime)
333                 m_cloud.dtime = (time - m_cloud.lasttime) / 1000.0;
334         else
335                 m_cloud.dtime = 0;
336
337         m_cloud.lasttime = time;
338
339         m_cloud.clouds->step(m_cloud.dtime*3);
340         m_cloud.clouds->render();
341         m_smgr->drawAll();
342 }
343
344 /******************************************************************************/
345 void GUIEngine::cloudPostProcess()
346 {
347         float fps_max = g_settings->getFloat("pause_fps_max");
348         // Time of frame without fps limit
349         u32 busytime_u32;
350
351         // not using getRealTime is necessary for wine
352         u32 time = RenderingEngine::get_timer_time();
353         if(time > m_cloud.lasttime)
354                 busytime_u32 = time - m_cloud.lasttime;
355         else
356                 busytime_u32 = 0;
357
358         // FPS limiter
359         u32 frametime_min = 1000./fps_max;
360
361         if (busytime_u32 < frametime_min) {
362                 u32 sleeptime = frametime_min - busytime_u32;
363                 RenderingEngine::get_raw_device()->sleep(sleeptime);
364         }
365 }
366
367 /******************************************************************************/
368 void GUIEngine::drawBackground(video::IVideoDriver *driver)
369 {
370         v2u32 screensize = driver->getScreenSize();
371
372         video::ITexture* texture = m_textures[TEX_LAYER_BACKGROUND].texture;
373
374         /* If no texture, draw background of solid color */
375         if(!texture){
376                 video::SColor color(255,80,58,37);
377                 core::rect<s32> rect(0, 0, screensize.X, screensize.Y);
378                 driver->draw2DRectangle(color, rect, NULL);
379                 return;
380         }
381
382         v2u32 sourcesize = texture->getOriginalSize();
383
384         if (m_textures[TEX_LAYER_BACKGROUND].tile)
385         {
386                 v2u32 tilesize(
387                                 MYMAX(sourcesize.X,m_textures[TEX_LAYER_BACKGROUND].minsize),
388                                 MYMAX(sourcesize.Y,m_textures[TEX_LAYER_BACKGROUND].minsize));
389                 for (unsigned int x = 0; x < screensize.X; x += tilesize.X )
390                 {
391                         for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y )
392                         {
393                                 draw2DImageFilterScaled(driver, texture,
394                                         core::rect<s32>(x, y, x+tilesize.X, y+tilesize.Y),
395                                         core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
396                                         NULL, NULL, true);
397                         }
398                 }
399                 return;
400         }
401
402         /* Draw background texture */
403         draw2DImageFilterScaled(driver, texture,
404                 core::rect<s32>(0, 0, screensize.X, screensize.Y),
405                 core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
406                 NULL, NULL, true);
407 }
408
409 /******************************************************************************/
410 void GUIEngine::drawOverlay(video::IVideoDriver *driver)
411 {
412         v2u32 screensize = driver->getScreenSize();
413
414         video::ITexture* texture = m_textures[TEX_LAYER_OVERLAY].texture;
415
416         /* If no texture, draw nothing */
417         if(!texture)
418                 return;
419
420         /* Draw background texture */
421         v2u32 sourcesize = texture->getOriginalSize();
422         draw2DImageFilterScaled(driver, texture,
423                 core::rect<s32>(0, 0, screensize.X, screensize.Y),
424                 core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
425                 NULL, NULL, true);
426 }
427
428 /******************************************************************************/
429 void GUIEngine::drawHeader(video::IVideoDriver *driver)
430 {
431         core::dimension2d<u32> screensize = driver->getScreenSize();
432
433         video::ITexture* texture = m_textures[TEX_LAYER_HEADER].texture;
434
435         /* If no texture, draw nothing */
436         if(!texture)
437                 return;
438
439         f32 mult = (((f32)screensize.Width / 2.0)) /
440                         ((f32)texture->getOriginalSize().Width);
441
442         v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult,
443                         ((f32)texture->getOriginalSize().Height) * mult);
444
445         // Don't draw the header if there isn't enough room
446         s32 free_space = (((s32)screensize.Height)-320)/2;
447
448         if (free_space > splashsize.Y) {
449                 core::rect<s32> splashrect(0, 0, splashsize.X, splashsize.Y);
450                 splashrect += v2s32((screensize.Width/2)-(splashsize.X/2),
451                                 ((free_space/2)-splashsize.Y/2)+10);
452
453         video::SColor bgcolor(255,50,50,50);
454
455         draw2DImageFilterScaled(driver, texture, splashrect,
456                 core::rect<s32>(core::position2d<s32>(0,0),
457                 core::dimension2di(texture->getOriginalSize())),
458                 NULL, NULL, true);
459         }
460 }
461
462 /******************************************************************************/
463 void GUIEngine::drawFooter(video::IVideoDriver *driver)
464 {
465         core::dimension2d<u32> screensize = driver->getScreenSize();
466
467         video::ITexture* texture = m_textures[TEX_LAYER_FOOTER].texture;
468
469         /* If no texture, draw nothing */
470         if(!texture)
471                 return;
472
473         f32 mult = (((f32)screensize.Width)) /
474                         ((f32)texture->getOriginalSize().Width);
475
476         v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult,
477                         ((f32)texture->getOriginalSize().Height) * mult);
478
479         // Don't draw the footer if there isn't enough room
480         s32 free_space = (((s32)screensize.Height)-320)/2;
481
482         if (free_space > footersize.Y) {
483                 core::rect<s32> rect(0,0,footersize.X,footersize.Y);
484                 rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y);
485                 rect -= v2s32(footersize.X/2, 0);
486
487                 draw2DImageFilterScaled(driver, texture, rect,
488                         core::rect<s32>(core::position2d<s32>(0,0),
489                         core::dimension2di(texture->getOriginalSize())),
490                         NULL, NULL, true);
491         }
492 }
493
494 /******************************************************************************/
495 bool GUIEngine::setTexture(texture_layer layer, std::string texturepath,
496                 bool tile_image, unsigned int minsize)
497 {
498         video::IVideoDriver *driver = RenderingEngine::get_video_driver();
499
500         if (m_textures[layer].texture) {
501                 driver->removeTexture(m_textures[layer].texture);
502                 m_textures[layer].texture = NULL;
503         }
504
505         if (texturepath.empty() || !fs::PathExists(texturepath)) {
506                 return false;
507         }
508
509         m_textures[layer].texture = driver->getTexture(texturepath.c_str());
510         m_textures[layer].tile    = tile_image;
511         m_textures[layer].minsize = minsize;
512
513         if (!m_textures[layer].texture) {
514                 return false;
515         }
516
517         return true;
518 }
519
520 /******************************************************************************/
521 bool GUIEngine::downloadFile(const std::string &url, const std::string &target)
522 {
523 #if USE_CURL
524         std::ofstream target_file(target.c_str(), std::ios::out | std::ios::binary);
525
526         if (!target_file.good()) {
527                 return false;
528         }
529
530         HTTPFetchRequest fetch_request;
531         HTTPFetchResult fetch_result;
532         fetch_request.url = url;
533         fetch_request.caller = HTTPFETCH_SYNC;
534         fetch_request.timeout = g_settings->getS32("curl_file_download_timeout");
535         httpfetch_sync(fetch_request, fetch_result);
536
537         if (!fetch_result.succeeded) {
538                 return false;
539         }
540         target_file << fetch_result.data;
541
542         return true;
543 #else
544         return false;
545 #endif
546 }
547
548 /******************************************************************************/
549 void GUIEngine::setTopleftText(const std::string &text)
550 {
551         m_toplefttext = translate_string(utf8_to_wide(text));
552
553         updateTopLeftTextSize();
554 }
555
556 /******************************************************************************/
557 void GUIEngine::updateTopLeftTextSize()
558 {
559         core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
560                 g_fontengine->getTextHeight());
561         rect += v2s32(4, 0);
562
563         m_irr_toplefttext->remove();
564         m_irr_toplefttext = gui::StaticText::add(RenderingEngine::get_gui_env(),
565                         m_toplefttext, rect, false, true, 0, -1);
566 }
567
568 /******************************************************************************/
569 s32 GUIEngine::playSound(SimpleSoundSpec spec, bool looped)
570 {
571         s32 handle = m_sound_manager->playSound(spec, looped);
572         return handle;
573 }
574
575 /******************************************************************************/
576 void GUIEngine::stopSound(s32 handle)
577 {
578         m_sound_manager->stopSound(handle);
579 }
580
581 /******************************************************************************/
582 unsigned int GUIEngine::queueAsync(const std::string &serialized_func,
583                 const std::string &serialized_params)
584 {
585         return m_script->queueAsync(serialized_func, serialized_params);
586 }
587