The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
24#include "game_config.hpp"
26#include "game_launcher.hpp"
41#include "language.hpp"
42#include "log.hpp"
44//#define DEBUG_TOOLTIP
45#ifdef DEBUG_TOOLTIP
47#endif
49#include "gui/widgets/image.hpp"
50#include "gui/widgets/label.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
66static 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
70namespace gui2::dialogs
71{
72
73REGISTER_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
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
120namespace
121{
122void show_lua_console()
123{
125}
126
127void 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 */
152static 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
160 gui2::tip::remove();
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);
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 //
264
265 //
266 // About
267 //
268 register_button("about", hotkey::HOTKEY_NULL, [] { game_version::display(); });
269
270 //
271 // Campaign
272 //
273 register_button("campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
274 try{
275 if(game_.new_campaign()) {
276 // Suspend drawing of the title screen,
277 // so it doesn't flicker in between loading screens.
278 hide();
279 set_retval(LAUNCH_GAME);
280 }
281 } catch (const config::error& e) {
283 }
284 });
285
286 //
287 // Multiplayer
288 //
291
292 //
293 // Load game
294 //
295 register_button("load", hotkey::HOTKEY_LOAD_GAME, [this]() {
296 if(game_.load_game_prompt()) {
297 // Suspend drawing of the title screen,
298 // so it doesn't flicker in between loading screens.
299 hide();
300 set_retval(LOAD_GAME);
301 }
302 });
303
304 //
305 // Addons
306 //
308 if(manage_addons()) {
310 }
311 });
312
313 //
314 // Editor
315 //
317
318 //
319 // Language
320 //
321 register_button("language", hotkey::HOTKEY_LANGUAGE, [this]() {
322 try {
323 if(game_.change_language()) {
324 on_resize();
325 update_static_labels();
326 }
327 } catch(const std::runtime_error& e) {
329 }
330 });
331
332 //
333 // Preferences
334 //
336 std::bind(&title_screen::show_preferences, this));
337
338 //
339 // Achievements
340 //
342 [] { dialogs::achievements_dialog::display(); });
343
344 //
345 // Community
346 //
348 [] { dialogs::game_version::display(4); }); // shows the 5th tab, community
349
350 //
351 // Quit
352 //
353#ifdef __ANDROID__
355 [this]() { set_retval(QUIT_GAME); },
356 [this]() { quit_confirmation().quit_to_desktop(); });
357#else
359#endif
360 // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
361 find_widget<button>("quit", false, true);
362
363 //
364 // Debug clock
365 //
367 std::bind(&title_screen::show_debug_clock_window, this));
368
369 auto clock = find_widget<button>("clock", false, false);
370 if(clock) {
371 clock->set_visible(show_debug_clock_button);
372 }
373
374 //
375 // GUI Test and Debug Window
376 //
378 [] { dialogs::gui_test_dialog::display(); });
379
380 auto test_dialog = find_widget<button>("test_dialog", false, false);
381 if(test_dialog) {
382 test_dialog->set_visible(show_debug_clock_button);
383 }
384
385 //
386 // Static labels (version and language)
387 //
389}
390
392{
393 //
394 // Version menu label
395 //
396 const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
397
398 if(label* version_label = find_widget<label>("revision_number", false, false)) {
399 version_label->set_label(version_string);
400 }
401
402 get_canvas(1).set_variable("revision_number", wfl::variant(version_string));
403
404 //
405 // Language menu label
406 //
407 if(auto* lang_button = find_widget<button>("language", false, false); lang_button) {
408 const auto& locale = translation::get_effective_locale_info();
409 // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
410 // and strip the charset from the Boost locale identifier.
411 const auto& locale_id = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
412
413 if(locale_id == "c" || locale_id == "C") {
414 // If you fork Wesnoth and change the neutral language to something other than US English,
415 // you want to change this too.
416 lang_button->set_label("English (US)");
417 } else {
418 std::string lname = get_translation_name(locale_id);
419 if(!lname.empty()) {
420 lang_button->set_label(lname);
421 } else {
422 // If somehow the locale doesn't match a known locale,
423 // use the locale identifier as a last resort
424 lang_button->set_label(locale_id);
425 }
426 }
427 }
428}
429
434
435void title_screen::update_tip(const bool previous)
436{
437 multi_page* tip_pages = find_widget<multi_page>("tips", false, false);
438 if(tip_pages == nullptr) {
439 return;
440 }
441 if(tip_pages->get_page_count() == 0) {
442 return;
443 }
444
445 int page = tip_pages->get_selected_page();
446 if(previous) {
447 if(page <= 0) {
448 page = tip_pages->get_page_count();
449 }
450 --page;
451 } else {
452 ++page;
453 if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
454 page = 0;
455 }
456 }
457
458 tip_pages->select_page(page);
459}
460
462{
464
465 if(debug_clock_) {
466 debug_clock_.reset(nullptr);
467 } else {
468 debug_clock_.reset(new debug_clock());
469 debug_clock_->show(true);
470 }
471}
472
474{
476
477 std::vector<std::string> options;
478 for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
479 if(!sc["is_unit_test"].to_bool(false)) {
480 options.emplace_back(sc["id"]);
481 }
482 }
483
484 std::sort(options.begin(), options.end());
485
486 gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
487 dlg.show();
488
489 int choice = dlg.selected_index();
490 if(choice >= 0) {
491 game_.set_test(options[choice]);
493 }
494}
495
497{
499 pref_dlg.show();
500 if (pref_dlg.get_retval() == RELOAD_UI) {
502 }
503
504 // Currently blurred windows don't capture well if there is something
505 // on top of them at the time of blur. Resizing the game window in
506 // preferences will cause the title screen tip and menu panels to
507 // capture the prefs dialog in their blur. This workaround simply
508 // forces them to re-capture the blur after the dialog closes.
509 panel* tip_panel = find_widget<panel>("tip_panel", false, false);
510 if(tip_panel != nullptr) {
511 tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
512 tip_panel->queue_redraw();
513 }
514 panel* menu_panel = find_widget<panel>("menu_panel", false, false);
515 if(menu_panel != nullptr) {
516 menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
517 menu_panel->queue_redraw();
518 }
519}
520
522{
523 while(true) {
525 dlg.show();
526
527 if(dlg.get_retval() != gui2::retval::OK) {
528 return;
529 }
530
531 const auto res = dlg.get_choice();
532
533 if(res == decltype(dlg)::choice::HOST && prefs::get().mp_server_warning_disabled() < 2) {
534 if(!gui2::dialogs::mp_host_game_prompt::execute()) {
535 continue;
536 }
537 }
538
539 switch(res) {
540 case decltype(dlg)::choice::JOIN:
541 game_.select_mp_server(prefs::get().builtin_servers_list().front().address);
543 break;
544 case decltype(dlg)::choice::CONNECT:
547 break;
548 case decltype(dlg)::choice::HOST:
549 game_.select_mp_server("localhost");
551 break;
552 case decltype(dlg)::choice::LOCAL:
554 break;
555 }
556
557 return;
558 }
559}
560
562{
563 int current = 0;
564
565 std::vector<config> cores;
566 for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
567 cores.push_back(core);
568
569 if(core["id"] == prefs::get().core()) {
570 current = cores.size() - 1;
571 }
572 }
573
574 gui2::dialogs::core_selection core_dlg(cores, current);
575 if(core_dlg.show()) {
576 const std::string& core_id = cores[core_dlg.get_choice()]["id"];
577
578 prefs::get().set_core(core_id);
580 }
581}
582
583} // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
static game_config_manager * get()
void load_game_config_for_create(bool is_mp, bool is_test=false)
const game_config_view & game_config() const
config_array_view child_range(std::string_view key) const
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:165
void queue_reblur()
Clear the cached blur texture, forcing it to regenerate.
Definition canvas.cpp:781
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.
unsigned get_page_count() const
Returns the number of pages.
int get_selected_page() const
Returns the selected page.
void select_page(const unsigned page, const bool select=true)
Selects a page.
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()
#define ERR_CF
Definition config.cpp:37
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition gettext.hpp:100
#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::string get_translation_name(const std::string &locale_id)
Definition language.cpp:178
Standard logging facilities (interface).
bool manage_addons()
Shows the add-ons server connection dialog, for access to the various management front-ends.
#define REGISTER_DIALOG(window_id)
Wrapper for REGISTER_DIALOG2.
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...
std::string game_title_background
std::string game_logo_background
const std::string revision
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
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.
Definition help.cpp:84
@ HOTKEY_ACHIEVEMENTS
@ TITLE_SCREEN__MULTIPLAYER
@ HOTKEY_QUIT_TO_DESKTOP
@ TITLE_SCREEN__PREVIOUS_TIP
@ TITLE_SCREEN__CAMPAIGN
@ TITLE_SCREEN__RELOAD_WML
@ TITLE_SCREEN__NEXT_TIP
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.
Definition gettext.cpp:572
surface read_pixels(rect *r)
Copy back a portion of the render target that is already drawn.
Definition video.cpp:625
This file contains the settings handling of the widget library.
std::string filename
Filename.
Holds a 2D point.
Definition point.hpp:25
Helper class, don't construct this directly.
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 i
#define e