Fix #10490: Allow ships to exit depots if another is not moving at the exit point...
[openttd-github.git] / src / settings_gui.cpp
blob4c76cf9c0432ddac53f5840d990d608bc6909215
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file settings_gui.cpp GUI for settings. */
10 #include "stdafx.h"
11 #include "currency.h"
12 #include "error.h"
13 #include "settings_gui.h"
14 #include "textbuf_gui.h"
15 #include "command_func.h"
16 #include "network/network.h"
17 #include "town.h"
18 #include "settings_internal.h"
19 #include "strings_func.h"
20 #include "window_func.h"
21 #include "string_func.h"
22 #include "widgets/dropdown_type.h"
23 #include "widgets/dropdown_func.h"
24 #include "widgets/slider_func.h"
25 #include "highscore.h"
26 #include "base_media_base.h"
27 #include "company_base.h"
28 #include "company_func.h"
29 #include "viewport_func.h"
30 #include "core/geometry_func.hpp"
31 #include "ai/ai.hpp"
32 #include "blitter/factory.hpp"
33 #include "language.h"
34 #include "textfile_gui.h"
35 #include "stringfilter_type.h"
36 #include "querystring_gui.h"
37 #include "fontcache.h"
38 #include "zoom_func.h"
39 #include "rev.h"
40 #include "video/video_driver.hpp"
41 #include "music/music_driver.hpp"
42 #include "gui.h"
43 #include "mixer.h"
44 #include "newgrf_config.h"
45 #include "network/core/config.h"
46 #include "network/network_gui.h"
47 #include "network/network_survey.h"
48 #include "video/video_driver.hpp"
49 #include "social_integration.h"
51 #include "safeguards.h"
54 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
55 # define HAS_TRUETYPE_FONT
56 #endif
58 static const StringID _autosave_dropdown[] = {
59 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF,
60 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_10_MINUTES,
61 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_30_MINUTES,
62 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_60_MINUTES,
63 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_120_MINUTES,
64 INVALID_STRING_ID,
67 /** Available settings for autosave intervals. */
68 static const uint32_t _autosave_dropdown_to_minutes[] = {
69 0, ///< never
70 10,
71 30,
72 60,
73 120,
76 static Dimension _circle_size; ///< Dimension of the circle +/- icon. This is here as not all users are within the class of the settings window.
78 static const void *ResolveObject(const GameSettings *settings_ptr, const IntSettingDesc *sd);
80 /**
81 * Get index of the current screen resolution.
82 * @return Index of the current screen resolution if it is a known resolution, _resolutions.size() otherwise.
84 static uint GetCurrentResolutionIndex()
86 auto it = std::find(_resolutions.begin(), _resolutions.end(), Dimension(_screen.width, _screen.height));
87 return std::distance(_resolutions.begin(), it);
90 static void ShowCustCurrency();
92 /** Window for displaying the textfile of a BaseSet. */
93 template <class TBaseSet>
94 struct BaseSetTextfileWindow : public TextfileWindow {
95 const TBaseSet *baseset; ///< View the textfile of this BaseSet.
96 StringID content_type; ///< STR_CONTENT_TYPE_xxx for title.
98 BaseSetTextfileWindow(TextfileType file_type, const TBaseSet *baseset, StringID content_type) : TextfileWindow(file_type), baseset(baseset), content_type(content_type)
100 this->ConstructWindow();
102 auto textfile = this->baseset->GetTextfile(file_type);
103 this->LoadTextfile(textfile.value(), BASESET_DIR);
106 void SetStringParameters(WidgetID widget) const override
108 if (widget == WID_TF_CAPTION) {
109 SetDParam(0, content_type);
110 SetDParamStr(1, this->baseset->name);
116 * Open the BaseSet version of the textfile window.
117 * @param file_type The type of textfile to display.
118 * @param baseset The BaseSet to use.
119 * @param content_type STR_CONTENT_TYPE_xxx for title.
121 template <class TBaseSet>
122 void ShowBaseSetTextfileWindow(TextfileType file_type, const TBaseSet *baseset, StringID content_type)
124 CloseWindowById(WC_TEXTFILE, file_type);
125 new BaseSetTextfileWindow<TBaseSet>(file_type, baseset, content_type);
128 std::set<int> _refresh_rates = { 30, 60, 75, 90, 100, 120, 144, 240 };
131 * Add the refresh rate from the config and the refresh rates from all the monitors to
132 * our list of refresh rates shown in the GUI.
134 static void AddCustomRefreshRates()
136 /* Add the refresh rate as selected in the config. */
137 _refresh_rates.insert(_settings_client.gui.refresh_rate);
139 /* Add all the refresh rates of all monitors connected to the machine. */
140 std::vector<int> monitorRates = VideoDriver::GetInstance()->GetListOfMonitorRefreshRates();
141 std::copy(monitorRates.begin(), monitorRates.end(), std::inserter(_refresh_rates, _refresh_rates.end()));
144 static const std::map<int, StringID> _scale_labels = {
145 { 100, STR_GAME_OPTIONS_GUI_SCALE_1X },
146 { 125, STR_NULL },
147 { 150, STR_NULL },
148 { 175, STR_NULL },
149 { 200, STR_GAME_OPTIONS_GUI_SCALE_2X },
150 { 225, STR_NULL },
151 { 250, STR_NULL },
152 { 275, STR_NULL },
153 { 300, STR_GAME_OPTIONS_GUI_SCALE_3X },
154 { 325, STR_NULL },
155 { 350, STR_NULL },
156 { 375, STR_NULL },
157 { 400, STR_GAME_OPTIONS_GUI_SCALE_4X },
158 { 425, STR_NULL },
159 { 450, STR_NULL },
160 { 475, STR_NULL },
161 { 500, STR_GAME_OPTIONS_GUI_SCALE_5X },
164 static const std::map<int, StringID> _volume_labels = {
165 { 0, STR_GAME_OPTIONS_VOLUME_0 },
166 { 15, STR_NULL },
167 { 31, STR_GAME_OPTIONS_VOLUME_25 },
168 { 47, STR_NULL },
169 { 63, STR_GAME_OPTIONS_VOLUME_50 },
170 { 79, STR_NULL },
171 { 95, STR_GAME_OPTIONS_VOLUME_75 },
172 { 111, STR_NULL },
173 { 127, STR_GAME_OPTIONS_VOLUME_100 },
176 static const NWidgetPart _nested_social_plugins_widgets[] = {
177 NWidget(NWID_HORIZONTAL),
178 NWidget(WWT_FRAME, COLOUR_GREY, WID_GO_SOCIAL_PLUGIN_TITLE), SetDataTip(STR_JUST_STRING2, STR_NULL),
179 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
180 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_SOCIAL_PLUGIN_PLATFORM, STR_NULL),
181 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_SOCIAL_PLUGIN_PLATFORM), SetMinimalSize(100, 12), SetDataTip(STR_JUST_RAW_STRING, STR_NULL), SetAlignment(SA_RIGHT),
182 EndContainer(),
183 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
184 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE, STR_NULL),
185 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_SOCIAL_PLUGIN_STATE), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING1, STR_NULL), SetAlignment(SA_RIGHT),
186 EndContainer(),
187 EndContainer(),
188 EndContainer(),
191 static const NWidgetPart _nested_social_plugins_none_widgets[] = {
192 NWidget(NWID_HORIZONTAL),
193 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_SOCIAL_PLUGINS_NONE, STR_NULL),
194 EndContainer(),
197 class NWidgetSocialPlugins : public NWidgetVertical {
198 public:
199 NWidgetSocialPlugins()
201 this->plugins = SocialIntegration::GetPlugins();
203 if (this->plugins.empty()) {
204 auto widget = MakeNWidgets(std::begin(_nested_social_plugins_none_widgets), std::end(_nested_social_plugins_none_widgets), nullptr);
205 this->Add(std::move(widget));
206 } else {
207 for (size_t i = 0; i < this->plugins.size(); i++) {
208 auto widget = MakeNWidgets(std::begin(_nested_social_plugins_widgets), std::end(_nested_social_plugins_widgets), nullptr);
209 this->Add(std::move(widget));
213 this->SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0);
216 void FillWidgetLookup(WidgetLookup &widget_lookup) override
218 widget_lookup[WID_GO_SOCIAL_PLUGINS] = this;
219 NWidgetVertical::FillWidgetLookup(widget_lookup);
222 void SetupSmallestSize(Window *w) override
224 this->current_index = -1;
225 NWidgetVertical::SetupSmallestSize(w);
229 * Find of all the plugins the one where the member is the widest (in pixels).
231 * @param member The member to check with.
232 * @return The plugin that has the widest value (in pixels) for the given member.
234 template <typename T>
235 std::string &GetWidestPlugin(T SocialIntegrationPlugin::*member) const
237 std::string *longest = &(this->plugins[0]->*member);
238 int longest_length = 0;
240 for (auto *plugin : this->plugins) {
241 int length = GetStringBoundingBox(plugin->*member).width;
242 if (length > longest_length) {
243 longest_length = length;
244 longest = &(plugin->*member);
248 return *longest;
251 void SetStringParameters(int widget) const
253 switch (widget) {
254 case WID_GO_SOCIAL_PLUGIN_TITLE:
255 /* For SetupSmallestSize, use the longest string we have. */
256 if (this->current_index < 0) {
257 SetDParamStr(0, GetWidestPlugin(&SocialIntegrationPlugin::name));
258 SetDParamStr(1, GetWidestPlugin(&SocialIntegrationPlugin::version));
259 break;
262 if (this->plugins[this->current_index]->name.empty()) {
263 SetDParam(0, STR_JUST_RAW_STRING);
264 SetDParamStr(1, this->plugins[this->current_index]->basepath);
265 } else {
266 SetDParam(0, STR_GAME_OPTIONS_SOCIAL_PLUGIN_TITLE);
267 SetDParamStr(1, this->plugins[this->current_index]->name);
268 SetDParamStr(2, this->plugins[this->current_index]->version);
270 break;
272 case WID_GO_SOCIAL_PLUGIN_PLATFORM:
273 /* For SetupSmallestSize, use the longest string we have. */
274 if (this->current_index < 0) {
275 SetDParamStr(0, GetWidestPlugin(&SocialIntegrationPlugin::social_platform));
276 break;
279 SetDParamStr(0, this->plugins[this->current_index]->social_platform);
280 break;
282 case WID_GO_SOCIAL_PLUGIN_STATE: {
283 static const std::pair<SocialIntegrationPlugin::State, StringID> state_to_string[] = {
284 { SocialIntegrationPlugin::RUNNING, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_RUNNING },
285 { SocialIntegrationPlugin::FAILED, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_FAILED },
286 { SocialIntegrationPlugin::PLATFORM_NOT_RUNNING, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_PLATFORM_NOT_RUNNING },
287 { SocialIntegrationPlugin::UNLOADED, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_UNLOADED },
288 { SocialIntegrationPlugin::DUPLICATE, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_DUPLICATE },
289 { SocialIntegrationPlugin::UNSUPPORTED_API, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_UNSUPPORTED_API },
290 { SocialIntegrationPlugin::INVALID_SIGNATURE, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_INVALID_SIGNATURE },
293 /* For SetupSmallestSize, use the longest string we have. */
294 if (this->current_index < 0) {
295 auto longest_plugin = GetWidestPlugin(&SocialIntegrationPlugin::social_platform);
297 /* Set the longest plugin when looking for the longest status. */
298 SetDParamStr(0, longest_plugin);
300 StringID longest = STR_NULL;
301 int longest_length = 0;
302 for (auto state : state_to_string) {
303 int length = GetStringBoundingBox(state.second).width;
304 if (length > longest_length) {
305 longest_length = length;
306 longest = state.second;
310 SetDParam(0, longest);
311 SetDParamStr(1, longest_plugin);
312 break;
315 auto plugin = this->plugins[this->current_index];
317 /* Default string, in case no state matches. */
318 SetDParam(0, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_FAILED);
319 SetDParamStr(1, plugin->social_platform);
321 /* Find the string for the state. */
322 for (auto state : state_to_string) {
323 if (plugin->state == state.first) {
324 SetDParam(0, state.second);
325 break;
329 break;
333 void Draw(const Window *w) override
335 this->current_index = 0;
337 for (auto &wid : this->children) {
338 wid->Draw(w);
339 this->current_index++;
343 private:
344 int current_index = -1;
345 std::vector<SocialIntegrationPlugin *> plugins;
348 /** Construct nested container widget for managing the list of social plugins. */
349 std::unique_ptr<NWidgetBase> MakeNWidgetSocialPlugins()
351 return std::make_unique<NWidgetSocialPlugins>();
354 struct GameOptionsWindow : Window {
355 GameSettings *opt;
356 bool reload;
357 int gui_scale;
358 static inline WidgetID active_tab = WID_GO_TAB_GENERAL;
360 GameOptionsWindow(WindowDesc *desc) : Window(desc)
362 this->opt = &GetGameSettings();
363 this->reload = false;
364 this->gui_scale = _gui_scale;
366 AddCustomRefreshRates();
368 this->InitNested(WN_GAME_OPTIONS_GAME_OPTIONS);
369 this->OnInvalidateData(0);
371 this->SetTab(GameOptionsWindow::active_tab);
373 if constexpr (!NetworkSurveyHandler::IsSurveyPossible()) this->GetWidget<NWidgetStacked>(WID_GO_SURVEY_SEL)->SetDisplayedPlane(SZSP_NONE);
376 void Close([[maybe_unused]] int data = 0) override
378 CloseWindowById(WC_CUSTOM_CURRENCY, 0);
379 CloseWindowByClass(WC_TEXTFILE);
380 if (this->reload) _switch_mode = SM_MENU;
381 this->Window::Close();
385 * Build the dropdown list for a specific widget.
386 * @param widget Widget to build list for
387 * @param selected_index Currently selected item
388 * @return the built dropdown list, or nullptr if the widget has no dropdown menu.
390 DropDownList BuildDropDownList(WidgetID widget, int *selected_index) const
392 DropDownList list;
393 switch (widget) {
394 case WID_GO_CURRENCY_DROPDOWN: { // Setup currencies dropdown
395 *selected_index = this->opt->locale.currency;
396 uint64_t disabled = _game_mode == GM_MENU ? 0LL : ~GetMaskOfAllowedCurrencies();
398 /* Add non-custom currencies; sorted naturally */
399 for (const CurrencySpec &currency : _currency_specs) {
400 int i = &currency - _currency_specs;
401 if (i == CURRENCY_CUSTOM) continue;
402 if (currency.code.empty()) {
403 list.push_back(std::make_unique<DropDownListStringItem>(currency.name, i, HasBit(disabled, i)));
404 } else {
405 SetDParam(0, currency.name);
406 SetDParamStr(1, currency.code);
407 list.push_back(std::make_unique<DropDownListStringItem>(STR_GAME_OPTIONS_CURRENCY_CODE, i, HasBit(disabled, i)));
410 std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
412 /* Append custom currency at the end */
413 list.push_back(std::make_unique<DropDownListDividerItem>(-1, false)); // separator line
414 list.push_back(std::make_unique<DropDownListStringItem>(STR_GAME_OPTIONS_CURRENCY_CUSTOM, CURRENCY_CUSTOM, HasBit(disabled, CURRENCY_CUSTOM)));
415 break;
418 case WID_GO_AUTOSAVE_DROPDOWN: { // Setup autosave dropdown
419 int index = 0;
420 for (auto &minutes : _autosave_dropdown_to_minutes) {
421 index++;
422 if (_settings_client.gui.autosave_interval <= minutes) break;
424 *selected_index = index - 1;
426 const StringID *items = _autosave_dropdown;
427 for (uint i = 0; *items != INVALID_STRING_ID; items++, i++) {
428 list.push_back(std::make_unique<DropDownListStringItem>(*items, i, false));
430 break;
433 case WID_GO_LANG_DROPDOWN: { // Setup interface language dropdown
434 for (uint i = 0; i < _languages.size(); i++) {
435 bool hide_language = IsReleasedVersion() && !_languages[i].IsReasonablyFinished();
436 if (hide_language) continue;
437 bool hide_percentage = IsReleasedVersion() || _languages[i].missing < _settings_client.gui.missing_strings_threshold;
438 if (&_languages[i] == _current_language) {
439 *selected_index = i;
440 SetDParamStr(0, _languages[i].own_name);
441 } else {
442 /* Especially with sprite-fonts, not all localized
443 * names can be rendered. So instead, we use the
444 * international names for anything but the current
445 * selected language. This avoids showing a few ????
446 * entries in the dropdown list. */
447 SetDParamStr(0, _languages[i].name);
449 SetDParam(1, (LANGUAGE_TOTAL_STRINGS - _languages[i].missing) * 100 / LANGUAGE_TOTAL_STRINGS);
450 list.push_back(std::make_unique<DropDownListStringItem>(hide_percentage ? STR_JUST_RAW_STRING : STR_GAME_OPTIONS_LANGUAGE_PERCENTAGE, i, false));
452 std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc);
453 break;
456 case WID_GO_RESOLUTION_DROPDOWN: // Setup resolution dropdown
457 if (_resolutions.empty()) break;
459 *selected_index = GetCurrentResolutionIndex();
460 for (uint i = 0; i < _resolutions.size(); i++) {
461 SetDParam(0, _resolutions[i].width);
462 SetDParam(1, _resolutions[i].height);
463 list.push_back(std::make_unique<DropDownListStringItem>(STR_GAME_OPTIONS_RESOLUTION_ITEM, i, false));
465 break;
467 case WID_GO_REFRESH_RATE_DROPDOWN: // Setup refresh rate dropdown
468 for (auto it = _refresh_rates.begin(); it != _refresh_rates.end(); it++) {
469 auto i = std::distance(_refresh_rates.begin(), it);
470 if (*it == _settings_client.gui.refresh_rate) *selected_index = i;
471 SetDParam(0, *it);
472 list.push_back(std::make_unique<DropDownListStringItem>(STR_GAME_OPTIONS_REFRESH_RATE_ITEM, i, false));
474 break;
476 case WID_GO_BASE_GRF_DROPDOWN:
477 list = BuildSetDropDownList<BaseGraphics>(selected_index);
478 break;
480 case WID_GO_BASE_SFX_DROPDOWN:
481 list = BuildSetDropDownList<BaseSounds>(selected_index);
482 break;
484 case WID_GO_BASE_MUSIC_DROPDOWN:
485 list = BuildSetDropDownList<BaseMusic>(selected_index);
486 break;
489 return list;
492 void SetStringParameters(WidgetID widget) const override
494 switch (widget) {
495 case WID_GO_CURRENCY_DROPDOWN: {
496 const CurrencySpec &currency = _currency_specs[this->opt->locale.currency];
497 if (currency.code.empty()) {
498 SetDParam(0, currency.name);
499 } else {
500 SetDParam(0, STR_GAME_OPTIONS_CURRENCY_CODE);
501 SetDParam(1, currency.name);
502 SetDParamStr(2, currency.code);
504 break;
506 case WID_GO_AUTOSAVE_DROPDOWN: {
507 int index = 0;
508 for (auto &minutes : _autosave_dropdown_to_minutes) {
509 index++;
510 if (_settings_client.gui.autosave_interval <= minutes) break;
512 SetDParam(0, _autosave_dropdown[index - 1]);
513 break;
515 case WID_GO_LANG_DROPDOWN: SetDParamStr(0, _current_language->own_name); break;
516 case WID_GO_BASE_GRF_DROPDOWN: SetDParamStr(0, BaseGraphics::GetUsedSet()->GetListLabel()); break;
517 case WID_GO_BASE_SFX_DROPDOWN: SetDParamStr(0, BaseSounds::GetUsedSet()->GetListLabel()); break;
518 case WID_GO_BASE_MUSIC_DROPDOWN: SetDParamStr(0, BaseMusic::GetUsedSet()->GetListLabel()); break;
519 case WID_GO_REFRESH_RATE_DROPDOWN: SetDParam(0, _settings_client.gui.refresh_rate); break;
520 case WID_GO_RESOLUTION_DROPDOWN: {
521 auto current_resolution = GetCurrentResolutionIndex();
523 if (current_resolution == _resolutions.size()) {
524 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_OTHER);
525 } else {
526 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_ITEM);
527 SetDParam(1, _resolutions[current_resolution].width);
528 SetDParam(2, _resolutions[current_resolution].height);
530 break;
533 case WID_GO_SOCIAL_PLUGIN_TITLE:
534 case WID_GO_SOCIAL_PLUGIN_PLATFORM:
535 case WID_GO_SOCIAL_PLUGIN_STATE: {
536 const NWidgetSocialPlugins *plugin = this->GetWidget<NWidgetSocialPlugins>(WID_GO_SOCIAL_PLUGINS);
537 assert(plugin != nullptr);
539 plugin->SetStringParameters(widget);
540 break;
545 void DrawWidget(const Rect &r, WidgetID widget) const override
547 switch (widget) {
548 case WID_GO_BASE_GRF_DESCRIPTION:
549 SetDParamStr(0, BaseGraphics::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
550 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK);
551 break;
553 case WID_GO_BASE_SFX_DESCRIPTION:
554 SetDParamStr(0, BaseSounds::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
555 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK);
556 break;
558 case WID_GO_BASE_MUSIC_DESCRIPTION:
559 SetDParamStr(0, BaseMusic::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
560 DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK);
561 break;
563 case WID_GO_GUI_SCALE:
564 DrawSliderWidget(r, MIN_INTERFACE_SCALE, MAX_INTERFACE_SCALE, this->gui_scale, _scale_labels);
565 break;
567 case WID_GO_VIDEO_DRIVER_INFO:
568 SetDParamStr(0, VideoDriver::GetInstance()->GetInfoString());
569 DrawStringMultiLine(r, STR_GAME_OPTIONS_VIDEO_DRIVER_INFO);
570 break;
572 case WID_GO_BASE_SFX_VOLUME:
573 DrawSliderWidget(r, 0, INT8_MAX, _settings_client.music.effect_vol, _volume_labels);
574 break;
576 case WID_GO_BASE_MUSIC_VOLUME:
577 DrawSliderWidget(r, 0, INT8_MAX, _settings_client.music.music_vol, _volume_labels);
578 break;
582 void SetTab(WidgetID widget)
584 this->SetWidgetsLoweredState(false, WID_GO_TAB_GENERAL, WID_GO_TAB_GRAPHICS, WID_GO_TAB_SOUND, WID_GO_TAB_SOCIAL);
585 this->LowerWidget(widget);
586 GameOptionsWindow::active_tab = widget;
588 int pane;
589 switch (widget) {
590 case WID_GO_TAB_GENERAL: pane = 0; break;
591 case WID_GO_TAB_GRAPHICS: pane = 1; break;
592 case WID_GO_TAB_SOUND: pane = 2; break;
593 case WID_GO_TAB_SOCIAL: pane = 3; break;
594 default: NOT_REACHED();
597 this->GetWidget<NWidgetStacked>(WID_GO_TAB_SELECTION)->SetDisplayedPlane(pane);
598 this->SetDirty();
601 void OnResize() override
603 bool changed = false;
605 NWidgetResizeBase *wid = this->GetWidget<NWidgetResizeBase>(WID_GO_BASE_GRF_DESCRIPTION);
606 int y = 0;
607 for (int i = 0; i < BaseGraphics::GetNumSets(); i++) {
608 SetDParamStr(0, BaseGraphics::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
609 y = std::max(y, GetStringHeight(STR_JUST_RAW_STRING, wid->current_x));
611 changed |= wid->UpdateVerticalSize(y);
613 wid = this->GetWidget<NWidgetResizeBase>(WID_GO_BASE_SFX_DESCRIPTION);
614 y = 0;
615 for (int i = 0; i < BaseSounds::GetNumSets(); i++) {
616 SetDParamStr(0, BaseSounds::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
617 y = std::max(y, GetStringHeight(STR_JUST_RAW_STRING, wid->current_x));
619 changed |= wid->UpdateVerticalSize(y);
621 wid = this->GetWidget<NWidgetResizeBase>(WID_GO_BASE_MUSIC_DESCRIPTION);
622 y = 0;
623 for (int i = 0; i < BaseMusic::GetNumSets(); i++) {
624 SetDParamStr(0, BaseMusic::GetSet(i)->GetDescription(GetCurrentLanguageIsoCode()));
625 y = std::max(y, GetStringHeight(STR_JUST_RAW_STRING, wid->current_x));
627 changed |= wid->UpdateVerticalSize(y);
629 wid = this->GetWidget<NWidgetResizeBase>(WID_GO_VIDEO_DRIVER_INFO);
630 SetDParamStr(0, VideoDriver::GetInstance()->GetInfoString());
631 y = GetStringHeight(STR_GAME_OPTIONS_VIDEO_DRIVER_INFO, wid->current_x);
632 changed |= wid->UpdateVerticalSize(y);
634 if (changed) this->ReInit(0, 0, this->flags & WF_CENTERED);
637 void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
639 switch (widget) {
640 case WID_GO_TEXT_SFX_VOLUME:
641 case WID_GO_TEXT_MUSIC_VOLUME: {
642 Dimension d = maxdim(GetStringBoundingBox(STR_GAME_OPTIONS_SFX_VOLUME), GetStringBoundingBox(STR_GAME_OPTIONS_MUSIC_VOLUME));
643 d.width += padding.width;
644 d.height += padding.height;
645 *size = maxdim(*size, d);
646 break;
649 case WID_GO_CURRENCY_DROPDOWN:
650 case WID_GO_AUTOSAVE_DROPDOWN:
651 case WID_GO_LANG_DROPDOWN:
652 case WID_GO_RESOLUTION_DROPDOWN:
653 case WID_GO_REFRESH_RATE_DROPDOWN:
654 case WID_GO_BASE_GRF_DROPDOWN:
655 case WID_GO_BASE_SFX_DROPDOWN:
656 case WID_GO_BASE_MUSIC_DROPDOWN: {
657 int selected;
658 size->width = std::max(size->width, GetDropDownListDimension(this->BuildDropDownList(widget, &selected)).width + padding.width);
659 break;
664 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
666 if (widget >= WID_GO_BASE_GRF_TEXTFILE && widget < WID_GO_BASE_GRF_TEXTFILE + TFT_CONTENT_END) {
667 if (BaseGraphics::GetUsedSet() == nullptr) return;
669 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_GRF_TEXTFILE), BaseGraphics::GetUsedSet(), STR_CONTENT_TYPE_BASE_GRAPHICS);
670 return;
672 if (widget >= WID_GO_BASE_SFX_TEXTFILE && widget < WID_GO_BASE_SFX_TEXTFILE + TFT_CONTENT_END) {
673 if (BaseSounds::GetUsedSet() == nullptr) return;
675 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_SFX_TEXTFILE), BaseSounds::GetUsedSet(), STR_CONTENT_TYPE_BASE_SOUNDS);
676 return;
678 if (widget >= WID_GO_BASE_MUSIC_TEXTFILE && widget < WID_GO_BASE_MUSIC_TEXTFILE + TFT_CONTENT_END) {
679 if (BaseMusic::GetUsedSet() == nullptr) return;
681 ShowBaseSetTextfileWindow((TextfileType)(widget - WID_GO_BASE_MUSIC_TEXTFILE), BaseMusic::GetUsedSet(), STR_CONTENT_TYPE_BASE_MUSIC);
682 return;
684 switch (widget) {
685 case WID_GO_TAB_GENERAL:
686 case WID_GO_TAB_GRAPHICS:
687 case WID_GO_TAB_SOUND:
688 case WID_GO_TAB_SOCIAL:
689 this->SetTab(widget);
690 break;
692 case WID_GO_SURVEY_PARTICIPATE_BUTTON:
693 switch (_settings_client.network.participate_survey) {
694 case PS_ASK:
695 case PS_NO:
696 _settings_client.network.participate_survey = PS_YES;
697 break;
699 case PS_YES:
700 _settings_client.network.participate_survey = PS_NO;
701 break;
704 this->SetWidgetLoweredState(WID_GO_SURVEY_PARTICIPATE_BUTTON, _settings_client.network.participate_survey == PS_YES);
705 this->SetWidgetDirty(WID_GO_SURVEY_PARTICIPATE_BUTTON);
706 break;
708 case WID_GO_SURVEY_LINK_BUTTON:
709 OpenBrowser(NETWORK_SURVEY_DETAILS_LINK);
710 break;
712 case WID_GO_SURVEY_PREVIEW_BUTTON:
713 ShowSurveyResultTextfileWindow();
714 break;
716 case WID_GO_FULLSCREEN_BUTTON: // Click fullscreen on/off
717 /* try to toggle full-screen on/off */
718 if (!ToggleFullScreen(!_fullscreen)) {
719 ShowErrorMessage(STR_ERROR_FULLSCREEN_FAILED, INVALID_STRING_ID, WL_ERROR);
721 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
722 this->SetWidgetDirty(WID_GO_FULLSCREEN_BUTTON);
723 break;
725 case WID_GO_VIDEO_ACCEL_BUTTON:
726 _video_hw_accel = !_video_hw_accel;
727 ShowErrorMessage(STR_GAME_OPTIONS_VIDEO_ACCELERATION_RESTART, INVALID_STRING_ID, WL_INFO);
728 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
729 this->SetWidgetDirty(WID_GO_VIDEO_ACCEL_BUTTON);
730 #ifndef __APPLE__
731 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON, _video_hw_accel && _video_vsync);
732 this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON, !_video_hw_accel);
733 this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON);
734 #endif
735 break;
737 case WID_GO_VIDEO_VSYNC_BUTTON:
738 if (!_video_hw_accel) break;
740 _video_vsync = !_video_vsync;
741 VideoDriver::GetInstance()->ToggleVsync(_video_vsync);
743 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON, _video_vsync);
744 this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON);
745 this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN, _video_vsync);
746 this->SetWidgetDirty(WID_GO_REFRESH_RATE_DROPDOWN);
747 break;
749 case WID_GO_GUI_SCALE_BEVEL_BUTTON: {
750 _settings_client.gui.scale_bevels = !_settings_client.gui.scale_bevels;
752 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_BEVEL_BUTTON, _settings_client.gui.scale_bevels);
753 this->SetDirty();
755 SetupWidgetDimensions();
756 ReInitAllWindows(true);
757 break;
760 #ifdef HAS_TRUETYPE_FONT
761 case WID_GO_GUI_FONT_SPRITE:
762 _fcsettings.prefer_sprite = !_fcsettings.prefer_sprite;
764 this->SetWidgetLoweredState(WID_GO_GUI_FONT_SPRITE, _fcsettings.prefer_sprite);
765 this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA, _fcsettings.prefer_sprite);
766 this->SetDirty();
768 InitFontCache(false);
769 InitFontCache(true);
770 ClearFontCache();
771 CheckForMissingGlyphs();
772 SetupWidgetDimensions();
773 UpdateAllVirtCoords();
774 ReInitAllWindows(true);
775 break;
777 case WID_GO_GUI_FONT_AA:
778 _fcsettings.global_aa = !_fcsettings.global_aa;
780 this->SetWidgetLoweredState(WID_GO_GUI_FONT_AA, _fcsettings.global_aa);
781 MarkWholeScreenDirty();
783 ClearFontCache();
784 break;
785 #endif /* HAS_TRUETYPE_FONT */
787 case WID_GO_GUI_SCALE:
788 if (ClickSliderWidget(this->GetWidget<NWidgetBase>(widget)->GetCurrentRect(), pt, MIN_INTERFACE_SCALE, MAX_INTERFACE_SCALE, this->gui_scale)) {
789 if (!_ctrl_pressed) this->gui_scale = ((this->gui_scale + 12) / 25) * 25;
790 this->SetWidgetDirty(widget);
793 if (click_count > 0) this->mouse_capture_widget = widget;
794 break;
796 case WID_GO_GUI_SCALE_AUTO:
798 if (_gui_scale_cfg == -1) {
799 _gui_scale_cfg = _gui_scale;
800 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO, false);
801 } else {
802 _gui_scale_cfg = -1;
803 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO, true);
804 if (AdjustGUIZoom(false)) ReInitAllWindows(true);
805 this->gui_scale = _gui_scale;
807 this->SetWidgetDirty(widget);
808 break;
811 case WID_GO_BASE_GRF_PARAMETERS: {
812 auto *used_set = BaseGraphics::GetUsedSet();
813 if (used_set == nullptr || !used_set->IsConfigurable()) break;
814 GRFConfig &extra_cfg = used_set->GetOrCreateExtraConfig();
815 if (extra_cfg.num_params == 0) extra_cfg.SetParameterDefaults();
816 OpenGRFParameterWindow(true, &extra_cfg, _game_mode == GM_MENU);
817 if (_game_mode == GM_MENU) this->reload = true;
818 break;
821 case WID_GO_BASE_SFX_VOLUME:
822 case WID_GO_BASE_MUSIC_VOLUME: {
823 byte &vol = (widget == WID_GO_BASE_MUSIC_VOLUME) ? _settings_client.music.music_vol : _settings_client.music.effect_vol;
824 if (ClickSliderWidget(this->GetWidget<NWidgetBase>(widget)->GetCurrentRect(), pt, 0, INT8_MAX, vol)) {
825 if (widget == WID_GO_BASE_MUSIC_VOLUME) {
826 MusicDriver::GetInstance()->SetVolume(vol);
827 } else {
828 SetEffectVolume(vol);
830 this->SetWidgetDirty(widget);
831 SetWindowClassesDirty(WC_MUSIC_WINDOW);
834 if (click_count > 0) this->mouse_capture_widget = widget;
835 break;
838 case WID_GO_BASE_MUSIC_JUKEBOX: {
839 ShowMusicWindow();
840 break;
843 case WID_GO_BASE_GRF_OPEN_URL:
844 if (BaseGraphics::GetUsedSet() == nullptr || BaseGraphics::GetUsedSet()->url.empty()) return;
845 OpenBrowser(BaseGraphics::GetUsedSet()->url);
846 break;
848 case WID_GO_BASE_SFX_OPEN_URL:
849 if (BaseSounds::GetUsedSet() == nullptr || BaseSounds::GetUsedSet()->url.empty()) return;
850 OpenBrowser(BaseSounds::GetUsedSet()->url);
851 break;
853 case WID_GO_BASE_MUSIC_OPEN_URL:
854 if (BaseMusic::GetUsedSet() == nullptr || BaseMusic::GetUsedSet()->url.empty()) return;
855 OpenBrowser(BaseMusic::GetUsedSet()->url);
856 break;
858 case WID_GO_CURRENCY_DROPDOWN:
859 case WID_GO_AUTOSAVE_DROPDOWN:
860 case WID_GO_LANG_DROPDOWN:
861 case WID_GO_RESOLUTION_DROPDOWN:
862 case WID_GO_REFRESH_RATE_DROPDOWN:
863 case WID_GO_BASE_GRF_DROPDOWN:
864 case WID_GO_BASE_SFX_DROPDOWN:
865 case WID_GO_BASE_MUSIC_DROPDOWN: {
866 int selected;
867 DropDownList list = this->BuildDropDownList(widget, &selected);
868 if (!list.empty()) {
869 ShowDropDownList(this, std::move(list), selected, widget);
870 } else {
871 if (widget == WID_GO_RESOLUTION_DROPDOWN) ShowErrorMessage(STR_ERROR_RESOLUTION_LIST_FAILED, INVALID_STRING_ID, WL_ERROR);
873 break;
878 void OnMouseLoop() override
880 if (_left_button_down || this->gui_scale == _gui_scale) return;
882 _gui_scale_cfg = this->gui_scale;
884 if (AdjustGUIZoom(false)) {
885 ReInitAllWindows(true);
886 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO, false);
887 this->SetDirty();
891 void OnDropdownSelect(WidgetID widget, int index) override
893 switch (widget) {
894 case WID_GO_CURRENCY_DROPDOWN: // Currency
895 if (index == CURRENCY_CUSTOM) ShowCustCurrency();
896 this->opt->locale.currency = index;
897 ReInitAllWindows(false);
898 break;
900 case WID_GO_AUTOSAVE_DROPDOWN: // Autosave options
901 _settings_client.gui.autosave_interval = _autosave_dropdown_to_minutes[index];
902 ChangeAutosaveFrequency(false);
903 this->SetDirty();
904 break;
906 case WID_GO_LANG_DROPDOWN: // Change interface language
907 ReadLanguagePack(&_languages[index]);
908 CloseWindowByClass(WC_QUERY_STRING);
909 CheckForMissingGlyphs();
910 ClearAllCachedNames();
911 UpdateAllVirtCoords();
912 CheckBlitter();
913 ReInitAllWindows(false);
914 break;
916 case WID_GO_RESOLUTION_DROPDOWN: // Change resolution
917 if ((uint)index < _resolutions.size() && ChangeResInGame(_resolutions[index].width, _resolutions[index].height)) {
918 this->SetDirty();
920 break;
922 case WID_GO_REFRESH_RATE_DROPDOWN: {
923 _settings_client.gui.refresh_rate = *std::next(_refresh_rates.begin(), index);
924 if (_settings_client.gui.refresh_rate > 60) {
925 /* Show warning to the user that this refresh rate might not be suitable on
926 * larger maps with many NewGRFs and vehicles. */
927 ShowErrorMessage(STR_GAME_OPTIONS_REFRESH_RATE_WARNING, INVALID_STRING_ID, WL_INFO);
929 break;
932 case WID_GO_BASE_GRF_DROPDOWN:
933 if (_game_mode == GM_MENU) {
934 CloseWindowByClass(WC_GRF_PARAMETERS);
935 auto set = BaseGraphics::GetSet(index);
936 BaseGraphics::SetSet(set);
937 this->reload = true;
938 this->InvalidateData();
940 break;
942 case WID_GO_BASE_SFX_DROPDOWN:
943 if (_game_mode == GM_MENU) {
944 auto set = BaseSounds::GetSet(index);
945 BaseSounds::ini_set = set->name;
946 BaseSounds::SetSet(set);
947 this->reload = true;
948 this->InvalidateData();
950 break;
952 case WID_GO_BASE_MUSIC_DROPDOWN:
953 ChangeMusicSet(index);
954 break;
959 * Some data on this window has become invalid.
960 * @param data Information about the changed data. @see GameOptionsInvalidationData
961 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
963 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
965 if (!gui_scope) return;
966 this->SetWidgetLoweredState(WID_GO_SURVEY_PARTICIPATE_BUTTON, _settings_client.network.participate_survey == PS_YES);
967 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON, _fullscreen);
968 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON, _video_hw_accel);
969 this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN, _video_vsync);
971 #ifndef __APPLE__
972 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON, _video_hw_accel && _video_vsync);
973 this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON, !_video_hw_accel);
974 #endif
976 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO, _gui_scale_cfg == -1);
977 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_BEVEL_BUTTON, _settings_client.gui.scale_bevels);
978 #ifdef HAS_TRUETYPE_FONT
979 this->SetWidgetLoweredState(WID_GO_GUI_FONT_SPRITE, _fcsettings.prefer_sprite);
980 this->SetWidgetLoweredState(WID_GO_GUI_FONT_AA, _fcsettings.global_aa);
981 this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA, _fcsettings.prefer_sprite);
982 #endif /* HAS_TRUETYPE_FONT */
984 this->SetWidgetDisabledState(WID_GO_BASE_GRF_DROPDOWN, _game_mode != GM_MENU);
985 this->SetWidgetDisabledState(WID_GO_BASE_SFX_DROPDOWN, _game_mode != GM_MENU);
987 this->SetWidgetDisabledState(WID_GO_BASE_GRF_PARAMETERS, BaseGraphics::GetUsedSet() == nullptr || !BaseGraphics::GetUsedSet()->IsConfigurable());
989 this->SetWidgetDisabledState(WID_GO_BASE_GRF_OPEN_URL, BaseGraphics::GetUsedSet() == nullptr || BaseGraphics::GetUsedSet()->url.empty());
990 this->SetWidgetDisabledState(WID_GO_BASE_SFX_OPEN_URL, BaseSounds::GetUsedSet() == nullptr || BaseSounds::GetUsedSet()->url.empty());
991 this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_OPEN_URL, BaseMusic::GetUsedSet() == nullptr || BaseMusic::GetUsedSet()->url.empty());
993 for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) {
994 this->SetWidgetDisabledState(WID_GO_BASE_GRF_TEXTFILE + tft, BaseGraphics::GetUsedSet() == nullptr || !BaseGraphics::GetUsedSet()->GetTextfile(tft).has_value());
995 this->SetWidgetDisabledState(WID_GO_BASE_SFX_TEXTFILE + tft, BaseSounds::GetUsedSet() == nullptr || !BaseSounds::GetUsedSet()->GetTextfile(tft).has_value());
996 this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_TEXTFILE + tft, BaseMusic::GetUsedSet() == nullptr || !BaseMusic::GetUsedSet()->GetTextfile(tft).has_value());
1001 static constexpr NWidgetPart _nested_game_options_widgets[] = {
1002 NWidget(NWID_HORIZONTAL),
1003 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1004 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1005 EndContainer(),
1006 NWidget(WWT_PANEL, COLOUR_GREY),
1007 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPadding(WidgetDimensions::unscaled.sparse),
1008 NWidget(WWT_TEXTBTN, COLOUR_YELLOW, WID_GO_TAB_GENERAL), SetMinimalTextLines(2, 0), SetDataTip(STR_GAME_OPTIONS_TAB_GENERAL, STR_GAME_OPTIONS_TAB_GENERAL_TT), SetFill(1, 0),
1009 NWidget(WWT_TEXTBTN, COLOUR_YELLOW, WID_GO_TAB_GRAPHICS), SetMinimalTextLines(2, 0), SetDataTip(STR_GAME_OPTIONS_TAB_GRAPHICS, STR_GAME_OPTIONS_TAB_GRAPHICS_TT), SetFill(1, 0),
1010 NWidget(WWT_TEXTBTN, COLOUR_YELLOW, WID_GO_TAB_SOUND), SetMinimalTextLines(2, 0), SetDataTip(STR_GAME_OPTIONS_TAB_SOUND, STR_GAME_OPTIONS_TAB_SOUND_TT), SetFill(1, 0),
1011 NWidget(WWT_TEXTBTN, COLOUR_YELLOW, WID_GO_TAB_SOCIAL), SetMinimalTextLines(2, 0), SetDataTip(STR_GAME_OPTIONS_TAB_SOCIAL, STR_GAME_OPTIONS_TAB_SOCIAL_TT), SetFill(1, 0),
1012 EndContainer(),
1013 EndContainer(),
1014 NWidget(WWT_PANEL, COLOUR_GREY),
1015 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_GO_TAB_SELECTION),
1016 /* General tab */
1017 NWidget(NWID_VERTICAL), SetPadding(WidgetDimensions::unscaled.sparse), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0),
1018 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_LANGUAGE, STR_NULL),
1019 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_LANG_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_RAW_STRING, STR_GAME_OPTIONS_LANGUAGE_TOOLTIP), SetFill(1, 0),
1020 EndContainer(),
1022 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_AUTOSAVE_FRAME, STR_NULL),
1023 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_AUTOSAVE_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING, STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_TOOLTIP), SetFill(1, 0),
1024 EndContainer(),
1026 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME, STR_NULL),
1027 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_CURRENCY_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING2, STR_GAME_OPTIONS_CURRENCY_UNITS_DROPDOWN_TOOLTIP), SetFill(1, 0),
1028 EndContainer(),
1030 NWidget(NWID_SELECTION, INVALID_COLOUR, WID_GO_SURVEY_SEL),
1031 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_FRAME, STR_NULL), SetPIP(0, WidgetDimensions::unscaled.vsep_sparse, 0),
1032 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1033 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY, STR_NULL),
1034 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_SURVEY_PARTICIPATE_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_TOOLTIP),
1035 EndContainer(),
1036 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1037 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_SURVEY_PREVIEW_BUTTON), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_PREVIEW, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_PREVIEW_TOOLTIP),
1038 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_SURVEY_LINK_BUTTON), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_LINK, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_LINK_TOOLTIP),
1039 EndContainer(),
1040 EndContainer(),
1041 EndContainer(),
1042 EndContainer(),
1044 /* Graphics tab */
1045 NWidget(NWID_VERTICAL), SetPadding(WidgetDimensions::unscaled.sparse), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0),
1046 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GUI_SCALE_FRAME, STR_NULL),
1047 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
1048 NWidget(WWT_EMPTY, COLOUR_GREY, WID_GO_GUI_SCALE), SetMinimalSize(67, 0), SetMinimalTextLines(1, 12 + WidgetDimensions::unscaled.vsep_normal, FS_SMALL), SetFill(0, 0), SetDataTip(0x0, STR_GAME_OPTIONS_GUI_SCALE_TOOLTIP),
1049 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1050 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_GUI_SCALE_AUTO, STR_NULL),
1051 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_GUI_SCALE_AUTO), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_GUI_SCALE_AUTO_TOOLTIP),
1052 EndContainer(),
1053 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1054 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_GUI_SCALE_BEVELS, STR_NULL),
1055 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_GUI_SCALE_BEVEL_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_GUI_SCALE_BEVELS_TOOLTIP),
1056 EndContainer(),
1057 #ifdef HAS_TRUETYPE_FONT
1058 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1059 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_GUI_FONT_SPRITE, STR_NULL),
1060 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_GUI_FONT_SPRITE), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_GUI_FONT_SPRITE_TOOLTIP),
1061 EndContainer(),
1062 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1063 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_GUI_FONT_AA, STR_NULL),
1064 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_GUI_FONT_AA), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_GUI_FONT_AA_TOOLTIP),
1065 EndContainer(),
1066 #endif /* HAS_TRUETYPE_FONT */
1067 EndContainer(),
1068 EndContainer(),
1070 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_GRAPHICS, STR_NULL),
1071 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
1072 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1073 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_RESOLUTION, STR_NULL),
1074 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_RESOLUTION_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING2, STR_GAME_OPTIONS_RESOLUTION_TOOLTIP),
1075 EndContainer(),
1076 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1077 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE, STR_NULL),
1078 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_REFRESH_RATE_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE_ITEM, STR_GAME_OPTIONS_REFRESH_RATE_TOOLTIP),
1079 EndContainer(),
1080 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1081 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_FULLSCREEN, STR_NULL),
1082 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_FULLSCREEN_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP),
1083 EndContainer(),
1084 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1085 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_VIDEO_ACCELERATION, STR_NULL),
1086 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_VIDEO_ACCEL_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_VIDEO_ACCELERATION_TOOLTIP),
1087 EndContainer(),
1088 #ifndef __APPLE__
1089 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
1090 NWidget(WWT_TEXT, COLOUR_GREY), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_VIDEO_VSYNC, STR_NULL),
1091 NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_GO_VIDEO_VSYNC_BUTTON), SetMinimalSize(21, 9), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_VIDEO_VSYNC_TOOLTIP),
1092 EndContainer(),
1093 #endif
1094 NWidget(NWID_HORIZONTAL),
1095 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GO_VIDEO_DRIVER_INFO), SetMinimalTextLines(1, 0), SetFill(1, 0),
1096 EndContainer(),
1097 EndContainer(),
1098 EndContainer(),
1100 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_GRF, STR_NULL), SetPIP(0, WidgetDimensions::unscaled.vsep_sparse, 0), SetFill(1, 0),
1101 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
1102 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_GRF_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_RAW_STRING, STR_GAME_OPTIONS_BASE_GRF_TOOLTIP), SetFill(1, 0),
1103 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_GRF_PARAMETERS), SetDataTip(STR_NEWGRF_SETTINGS_SET_PARAMETERS, STR_NULL),
1104 EndContainer(),
1105 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_BASE_GRF_DESCRIPTION), SetMinimalSize(200, 0), SetDataTip(STR_EMPTY, STR_GAME_OPTIONS_BASE_GRF_DESCRIPTION_TOOLTIP), SetFill(1, 0),
1106 NWidget(NWID_VERTICAL),
1107 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1108 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_GRF_OPEN_URL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_CONTENT_OPEN_URL, STR_CONTENT_OPEN_URL_TOOLTIP),
1109 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_GRF_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_TEXTFILE_VIEW_README_TOOLTIP),
1110 EndContainer(),
1111 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1112 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_GRF_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_TEXTFILE_VIEW_CHANGELOG_TOOLTIP),
1113 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_GRF_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_TEXTFILE_VIEW_LICENCE_TOOLTIP),
1114 EndContainer(),
1115 EndContainer(),
1116 EndContainer(),
1117 EndContainer(),
1119 /* Sound/Music tab */
1120 NWidget(NWID_VERTICAL), SetPadding(WidgetDimensions::unscaled.sparse), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0),
1121 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_VOLUME, STR_NULL), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0),
1122 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
1123 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_TEXT_SFX_VOLUME), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_SFX_VOLUME, STR_NULL),
1124 NWidget(WWT_EMPTY, COLOUR_GREY, WID_GO_BASE_SFX_VOLUME), SetMinimalSize(67, 0), SetMinimalTextLines(1, 12 + WidgetDimensions::unscaled.vsep_normal, FS_SMALL), SetFill(1, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
1125 EndContainer(),
1126 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
1127 NWidget(WWT_TEXT, COLOUR_GREY, WID_GO_TEXT_MUSIC_VOLUME), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_MUSIC_VOLUME, STR_NULL),
1128 NWidget(WWT_EMPTY, COLOUR_GREY, WID_GO_BASE_MUSIC_VOLUME), SetMinimalSize(67, 0), SetMinimalTextLines(1, 12 + WidgetDimensions::unscaled.vsep_normal, FS_SMALL), SetFill(1, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC),
1129 EndContainer(),
1130 EndContainer(),
1132 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_SFX, STR_NULL), SetPIP(0, WidgetDimensions::unscaled.vsep_sparse, 0),
1133 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_SFX_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_RAW_STRING, STR_GAME_OPTIONS_BASE_SFX_TOOLTIP), SetFill(1, 0),
1134 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GO_BASE_SFX_DESCRIPTION), SetMinimalSize(200, 0), SetMinimalTextLines(1, 0), SetDataTip(STR_NULL, STR_GAME_OPTIONS_BASE_SFX_DESCRIPTION_TOOLTIP), SetFill(1, 0),
1135 NWidget(NWID_VERTICAL),
1136 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1137 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_SFX_OPEN_URL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_CONTENT_OPEN_URL, STR_CONTENT_OPEN_URL_TOOLTIP),
1138 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_SFX_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_TEXTFILE_VIEW_README_TOOLTIP),
1139 EndContainer(),
1140 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1141 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_SFX_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_TEXTFILE_VIEW_CHANGELOG_TOOLTIP),
1142 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_SFX_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_TEXTFILE_VIEW_LICENCE_TOOLTIP),
1143 EndContainer(),
1144 EndContainer(),
1145 EndContainer(),
1147 NWidget(WWT_FRAME, COLOUR_GREY), SetDataTip(STR_GAME_OPTIONS_BASE_MUSIC, STR_NULL), SetPIP(0, WidgetDimensions::unscaled.vsep_sparse, 0),
1148 NWidget(WWT_DROPDOWN, COLOUR_GREY, WID_GO_BASE_MUSIC_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_RAW_STRING, STR_GAME_OPTIONS_BASE_MUSIC_TOOLTIP), SetFill(1, 0),
1149 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
1150 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GO_BASE_MUSIC_DESCRIPTION), SetMinimalSize(200, 0), SetMinimalTextLines(1, 0), SetDataTip(STR_NULL, STR_GAME_OPTIONS_BASE_MUSIC_DESCRIPTION_TOOLTIP), SetFill(1, 0),
1151 NWidget(NWID_VERTICAL), SetPIPRatio(0, 0, 1),
1152 NWidget(WWT_PUSHIMGBTN, COLOUR_GREY, WID_GO_BASE_MUSIC_JUKEBOX), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_MUSIC, STR_TOOLBAR_TOOLTIP_SHOW_SOUND_MUSIC_WINDOW),
1153 EndContainer(),
1154 EndContainer(),
1155 NWidget(NWID_VERTICAL),
1156 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1157 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_MUSIC_OPEN_URL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_CONTENT_OPEN_URL, STR_CONTENT_OPEN_URL_TOOLTIP),
1158 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_MUSIC_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_TEXTFILE_VIEW_README_TOOLTIP),
1159 EndContainer(),
1160 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
1161 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_MUSIC_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_TEXTFILE_VIEW_CHANGELOG_TOOLTIP),
1162 NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_GO_BASE_MUSIC_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_TEXTFILE_VIEW_LICENCE_TOOLTIP),
1163 EndContainer(),
1164 EndContainer(),
1165 EndContainer(),
1166 EndContainer(),
1168 /* Social tab */
1169 NWidget(NWID_VERTICAL), SetPadding(WidgetDimensions::unscaled.sparse), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0),
1170 NWidgetFunction(MakeNWidgetSocialPlugins),
1171 EndContainer(),
1172 EndContainer(),
1173 EndContainer(),
1176 static WindowDesc _game_options_desc(__FILE__, __LINE__,
1177 WDP_CENTER, nullptr, 0, 0,
1178 WC_GAME_OPTIONS, WC_NONE,
1180 std::begin(_nested_game_options_widgets), std::end(_nested_game_options_widgets)
1183 /** Open the game options window. */
1184 void ShowGameOptions()
1186 CloseWindowByClass(WC_GAME_OPTIONS);
1187 new GameOptionsWindow(&_game_options_desc);
1190 static int SETTING_HEIGHT = 11; ///< Height of a single setting in the tree view in pixels
1193 * Flags for #SettingEntry
1194 * @note The #SEF_BUTTONS_MASK matches expectations of the formal parameter 'state' of #DrawArrowButtons
1196 enum SettingEntryFlags {
1197 SEF_LEFT_DEPRESSED = 0x01, ///< Of a numeric setting entry, the left button is depressed
1198 SEF_RIGHT_DEPRESSED = 0x02, ///< Of a numeric setting entry, the right button is depressed
1199 SEF_BUTTONS_MASK = (SEF_LEFT_DEPRESSED | SEF_RIGHT_DEPRESSED), ///< Bit-mask for button flags
1201 SEF_LAST_FIELD = 0x04, ///< This entry is the last one in a (sub-)page
1202 SEF_FILTERED = 0x08, ///< Entry is hidden by the string filter
1205 /** How the list of advanced settings is filtered. */
1206 enum RestrictionMode {
1207 RM_BASIC, ///< Display settings associated to the "basic" list.
1208 RM_ADVANCED, ///< Display settings associated to the "advanced" list.
1209 RM_ALL, ///< List all settings regardless of the default/newgame/... values.
1210 RM_CHANGED_AGAINST_DEFAULT, ///< Show only settings which are different compared to default values.
1211 RM_CHANGED_AGAINST_NEW, ///< Show only settings which are different compared to the user's new game setting values.
1212 RM_END, ///< End for iteration.
1214 DECLARE_POSTFIX_INCREMENT(RestrictionMode)
1216 /** Filter for settings list. */
1217 struct SettingFilter {
1218 StringFilter string; ///< Filter string.
1219 RestrictionMode min_cat; ///< Minimum category needed to display all filtered strings (#RM_BASIC, #RM_ADVANCED, or #RM_ALL).
1220 bool type_hides; ///< Whether the type hides filtered strings.
1221 RestrictionMode mode; ///< Filter based on category.
1222 SettingType type; ///< Filter based on type.
1225 /** Data structure describing a single setting in a tab */
1226 struct BaseSettingEntry {
1227 byte flags; ///< Flags of the setting entry. @see SettingEntryFlags
1228 byte level; ///< Nesting level of this setting entry
1230 BaseSettingEntry() : flags(0), level(0) {}
1231 virtual ~BaseSettingEntry() = default;
1233 virtual void Init(byte level = 0);
1234 virtual void FoldAll() {}
1235 virtual void UnFoldAll() {}
1236 virtual void ResetAll() = 0;
1239 * Set whether this is the last visible entry of the parent node.
1240 * @param last_field Value to set
1242 void SetLastField(bool last_field) { if (last_field) SETBITS(this->flags, SEF_LAST_FIELD); else CLRBITS(this->flags, SEF_LAST_FIELD); }
1244 virtual uint Length() const = 0;
1245 virtual void GetFoldingState([[maybe_unused]] bool &all_folded, [[maybe_unused]] bool &all_unfolded) const {}
1246 virtual bool IsVisible(const BaseSettingEntry *item) const;
1247 virtual BaseSettingEntry *FindEntry(uint row, uint *cur_row);
1248 virtual uint GetMaxHelpHeight([[maybe_unused]] int maxw) { return 0; }
1251 * Check whether an entry is hidden due to filters
1252 * @return true if hidden.
1254 bool IsFiltered() const { return (this->flags & SEF_FILTERED) != 0; }
1256 virtual bool UpdateFilterState(SettingFilter &filter, bool force_visible) = 0;
1258 virtual uint Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row = 0, uint parent_last = 0) const;
1260 protected:
1261 virtual void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const = 0;
1264 /** Standard setting */
1265 struct SettingEntry : BaseSettingEntry {
1266 const char *name; ///< Name of the setting
1267 const IntSettingDesc *setting; ///< Setting description of the setting
1269 SettingEntry(const char *name);
1271 void Init(byte level = 0) override;
1272 void ResetAll() override;
1273 uint Length() const override;
1274 uint GetMaxHelpHeight(int maxw) override;
1275 bool UpdateFilterState(SettingFilter &filter, bool force_visible) override;
1277 void SetButtons(byte new_val);
1279 protected:
1280 void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const override;
1282 private:
1283 bool IsVisibleByRestrictionMode(RestrictionMode mode) const;
1286 /** Containers for BaseSettingEntry */
1287 struct SettingsContainer {
1288 typedef std::vector<BaseSettingEntry*> EntryVector;
1289 EntryVector entries; ///< Settings on this page
1291 template<typename T>
1292 T *Add(T *item)
1294 this->entries.push_back(item);
1295 return item;
1298 void Init(byte level = 0);
1299 void ResetAll();
1300 void FoldAll();
1301 void UnFoldAll();
1303 uint Length() const;
1304 void GetFoldingState(bool &all_folded, bool &all_unfolded) const;
1305 bool IsVisible(const BaseSettingEntry *item) const;
1306 BaseSettingEntry *FindEntry(uint row, uint *cur_row);
1307 uint GetMaxHelpHeight(int maxw);
1309 bool UpdateFilterState(SettingFilter &filter, bool force_visible);
1311 uint Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row = 0, uint parent_last = 0) const;
1314 /** Data structure describing one page of settings in the settings window. */
1315 struct SettingsPage : BaseSettingEntry, SettingsContainer {
1316 StringID title; ///< Title of the sub-page
1317 bool folded; ///< Sub-page is folded (not visible except for its title)
1319 SettingsPage(StringID title);
1321 void Init(byte level = 0) override;
1322 void ResetAll() override;
1323 void FoldAll() override;
1324 void UnFoldAll() override;
1326 uint Length() const override;
1327 void GetFoldingState(bool &all_folded, bool &all_unfolded) const override;
1328 bool IsVisible(const BaseSettingEntry *item) const override;
1329 BaseSettingEntry *FindEntry(uint row, uint *cur_row) override;
1330 uint GetMaxHelpHeight(int maxw) override { return SettingsContainer::GetMaxHelpHeight(maxw); }
1332 bool UpdateFilterState(SettingFilter &filter, bool force_visible) override;
1334 uint Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row = 0, uint parent_last = 0) const override;
1336 protected:
1337 void DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const override;
1340 /* == BaseSettingEntry methods == */
1343 * Initialization of a setting entry
1344 * @param level Page nesting level of this entry
1346 void BaseSettingEntry::Init(byte level)
1348 this->level = level;
1352 * Check whether an entry is visible and not folded or filtered away.
1353 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1354 * @param item Entry to search for.
1355 * @return true if entry is visible.
1357 bool BaseSettingEntry::IsVisible(const BaseSettingEntry *item) const
1359 if (this->IsFiltered()) return false;
1360 return this == item;
1364 * Find setting entry at row \a row_num
1365 * @param row_num Index of entry to return
1366 * @param cur_row Current row number
1367 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
1369 BaseSettingEntry *BaseSettingEntry::FindEntry(uint row_num, uint *cur_row)
1371 if (this->IsFiltered()) return nullptr;
1372 if (row_num == *cur_row) return this;
1373 (*cur_row)++;
1374 return nullptr;
1378 * Draw a row in the settings panel.
1380 * The scrollbar uses rows of the page, while the page data structure is a tree of #SettingsPage and #SettingEntry objects.
1381 * As a result, the drawing routing traverses the tree from top to bottom, counting rows in \a cur_row until it reaches \a first_row.
1382 * Then it enables drawing rows while traversing until \a max_row is reached, at which point drawing is terminated.
1384 * The \a parent_last parameter ensures that the vertical lines at the left are
1385 * only drawn when another entry follows, that it prevents output like
1386 * \verbatim
1387 * |-- setting
1388 * |-- (-) - Title
1389 * | |-- setting
1390 * | |-- setting
1391 * \endverbatim
1392 * The left-most vertical line is not wanted. It is prevented by setting the
1393 * appropriate bit in the \a parent_last parameter.
1395 * @param settings_ptr Pointer to current values of all settings
1396 * @param left Left-most position in window/panel to start drawing \a first_row
1397 * @param right Right-most x position to draw strings at.
1398 * @param y Upper-most position in window/panel to start drawing \a first_row
1399 * @param first_row First row number to draw
1400 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1401 * @param selected Selected entry by the user.
1402 * @param cur_row Current row number (internal variable)
1403 * @param parent_last Last-field booleans of parent page level (page level \e i sets bit \e i to 1 if it is its last field)
1404 * @return Row number of the next row to draw
1406 uint BaseSettingEntry::Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row, uint parent_last) const
1408 if (this->IsFiltered()) return cur_row;
1409 if (cur_row >= max_row) return cur_row;
1411 bool rtl = _current_text_dir == TD_RTL;
1412 int offset = (rtl ? -(int)_circle_size.width : (int)_circle_size.width) / 2;
1413 int level_width = rtl ? -WidgetDimensions::scaled.hsep_indent : WidgetDimensions::scaled.hsep_indent;
1415 int x = rtl ? right : left;
1416 if (cur_row >= first_row) {
1417 int colour = _colour_gradient[COLOUR_ORANGE][4];
1418 y += (cur_row - first_row) * SETTING_HEIGHT; // Compute correct y start position
1420 /* Draw vertical for parent nesting levels */
1421 for (uint lvl = 0; lvl < this->level; lvl++) {
1422 if (!HasBit(parent_last, lvl)) GfxDrawLine(x + offset, y, x + offset, y + SETTING_HEIGHT - 1, colour);
1423 x += level_width;
1425 /* draw own |- prefix */
1426 int halfway_y = y + SETTING_HEIGHT / 2;
1427 int bottom_y = (flags & SEF_LAST_FIELD) ? halfway_y : y + SETTING_HEIGHT - 1;
1428 GfxDrawLine(x + offset, y, x + offset, bottom_y, colour);
1429 /* Small horizontal line from the last vertical line */
1430 GfxDrawLine(x + offset, halfway_y, x + level_width - (rtl ? -WidgetDimensions::scaled.hsep_normal : WidgetDimensions::scaled.hsep_normal), halfway_y, colour);
1431 x += level_width;
1433 this->DrawSetting(settings_ptr, rtl ? left : x, rtl ? x : right, y, this == selected);
1435 cur_row++;
1437 return cur_row;
1440 /* == SettingEntry methods == */
1443 * Constructor for a single setting in the 'advanced settings' window
1444 * @param name Name of the setting in the setting table
1446 SettingEntry::SettingEntry(const char *name)
1448 this->name = name;
1449 this->setting = nullptr;
1453 * Initialization of a setting entry
1454 * @param level Page nesting level of this entry
1456 void SettingEntry::Init(byte level)
1458 BaseSettingEntry::Init(level);
1459 this->setting = GetSettingFromName(this->name)->AsIntSetting();
1462 /* Sets the given setting entry to its default value */
1463 void SettingEntry::ResetAll()
1465 SetSettingValue(this->setting, this->setting->def);
1469 * Set the button-depressed flags (#SEF_LEFT_DEPRESSED and #SEF_RIGHT_DEPRESSED) to a specified value
1470 * @param new_val New value for the button flags
1471 * @see SettingEntryFlags
1473 void SettingEntry::SetButtons(byte new_val)
1475 assert((new_val & ~SEF_BUTTONS_MASK) == 0); // Should not touch any flags outside the buttons
1476 this->flags = (this->flags & ~SEF_BUTTONS_MASK) | new_val;
1479 /** Return number of rows needed to display the (filtered) entry */
1480 uint SettingEntry::Length() const
1482 return this->IsFiltered() ? 0 : 1;
1486 * Get the biggest height of the help text(s), if the width is at least \a maxw. Help text gets wrapped if needed.
1487 * @param maxw Maximal width of a line help text.
1488 * @return Biggest height needed to display any help text of this node (and its descendants).
1490 uint SettingEntry::GetMaxHelpHeight(int maxw)
1492 return GetStringHeight(this->setting->GetHelp(), maxw);
1496 * Checks whether an entry shall be made visible based on the restriction mode.
1497 * @param mode The current status of the restriction drop down box.
1498 * @return true if the entry shall be visible.
1500 bool SettingEntry::IsVisibleByRestrictionMode(RestrictionMode mode) const
1502 /* There shall not be any restriction, i.e. all settings shall be visible. */
1503 if (mode == RM_ALL) return true;
1505 const IntSettingDesc *sd = this->setting;
1507 if (mode == RM_BASIC) return (this->setting->cat & SC_BASIC_LIST) != 0;
1508 if (mode == RM_ADVANCED) return (this->setting->cat & SC_ADVANCED_LIST) != 0;
1510 /* Read the current value. */
1511 const void *object = ResolveObject(&GetGameSettings(), sd);
1512 int64_t current_value = sd->Read(object);
1513 int64_t filter_value;
1515 if (mode == RM_CHANGED_AGAINST_DEFAULT) {
1516 /* This entry shall only be visible, if the value deviates from its default value. */
1518 /* Read the default value. */
1519 filter_value = sd->def;
1520 } else {
1521 assert(mode == RM_CHANGED_AGAINST_NEW);
1522 /* This entry shall only be visible, if the value deviates from
1523 * its value is used when starting a new game. */
1525 /* Make sure we're not comparing the new game settings against itself. */
1526 assert(&GetGameSettings() != &_settings_newgame);
1528 /* Read the new game's value. */
1529 filter_value = sd->Read(ResolveObject(&_settings_newgame, sd));
1532 return current_value != filter_value;
1536 * Update the filter state.
1537 * @param filter Filter
1538 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1539 * @return true if item remains visible
1541 bool SettingEntry::UpdateFilterState(SettingFilter &filter, bool force_visible)
1543 CLRBITS(this->flags, SEF_FILTERED);
1545 bool visible = true;
1547 const IntSettingDesc *sd = this->setting;
1548 if (!force_visible && !filter.string.IsEmpty()) {
1549 /* Process the search text filter for this item. */
1550 filter.string.ResetState();
1552 SetDParam(0, STR_EMPTY);
1553 filter.string.AddLine(sd->GetTitle());
1554 filter.string.AddLine(sd->GetHelp());
1556 visible = filter.string.GetState();
1559 if (visible) {
1560 if (filter.type != ST_ALL && sd->GetType() != filter.type) {
1561 filter.type_hides = true;
1562 visible = false;
1564 if (!this->IsVisibleByRestrictionMode(filter.mode)) {
1565 while (filter.min_cat < RM_ALL && (filter.min_cat == filter.mode || !this->IsVisibleByRestrictionMode(filter.min_cat))) filter.min_cat++;
1566 visible = false;
1570 if (!visible) SETBITS(this->flags, SEF_FILTERED);
1571 return visible;
1574 static const void *ResolveObject(const GameSettings *settings_ptr, const IntSettingDesc *sd)
1576 if ((sd->flags & SF_PER_COMPANY) != 0) {
1577 if (Company::IsValidID(_local_company) && _game_mode != GM_MENU) {
1578 return &Company::Get(_local_company)->settings;
1580 return &_settings_client.company;
1582 return settings_ptr;
1586 * Function to draw setting value (button + text + current value)
1587 * @param settings_ptr Pointer to current values of all settings
1588 * @param left Left-most position in window/panel to start drawing
1589 * @param right Right-most position in window/panel to draw
1590 * @param y Upper-most position in window/panel to start drawing
1591 * @param highlight Highlight entry.
1593 void SettingEntry::DrawSetting(GameSettings *settings_ptr, int left, int right, int y, bool highlight) const
1595 const IntSettingDesc *sd = this->setting;
1596 int state = this->flags & SEF_BUTTONS_MASK;
1598 bool rtl = _current_text_dir == TD_RTL;
1599 uint buttons_left = rtl ? right + 1 - SETTING_BUTTON_WIDTH : left;
1600 uint text_left = left + (rtl ? 0 : SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide);
1601 uint text_right = right - (rtl ? SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide : 0);
1602 uint button_y = y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
1604 /* We do not allow changes of some items when we are a client in a networkgame */
1605 bool editable = sd->IsEditable();
1607 SetDParam(0, STR_CONFIG_SETTING_VALUE);
1608 int32_t value = sd->Read(ResolveObject(settings_ptr, sd));
1609 if (sd->IsBoolSetting()) {
1610 /* Draw checkbox for boolean-value either on/off */
1611 DrawBoolButton(buttons_left, button_y, value != 0, editable);
1612 } else if ((sd->flags & SF_GUI_DROPDOWN) != 0) {
1613 /* Draw [v] button for settings of an enum-type */
1614 DrawDropDownButton(buttons_left, button_y, COLOUR_YELLOW, state != 0, editable);
1615 } else {
1616 /* Draw [<][>] boxes for settings of an integer-type */
1617 DrawArrowButtons(buttons_left, button_y, COLOUR_YELLOW, state,
1618 editable && value != (sd->flags & SF_GUI_0_IS_SPECIAL ? 0 : sd->min), editable && (uint32_t)value != sd->max);
1620 sd->SetValueDParams(1, value);
1621 DrawString(text_left, text_right, y + (SETTING_HEIGHT - GetCharacterHeight(FS_NORMAL)) / 2, sd->GetTitle(), highlight ? TC_WHITE : TC_LIGHT_BLUE);
1624 /* == SettingsContainer methods == */
1627 * Initialization of an entire setting page
1628 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1630 void SettingsContainer::Init(byte level)
1632 for (auto &it : this->entries) {
1633 it->Init(level);
1637 /** Resets all settings to their default values */
1638 void SettingsContainer::ResetAll()
1640 for (auto settings_entry : this->entries) {
1641 settings_entry->ResetAll();
1645 /** Recursively close all folds of sub-pages */
1646 void SettingsContainer::FoldAll()
1648 for (auto &it : this->entries) {
1649 it->FoldAll();
1653 /** Recursively open all folds of sub-pages */
1654 void SettingsContainer::UnFoldAll()
1656 for (auto &it : this->entries) {
1657 it->UnFoldAll();
1662 * Recursively accumulate the folding state of the tree.
1663 * @param[in,out] all_folded Set to false, if one entry is not folded.
1664 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1666 void SettingsContainer::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1668 for (auto &it : this->entries) {
1669 it->GetFoldingState(all_folded, all_unfolded);
1674 * Update the filter state.
1675 * @param filter Filter
1676 * @param force_visible Whether to force all items visible, no matter what
1677 * @return true if item remains visible
1679 bool SettingsContainer::UpdateFilterState(SettingFilter &filter, bool force_visible)
1681 bool visible = false;
1682 bool first_visible = true;
1683 for (EntryVector::reverse_iterator it = this->entries.rbegin(); it != this->entries.rend(); ++it) {
1684 visible |= (*it)->UpdateFilterState(filter, force_visible);
1685 (*it)->SetLastField(first_visible);
1686 if (visible && first_visible) first_visible = false;
1688 return visible;
1693 * Check whether an entry is visible and not folded or filtered away.
1694 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1695 * @param item Entry to search for.
1696 * @return true if entry is visible.
1698 bool SettingsContainer::IsVisible(const BaseSettingEntry *item) const
1700 for (const auto &it : this->entries) {
1701 if (it->IsVisible(item)) return true;
1703 return false;
1706 /** Return number of rows needed to display the whole page */
1707 uint SettingsContainer::Length() const
1709 uint length = 0;
1710 for (const auto &it : this->entries) {
1711 length += it->Length();
1713 return length;
1717 * Find the setting entry at row number \a row_num
1718 * @param row_num Index of entry to return
1719 * @param cur_row Variable used for keeping track of the current row number. Should point to memory initialized to \c 0 when first called.
1720 * @return The requested setting entry or \c nullptr if it does not exist
1722 BaseSettingEntry *SettingsContainer::FindEntry(uint row_num, uint *cur_row)
1724 BaseSettingEntry *pe = nullptr;
1725 for (const auto &it : this->entries) {
1726 pe = it->FindEntry(row_num, cur_row);
1727 if (pe != nullptr) {
1728 break;
1731 return pe;
1735 * Get the biggest height of the help texts, if the width is at least \a maxw. Help text gets wrapped if needed.
1736 * @param maxw Maximal width of a line help text.
1737 * @return Biggest height needed to display any help text of this (sub-)tree.
1739 uint SettingsContainer::GetMaxHelpHeight(int maxw)
1741 uint biggest = 0;
1742 for (const auto &it : this->entries) {
1743 biggest = std::max(biggest, it->GetMaxHelpHeight(maxw));
1745 return biggest;
1750 * Draw a row in the settings panel.
1752 * @param settings_ptr Pointer to current values of all settings
1753 * @param left Left-most position in window/panel to start drawing \a first_row
1754 * @param right Right-most x position to draw strings at.
1755 * @param y Upper-most position in window/panel to start drawing \a first_row
1756 * @param first_row First row number to draw
1757 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1758 * @param selected Selected entry by the user.
1759 * @param cur_row Current row number (internal variable)
1760 * @param parent_last Last-field booleans of parent page level (page level \e i sets bit \e i to 1 if it is its last field)
1761 * @return Row number of the next row to draw
1763 uint SettingsContainer::Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row, uint parent_last) const
1765 for (const auto &it : this->entries) {
1766 cur_row = it->Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1767 if (cur_row >= max_row) break;
1769 return cur_row;
1772 /* == SettingsPage methods == */
1775 * Constructor for a sub-page in the 'advanced settings' window
1776 * @param title Title of the sub-page
1778 SettingsPage::SettingsPage(StringID title)
1780 this->title = title;
1781 this->folded = true;
1785 * Initialization of an entire setting page
1786 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1788 void SettingsPage::Init(byte level)
1790 BaseSettingEntry::Init(level);
1791 SettingsContainer::Init(level + 1);
1794 /** Resets all settings to their default values */
1795 void SettingsPage::ResetAll()
1797 for (auto settings_entry : this->entries) {
1798 settings_entry->ResetAll();
1802 /** Recursively close all (filtered) folds of sub-pages */
1803 void SettingsPage::FoldAll()
1805 if (this->IsFiltered()) return;
1806 this->folded = true;
1808 SettingsContainer::FoldAll();
1811 /** Recursively open all (filtered) folds of sub-pages */
1812 void SettingsPage::UnFoldAll()
1814 if (this->IsFiltered()) return;
1815 this->folded = false;
1817 SettingsContainer::UnFoldAll();
1821 * Recursively accumulate the folding state of the (filtered) tree.
1822 * @param[in,out] all_folded Set to false, if one entry is not folded.
1823 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1825 void SettingsPage::GetFoldingState(bool &all_folded, bool &all_unfolded) const
1827 if (this->IsFiltered()) return;
1829 if (this->folded) {
1830 all_unfolded = false;
1831 } else {
1832 all_folded = false;
1835 SettingsContainer::GetFoldingState(all_folded, all_unfolded);
1839 * Update the filter state.
1840 * @param filter Filter
1841 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1842 * @return true if item remains visible
1844 bool SettingsPage::UpdateFilterState(SettingFilter &filter, bool force_visible)
1846 if (!force_visible && !filter.string.IsEmpty()) {
1847 filter.string.ResetState();
1848 filter.string.AddLine(this->title);
1849 force_visible = filter.string.GetState();
1852 bool visible = SettingsContainer::UpdateFilterState(filter, force_visible);
1853 if (visible) {
1854 CLRBITS(this->flags, SEF_FILTERED);
1855 } else {
1856 SETBITS(this->flags, SEF_FILTERED);
1858 return visible;
1862 * Check whether an entry is visible and not folded or filtered away.
1863 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1864 * @param item Entry to search for.
1865 * @return true if entry is visible.
1867 bool SettingsPage::IsVisible(const BaseSettingEntry *item) const
1869 if (this->IsFiltered()) return false;
1870 if (this == item) return true;
1871 if (this->folded) return false;
1873 return SettingsContainer::IsVisible(item);
1876 /** Return number of rows needed to display the (filtered) entry */
1877 uint SettingsPage::Length() const
1879 if (this->IsFiltered()) return 0;
1880 if (this->folded) return 1; // Only displaying the title
1882 return 1 + SettingsContainer::Length();
1886 * Find setting entry at row \a row_num
1887 * @param row_num Index of entry to return
1888 * @param cur_row Current row number
1889 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
1891 BaseSettingEntry *SettingsPage::FindEntry(uint row_num, uint *cur_row)
1893 if (this->IsFiltered()) return nullptr;
1894 if (row_num == *cur_row) return this;
1895 (*cur_row)++;
1896 if (this->folded) return nullptr;
1898 return SettingsContainer::FindEntry(row_num, cur_row);
1902 * Draw a row in the settings panel.
1904 * @param settings_ptr Pointer to current values of all settings
1905 * @param left Left-most position in window/panel to start drawing \a first_row
1906 * @param right Right-most x position to draw strings at.
1907 * @param y Upper-most position in window/panel to start drawing \a first_row
1908 * @param first_row First row number to draw
1909 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1910 * @param selected Selected entry by the user.
1911 * @param cur_row Current row number (internal variable)
1912 * @param parent_last Last-field booleans of parent page level (page level \e i sets bit \e i to 1 if it is its last field)
1913 * @return Row number of the next row to draw
1915 uint SettingsPage::Draw(GameSettings *settings_ptr, int left, int right, int y, uint first_row, uint max_row, BaseSettingEntry *selected, uint cur_row, uint parent_last) const
1917 if (this->IsFiltered()) return cur_row;
1918 if (cur_row >= max_row) return cur_row;
1920 cur_row = BaseSettingEntry::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1922 if (!this->folded) {
1923 if (this->flags & SEF_LAST_FIELD) {
1924 assert(this->level < 8 * sizeof(parent_last));
1925 SetBit(parent_last, this->level); // Add own last-field state
1928 cur_row = SettingsContainer::Draw(settings_ptr, left, right, y, first_row, max_row, selected, cur_row, parent_last);
1931 return cur_row;
1935 * Function to draw setting value (button + text + current value)
1936 * @param left Left-most position in window/panel to start drawing
1937 * @param right Right-most position in window/panel to draw
1938 * @param y Upper-most position in window/panel to start drawing
1940 void SettingsPage::DrawSetting(GameSettings *, int left, int right, int y, bool) const
1942 bool rtl = _current_text_dir == TD_RTL;
1943 DrawSprite((this->folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED), PAL_NONE, rtl ? right - _circle_size.width : left, y + (SETTING_HEIGHT - _circle_size.height) / 2);
1944 DrawString(rtl ? left : left + _circle_size.width + WidgetDimensions::scaled.hsep_normal, rtl ? right - _circle_size.width - WidgetDimensions::scaled.hsep_normal : right, y + (SETTING_HEIGHT - GetCharacterHeight(FS_NORMAL)) / 2, this->title, TC_ORANGE);
1947 /** Construct settings tree */
1948 static SettingsContainer &GetSettingsTree()
1950 static SettingsContainer *main = nullptr;
1952 if (main == nullptr)
1954 /* Build up the dynamic settings-array only once per OpenTTD session */
1955 main = new SettingsContainer();
1957 SettingsPage *localisation = main->Add(new SettingsPage(STR_CONFIG_SETTING_LOCALISATION));
1959 localisation->Add(new SettingEntry("locale.units_velocity"));
1960 localisation->Add(new SettingEntry("locale.units_velocity_nautical"));
1961 localisation->Add(new SettingEntry("locale.units_power"));
1962 localisation->Add(new SettingEntry("locale.units_weight"));
1963 localisation->Add(new SettingEntry("locale.units_volume"));
1964 localisation->Add(new SettingEntry("locale.units_force"));
1965 localisation->Add(new SettingEntry("locale.units_height"));
1966 localisation->Add(new SettingEntry("gui.date_format_in_default_names"));
1969 SettingsPage *graphics = main->Add(new SettingsPage(STR_CONFIG_SETTING_GRAPHICS));
1971 graphics->Add(new SettingEntry("gui.zoom_min"));
1972 graphics->Add(new SettingEntry("gui.zoom_max"));
1973 graphics->Add(new SettingEntry("gui.sprite_zoom_min"));
1974 graphics->Add(new SettingEntry("gui.smallmap_land_colour"));
1975 graphics->Add(new SettingEntry("gui.linkgraph_colours"));
1976 graphics->Add(new SettingEntry("gui.graph_line_thickness"));
1979 SettingsPage *sound = main->Add(new SettingsPage(STR_CONFIG_SETTING_SOUND));
1981 sound->Add(new SettingEntry("sound.click_beep"));
1982 sound->Add(new SettingEntry("sound.confirm"));
1983 sound->Add(new SettingEntry("sound.news_ticker"));
1984 sound->Add(new SettingEntry("sound.news_full"));
1985 sound->Add(new SettingEntry("sound.new_year"));
1986 sound->Add(new SettingEntry("sound.disaster"));
1987 sound->Add(new SettingEntry("sound.vehicle"));
1988 sound->Add(new SettingEntry("sound.ambient"));
1991 SettingsPage *interface = main->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE));
1993 SettingsPage *general = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_GENERAL));
1995 general->Add(new SettingEntry("gui.osk_activation"));
1996 general->Add(new SettingEntry("gui.hover_delay_ms"));
1997 general->Add(new SettingEntry("gui.errmsg_duration"));
1998 general->Add(new SettingEntry("gui.window_snap_radius"));
1999 general->Add(new SettingEntry("gui.window_soft_limit"));
2000 general->Add(new SettingEntry("gui.right_click_wnd_close"));
2003 SettingsPage *viewports = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS));
2005 viewports->Add(new SettingEntry("gui.auto_scrolling"));
2006 viewports->Add(new SettingEntry("gui.scroll_mode"));
2007 viewports->Add(new SettingEntry("gui.smooth_scroll"));
2008 /* While the horizontal scrollwheel scrolling is written as general code, only
2009 * the cocoa (OSX) driver generates input for it.
2010 * Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
2011 viewports->Add(new SettingEntry("gui.scrollwheel_scrolling"));
2012 viewports->Add(new SettingEntry("gui.scrollwheel_multiplier"));
2013 #ifdef __APPLE__
2014 /* We might need to emulate a right mouse button on mac */
2015 viewports->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
2016 #endif
2017 viewports->Add(new SettingEntry("gui.population_in_label"));
2018 viewports->Add(new SettingEntry("gui.liveries"));
2019 viewports->Add(new SettingEntry("construction.train_signal_side"));
2020 viewports->Add(new SettingEntry("gui.measure_tooltip"));
2021 viewports->Add(new SettingEntry("gui.loading_indicators"));
2022 viewports->Add(new SettingEntry("gui.show_track_reservation"));
2025 SettingsPage *construction = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION));
2027 construction->Add(new SettingEntry("gui.link_terraform_toolbar"));
2028 construction->Add(new SettingEntry("gui.persistent_buildingtools"));
2029 construction->Add(new SettingEntry("gui.default_rail_type"));
2030 construction->Add(new SettingEntry("gui.semaphore_build_before"));
2031 construction->Add(new SettingEntry("gui.signal_gui_mode"));
2032 construction->Add(new SettingEntry("gui.cycle_signal_types"));
2033 construction->Add(new SettingEntry("gui.drag_signals_fixed_distance"));
2034 construction->Add(new SettingEntry("gui.auto_remove_signals"));
2037 interface->Add(new SettingEntry("gui.toolbar_pos"));
2038 interface->Add(new SettingEntry("gui.statusbar_pos"));
2039 interface->Add(new SettingEntry("gui.prefer_teamchat"));
2040 interface->Add(new SettingEntry("gui.advanced_vehicle_list"));
2041 interface->Add(new SettingEntry("gui.timetable_mode"));
2042 interface->Add(new SettingEntry("gui.timetable_arrival_departure"));
2043 interface->Add(new SettingEntry("gui.show_newgrf_name"));
2044 interface->Add(new SettingEntry("gui.show_cargo_in_vehicle_lists"));
2047 SettingsPage *advisors = main->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS));
2049 advisors->Add(new SettingEntry("gui.coloured_news_year"));
2050 advisors->Add(new SettingEntry("news_display.general"));
2051 advisors->Add(new SettingEntry("news_display.new_vehicles"));
2052 advisors->Add(new SettingEntry("news_display.accident"));
2053 advisors->Add(new SettingEntry("news_display.accident_other"));
2054 advisors->Add(new SettingEntry("news_display.company_info"));
2055 advisors->Add(new SettingEntry("news_display.acceptance"));
2056 advisors->Add(new SettingEntry("news_display.arrival_player"));
2057 advisors->Add(new SettingEntry("news_display.arrival_other"));
2058 advisors->Add(new SettingEntry("news_display.advice"));
2059 advisors->Add(new SettingEntry("gui.order_review_system"));
2060 advisors->Add(new SettingEntry("gui.vehicle_income_warn"));
2061 advisors->Add(new SettingEntry("gui.lost_vehicle_warn"));
2062 advisors->Add(new SettingEntry("gui.show_finances"));
2063 advisors->Add(new SettingEntry("news_display.economy"));
2064 advisors->Add(new SettingEntry("news_display.subsidies"));
2065 advisors->Add(new SettingEntry("news_display.open"));
2066 advisors->Add(new SettingEntry("news_display.close"));
2067 advisors->Add(new SettingEntry("news_display.production_player"));
2068 advisors->Add(new SettingEntry("news_display.production_other"));
2069 advisors->Add(new SettingEntry("news_display.production_nobody"));
2072 SettingsPage *company = main->Add(new SettingsPage(STR_CONFIG_SETTING_COMPANY));
2074 company->Add(new SettingEntry("gui.starting_colour"));
2075 company->Add(new SettingEntry("gui.starting_colour_secondary"));
2076 company->Add(new SettingEntry("company.engine_renew"));
2077 company->Add(new SettingEntry("company.engine_renew_months"));
2078 company->Add(new SettingEntry("company.engine_renew_money"));
2079 company->Add(new SettingEntry("vehicle.servint_ispercent"));
2080 company->Add(new SettingEntry("vehicle.servint_trains"));
2081 company->Add(new SettingEntry("vehicle.servint_roadveh"));
2082 company->Add(new SettingEntry("vehicle.servint_ships"));
2083 company->Add(new SettingEntry("vehicle.servint_aircraft"));
2086 SettingsPage *accounting = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING));
2088 accounting->Add(new SettingEntry("difficulty.infinite_money"));
2089 accounting->Add(new SettingEntry("economy.inflation"));
2090 accounting->Add(new SettingEntry("difficulty.initial_interest"));
2091 accounting->Add(new SettingEntry("difficulty.max_loan"));
2092 accounting->Add(new SettingEntry("difficulty.subsidy_multiplier"));
2093 accounting->Add(new SettingEntry("difficulty.subsidy_duration"));
2094 accounting->Add(new SettingEntry("economy.feeder_payment_share"));
2095 accounting->Add(new SettingEntry("economy.infrastructure_maintenance"));
2096 accounting->Add(new SettingEntry("difficulty.vehicle_costs"));
2097 accounting->Add(new SettingEntry("difficulty.construction_cost"));
2100 SettingsPage *vehicles = main->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES));
2102 SettingsPage *physics = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS));
2104 physics->Add(new SettingEntry("vehicle.train_acceleration_model"));
2105 physics->Add(new SettingEntry("vehicle.train_slope_steepness"));
2106 physics->Add(new SettingEntry("vehicle.wagon_speed_limits"));
2107 physics->Add(new SettingEntry("vehicle.freight_trains"));
2108 physics->Add(new SettingEntry("vehicle.roadveh_acceleration_model"));
2109 physics->Add(new SettingEntry("vehicle.roadveh_slope_steepness"));
2110 physics->Add(new SettingEntry("vehicle.smoke_amount"));
2111 physics->Add(new SettingEntry("vehicle.plane_speed"));
2114 SettingsPage *routing = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ROUTING));
2116 routing->Add(new SettingEntry("vehicle.road_side"));
2117 routing->Add(new SettingEntry("pf.pathfinder_for_trains"));
2118 routing->Add(new SettingEntry("difficulty.line_reverse_mode"));
2119 routing->Add(new SettingEntry("pf.reverse_at_signals"));
2120 routing->Add(new SettingEntry("pf.forbid_90_deg"));
2121 routing->Add(new SettingEntry("pf.pathfinder_for_roadvehs"));
2122 routing->Add(new SettingEntry("pf.pathfinder_for_ships"));
2125 SettingsPage *orders = vehicles->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ORDERS));
2127 orders->Add(new SettingEntry("gui.new_nonstop"));
2128 orders->Add(new SettingEntry("gui.quick_goto"));
2129 orders->Add(new SettingEntry("gui.stop_location"));
2133 SettingsPage *limitations = main->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS));
2135 limitations->Add(new SettingEntry("construction.command_pause_level"));
2136 limitations->Add(new SettingEntry("construction.autoslope"));
2137 limitations->Add(new SettingEntry("construction.extra_dynamite"));
2138 limitations->Add(new SettingEntry("construction.map_height_limit"));
2139 limitations->Add(new SettingEntry("construction.max_bridge_length"));
2140 limitations->Add(new SettingEntry("construction.max_bridge_height"));
2141 limitations->Add(new SettingEntry("construction.max_tunnel_length"));
2142 limitations->Add(new SettingEntry("station.never_expire_airports"));
2143 limitations->Add(new SettingEntry("vehicle.never_expire_vehicles"));
2144 limitations->Add(new SettingEntry("vehicle.max_trains"));
2145 limitations->Add(new SettingEntry("vehicle.max_roadveh"));
2146 limitations->Add(new SettingEntry("vehicle.max_aircraft"));
2147 limitations->Add(new SettingEntry("vehicle.max_ships"));
2148 limitations->Add(new SettingEntry("vehicle.max_train_length"));
2149 limitations->Add(new SettingEntry("station.station_spread"));
2150 limitations->Add(new SettingEntry("station.distant_join_stations"));
2151 limitations->Add(new SettingEntry("station.modified_catchment"));
2152 limitations->Add(new SettingEntry("construction.road_stop_on_town_road"));
2153 limitations->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
2154 limitations->Add(new SettingEntry("construction.crossing_with_competitor"));
2155 limitations->Add(new SettingEntry("vehicle.disable_elrails"));
2156 limitations->Add(new SettingEntry("order.station_length_loading_penalty"));
2159 SettingsPage *disasters = main->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS));
2161 disasters->Add(new SettingEntry("difficulty.disasters"));
2162 disasters->Add(new SettingEntry("difficulty.economy"));
2163 disasters->Add(new SettingEntry("vehicle.plane_crashes"));
2164 disasters->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
2165 disasters->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
2166 disasters->Add(new SettingEntry("order.serviceathelipad"));
2169 SettingsPage *genworld = main->Add(new SettingsPage(STR_CONFIG_SETTING_GENWORLD));
2171 genworld->Add(new SettingEntry("game_creation.landscape"));
2172 genworld->Add(new SettingEntry("game_creation.land_generator"));
2173 genworld->Add(new SettingEntry("difficulty.terrain_type"));
2174 genworld->Add(new SettingEntry("game_creation.tgen_smoothness"));
2175 genworld->Add(new SettingEntry("game_creation.variety"));
2176 genworld->Add(new SettingEntry("game_creation.snow_coverage"));
2177 genworld->Add(new SettingEntry("game_creation.snow_line_height"));
2178 genworld->Add(new SettingEntry("game_creation.desert_coverage"));
2179 genworld->Add(new SettingEntry("game_creation.amount_of_rivers"));
2182 SettingsPage *environment = main->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT));
2184 SettingsPage *time = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TIME));
2186 time->Add(new SettingEntry("economy.timekeeping_units"));
2187 time->Add(new SettingEntry("economy.minutes_per_calendar_year"));
2188 time->Add(new SettingEntry("game_creation.ending_year"));
2189 time->Add(new SettingEntry("gui.pause_on_newgame"));
2190 time->Add(new SettingEntry("gui.fast_forward_speed_limit"));
2193 SettingsPage *authorities = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES));
2195 authorities->Add(new SettingEntry("difficulty.town_council_tolerance"));
2196 authorities->Add(new SettingEntry("economy.bribe"));
2197 authorities->Add(new SettingEntry("economy.exclusive_rights"));
2198 authorities->Add(new SettingEntry("economy.fund_roads"));
2199 authorities->Add(new SettingEntry("economy.fund_buildings"));
2200 authorities->Add(new SettingEntry("economy.station_noise_level"));
2203 SettingsPage *towns = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TOWNS));
2205 towns->Add(new SettingEntry("economy.town_cargo_scale"));
2206 towns->Add(new SettingEntry("economy.town_growth_rate"));
2207 towns->Add(new SettingEntry("economy.allow_town_roads"));
2208 towns->Add(new SettingEntry("economy.allow_town_level_crossings"));
2209 towns->Add(new SettingEntry("economy.found_town"));
2210 towns->Add(new SettingEntry("economy.town_layout"));
2211 towns->Add(new SettingEntry("economy.larger_towns"));
2212 towns->Add(new SettingEntry("economy.initial_city_size"));
2213 towns->Add(new SettingEntry("economy.town_cargogen_mode"));
2216 SettingsPage *industries = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES));
2218 industries->Add(new SettingEntry("economy.industry_cargo_scale"));
2219 industries->Add(new SettingEntry("difficulty.industry_density"));
2220 industries->Add(new SettingEntry("construction.raw_industry_construction"));
2221 industries->Add(new SettingEntry("construction.industry_platform"));
2222 industries->Add(new SettingEntry("economy.multiple_industry_per_town"));
2223 industries->Add(new SettingEntry("game_creation.oil_refinery_limit"));
2224 industries->Add(new SettingEntry("economy.type"));
2225 industries->Add(new SettingEntry("station.serve_neutral_industries"));
2228 SettingsPage *cdist = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST));
2230 cdist->Add(new SettingEntry("linkgraph.recalc_time"));
2231 cdist->Add(new SettingEntry("linkgraph.recalc_interval"));
2232 cdist->Add(new SettingEntry("linkgraph.distribution_pax"));
2233 cdist->Add(new SettingEntry("linkgraph.distribution_mail"));
2234 cdist->Add(new SettingEntry("linkgraph.distribution_armoured"));
2235 cdist->Add(new SettingEntry("linkgraph.distribution_default"));
2236 cdist->Add(new SettingEntry("linkgraph.accuracy"));
2237 cdist->Add(new SettingEntry("linkgraph.demand_distance"));
2238 cdist->Add(new SettingEntry("linkgraph.demand_size"));
2239 cdist->Add(new SettingEntry("linkgraph.short_path_saturation"));
2242 SettingsPage *trees = environment->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TREES));
2244 trees->Add(new SettingEntry("game_creation.tree_placer"));
2245 trees->Add(new SettingEntry("construction.extra_tree_placement"));
2249 SettingsPage *ai = main->Add(new SettingsPage(STR_CONFIG_SETTING_AI));
2251 SettingsPage *npc = ai->Add(new SettingsPage(STR_CONFIG_SETTING_AI_NPC));
2253 npc->Add(new SettingEntry("script.script_max_opcode_till_suspend"));
2254 npc->Add(new SettingEntry("script.script_max_memory_megabytes"));
2255 npc->Add(new SettingEntry("difficulty.competitor_speed"));
2256 npc->Add(new SettingEntry("ai.ai_in_multiplayer"));
2257 npc->Add(new SettingEntry("ai.ai_disable_veh_train"));
2258 npc->Add(new SettingEntry("ai.ai_disable_veh_roadveh"));
2259 npc->Add(new SettingEntry("ai.ai_disable_veh_aircraft"));
2260 npc->Add(new SettingEntry("ai.ai_disable_veh_ship"));
2263 ai->Add(new SettingEntry("economy.give_money"));
2266 SettingsPage *network = main->Add(new SettingsPage(STR_CONFIG_SETTING_NETWORK));
2268 network->Add(new SettingEntry("network.use_relay_service"));
2271 main->Init();
2273 return *main;
2276 static const StringID _game_settings_restrict_dropdown[] = {
2277 STR_CONFIG_SETTING_RESTRICT_BASIC, // RM_BASIC
2278 STR_CONFIG_SETTING_RESTRICT_ADVANCED, // RM_ADVANCED
2279 STR_CONFIG_SETTING_RESTRICT_ALL, // RM_ALL
2280 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_DEFAULT, // RM_CHANGED_AGAINST_DEFAULT
2281 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_NEW, // RM_CHANGED_AGAINST_NEW
2283 static_assert(lengthof(_game_settings_restrict_dropdown) == RM_END);
2285 /** Warnings about hidden search results. */
2286 enum WarnHiddenResult {
2287 WHR_NONE, ///< Nothing was filtering matches away.
2288 WHR_CATEGORY, ///< Category setting filtered matches away.
2289 WHR_TYPE, ///< Type setting filtered matches away.
2290 WHR_CATEGORY_TYPE, ///< Both category and type settings filtered matches away.
2294 * Callback function for the reset all settings button
2295 * @param w Window which is calling this callback
2296 * @param confirmed boolean value, true when yes was clicked, false otherwise
2298 static void ResetAllSettingsConfirmationCallback(Window *w, bool confirmed)
2300 if (confirmed) {
2301 GetSettingsTree().ResetAll();
2302 GetSettingsTree().FoldAll();
2303 w->InvalidateData();
2307 /** Window to edit settings of the game. */
2308 struct GameSettingsWindow : Window {
2309 static GameSettings *settings_ptr; ///< Pointer to the game settings being displayed and modified.
2311 SettingEntry *valuewindow_entry; ///< If non-nullptr, pointer to setting for which a value-entering window has been opened.
2312 SettingEntry *clicked_entry; ///< If non-nullptr, pointer to a clicked numeric setting (with a depressed left or right button).
2313 SettingEntry *last_clicked; ///< If non-nullptr, pointer to the last clicked setting.
2314 SettingEntry *valuedropdown_entry; ///< If non-nullptr, pointer to the value for which a dropdown window is currently opened.
2315 bool closing_dropdown; ///< True, if the dropdown list is currently closing.
2317 SettingFilter filter; ///< Filter for the list.
2318 QueryString filter_editbox; ///< Filter editbox;
2319 bool manually_changed_folding; ///< Whether the user expanded/collapsed something manually.
2320 WarnHiddenResult warn_missing; ///< Whether and how to warn about missing search results.
2321 int warn_lines; ///< Number of lines used for warning about missing search results.
2323 Scrollbar *vscroll;
2325 GameSettingsWindow(WindowDesc *desc) : Window(desc), filter_editbox(50)
2327 this->warn_missing = WHR_NONE;
2328 this->warn_lines = 0;
2329 this->filter.mode = (RestrictionMode)_settings_client.gui.settings_restriction_mode;
2330 this->filter.min_cat = RM_ALL;
2331 this->filter.type = ST_ALL;
2332 this->filter.type_hides = false;
2333 this->settings_ptr = &GetGameSettings();
2335 GetSettingsTree().FoldAll(); // Close all sub-pages
2337 this->valuewindow_entry = nullptr; // No setting entry for which a entry window is opened
2338 this->clicked_entry = nullptr; // No numeric setting buttons are depressed
2339 this->last_clicked = nullptr;
2340 this->valuedropdown_entry = nullptr;
2341 this->closing_dropdown = false;
2342 this->manually_changed_folding = false;
2344 this->CreateNestedTree();
2345 this->vscroll = this->GetScrollbar(WID_GS_SCROLLBAR);
2346 this->FinishInitNested(WN_GAME_OPTIONS_GAME_SETTINGS);
2348 this->querystrings[WID_GS_FILTER] = &this->filter_editbox;
2349 this->filter_editbox.cancel_button = QueryString::ACTION_CLEAR;
2350 this->SetFocusedWidget(WID_GS_FILTER);
2352 this->InvalidateData();
2355 void OnInit() override
2357 _circle_size = maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED), GetSpriteSize(SPR_CIRCLE_UNFOLDED));
2360 void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
2362 switch (widget) {
2363 case WID_GS_OPTIONSPANEL:
2364 resize->height = SETTING_HEIGHT = std::max({(int)_circle_size.height, SETTING_BUTTON_HEIGHT, GetCharacterHeight(FS_NORMAL)}) + WidgetDimensions::scaled.vsep_normal;
2365 resize->width = 1;
2367 size->height = 5 * resize->height + WidgetDimensions::scaled.framerect.Vertical();
2368 break;
2370 case WID_GS_HELP_TEXT: {
2371 static const StringID setting_types[] = {
2372 STR_CONFIG_SETTING_TYPE_CLIENT,
2373 STR_CONFIG_SETTING_TYPE_COMPANY_MENU, STR_CONFIG_SETTING_TYPE_COMPANY_INGAME,
2374 STR_CONFIG_SETTING_TYPE_GAME_MENU, STR_CONFIG_SETTING_TYPE_GAME_INGAME,
2376 for (uint i = 0; i < lengthof(setting_types); i++) {
2377 SetDParam(0, setting_types[i]);
2378 size->width = std::max(size->width, GetStringBoundingBox(STR_CONFIG_SETTING_TYPE).width + padding.width);
2380 size->height = 2 * GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal +
2381 std::max(size->height, GetSettingsTree().GetMaxHelpHeight(size->width));
2382 break;
2385 case WID_GS_RESTRICT_CATEGORY:
2386 case WID_GS_RESTRICT_TYPE:
2387 size->width = std::max(GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_CATEGORY).width, GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_TYPE).width);
2388 break;
2390 default:
2391 break;
2395 void OnPaint() override
2397 if (this->closing_dropdown) {
2398 this->closing_dropdown = false;
2399 assert(this->valuedropdown_entry != nullptr);
2400 this->valuedropdown_entry->SetButtons(0);
2401 this->valuedropdown_entry = nullptr;
2404 /* Reserve the correct number of lines for the 'some search results are hidden' notice in the central settings display panel. */
2405 const Rect panel = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL)->GetCurrentRect().Shrink(WidgetDimensions::scaled.frametext);
2406 StringID warn_str = STR_CONFIG_SETTING_CATEGORY_HIDES - 1 + this->warn_missing;
2407 int new_warn_lines;
2408 if (this->warn_missing == WHR_NONE) {
2409 new_warn_lines = 0;
2410 } else {
2411 SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
2412 new_warn_lines = GetStringLineCount(warn_str, panel.Width());
2414 if (this->warn_lines != new_warn_lines) {
2415 this->vscroll->SetCount(this->vscroll->GetCount() - this->warn_lines + new_warn_lines);
2416 this->warn_lines = new_warn_lines;
2419 this->DrawWidgets();
2421 /* Draw the 'some search results are hidden' notice. */
2422 if (this->warn_missing != WHR_NONE) {
2423 SetDParam(0, _game_settings_restrict_dropdown[this->filter.min_cat]);
2424 DrawStringMultiLine(panel.WithHeight(this->warn_lines * GetCharacterHeight(FS_NORMAL)), warn_str, TC_FROMSTRING, SA_CENTER);
2428 void SetStringParameters(WidgetID widget) const override
2430 switch (widget) {
2431 case WID_GS_RESTRICT_DROPDOWN:
2432 SetDParam(0, _game_settings_restrict_dropdown[this->filter.mode]);
2433 break;
2435 case WID_GS_TYPE_DROPDOWN:
2436 switch (this->filter.type) {
2437 case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME); break;
2438 case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME); break;
2439 case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT); break;
2440 default: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL); break;
2442 break;
2446 DropDownList BuildDropDownList(WidgetID widget) const
2448 DropDownList list;
2449 switch (widget) {
2450 case WID_GS_RESTRICT_DROPDOWN:
2451 for (int mode = 0; mode != RM_END; mode++) {
2452 /* If we are in adv. settings screen for the new game's settings,
2453 * we don't want to allow comparing with new game's settings. */
2454 bool disabled = mode == RM_CHANGED_AGAINST_NEW && settings_ptr == &_settings_newgame;
2456 list.push_back(std::make_unique<DropDownListStringItem>(_game_settings_restrict_dropdown[mode], mode, disabled));
2458 break;
2460 case WID_GS_TYPE_DROPDOWN:
2461 list.push_back(std::make_unique<DropDownListStringItem>(STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL, ST_ALL, false));
2462 list.push_back(std::make_unique<DropDownListStringItem>(_game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME, ST_GAME, false));
2463 list.push_back(std::make_unique<DropDownListStringItem>(_game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME, ST_COMPANY, false));
2464 list.push_back(std::make_unique<DropDownListStringItem>(STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT, ST_CLIENT, false));
2465 break;
2467 return list;
2470 void DrawWidget(const Rect &r, WidgetID widget) const override
2472 switch (widget) {
2473 case WID_GS_OPTIONSPANEL: {
2474 Rect tr = r.Shrink(WidgetDimensions::scaled.frametext, WidgetDimensions::scaled.framerect);
2475 tr.top += this->warn_lines * SETTING_HEIGHT;
2476 uint last_row = this->vscroll->GetPosition() + this->vscroll->GetCapacity() - this->warn_lines;
2477 int next_row = GetSettingsTree().Draw(settings_ptr, tr.left, tr.right, tr.top,
2478 this->vscroll->GetPosition(), last_row, this->last_clicked);
2479 if (next_row == 0) DrawString(tr, STR_CONFIG_SETTINGS_NONE);
2480 break;
2483 case WID_GS_HELP_TEXT:
2484 if (this->last_clicked != nullptr) {
2485 const IntSettingDesc *sd = this->last_clicked->setting;
2487 Rect tr = r;
2488 switch (sd->GetType()) {
2489 case ST_COMPANY: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_COMPANY_MENU : STR_CONFIG_SETTING_TYPE_COMPANY_INGAME); break;
2490 case ST_CLIENT: SetDParam(0, STR_CONFIG_SETTING_TYPE_CLIENT); break;
2491 case ST_GAME: SetDParam(0, _game_mode == GM_MENU ? STR_CONFIG_SETTING_TYPE_GAME_MENU : STR_CONFIG_SETTING_TYPE_GAME_INGAME); break;
2492 default: NOT_REACHED();
2494 DrawString(tr, STR_CONFIG_SETTING_TYPE);
2495 tr.top += GetCharacterHeight(FS_NORMAL);
2497 sd->SetValueDParams(0, sd->def);
2498 DrawString(tr, STR_CONFIG_SETTING_DEFAULT_VALUE);
2499 tr.top += GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal;
2501 DrawStringMultiLine(tr, sd->GetHelp(), TC_WHITE);
2503 break;
2505 default:
2506 break;
2511 * Set the entry that should have its help text displayed, and mark the window dirty so it gets repainted.
2512 * @param pe Setting to display help text of, use \c nullptr to stop displaying help of the currently displayed setting.
2514 void SetDisplayedHelpText(SettingEntry *pe)
2516 if (this->last_clicked != pe) this->SetDirty();
2517 this->last_clicked = pe;
2520 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
2522 switch (widget) {
2523 case WID_GS_EXPAND_ALL:
2524 this->manually_changed_folding = true;
2525 GetSettingsTree().UnFoldAll();
2526 this->InvalidateData();
2527 break;
2529 case WID_GS_COLLAPSE_ALL:
2530 this->manually_changed_folding = true;
2531 GetSettingsTree().FoldAll();
2532 this->InvalidateData();
2533 break;
2535 case WID_GS_RESET_ALL:
2536 ShowQuery(
2537 STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_CAPTION,
2538 STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_TEXT,
2539 this,
2540 ResetAllSettingsConfirmationCallback
2542 break;
2544 case WID_GS_RESTRICT_DROPDOWN: {
2545 DropDownList list = this->BuildDropDownList(widget);
2546 if (!list.empty()) {
2547 ShowDropDownList(this, std::move(list), this->filter.mode, widget);
2549 break;
2552 case WID_GS_TYPE_DROPDOWN: {
2553 DropDownList list = this->BuildDropDownList(widget);
2554 if (!list.empty()) {
2555 ShowDropDownList(this, std::move(list), this->filter.type, widget);
2557 break;
2561 if (widget != WID_GS_OPTIONSPANEL) return;
2563 uint btn = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_GS_OPTIONSPANEL, WidgetDimensions::scaled.framerect.top);
2564 if (btn == INT_MAX || (int)btn < this->warn_lines) return;
2565 btn -= this->warn_lines;
2567 uint cur_row = 0;
2568 BaseSettingEntry *clicked_entry = GetSettingsTree().FindEntry(btn, &cur_row);
2570 if (clicked_entry == nullptr) return; // Clicked below the last setting of the page
2572 int x = (_current_text_dir == TD_RTL ? this->width - 1 - pt.x : pt.x) - WidgetDimensions::scaled.frametext.left - (clicked_entry->level + 1) * WidgetDimensions::scaled.hsep_indent; // Shift x coordinate
2573 if (x < 0) return; // Clicked left of the entry
2575 SettingsPage *clicked_page = dynamic_cast<SettingsPage*>(clicked_entry);
2576 if (clicked_page != nullptr) {
2577 this->SetDisplayedHelpText(nullptr);
2578 clicked_page->folded = !clicked_page->folded; // Flip 'folded'-ness of the sub-page
2580 this->manually_changed_folding = true;
2582 this->InvalidateData();
2583 return;
2586 SettingEntry *pe = dynamic_cast<SettingEntry*>(clicked_entry);
2587 assert(pe != nullptr);
2588 const IntSettingDesc *sd = pe->setting;
2590 /* return if action is only active in network, or only settable by server */
2591 if (!sd->IsEditable()) {
2592 this->SetDisplayedHelpText(pe);
2593 return;
2596 int32_t value = sd->Read(ResolveObject(settings_ptr, sd));
2598 /* clicked on the icon on the left side. Either scroller, bool on/off or dropdown */
2599 if (x < SETTING_BUTTON_WIDTH && (sd->flags & SF_GUI_DROPDOWN)) {
2600 this->SetDisplayedHelpText(pe);
2602 if (this->valuedropdown_entry == pe) {
2603 /* unclick the dropdown */
2604 this->CloseChildWindows(WC_DROPDOWN_MENU);
2605 this->closing_dropdown = false;
2606 this->valuedropdown_entry->SetButtons(0);
2607 this->valuedropdown_entry = nullptr;
2608 } else {
2609 if (this->valuedropdown_entry != nullptr) this->valuedropdown_entry->SetButtons(0);
2610 this->closing_dropdown = false;
2612 const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_GS_OPTIONSPANEL);
2613 int rel_y = (pt.y - wid->pos_y - WidgetDimensions::scaled.framerect.top) % wid->resize_y;
2615 Rect wi_rect;
2616 wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
2617 wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
2618 wi_rect.top = pt.y - rel_y + (SETTING_HEIGHT - SETTING_BUTTON_HEIGHT) / 2;
2619 wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
2621 /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
2622 if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
2623 this->valuedropdown_entry = pe;
2624 this->valuedropdown_entry->SetButtons(SEF_LEFT_DEPRESSED);
2626 DropDownList list;
2627 for (int i = sd->min; i <= (int)sd->max; i++) {
2628 sd->SetValueDParams(0, i);
2629 list.push_back(std::make_unique<DropDownListStringItem>(STR_JUST_STRING2, i, false));
2632 ShowDropDownListAt(this, std::move(list), value, WID_GS_SETTING_DROPDOWN, wi_rect, COLOUR_ORANGE);
2635 this->SetDirty();
2636 } else if (x < SETTING_BUTTON_WIDTH) {
2637 this->SetDisplayedHelpText(pe);
2638 int32_t oldvalue = value;
2640 if (sd->IsBoolSetting()) {
2641 value ^= 1;
2642 } else {
2643 /* Add a dynamic step-size to the scroller. In a maximum of
2644 * 50-steps you should be able to get from min to max,
2645 * unless specified otherwise in the 'interval' variable
2646 * of the current setting. */
2647 uint32_t step = (sd->interval == 0) ? ((sd->max - sd->min) / 50) : sd->interval;
2648 if (step == 0) step = 1;
2650 /* don't allow too fast scrolling */
2651 if ((this->flags & WF_TIMEOUT) && this->timeout_timer > 1) {
2652 _left_button_clicked = false;
2653 return;
2656 /* Increase or decrease the value and clamp it to extremes */
2657 if (x >= SETTING_BUTTON_WIDTH / 2) {
2658 value += step;
2659 if (sd->min < 0) {
2660 assert((int32_t)sd->max >= 0);
2661 if (value > (int32_t)sd->max) value = (int32_t)sd->max;
2662 } else {
2663 if ((uint32_t)value > sd->max) value = (int32_t)sd->max;
2665 if (value < sd->min) value = sd->min; // skip between "disabled" and minimum
2666 } else {
2667 value -= step;
2668 if (value < sd->min) value = (sd->flags & SF_GUI_0_IS_SPECIAL) ? 0 : sd->min;
2671 /* Set up scroller timeout for numeric values */
2672 if (value != oldvalue) {
2673 if (this->clicked_entry != nullptr) { // Release previous buttons if any
2674 this->clicked_entry->SetButtons(0);
2676 this->clicked_entry = pe;
2677 this->clicked_entry->SetButtons((x >= SETTING_BUTTON_WIDTH / 2) != (_current_text_dir == TD_RTL) ? SEF_RIGHT_DEPRESSED : SEF_LEFT_DEPRESSED);
2678 this->SetTimeout();
2679 _left_button_clicked = false;
2683 if (value != oldvalue) {
2684 SetSettingValue(sd, value);
2685 this->SetDirty();
2687 } else {
2688 /* Only open editbox if clicked for the second time, and only for types where it is sensible for. */
2689 if (this->last_clicked == pe && !sd->IsBoolSetting() && !(sd->flags & SF_GUI_DROPDOWN)) {
2690 int64_t value64 = value;
2691 /* Show the correct currency-translated value */
2692 if (sd->flags & SF_GUI_CURRENCY) value64 *= _currency->rate;
2694 CharSetFilter charset_filter = CS_NUMERAL; //default, only numeric input allowed
2695 if (sd->min < 0) charset_filter = CS_NUMERAL_SIGNED; // special case, also allow '-' sign for negative input
2697 this->valuewindow_entry = pe;
2698 SetDParam(0, value64);
2699 /* Limit string length to 14 so that MAX_INT32 * max currency rate doesn't exceed MAX_INT64. */
2700 ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 15, this, charset_filter, QSF_ENABLE_DEFAULT);
2702 this->SetDisplayedHelpText(pe);
2706 void OnTimeout() override
2708 if (this->clicked_entry != nullptr) { // On timeout, release any depressed buttons
2709 this->clicked_entry->SetButtons(0);
2710 this->clicked_entry = nullptr;
2711 this->SetDirty();
2715 void OnQueryTextFinished(char *str) override
2717 /* The user pressed cancel */
2718 if (str == nullptr) return;
2720 assert(this->valuewindow_entry != nullptr);
2721 const IntSettingDesc *sd = this->valuewindow_entry->setting;
2723 int32_t value;
2724 if (!StrEmpty(str)) {
2725 long long llvalue = atoll(str);
2727 /* Save the correct currency-translated value */
2728 if (sd->flags & SF_GUI_CURRENCY) llvalue /= _currency->rate;
2730 value = ClampTo<int32_t>(llvalue);
2731 } else {
2732 value = sd->def;
2735 SetSettingValue(this->valuewindow_entry->setting, value);
2736 this->SetDirty();
2739 void OnDropdownSelect(WidgetID widget, int index) override
2741 switch (widget) {
2742 case WID_GS_RESTRICT_DROPDOWN:
2743 this->filter.mode = (RestrictionMode)index;
2744 if (this->filter.mode == RM_CHANGED_AGAINST_DEFAULT ||
2745 this->filter.mode == RM_CHANGED_AGAINST_NEW) {
2747 if (!this->manually_changed_folding) {
2748 /* Expand all when selecting 'changes'. Update the filter state first, in case it becomes less restrictive in some cases. */
2749 GetSettingsTree().UpdateFilterState(this->filter, false);
2750 GetSettingsTree().UnFoldAll();
2752 } else {
2753 /* Non-'changes' filter. Save as default. */
2754 _settings_client.gui.settings_restriction_mode = this->filter.mode;
2756 this->InvalidateData();
2757 break;
2759 case WID_GS_TYPE_DROPDOWN:
2760 this->filter.type = (SettingType)index;
2761 this->InvalidateData();
2762 break;
2764 case WID_GS_SETTING_DROPDOWN:
2765 /* Deal with drop down boxes on the panel. */
2766 assert(this->valuedropdown_entry != nullptr);
2767 const IntSettingDesc *sd = this->valuedropdown_entry->setting;
2768 assert(sd->flags & SF_GUI_DROPDOWN);
2770 SetSettingValue(sd, index);
2771 this->SetDirty();
2772 break;
2776 void OnDropdownClose(Point pt, WidgetID widget, int index, bool instant_close) override
2778 if (widget != WID_GS_SETTING_DROPDOWN) {
2779 /* Normally the default implementation of OnDropdownClose() takes care of
2780 * a few things. We want that behaviour here too, but only for
2781 * "normal" dropdown boxes. The special dropdown boxes added for every
2782 * setting that needs one can't have this call. */
2783 Window::OnDropdownClose(pt, widget, index, instant_close);
2784 } else {
2785 /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
2786 * the same dropdown button was clicked again, and then not open the dropdown again.
2787 * So, we only remember that it was closed, and process it on the next OnPaint, which is
2788 * after OnClick. */
2789 assert(this->valuedropdown_entry != nullptr);
2790 this->closing_dropdown = true;
2791 this->SetDirty();
2795 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
2797 if (!gui_scope) return;
2799 /* Update which settings are to be visible. */
2800 RestrictionMode min_level = (this->filter.mode <= RM_ALL) ? this->filter.mode : RM_BASIC;
2801 this->filter.min_cat = min_level;
2802 this->filter.type_hides = false;
2803 GetSettingsTree().UpdateFilterState(this->filter, false);
2805 if (this->filter.string.IsEmpty()) {
2806 this->warn_missing = WHR_NONE;
2807 } else if (min_level < this->filter.min_cat) {
2808 this->warn_missing = this->filter.type_hides ? WHR_CATEGORY_TYPE : WHR_CATEGORY;
2809 } else {
2810 this->warn_missing = this->filter.type_hides ? WHR_TYPE : WHR_NONE;
2812 this->vscroll->SetCount(GetSettingsTree().Length() + this->warn_lines);
2814 if (this->last_clicked != nullptr && !GetSettingsTree().IsVisible(this->last_clicked)) {
2815 this->SetDisplayedHelpText(nullptr);
2818 bool all_folded = true;
2819 bool all_unfolded = true;
2820 GetSettingsTree().GetFoldingState(all_folded, all_unfolded);
2821 this->SetWidgetDisabledState(WID_GS_EXPAND_ALL, all_unfolded);
2822 this->SetWidgetDisabledState(WID_GS_COLLAPSE_ALL, all_folded);
2825 void OnEditboxChanged(WidgetID wid) override
2827 if (wid == WID_GS_FILTER) {
2828 this->filter.string.SetFilterTerm(this->filter_editbox.text.buf);
2829 if (!this->filter.string.IsEmpty() && !this->manually_changed_folding) {
2830 /* User never expanded/collapsed single pages and entered a filter term.
2831 * Expand everything, to save weird expand clicks, */
2832 GetSettingsTree().UnFoldAll();
2834 this->InvalidateData();
2838 void OnResize() override
2840 this->vscroll->SetCapacityFromWidget(this, WID_GS_OPTIONSPANEL, WidgetDimensions::scaled.framerect.Vertical());
2844 GameSettings *GameSettingsWindow::settings_ptr = nullptr;
2846 static constexpr NWidgetPart _nested_settings_selection_widgets[] = {
2847 NWidget(NWID_HORIZONTAL),
2848 NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
2849 NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_CONFIG_SETTING_TREE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
2850 NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
2851 EndContainer(),
2852 NWidget(WWT_PANEL, COLOUR_MAUVE),
2853 NWidget(NWID_VERTICAL), SetPIP(WidgetDimensions::unscaled.frametext.top, WidgetDimensions::unscaled.vsep_normal, WidgetDimensions::unscaled.frametext.bottom),
2854 NWidget(NWID_HORIZONTAL), SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.hsep_wide, WidgetDimensions::unscaled.frametext.right),
2855 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_CATEGORY), SetDataTip(STR_CONFIG_SETTING_RESTRICT_CATEGORY, STR_NULL),
2856 NWidget(WWT_DROPDOWN, COLOUR_MAUVE, WID_GS_RESTRICT_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING, STR_CONFIG_SETTING_RESTRICT_DROPDOWN_HELPTEXT), SetFill(1, 0), SetResize(1, 0),
2857 EndContainer(),
2858 NWidget(NWID_HORIZONTAL), SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.hsep_wide, WidgetDimensions::unscaled.frametext.right),
2859 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_GS_RESTRICT_TYPE), SetDataTip(STR_CONFIG_SETTING_RESTRICT_TYPE, STR_NULL),
2860 NWidget(WWT_DROPDOWN, COLOUR_MAUVE, WID_GS_TYPE_DROPDOWN), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING, STR_CONFIG_SETTING_TYPE_DROPDOWN_HELPTEXT), SetFill(1, 0), SetResize(1, 0),
2861 EndContainer(),
2862 NWidget(NWID_HORIZONTAL), SetPIP(WidgetDimensions::unscaled.frametext.left, WidgetDimensions::unscaled.hsep_wide, WidgetDimensions::unscaled.frametext.right),
2863 NWidget(WWT_TEXT, COLOUR_MAUVE), SetFill(0, 1), SetDataTip(STR_CONFIG_SETTING_FILTER_TITLE, STR_NULL),
2864 NWidget(WWT_EDITBOX, COLOUR_MAUVE, WID_GS_FILTER), SetMinimalSize(50, 12), SetDataTip(STR_LIST_FILTER_OSKTITLE, STR_LIST_FILTER_TOOLTIP), SetFill(1, 0), SetResize(1, 0),
2865 EndContainer(),
2866 EndContainer(),
2867 EndContainer(),
2868 NWidget(NWID_HORIZONTAL),
2869 NWidget(WWT_PANEL, COLOUR_MAUVE, WID_GS_OPTIONSPANEL), SetMinimalSize(400, 174), SetScrollbar(WID_GS_SCROLLBAR), EndContainer(),
2870 NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_GS_SCROLLBAR),
2871 EndContainer(),
2872 NWidget(WWT_PANEL, COLOUR_MAUVE),
2873 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GS_HELP_TEXT), SetMinimalSize(300, 25), SetFill(1, 1), SetResize(1, 0),
2874 SetPadding(WidgetDimensions::unscaled.frametext),
2875 EndContainer(),
2876 NWidget(NWID_HORIZONTAL),
2877 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_EXPAND_ALL), SetDataTip(STR_CONFIG_SETTING_EXPAND_ALL, STR_NULL),
2878 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_COLLAPSE_ALL), SetDataTip(STR_CONFIG_SETTING_COLLAPSE_ALL, STR_NULL),
2879 NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_GS_RESET_ALL), SetDataTip(STR_CONFIG_SETTING_RESET_ALL, STR_NULL),
2880 NWidget(WWT_PANEL, COLOUR_MAUVE), SetFill(1, 0), SetResize(1, 0),
2881 EndContainer(),
2882 NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
2883 EndContainer(),
2886 static WindowDesc _settings_selection_desc(__FILE__, __LINE__,
2887 WDP_CENTER, "settings", 510, 450,
2888 WC_GAME_OPTIONS, WC_NONE,
2890 std::begin(_nested_settings_selection_widgets), std::end(_nested_settings_selection_widgets)
2893 /** Open advanced settings window. */
2894 void ShowGameSettings()
2896 CloseWindowByClass(WC_GAME_OPTIONS);
2897 new GameSettingsWindow(&_settings_selection_desc);
2902 * Draw [<][>] boxes.
2903 * @param x the x position to draw
2904 * @param y the y position to draw
2905 * @param button_colour the colour of the button
2906 * @param state 0 = none clicked, 1 = first clicked, 2 = second clicked
2907 * @param clickable_left is the left button clickable?
2908 * @param clickable_right is the right button clickable?
2910 void DrawArrowButtons(int x, int y, Colours button_colour, byte state, bool clickable_left, bool clickable_right)
2912 int colour = _colour_gradient[button_colour][2];
2913 Dimension dim = NWidgetScrollbar::GetHorizontalDimension();
2915 Rect lr = {x, y, x + (int)dim.width - 1, y + (int)dim.height - 1};
2916 Rect rr = {x + (int)dim.width, y, x + (int)dim.width * 2 - 1, y + (int)dim.height - 1};
2918 DrawFrameRect(lr, button_colour, (state == 1) ? FR_LOWERED : FR_NONE);
2919 DrawFrameRect(rr, button_colour, (state == 2) ? FR_LOWERED : FR_NONE);
2920 DrawSpriteIgnorePadding(SPR_ARROW_LEFT, PAL_NONE, lr, SA_CENTER);
2921 DrawSpriteIgnorePadding(SPR_ARROW_RIGHT, PAL_NONE, rr, SA_CENTER);
2923 /* Grey out the buttons that aren't clickable */
2924 bool rtl = _current_text_dir == TD_RTL;
2925 if (rtl ? !clickable_right : !clickable_left) {
2926 GfxFillRect(lr.Shrink(WidgetDimensions::scaled.bevel), colour, FILLRECT_CHECKER);
2928 if (rtl ? !clickable_left : !clickable_right) {
2929 GfxFillRect(rr.Shrink(WidgetDimensions::scaled.bevel), colour, FILLRECT_CHECKER);
2934 * Draw a dropdown button.
2935 * @param x the x position to draw
2936 * @param y the y position to draw
2937 * @param button_colour the colour of the button
2938 * @param state true = lowered
2939 * @param clickable is the button clickable?
2941 void DrawDropDownButton(int x, int y, Colours button_colour, bool state, bool clickable)
2943 int colour = _colour_gradient[button_colour][2];
2945 Rect r = {x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1};
2947 DrawFrameRect(r, button_colour, state ? FR_LOWERED : FR_NONE);
2948 DrawSpriteIgnorePadding(SPR_ARROW_DOWN, PAL_NONE, r, SA_CENTER);
2950 if (!clickable) {
2951 GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), colour, FILLRECT_CHECKER);
2956 * Draw a toggle button.
2957 * @param x the x position to draw
2958 * @param y the y position to draw
2959 * @param state true = lowered
2960 * @param clickable is the button clickable?
2962 void DrawBoolButton(int x, int y, bool state, bool clickable)
2964 static const Colours _bool_ctabs[2][2] = {{COLOUR_CREAM, COLOUR_RED}, {COLOUR_DARK_GREEN, COLOUR_GREEN}};
2966 Rect r = {x, y, x + SETTING_BUTTON_WIDTH - 1, y + SETTING_BUTTON_HEIGHT - 1};
2967 DrawFrameRect(r, _bool_ctabs[state][clickable], state ? FR_LOWERED : FR_NONE);
2970 struct CustomCurrencyWindow : Window {
2971 int query_widget;
2973 CustomCurrencyWindow(WindowDesc *desc) : Window(desc)
2975 this->InitNested();
2977 SetButtonState();
2980 void SetButtonState()
2982 this->SetWidgetDisabledState(WID_CC_RATE_DOWN, _custom_currency.rate == 1);
2983 this->SetWidgetDisabledState(WID_CC_RATE_UP, _custom_currency.rate == UINT16_MAX);
2984 this->SetWidgetDisabledState(WID_CC_YEAR_DOWN, _custom_currency.to_euro == CF_NOEURO);
2985 this->SetWidgetDisabledState(WID_CC_YEAR_UP, _custom_currency.to_euro == CalendarTime::MAX_YEAR);
2988 void SetStringParameters(WidgetID widget) const override
2990 switch (widget) {
2991 case WID_CC_RATE: SetDParam(0, 1); SetDParam(1, 1); break;
2992 case WID_CC_SEPARATOR: SetDParamStr(0, _custom_currency.separator); break;
2993 case WID_CC_PREFIX: SetDParamStr(0, _custom_currency.prefix); break;
2994 case WID_CC_SUFFIX: SetDParamStr(0, _custom_currency.suffix); break;
2995 case WID_CC_YEAR:
2996 SetDParam(0, (_custom_currency.to_euro != CF_NOEURO) ? STR_CURRENCY_SWITCH_TO_EURO : STR_CURRENCY_SWITCH_TO_EURO_NEVER);
2997 SetDParam(1, _custom_currency.to_euro);
2998 break;
3000 case WID_CC_PREVIEW:
3001 SetDParam(0, 10000);
3002 break;
3006 void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
3008 switch (widget) {
3009 /* Set the appropriate width for the up/down buttons. */
3010 case WID_CC_RATE_DOWN:
3011 case WID_CC_RATE_UP:
3012 case WID_CC_YEAR_DOWN:
3013 case WID_CC_YEAR_UP:
3014 *size = maxdim(*size, {(uint)SETTING_BUTTON_WIDTH / 2, (uint)SETTING_BUTTON_HEIGHT});
3015 break;
3017 /* Set the appropriate width for the edit buttons. */
3018 case WID_CC_SEPARATOR_EDIT:
3019 case WID_CC_PREFIX_EDIT:
3020 case WID_CC_SUFFIX_EDIT:
3021 *size = maxdim(*size, {(uint)SETTING_BUTTON_WIDTH, (uint)SETTING_BUTTON_HEIGHT});
3022 break;
3024 /* Make sure the window is wide enough for the widest exchange rate */
3025 case WID_CC_RATE:
3026 SetDParam(0, 1);
3027 SetDParam(1, INT32_MAX);
3028 *size = GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE);
3029 break;
3033 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
3035 int line = 0;
3036 int len = 0;
3037 StringID str = 0;
3038 CharSetFilter afilter = CS_ALPHANUMERAL;
3040 switch (widget) {
3041 case WID_CC_RATE_DOWN:
3042 if (_custom_currency.rate > 1) _custom_currency.rate--;
3043 if (_custom_currency.rate == 1) this->DisableWidget(WID_CC_RATE_DOWN);
3044 this->EnableWidget(WID_CC_RATE_UP);
3045 break;
3047 case WID_CC_RATE_UP:
3048 if (_custom_currency.rate < UINT16_MAX) _custom_currency.rate++;
3049 if (_custom_currency.rate == UINT16_MAX) this->DisableWidget(WID_CC_RATE_UP);
3050 this->EnableWidget(WID_CC_RATE_DOWN);
3051 break;
3053 case WID_CC_RATE:
3054 SetDParam(0, _custom_currency.rate);
3055 str = STR_JUST_INT;
3056 len = 5;
3057 line = WID_CC_RATE;
3058 afilter = CS_NUMERAL;
3059 break;
3061 case WID_CC_SEPARATOR_EDIT:
3062 case WID_CC_SEPARATOR:
3063 SetDParamStr(0, _custom_currency.separator);
3064 str = STR_JUST_RAW_STRING;
3065 len = 7;
3066 line = WID_CC_SEPARATOR;
3067 break;
3069 case WID_CC_PREFIX_EDIT:
3070 case WID_CC_PREFIX:
3071 SetDParamStr(0, _custom_currency.prefix);
3072 str = STR_JUST_RAW_STRING;
3073 len = 15;
3074 line = WID_CC_PREFIX;
3075 break;
3077 case WID_CC_SUFFIX_EDIT:
3078 case WID_CC_SUFFIX:
3079 SetDParamStr(0, _custom_currency.suffix);
3080 str = STR_JUST_RAW_STRING;
3081 len = 15;
3082 line = WID_CC_SUFFIX;
3083 break;
3085 case WID_CC_YEAR_DOWN:
3086 _custom_currency.to_euro = (_custom_currency.to_euro <= MIN_EURO_YEAR) ? CF_NOEURO : _custom_currency.to_euro - 1;
3087 if (_custom_currency.to_euro == CF_NOEURO) this->DisableWidget(WID_CC_YEAR_DOWN);
3088 this->EnableWidget(WID_CC_YEAR_UP);
3089 break;
3091 case WID_CC_YEAR_UP:
3092 _custom_currency.to_euro = Clamp(_custom_currency.to_euro + 1, MIN_EURO_YEAR, CalendarTime::MAX_YEAR);
3093 if (_custom_currency.to_euro == CalendarTime::MAX_YEAR) this->DisableWidget(WID_CC_YEAR_UP);
3094 this->EnableWidget(WID_CC_YEAR_DOWN);
3095 break;
3097 case WID_CC_YEAR:
3098 SetDParam(0, _custom_currency.to_euro);
3099 str = STR_JUST_INT;
3100 len = 7;
3101 line = WID_CC_YEAR;
3102 afilter = CS_NUMERAL;
3103 break;
3106 if (len != 0) {
3107 this->query_widget = line;
3108 ShowQueryString(str, STR_CURRENCY_CHANGE_PARAMETER, len + 1, this, afilter, QSF_NONE);
3111 this->SetTimeout();
3112 this->SetDirty();
3115 void OnQueryTextFinished(char *str) override
3117 if (str == nullptr) return;
3119 switch (this->query_widget) {
3120 case WID_CC_RATE:
3121 _custom_currency.rate = Clamp(atoi(str), 1, UINT16_MAX);
3122 break;
3124 case WID_CC_SEPARATOR: // Thousands separator
3125 _custom_currency.separator = str;
3126 break;
3128 case WID_CC_PREFIX:
3129 _custom_currency.prefix = str;
3130 break;
3132 case WID_CC_SUFFIX:
3133 _custom_currency.suffix = str;
3134 break;
3136 case WID_CC_YEAR: { // Year to switch to euro
3137 TimerGameCalendar::Year val = atoi(str);
3139 _custom_currency.to_euro = (val < MIN_EURO_YEAR ? CF_NOEURO : std::min(val, CalendarTime::MAX_YEAR));
3140 break;
3143 MarkWholeScreenDirty();
3144 SetButtonState();
3147 void OnTimeout() override
3149 this->SetDirty();
3153 static constexpr NWidgetPart _nested_cust_currency_widgets[] = {
3154 NWidget(NWID_HORIZONTAL),
3155 NWidget(WWT_CLOSEBOX, COLOUR_GREY),
3156 NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_CURRENCY_WINDOW, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
3157 EndContainer(),
3158 NWidget(WWT_PANEL, COLOUR_GREY),
3159 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0), SetPadding(WidgetDimensions::unscaled.sparse),
3160 NWidget(NWID_VERTICAL, NC_EQUALSIZE), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
3161 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
3162 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
3163 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP),
3164 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_RATE_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP),
3165 EndContainer(),
3166 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_RATE), SetDataTip(STR_CURRENCY_EXCHANGE_RATE, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP), SetFill(1, 0),
3167 EndContainer(),
3168 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
3169 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SEPARATOR_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(0, 1),
3170 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SEPARATOR), SetDataTip(STR_CURRENCY_SEPARATOR, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP), SetFill(1, 0),
3171 EndContainer(),
3172 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
3173 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_PREFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(0, 1),
3174 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_PREFIX), SetDataTip(STR_CURRENCY_PREFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP), SetFill(1, 0),
3175 EndContainer(),
3176 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
3177 NWidget(WWT_PUSHBTN, COLOUR_DARK_BLUE, WID_CC_SUFFIX_EDIT), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(0, 1),
3178 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_SUFFIX), SetDataTip(STR_CURRENCY_SUFFIX, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP), SetFill(1, 0),
3179 EndContainer(),
3180 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
3181 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
3182 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_DOWN), SetDataTip(AWV_DECREASE, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
3183 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_CC_YEAR_UP), SetDataTip(AWV_INCREASE, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP),
3184 EndContainer(),
3185 NWidget(WWT_TEXT, COLOUR_BLUE, WID_CC_YEAR), SetDataTip(STR_JUST_STRING1, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP), SetFill(1, 0),
3186 EndContainer(),
3187 EndContainer(),
3188 NWidget(WWT_LABEL, COLOUR_BLUE, WID_CC_PREVIEW),
3189 SetDataTip(STR_CURRENCY_PREVIEW, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP),
3190 EndContainer(),
3191 EndContainer(),
3194 static WindowDesc _cust_currency_desc(__FILE__, __LINE__,
3195 WDP_CENTER, nullptr, 0, 0,
3196 WC_CUSTOM_CURRENCY, WC_NONE,
3198 std::begin(_nested_cust_currency_widgets), std::end(_nested_cust_currency_widgets)
3201 /** Open custom currency window. */
3202 static void ShowCustCurrency()
3204 CloseWindowById(WC_CUSTOM_CURRENCY, 0);
3205 new CustomCurrencyWindow(&_cust_currency_desc);