The Battle for Wesnoth  1.19.15+dev
title_screen.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2025
3  by Mark de Wever <koraq@xs4all.nl>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
19 
20 #include "addon/manager_ui.hpp"
21 #include "filesystem.hpp"
22 #include "font/font_config.hpp"
23 #include "formula/string_utils.hpp"
24 #include "game_config.hpp"
25 #include "game_config_manager.hpp"
26 #include "game_launcher.hpp"
27 #include "gui/auxiliary/tips.hpp"
33 #include "gui/dialogs/message.hpp"
41 #include "language.hpp"
42 #include "log.hpp"
44 //#define DEBUG_TOOLTIP
45 #ifdef DEBUG_TOOLTIP
46 #include "gui/dialogs/tooltip.hpp"
47 #endif
48 #include "gui/widgets/button.hpp"
49 #include "gui/widgets/image.hpp"
50 #include "gui/widgets/label.hpp"
52 #include "gui/widgets/settings.hpp"
53 #include "gui/widgets/window.hpp"
54 #include "help/help.hpp"
55 #include "quit_confirmation.hpp"
56 #include "sdl/surface.hpp"
58 #include "video.hpp"
59 #include "wml_exception.hpp"
60 
61 #include <algorithm>
62 #include <functional>
63 
64 #include <boost/algorithm/string/erase.hpp>
65 
66 static lg::log_domain log_config("config");
67 #define ERR_CF LOG_STREAM(err, log_config)
68 #define WRN_CF LOG_STREAM(warn, log_config)
69 
70 namespace gui2::dialogs
71 {
72 
73 REGISTER_DIALOG(title_screen)
74 
76 
78  : modal_dialog(window_id())
79  , debug_clock_()
80  , game_(game)
81 {
82  set_allow_plugin_skip(false);
83  init_callbacks();
84 }
85 
87 {
88 }
89 
91  const std::string& id,
93  const std::function<void()>& callback)
94 {
95  register_button(id, hk, callback, callback);
96 }
97 
99  const std::string& id,
101  const std::function<void()>& callback_btn,
102  const std::function<void()>& callback_hotkey)
103 {
104  if(hk != hotkey::HOTKEY_NULL) {
105  register_hotkey(hk, [callback_hotkey](auto&&...) { callback_hotkey(); return true; });
106  }
107 
108  try {
109  button& btn = find_widget<button>(id);
110  connect_signal_mouse_left_click(btn, [callback_btn](auto&&...) {
111  std::invoke(callback_btn);
112  });
113  } catch(const wml_exception& e) {
114  ERR_GUI_P << e.user_message;
115  prefs::get().set_gui2_theme("default");
117  }
118 }
119 
120 namespace
121 {
122 void show_lua_console()
123 {
125 }
126 
127 void make_screenshot()
128 {
129  surface screenshot = video::read_pixels();
130  if(screenshot) {
131  std::string filename = filesystem::get_screenshot_dir() + "/" + _("Screenshot") + "_";
133  gui2::dialogs::screenshot_notification::display(filename, screenshot);
134  }
135 }
136 
137 } // anon namespace
138 
139 #ifdef DEBUG_TOOLTIP
140 /*
141  * This function is used to test the tooltip placement algorithms as
142  * described in the »Tooltip placement« section in the GUI2 design
143  * document.
144  *
145  * Use a 1024 x 768 screen size, set the maximum loop iteration to:
146  * - 0 to test with a normal tooltip placement.
147  * - 30 to test with a larger normal tooltip placement.
148  * - 60 to test with a huge tooltip placement.
149  * - 150 to test with a borderline to insanely huge tooltip placement.
150  * - 180 to test with an insanely huge tooltip placement.
151  */
152 static void debug_tooltip(window& /*window*/, bool& handled, const point& coordinate)
153 {
154  std::string message = "Hello world.";
155 
156  for(int i = 0; i < 0; ++i) {
157  message += " More greetings.";
158  }
159 
161  gui2::tip::show("tooltip", message, coordinate);
162 
163  handled = true;
164 }
165 #endif
166 
168 {
169  set_click_dismiss(false);
170  set_enter_disabled(true);
171  set_escape_disabled(true);
172 
173 #ifdef DEBUG_TOOLTIP
174  connect_signal<event::SDL_MOUSE_MOTION>(
175  std::bind(debug_tooltip, std::ref(*this), std::placeholders::_3, std::placeholders::_5),
177 #endif
178 
179  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(&title_screen::on_resize, this));
180 
181  //
182  // General hotkeys
183  //
185  [this](auto&&...) { set_retval(RELOAD_GAME_DATA); return true; });
186 
188  [this](auto&&...) { hotkey_callback_select_tests(); return true; });
189 
191  [this](auto&&...) { button_callback_cores(); return true; });
192 
194  [](auto&&...) { show_lua_console(); return true; });
195 
196  /** @todo: should eventually become part of global hotkey handling. */
198  [](auto&&...) { make_screenshot(); return true; });
199 
200  //
201  // Background and logo images
202  //
204  // game works just fine if just one of the background images are missing
205  ERR_CF << "No titlescreen background defined in game config";
206  }
207 
210 
211  find_widget<image>("logo-bg").set_image(game_config::images::game_logo_background);
212  find_widget<image>("logo").set_image(game_config::images::game_logo);
213 
214  //
215  // Tip-of-the-day browser
216  //
217  if(auto tip_pages = find_widget<multi_page>("tips", false, false)) {
219  tip_pages->add_page({
220  { "tip", {
221  { "use_markup", "true" },
222  { "label", tip.text }
223  }},
224  { "source", {
225  { "use_markup", "true" },
226  { "label", tip.source }
227  }}
228  });
229  }
230 
231  update_tip(true);
232  }
233 
235  std::bind(&title_screen::update_tip, this, true));
236 
238  std::bind(&title_screen::update_tip, this, false));
239 
240  // Tip panel visiblity and close button
241  panel& tip_panel = find_widget<panel>("tip_panel");
242 
243  tip_panel.set_visible(prefs::get().show_tips()
246 
247  if(auto toggle_tips = find_widget<button>("toggle_tip_panel", false, false)) {
248  connect_signal_mouse_left_click(*toggle_tips, [&tip_panel](auto&&...) {
249  const bool currently_hidden = tip_panel.get_visible() == widget::visibility::hidden;
250 
251  tip_panel.set_visible(currently_hidden
254 
255  // If previously hidden, will now be visible, so we can reuse the same value
256  prefs::get().set_show_tips(currently_hidden);
257  });
258  }
259 
260  //
261  // Help
262  //
263  register_button("help", hotkey::HOTKEY_HELP, []() {
265  help::show_help();
266  });
267 
268  //
269  // About
270  //
271  register_button("about", hotkey::HOTKEY_NULL, [] { game_version::display(); });
272 
273  //
274  // Campaign
275  //
276  register_button("campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
277  try{
278  if(game_.new_campaign()) {
279  // Suspend drawing of the title screen,
280  // so it doesn't flicker in between loading screens.
281  hide();
282  set_retval(LAUNCH_GAME);
283  }
284  } catch (const config::error& e) {
285  gui2::show_error_message(e.what());
286  }
287  });
288 
289  //
290  // Multiplayer
291  //
294 
295  //
296  // Load game
297  //
298  register_button("load", hotkey::HOTKEY_LOAD_GAME, [this]() {
299  if(game_.load_game_prompt()) {
300  // Suspend drawing of the title screen,
301  // so it doesn't flicker in between loading screens.
302  hide();
303  set_retval(LOAD_GAME);
304  }
305  });
306 
307  //
308  // Addons
309  //
310  register_button("addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
311  if(manage_addons()) {
313  }
314  });
315 
316  //
317  // Editor
318  //
320 
321  //
322  // Language
323  //
324  register_button("language", hotkey::HOTKEY_LANGUAGE, [this]() {
325  try {
326  if(game_.change_language()) {
327  on_resize();
328  update_static_labels();
329  }
330  } catch(const std::runtime_error& e) {
331  gui2::show_error_message(e.what());
332  }
333  });
334 
335  //
336  // Preferences
337  //
339  std::bind(&title_screen::show_preferences, this));
340 
341  //
342  // Achievements
343  //
345  [] { dialogs::achievements_dialog::display(); });
346 
347  //
348  // Community
349  //
351  [] { dialogs::game_version::display(4); }); // shows the 5th tab, community
352 
353  //
354  // Quit
355  //
356 #ifdef __ANDROID__
358  [this]() { set_retval(QUIT_GAME); },
359  [this]() { quit_confirmation().quit_to_desktop(); });
360 #else
362 #endif
363  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
364  find_widget<button>("quit", false, true);
365 
366  //
367  // Debug clock
368  //
370  std::bind(&title_screen::show_debug_clock_window, this));
371 
372  auto clock = find_widget<button>("clock", false, false);
373  if(clock) {
374  clock->set_visible(show_debug_clock_button);
375  }
376 
377  //
378  // GUI Test and Debug Window
379  //
380  register_button("test_dialog", hotkey::HOTKEY_NULL,
381  [] { dialogs::gui_test_dialog::display(); });
382 
383  auto test_dialog = find_widget<button>("test_dialog", false, false);
384  if(test_dialog) {
385  test_dialog->set_visible(show_debug_clock_button);
386  }
387 
388  //
389  // Static labels (version and language)
390  //
392 }
393 
395 {
396  //
397  // Version menu label
398  //
399  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
400 
401  if(label* version_label = find_widget<label>("revision_number", false, false)) {
402  version_label->set_label(version_string);
403  }
404 
405  get_canvas(1).set_variable("revision_number", wfl::variant(version_string));
406 
407  //
408  // Language menu label
409  //
410  if(auto* lang_button = find_widget<button>("language", false, false); lang_button) {
411  const auto& locale = translation::get_effective_locale_info();
412  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
413  // and strip the charset from the Boost locale identifier.
414  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
415  const auto& langs = get_languages(true);
416 
417  auto lang_def = utils::ranges::find(langs, boost_name, &language_def::localename);
418  if(lang_def != langs.end()) {
419  lang_button->set_label(lang_def->language.str());
420  } else if(boost_name == "c" || boost_name == "C") {
421  // HACK: sometimes System Default doesn't match anything on the list. If you fork
422  // Wesnoth and change the neutral language to something other than US English, you
423  // want to change this too.
424  lang_button->set_label("English (US)");
425  } else {
426  // If somehow the locale doesn't match a known translation, use the
427  // locale identifier as a last resort
428  lang_button->set_label(boost_name);
429  }
430  }
431 }
432 
434 {
436 }
437 
438 void title_screen::update_tip(const bool previous)
439 {
440  multi_page* tip_pages = find_widget<multi_page>("tips", false, false);
441  if(tip_pages == nullptr) {
442  return;
443  }
444  if(tip_pages->get_page_count() == 0) {
445  return;
446  }
447 
448  int page = tip_pages->get_selected_page();
449  if(previous) {
450  if(page <= 0) {
451  page = tip_pages->get_page_count();
452  }
453  --page;
454  } else {
455  ++page;
456  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
457  page = 0;
458  }
459  }
460 
461  tip_pages->select_page(page);
462 }
463 
465 {
466  assert(show_debug_clock_button);
467 
468  if(debug_clock_) {
469  debug_clock_.reset(nullptr);
470  } else {
471  debug_clock_.reset(new debug_clock());
472  debug_clock_->show(true);
473  }
474 }
475 
477 {
479 
480  std::vector<std::string> options;
481  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
482  if(!sc["is_unit_test"].to_bool(false)) {
483  options.emplace_back(sc["id"]);
484  }
485  }
486 
487  std::sort(options.begin(), options.end());
488 
489  gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
490  dlg.show();
491 
492  int choice = dlg.selected_index();
493  if(choice >= 0) {
494  game_.set_test(options[choice]);
496  }
497 }
498 
500 {
502  pref_dlg.show();
503  if (pref_dlg.get_retval() == RELOAD_UI) {
505  }
506 
507  // Currently blurred windows don't capture well if there is something
508  // on top of them at the time of blur. Resizing the game window in
509  // preferences will cause the title screen tip and menu panels to
510  // capture the prefs dialog in their blur. This workaround simply
511  // forces them to re-capture the blur after the dialog closes.
512  panel* tip_panel = find_widget<panel>("tip_panel", false, false);
513  if(tip_panel != nullptr) {
514  tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
515  tip_panel->queue_redraw();
516  }
517  panel* menu_panel = find_widget<panel>("menu_panel", false, false);
518  if(menu_panel != nullptr) {
519  menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
520  menu_panel->queue_redraw();
521  }
522 }
523 
525 {
526  while(true) {
528  dlg.show();
529 
530  if(dlg.get_retval() != gui2::retval::OK) {
531  return;
532  }
533 
534  const auto res = dlg.get_choice();
535 
536  if(res == decltype(dlg)::choice::HOST && prefs::get().mp_server_warning_disabled() < 2) {
537  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
538  continue;
539  }
540  }
541 
542  switch(res) {
543  case decltype(dlg)::choice::JOIN:
544  game_.select_mp_server(prefs::get().builtin_servers_list().front().address);
546  break;
547  case decltype(dlg)::choice::CONNECT:
550  break;
551  case decltype(dlg)::choice::HOST:
552  game_.select_mp_server("localhost");
554  break;
555  case decltype(dlg)::choice::LOCAL:
557  break;
558  }
559 
560  return;
561  }
562 }
563 
565 {
566  int current = 0;
567 
568  std::vector<config> cores;
569  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
570  cores.push_back(core);
571 
572  if(core["id"] == prefs::get().core()) {
573  current = cores.size() - 1;
574  }
575  }
576 
577  gui2::dialogs::core_selection core_dlg(cores, current);
578  if(core_dlg.show()) {
579  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
580 
581  prefs::get().set_core(core_id);
583  }
584 }
585 
586 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
static game_config_manager * get()
void load_game_config_for_create(bool is_mp, bool is_test=false)
bool change_language()
bool load_game_prompt()
void select_mp_server(const std::string &server)
void set_test(const std::string &id)
Simple push button.
Definition: button.hpp:36
void set_variable(const std::string &key, wfl::variant &&value)
Definition: canvas.hpp:157
void queue_reblur()
Clear the cached blur texture, forcing it to regenerate.
Definition: canvas.cpp:643
static void display(lua_kernel_base *lk)
Display a new console, using given video and lua kernel.
Abstract base class for all modal dialogs.
bool show(const unsigned auto_close_time=0)
Shows the window.
int selected_index() const
Returns the selected item index after displaying.
This class implements the title screen.
std::unique_ptr< modeless_dialog > debug_clock_
Holds the debug clock dialog.
void update_tip(const bool previous)
Updates the tip of day widget.
void update_static_labels()
Updates UI labels that are not t_string after a language change.
void show_debug_clock_window()
Shows the debug clock.
void show_preferences()
Shows the preferences dialog.
void register_button(const std::string &id, hotkey::HOTKEY_COMMAND hk, const std::function< void()> &callback_btn, const std::function< void()> &callback_hotkey)
void register_hotkey(const hotkey::HOTKEY_COMMAND id, Func &&function)
Registers a hotkey.
Definition: dispatcher.hpp:444
unsigned get_page_count() const
Returns the number of pages.
Definition: multi_page.cpp:113
int get_selected_page() const
Returns the selected page.
Definition: multi_page.cpp:128
void select_page(const unsigned page, const bool select=true)
Selects a page.
Definition: multi_page.cpp:119
virtual unsigned get_state() const override
See styled_widget::get_state.
Definition: panel.cpp:61
canvas & get_canvas(const unsigned index)
void set_visible(const visibility visible)
Definition: widget.cpp:479
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:464
visibility get_visible() const
Definition: widget.cpp:506
@ visible
The user sets the widget visible, that means:
@ hidden
The user sets the widget hidden, that means:
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:313
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:387
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:326
void set_click_dismiss(const bool click_dismiss)
Definition: window.hpp:399
int get_retval()
Definition: window.hpp:394
static prefs & get()
Implements a quit confirmation dialog.
static void quit_to_desktop()
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1032
static std::string _(const char *str)
Definition: gettext.hpp:97
#define ERR_GUI_P
Definition: log.hpp:69
This file contains the window object, this object is a top level container which has the event manage...
std::vector< language_def > get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:130
Standard logging facilities (interface).
bool manage_addons()
Shows the add-ons server connection dialog, for access to the various management front-ends.
Definition: manager_ui.cpp:228
std::string get_screenshot_dir()
std::string get_next_filename(const std::string &name, const std::string &extension)
Get the next free filename using "name + number (3 digits) + extension" maximum 1000 files then start...
Definition: filesystem.cpp:601
std::string game_title_background
std::string game_title
std::string game_logo
std::string game_logo_background
Game configuration data as global variables.
Definition: build_info.cpp:61
const std::string revision
void show(const gui2::tracked_drawable &target)
Displays the fps report popup for the given tracked_drawable.
Definition: fps_report.cpp:131
void remove()
Removes a tip.
Definition: tooltip.cpp:94
static std::unique_ptr< tooltip > tip
Definition: tooltip.cpp:62
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
REGISTER_DIALOG(editor_edit_unit)
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:163
std::vector< game_tip > tips
Definition: settings.cpp:57
std::vector< game_tip > shuffle(const std::vector< game_tip > &tips)
Shuffles the tips.
Definition: tips.cpp:43
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:201
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
void show_help(const std::string &show_topic)
Open the help browser, show topic with id show_topic.
Definition: help.cpp:139
@ HOTKEY_SCREENSHOT
@ HOTKEY_ACHIEVEMENTS
@ TITLE_SCREEN__MULTIPLAYER
@ TITLE_SCREEN__ADDONS
@ TITLE_SCREEN__EDITOR
@ HOTKEY_QUIT_TO_DESKTOP
@ HOTKEY_LOAD_GAME
@ TITLE_SCREEN__TEST
@ TITLE_SCREEN__PREVIOUS_TIP
@ HOTKEY_PREFERENCES
@ TITLE_SCREEN__CORES
@ TITLE_SCREEN__CAMPAIGN
@ TITLE_SCREEN__RELOAD_WML
@ TITLE_SCREEN__NEXT_TIP
int show_lua_console(lua_State *, lua_kernel_base *lk)
Definition: lua_gui2.cpp:250
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.
Definition: gettext.cpp:572
auto find(Container &container, const Value &value, const Projection &projection={})
Definition: general.hpp:179
surface read_pixels(rect *r)
Copy back a portion of the render target that is already drawn.
Definition: video.cpp:617
This file contains the settings handling of the widget library.
std::string filename
Filename.
The help implementation caches data parsed from the game_config.
Definition: help.hpp:39
std::string localename
Definition: language.hpp:32
Holds a 2D point.
Definition: point.hpp:25
Helper class, don't construct this directly.
#define ERR_CF
static lg::log_domain log_config("config")
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e