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/>.
8 /** @file settings_gui.cpp GUI for settings. */
13 #include "settings_gui.h"
14 #include "textbuf_gui.h"
15 #include "command_func.h"
16 #include "network/network.h"
17 #include "network/network_content.h"
19 #include "settings_internal.h"
20 #include "strings_func.h"
21 #include "window_func.h"
22 #include "string_func.h"
23 #include "dropdown_type.h"
24 #include "dropdown_func.h"
25 #include "dropdown_common_type.h"
26 #include "slider_func.h"
27 #include "highscore.h"
28 #include "base_media_base.h"
29 #include "company_base.h"
30 #include "company_func.h"
31 #include "viewport_func.h"
32 #include "core/geometry_func.hpp"
34 #include "blitter/factory.hpp"
36 #include "textfile_gui.h"
37 #include "stringfilter_type.h"
38 #include "querystring_gui.h"
39 #include "fontcache.h"
40 #include "zoom_func.h"
42 #include "video/video_driver.hpp"
43 #include "music/music_driver.hpp"
46 #include "newgrf_config.h"
47 #include "network/core/config.h"
48 #include "network/network_gui.h"
49 #include "network/network_survey.h"
50 #include "video/video_driver.hpp"
51 #include "social_integration.h"
52 #include "sound_func.h"
54 #include "safeguards.h"
57 #if defined(WITH_FREETYPE) || defined(_WIN32) || defined(WITH_COCOA)
58 # define HAS_TRUETYPE_FONT
61 static const StringID _autosave_dropdown
[] = {
62 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_OFF
,
63 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_10_MINUTES
,
64 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_30_MINUTES
,
65 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_60_MINUTES
,
66 STR_GAME_OPTIONS_AUTOSAVE_DROPDOWN_EVERY_120_MINUTES
,
70 /** Available settings for autosave intervals. */
71 static const uint32_t _autosave_dropdown_to_minutes
[] = {
79 static Dimension _circle_size
; ///< Dimension of the circle +/- icon. This is here as not all users are within the class of the settings window.
81 static const void *ResolveObject(const GameSettings
*settings_ptr
, const IntSettingDesc
*sd
);
84 * Get index of the current screen resolution.
85 * @return Index of the current screen resolution if it is a known resolution, _resolutions.size() otherwise.
87 static uint
GetCurrentResolutionIndex()
89 auto it
= std::find(_resolutions
.begin(), _resolutions
.end(), Dimension(_screen
.width
, _screen
.height
));
90 return std::distance(_resolutions
.begin(), it
);
93 static void ShowCustCurrency();
95 /** Window for displaying the textfile of a BaseSet. */
96 struct BaseSetTextfileWindow
: public TextfileWindow
{
97 const std::string name
; ///< Name of the content.
98 const StringID content_type
; ///< STR_CONTENT_TYPE_xxx for title.
100 BaseSetTextfileWindow(TextfileType file_type
, const std::string
&name
, const std::string
&textfile
, StringID content_type
) : TextfileWindow(file_type
), name(name
), content_type(content_type
)
102 this->ConstructWindow();
103 this->LoadTextfile(textfile
, BASESET_DIR
);
106 void SetStringParameters(WidgetID widget
) const override
108 if (widget
== WID_TF_CAPTION
) {
109 SetDParam(0, content_type
);
110 SetDParamStr(1, this->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(file_type
, baseset
->name
, *baseset
->GetTextfile(file_type
), content_type
);
129 DropDownList
BuildSetDropDownList(int *selected_index
)
131 int n
= T::GetNumSets();
132 *selected_index
= T::GetIndexOfUsedSet();
134 for (int i
= 0; i
< n
; i
++) {
135 list
.push_back(MakeDropDownListStringItem(T::GetSet(i
)->GetListLabel(), i
));
140 std::set
<int> _refresh_rates
= { 30, 60, 75, 90, 100, 120, 144, 240 };
143 * Add the refresh rate from the config and the refresh rates from all the monitors to
144 * our list of refresh rates shown in the GUI.
146 static void AddCustomRefreshRates()
148 /* Add the refresh rate as selected in the config. */
149 _refresh_rates
.insert(_settings_client
.gui
.refresh_rate
);
151 /* Add all the refresh rates of all monitors connected to the machine. */
152 std::vector
<int> monitorRates
= VideoDriver::GetInstance()->GetListOfMonitorRefreshRates();
153 std::copy(monitorRates
.begin(), monitorRates
.end(), std::inserter(_refresh_rates
, _refresh_rates
.end()));
156 static const int SCALE_NMARKS
= (MAX_INTERFACE_SCALE
- MIN_INTERFACE_SCALE
) / 25 + 1; // Show marks at 25% increments
157 static const int VOLUME_NMARKS
= 9; // Show 5 values and 4 empty marks.
159 static StringID
ScaleMarkFunc(int, int, int value
)
161 /* Label only every 100% mark. */
162 if (value
% 100 != 0) return STR_NULL
;
164 SetDParam(0, value
/ 100);
166 return STR_GAME_OPTIONS_GUI_SCALE_MARK
;
169 static StringID
VolumeMarkFunc(int, int mark
, int value
)
171 /* Label only every other mark. */
172 if (mark
% 2 != 0) return STR_NULL
;
174 SetDParam(0, value
/ 31 * 25); // 0-127 does not map nicely to 0-100. Dividing first gives us nice round numbers.
175 return STR_GAME_OPTIONS_VOLUME_MARK
;
178 static constexpr NWidgetPart _nested_social_plugins_widgets
[] = {
179 NWidget(NWID_HORIZONTAL
),
180 NWidget(WWT_FRAME
, COLOUR_GREY
, WID_GO_SOCIAL_PLUGIN_TITLE
), SetDataTip(STR_JUST_STRING2
, STR_NULL
),
181 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_normal
, 0),
182 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_SOCIAL_PLUGIN_PLATFORM
, STR_NULL
),
183 NWidget(WWT_TEXT
, COLOUR_GREY
, WID_GO_SOCIAL_PLUGIN_PLATFORM
), SetMinimalSize(100, 12), SetDataTip(STR_JUST_RAW_STRING
, STR_NULL
), SetAlignment(SA_RIGHT
),
185 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_normal
, 0),
186 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE
, STR_NULL
),
187 NWidget(WWT_TEXT
, COLOUR_GREY
, WID_GO_SOCIAL_PLUGIN_STATE
), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING1
, STR_NULL
), SetAlignment(SA_RIGHT
),
193 static constexpr NWidgetPart _nested_social_plugins_none_widgets
[] = {
194 NWidget(NWID_HORIZONTAL
),
195 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_SOCIAL_PLUGINS_NONE
, STR_NULL
),
199 class NWidgetSocialPlugins
: public NWidgetVertical
{
201 NWidgetSocialPlugins()
203 this->plugins
= SocialIntegration::GetPlugins();
205 if (this->plugins
.empty()) {
206 auto widget
= MakeNWidgets(_nested_social_plugins_none_widgets
, nullptr);
207 this->Add(std::move(widget
));
209 for (size_t i
= 0; i
< this->plugins
.size(); i
++) {
210 auto widget
= MakeNWidgets(_nested_social_plugins_widgets
, nullptr);
211 this->Add(std::move(widget
));
215 this->SetPIP(0, WidgetDimensions::unscaled
.vsep_wide
, 0);
218 void FillWidgetLookup(WidgetLookup
&widget_lookup
) override
220 widget_lookup
[WID_GO_SOCIAL_PLUGINS
] = this;
221 NWidgetVertical::FillWidgetLookup(widget_lookup
);
224 void SetupSmallestSize(Window
*w
) override
226 this->current_index
= -1;
227 NWidgetVertical::SetupSmallestSize(w
);
231 * Find of all the plugins the one where the member is the widest (in pixels).
233 * @param member The member to check with.
234 * @return The plugin that has the widest value (in pixels) for the given member.
236 template <typename T
>
237 std::string
&GetWidestPlugin(T
SocialIntegrationPlugin::*member
) const
239 std::string
*longest
= &(this->plugins
[0]->*member
);
240 int longest_length
= 0;
242 for (auto *plugin
: this->plugins
) {
243 int length
= GetStringBoundingBox(plugin
->*member
).width
;
244 if (length
> longest_length
) {
245 longest_length
= length
;
246 longest
= &(plugin
->*member
);
253 void SetStringParameters(int widget
) const
256 case WID_GO_SOCIAL_PLUGIN_TITLE
:
257 /* For SetupSmallestSize, use the longest string we have. */
258 if (this->current_index
< 0) {
259 SetDParam(0, STR_GAME_OPTIONS_SOCIAL_PLUGIN_TITLE
);
260 SetDParamStr(1, GetWidestPlugin(&SocialIntegrationPlugin::name
));
261 SetDParamStr(2, GetWidestPlugin(&SocialIntegrationPlugin::version
));
265 if (this->plugins
[this->current_index
]->name
.empty()) {
266 SetDParam(0, STR_JUST_RAW_STRING
);
267 SetDParamStr(1, this->plugins
[this->current_index
]->basepath
);
269 SetDParam(0, STR_GAME_OPTIONS_SOCIAL_PLUGIN_TITLE
);
270 SetDParamStr(1, this->plugins
[this->current_index
]->name
);
271 SetDParamStr(2, this->plugins
[this->current_index
]->version
);
275 case WID_GO_SOCIAL_PLUGIN_PLATFORM
:
276 /* For SetupSmallestSize, use the longest string we have. */
277 if (this->current_index
< 0) {
278 SetDParamStr(0, GetWidestPlugin(&SocialIntegrationPlugin::social_platform
));
282 SetDParamStr(0, this->plugins
[this->current_index
]->social_platform
);
285 case WID_GO_SOCIAL_PLUGIN_STATE
: {
286 static const std::pair
<SocialIntegrationPlugin::State
, StringID
> state_to_string
[] = {
287 { SocialIntegrationPlugin::RUNNING
, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_RUNNING
},
288 { SocialIntegrationPlugin::FAILED
, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_FAILED
},
289 { SocialIntegrationPlugin::PLATFORM_NOT_RUNNING
, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_PLATFORM_NOT_RUNNING
},
290 { SocialIntegrationPlugin::UNLOADED
, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_UNLOADED
},
291 { SocialIntegrationPlugin::DUPLICATE
, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_DUPLICATE
},
292 { SocialIntegrationPlugin::UNSUPPORTED_API
, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_UNSUPPORTED_API
},
293 { SocialIntegrationPlugin::INVALID_SIGNATURE
, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_INVALID_SIGNATURE
},
296 /* For SetupSmallestSize, use the longest string we have. */
297 if (this->current_index
< 0) {
298 auto longest_plugin
= GetWidestPlugin(&SocialIntegrationPlugin::social_platform
);
300 /* Set the longest plugin when looking for the longest status. */
301 SetDParamStr(0, longest_plugin
);
303 StringID longest
= STR_NULL
;
304 int longest_length
= 0;
305 for (auto state
: state_to_string
) {
306 int length
= GetStringBoundingBox(state
.second
).width
;
307 if (length
> longest_length
) {
308 longest_length
= length
;
309 longest
= state
.second
;
313 SetDParam(0, longest
);
314 SetDParamStr(1, longest_plugin
);
318 auto plugin
= this->plugins
[this->current_index
];
320 /* Default string, in case no state matches. */
321 SetDParam(0, STR_GAME_OPTIONS_SOCIAL_PLUGIN_STATE_FAILED
);
322 SetDParamStr(1, plugin
->social_platform
);
324 /* Find the string for the state. */
325 for (auto state
: state_to_string
) {
326 if (plugin
->state
== state
.first
) {
327 SetDParam(0, state
.second
);
336 void Draw(const Window
*w
) override
338 this->current_index
= 0;
340 for (auto &wid
: this->children
) {
342 this->current_index
++;
347 int current_index
= -1;
348 std::vector
<SocialIntegrationPlugin
*> plugins
;
351 /** Construct nested container widget for managing the list of social plugins. */
352 std::unique_ptr
<NWidgetBase
> MakeNWidgetSocialPlugins()
354 return std::make_unique
<NWidgetSocialPlugins
>();
357 struct GameOptionsWindow
: Window
{
361 static inline WidgetID active_tab
= WID_GO_TAB_GENERAL
;
363 GameOptionsWindow(WindowDesc
&desc
) : Window(desc
)
365 this->opt
= &GetGameSettings();
366 this->reload
= false;
367 this->gui_scale
= _gui_scale
;
369 AddCustomRefreshRates();
371 this->InitNested(WN_GAME_OPTIONS_GAME_OPTIONS
);
372 this->OnInvalidateData(0);
374 this->SetTab(GameOptionsWindow::active_tab
);
376 if constexpr (!NetworkSurveyHandler::IsSurveyPossible()) this->GetWidget
<NWidgetStacked
>(WID_GO_SURVEY_SEL
)->SetDisplayedPlane(SZSP_NONE
);
379 void Close([[maybe_unused
]] int data
= 0) override
381 CloseWindowById(WC_CUSTOM_CURRENCY
, 0);
382 CloseWindowByClass(WC_TEXTFILE
);
383 if (this->reload
) _switch_mode
= SM_MENU
;
384 this->Window::Close();
388 * Build the dropdown list for a specific widget.
389 * @param widget Widget to build list for
390 * @param selected_index Currently selected item
391 * @return the built dropdown list, or nullptr if the widget has no dropdown menu.
393 DropDownList
BuildDropDownList(WidgetID widget
, int *selected_index
) const
397 case WID_GO_CURRENCY_DROPDOWN
: { // Setup currencies dropdown
398 *selected_index
= this->opt
->locale
.currency
;
399 uint64_t disabled
= _game_mode
== GM_MENU
? 0LL : ~GetMaskOfAllowedCurrencies();
401 /* Add non-custom currencies; sorted naturally */
402 for (const CurrencySpec
¤cy
: _currency_specs
) {
403 int i
= ¤cy
- _currency_specs
.data();
404 if (i
== CURRENCY_CUSTOM
) continue;
405 if (currency
.code
.empty()) {
406 list
.push_back(MakeDropDownListStringItem(currency
.name
, i
, HasBit(disabled
, i
)));
408 SetDParam(0, currency
.name
);
409 SetDParamStr(1, currency
.code
);
410 list
.push_back(MakeDropDownListStringItem(STR_GAME_OPTIONS_CURRENCY_CODE
, i
, HasBit(disabled
, i
)));
413 std::sort(list
.begin(), list
.end(), DropDownListStringItem::NatSortFunc
);
415 /* Append custom currency at the end */
416 list
.push_back(MakeDropDownListDividerItem()); // separator line
417 list
.push_back(MakeDropDownListStringItem(STR_GAME_OPTIONS_CURRENCY_CUSTOM
, CURRENCY_CUSTOM
, HasBit(disabled
, CURRENCY_CUSTOM
)));
421 case WID_GO_AUTOSAVE_DROPDOWN
: { // Setup autosave dropdown
423 for (auto &minutes
: _autosave_dropdown_to_minutes
) {
425 if (_settings_client
.gui
.autosave_interval
<= minutes
) break;
427 *selected_index
= index
- 1;
429 const StringID
*items
= _autosave_dropdown
;
430 for (uint i
= 0; *items
!= INVALID_STRING_ID
; items
++, i
++) {
431 list
.push_back(MakeDropDownListStringItem(*items
, i
));
436 case WID_GO_LANG_DROPDOWN
: { // Setup interface language dropdown
437 for (uint i
= 0; i
< _languages
.size(); i
++) {
438 bool hide_language
= IsReleasedVersion() && !_languages
[i
].IsReasonablyFinished();
439 if (hide_language
) continue;
440 bool hide_percentage
= IsReleasedVersion() || _languages
[i
].missing
< _settings_client
.gui
.missing_strings_threshold
;
441 if (&_languages
[i
] == _current_language
) {
443 SetDParamStr(0, _languages
[i
].own_name
);
445 /* Especially with sprite-fonts, not all localized
446 * names can be rendered. So instead, we use the
447 * international names for anything but the current
448 * selected language. This avoids showing a few ????
449 * entries in the dropdown list. */
450 SetDParamStr(0, _languages
[i
].name
);
452 SetDParam(1, (LANGUAGE_TOTAL_STRINGS
- _languages
[i
].missing
) * 100 / LANGUAGE_TOTAL_STRINGS
);
453 list
.push_back(MakeDropDownListStringItem(hide_percentage
? STR_JUST_RAW_STRING
: STR_GAME_OPTIONS_LANGUAGE_PERCENTAGE
, i
));
455 std::sort(list
.begin(), list
.end(), DropDownListStringItem::NatSortFunc
);
459 case WID_GO_RESOLUTION_DROPDOWN
: // Setup resolution dropdown
460 if (_resolutions
.empty()) break;
462 *selected_index
= GetCurrentResolutionIndex();
463 for (uint i
= 0; i
< _resolutions
.size(); i
++) {
464 SetDParam(0, _resolutions
[i
].width
);
465 SetDParam(1, _resolutions
[i
].height
);
466 list
.push_back(MakeDropDownListStringItem(STR_GAME_OPTIONS_RESOLUTION_ITEM
, i
));
470 case WID_GO_REFRESH_RATE_DROPDOWN
: // Setup refresh rate dropdown
471 for (auto it
= _refresh_rates
.begin(); it
!= _refresh_rates
.end(); it
++) {
472 auto i
= std::distance(_refresh_rates
.begin(), it
);
473 if (*it
== _settings_client
.gui
.refresh_rate
) *selected_index
= i
;
475 list
.push_back(MakeDropDownListStringItem(STR_GAME_OPTIONS_REFRESH_RATE_ITEM
, i
));
479 case WID_GO_BASE_GRF_DROPDOWN
:
480 list
= BuildSetDropDownList
<BaseGraphics
>(selected_index
);
483 case WID_GO_BASE_SFX_DROPDOWN
:
484 list
= BuildSetDropDownList
<BaseSounds
>(selected_index
);
487 case WID_GO_BASE_MUSIC_DROPDOWN
:
488 list
= BuildSetDropDownList
<BaseMusic
>(selected_index
);
495 void SetStringParameters(WidgetID widget
) const override
498 case WID_GO_CURRENCY_DROPDOWN
: {
499 const CurrencySpec
¤cy
= _currency_specs
[this->opt
->locale
.currency
];
500 if (currency
.code
.empty()) {
501 SetDParam(0, currency
.name
);
503 SetDParam(0, STR_GAME_OPTIONS_CURRENCY_CODE
);
504 SetDParam(1, currency
.name
);
505 SetDParamStr(2, currency
.code
);
509 case WID_GO_AUTOSAVE_DROPDOWN
: {
511 for (auto &minutes
: _autosave_dropdown_to_minutes
) {
513 if (_settings_client
.gui
.autosave_interval
<= minutes
) break;
515 SetDParam(0, _autosave_dropdown
[index
- 1]);
518 case WID_GO_LANG_DROPDOWN
: SetDParamStr(0, _current_language
->own_name
); break;
519 case WID_GO_BASE_GRF_DROPDOWN
: SetDParamStr(0, BaseGraphics::GetUsedSet()->GetListLabel()); break;
520 case WID_GO_BASE_SFX_DROPDOWN
: SetDParamStr(0, BaseSounds::GetUsedSet()->GetListLabel()); break;
521 case WID_GO_BASE_MUSIC_DROPDOWN
: SetDParamStr(0, BaseMusic::GetUsedSet()->GetListLabel()); break;
522 case WID_GO_REFRESH_RATE_DROPDOWN
: SetDParam(0, _settings_client
.gui
.refresh_rate
); break;
523 case WID_GO_RESOLUTION_DROPDOWN
: {
524 auto current_resolution
= GetCurrentResolutionIndex();
526 if (current_resolution
== _resolutions
.size()) {
527 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_OTHER
);
529 SetDParam(0, STR_GAME_OPTIONS_RESOLUTION_ITEM
);
530 SetDParam(1, _resolutions
[current_resolution
].width
);
531 SetDParam(2, _resolutions
[current_resolution
].height
);
536 case WID_GO_SOCIAL_PLUGIN_TITLE
:
537 case WID_GO_SOCIAL_PLUGIN_PLATFORM
:
538 case WID_GO_SOCIAL_PLUGIN_STATE
: {
539 const NWidgetSocialPlugins
*plugin
= this->GetWidget
<NWidgetSocialPlugins
>(WID_GO_SOCIAL_PLUGINS
);
540 assert(plugin
!= nullptr);
542 plugin
->SetStringParameters(widget
);
548 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
551 case WID_GO_BASE_GRF_DESCRIPTION
:
552 SetDParamStr(0, BaseGraphics::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
553 DrawStringMultiLine(r
.left
, r
.right
, r
.top
, UINT16_MAX
, STR_JUST_RAW_STRING
, TC_BLACK
);
556 case WID_GO_BASE_SFX_DESCRIPTION
:
557 SetDParamStr(0, BaseSounds::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
558 DrawStringMultiLine(r
.left
, r
.right
, r
.top
, UINT16_MAX
, STR_JUST_RAW_STRING
, TC_BLACK
);
561 case WID_GO_BASE_MUSIC_DESCRIPTION
:
562 SetDParamStr(0, BaseMusic::GetUsedSet()->GetDescription(GetCurrentLanguageIsoCode()));
563 DrawStringMultiLine(r
.left
, r
.right
, r
.top
, UINT16_MAX
, STR_JUST_RAW_STRING
, TC_BLACK
);
566 case WID_GO_GUI_SCALE
:
567 DrawSliderWidget(r
, MIN_INTERFACE_SCALE
, MAX_INTERFACE_SCALE
, SCALE_NMARKS
, this->gui_scale
, ScaleMarkFunc
);
570 case WID_GO_VIDEO_DRIVER_INFO
:
571 SetDParamStr(0, std::string
{VideoDriver::GetInstance()->GetInfoString()});
572 DrawStringMultiLine(r
, STR_GAME_OPTIONS_VIDEO_DRIVER_INFO
);
575 case WID_GO_BASE_SFX_VOLUME
:
576 DrawSliderWidget(r
, 0, INT8_MAX
, VOLUME_NMARKS
, _settings_client
.music
.effect_vol
, VolumeMarkFunc
);
579 case WID_GO_BASE_MUSIC_VOLUME
:
580 DrawSliderWidget(r
, 0, INT8_MAX
, VOLUME_NMARKS
, _settings_client
.music
.music_vol
, VolumeMarkFunc
);
585 void SetTab(WidgetID widget
)
587 this->SetWidgetsLoweredState(false, WID_GO_TAB_GENERAL
, WID_GO_TAB_GRAPHICS
, WID_GO_TAB_SOUND
, WID_GO_TAB_SOCIAL
);
588 this->LowerWidget(widget
);
589 GameOptionsWindow::active_tab
= widget
;
593 case WID_GO_TAB_GENERAL
: pane
= 0; break;
594 case WID_GO_TAB_GRAPHICS
: pane
= 1; break;
595 case WID_GO_TAB_SOUND
: pane
= 2; break;
596 case WID_GO_TAB_SOCIAL
: pane
= 3; break;
597 default: NOT_REACHED();
600 this->GetWidget
<NWidgetStacked
>(WID_GO_TAB_SELECTION
)->SetDisplayedPlane(pane
);
604 void OnResize() override
606 bool changed
= false;
608 NWidgetResizeBase
*wid
= this->GetWidget
<NWidgetResizeBase
>(WID_GO_BASE_GRF_DESCRIPTION
);
610 for (int i
= 0; i
< BaseGraphics::GetNumSets(); i
++) {
611 SetDParamStr(0, BaseGraphics::GetSet(i
)->GetDescription(GetCurrentLanguageIsoCode()));
612 y
= std::max(y
, GetStringHeight(STR_JUST_RAW_STRING
, wid
->current_x
));
614 changed
|= wid
->UpdateVerticalSize(y
);
616 wid
= this->GetWidget
<NWidgetResizeBase
>(WID_GO_BASE_SFX_DESCRIPTION
);
618 for (int i
= 0; i
< BaseSounds::GetNumSets(); i
++) {
619 SetDParamStr(0, BaseSounds::GetSet(i
)->GetDescription(GetCurrentLanguageIsoCode()));
620 y
= std::max(y
, GetStringHeight(STR_JUST_RAW_STRING
, wid
->current_x
));
622 changed
|= wid
->UpdateVerticalSize(y
);
624 wid
= this->GetWidget
<NWidgetResizeBase
>(WID_GO_BASE_MUSIC_DESCRIPTION
);
626 for (int i
= 0; i
< BaseMusic::GetNumSets(); i
++) {
627 SetDParamStr(0, BaseMusic::GetSet(i
)->GetDescription(GetCurrentLanguageIsoCode()));
628 y
= std::max(y
, GetStringHeight(STR_JUST_RAW_STRING
, wid
->current_x
));
630 changed
|= wid
->UpdateVerticalSize(y
);
632 wid
= this->GetWidget
<NWidgetResizeBase
>(WID_GO_VIDEO_DRIVER_INFO
);
633 SetDParamStr(0, std::string
{VideoDriver::GetInstance()->GetInfoString()});
634 y
= GetStringHeight(STR_GAME_OPTIONS_VIDEO_DRIVER_INFO
, wid
->current_x
);
635 changed
|= wid
->UpdateVerticalSize(y
);
637 if (changed
) this->ReInit(0, 0, this->flags
& WF_CENTERED
);
640 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
643 case WID_GO_TEXT_SFX_VOLUME
:
644 case WID_GO_TEXT_MUSIC_VOLUME
: {
645 Dimension d
= maxdim(GetStringBoundingBox(STR_GAME_OPTIONS_SFX_VOLUME
), GetStringBoundingBox(STR_GAME_OPTIONS_MUSIC_VOLUME
));
646 d
.width
+= padding
.width
;
647 d
.height
+= padding
.height
;
648 size
= maxdim(size
, d
);
652 case WID_GO_CURRENCY_DROPDOWN
:
653 case WID_GO_AUTOSAVE_DROPDOWN
:
654 case WID_GO_LANG_DROPDOWN
:
655 case WID_GO_RESOLUTION_DROPDOWN
:
656 case WID_GO_REFRESH_RATE_DROPDOWN
:
657 case WID_GO_BASE_GRF_DROPDOWN
:
658 case WID_GO_BASE_SFX_DROPDOWN
:
659 case WID_GO_BASE_MUSIC_DROPDOWN
: {
661 size
.width
= std::max(size
.width
, GetDropDownListDimension(this->BuildDropDownList(widget
, &selected
)).width
+ padding
.width
);
667 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
669 if (widget
>= WID_GO_BASE_GRF_TEXTFILE
&& widget
< WID_GO_BASE_GRF_TEXTFILE
+ TFT_CONTENT_END
) {
670 if (BaseGraphics::GetUsedSet() == nullptr) return;
672 ShowBaseSetTextfileWindow((TextfileType
)(widget
- WID_GO_BASE_GRF_TEXTFILE
), BaseGraphics::GetUsedSet(), STR_CONTENT_TYPE_BASE_GRAPHICS
);
675 if (widget
>= WID_GO_BASE_SFX_TEXTFILE
&& widget
< WID_GO_BASE_SFX_TEXTFILE
+ TFT_CONTENT_END
) {
676 if (BaseSounds::GetUsedSet() == nullptr) return;
678 ShowBaseSetTextfileWindow((TextfileType
)(widget
- WID_GO_BASE_SFX_TEXTFILE
), BaseSounds::GetUsedSet(), STR_CONTENT_TYPE_BASE_SOUNDS
);
681 if (widget
>= WID_GO_BASE_MUSIC_TEXTFILE
&& widget
< WID_GO_BASE_MUSIC_TEXTFILE
+ TFT_CONTENT_END
) {
682 if (BaseMusic::GetUsedSet() == nullptr) return;
684 ShowBaseSetTextfileWindow((TextfileType
)(widget
- WID_GO_BASE_MUSIC_TEXTFILE
), BaseMusic::GetUsedSet(), STR_CONTENT_TYPE_BASE_MUSIC
);
688 case WID_GO_TAB_GENERAL
:
689 case WID_GO_TAB_GRAPHICS
:
690 case WID_GO_TAB_SOUND
:
691 case WID_GO_TAB_SOCIAL
:
692 this->SetTab(widget
);
695 case WID_GO_SURVEY_PARTICIPATE_BUTTON
:
696 switch (_settings_client
.network
.participate_survey
) {
699 _settings_client
.network
.participate_survey
= PS_YES
;
703 _settings_client
.network
.participate_survey
= PS_NO
;
707 this->SetWidgetLoweredState(WID_GO_SURVEY_PARTICIPATE_BUTTON
, _settings_client
.network
.participate_survey
== PS_YES
);
708 this->SetWidgetDirty(WID_GO_SURVEY_PARTICIPATE_BUTTON
);
711 case WID_GO_SURVEY_LINK_BUTTON
:
712 OpenBrowser(NETWORK_SURVEY_DETAILS_LINK
);
715 case WID_GO_SURVEY_PREVIEW_BUTTON
:
716 ShowSurveyResultTextfileWindow();
719 case WID_GO_FULLSCREEN_BUTTON
: // Click fullscreen on/off
720 /* try to toggle full-screen on/off */
721 if (!ToggleFullScreen(!_fullscreen
)) {
722 ShowErrorMessage(STR_ERROR_FULLSCREEN_FAILED
, INVALID_STRING_ID
, WL_ERROR
);
724 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON
, _fullscreen
);
725 this->SetWidgetDirty(WID_GO_FULLSCREEN_BUTTON
);
728 case WID_GO_VIDEO_ACCEL_BUTTON
:
729 _video_hw_accel
= !_video_hw_accel
;
730 ShowErrorMessage(STR_GAME_OPTIONS_VIDEO_ACCELERATION_RESTART
, INVALID_STRING_ID
, WL_INFO
);
731 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON
, _video_hw_accel
);
732 this->SetWidgetDirty(WID_GO_VIDEO_ACCEL_BUTTON
);
734 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON
, _video_hw_accel
&& _video_vsync
);
735 this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON
, !_video_hw_accel
);
736 this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON
);
740 case WID_GO_VIDEO_VSYNC_BUTTON
:
741 if (!_video_hw_accel
) break;
743 _video_vsync
= !_video_vsync
;
744 VideoDriver::GetInstance()->ToggleVsync(_video_vsync
);
746 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON
, _video_vsync
);
747 this->SetWidgetDirty(WID_GO_VIDEO_VSYNC_BUTTON
);
748 this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN
, _video_vsync
);
749 this->SetWidgetDirty(WID_GO_REFRESH_RATE_DROPDOWN
);
752 case WID_GO_GUI_SCALE_BEVEL_BUTTON
: {
753 _settings_client
.gui
.scale_bevels
= !_settings_client
.gui
.scale_bevels
;
755 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_BEVEL_BUTTON
, _settings_client
.gui
.scale_bevels
);
758 SetupWidgetDimensions();
759 ReInitAllWindows(true);
763 #ifdef HAS_TRUETYPE_FONT
764 case WID_GO_GUI_FONT_SPRITE
:
765 _fcsettings
.prefer_sprite
= !_fcsettings
.prefer_sprite
;
767 this->SetWidgetLoweredState(WID_GO_GUI_FONT_SPRITE
, _fcsettings
.prefer_sprite
);
768 this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA
, _fcsettings
.prefer_sprite
);
771 InitFontCache(false);
774 CheckForMissingGlyphs();
775 SetupWidgetDimensions();
776 UpdateAllVirtCoords();
777 ReInitAllWindows(true);
780 case WID_GO_GUI_FONT_AA
:
781 _fcsettings
.global_aa
= !_fcsettings
.global_aa
;
783 this->SetWidgetLoweredState(WID_GO_GUI_FONT_AA
, _fcsettings
.global_aa
);
784 MarkWholeScreenDirty();
788 #endif /* HAS_TRUETYPE_FONT */
790 case WID_GO_GUI_SCALE
:
791 if (ClickSliderWidget(this->GetWidget
<NWidgetBase
>(widget
)->GetCurrentRect(), pt
, MIN_INTERFACE_SCALE
, MAX_INTERFACE_SCALE
, _ctrl_pressed
? 0 : SCALE_NMARKS
, this->gui_scale
)) {
792 this->SetWidgetDirty(widget
);
795 if (click_count
> 0) this->mouse_capture_widget
= widget
;
798 case WID_GO_GUI_SCALE_AUTO
:
800 if (_gui_scale_cfg
== -1) {
801 _gui_scale_cfg
= _gui_scale
;
802 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO
, false);
805 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO
, true);
806 if (AdjustGUIZoom(false)) ReInitAllWindows(true);
807 this->gui_scale
= _gui_scale
;
809 this->SetWidgetDirty(widget
);
813 case WID_GO_BASE_GRF_PARAMETERS
: {
814 auto *used_set
= BaseGraphics::GetUsedSet();
815 if (used_set
== nullptr || !used_set
->IsConfigurable()) break;
816 GRFConfig
&extra_cfg
= used_set
->GetOrCreateExtraConfig();
817 if (extra_cfg
.num_params
== 0) extra_cfg
.SetParameterDefaults();
818 OpenGRFParameterWindow(true, &extra_cfg
, _game_mode
== GM_MENU
);
819 if (_game_mode
== GM_MENU
) this->reload
= true;
823 case WID_GO_BASE_SFX_VOLUME
:
824 case WID_GO_BASE_MUSIC_VOLUME
: {
825 uint8_t &vol
= (widget
== WID_GO_BASE_MUSIC_VOLUME
) ? _settings_client
.music
.music_vol
: _settings_client
.music
.effect_vol
;
826 if (ClickSliderWidget(this->GetWidget
<NWidgetBase
>(widget
)->GetCurrentRect(), pt
, 0, INT8_MAX
, 0, vol
)) {
827 if (widget
== WID_GO_BASE_MUSIC_VOLUME
) {
828 MusicDriver::GetInstance()->SetVolume(vol
);
830 SetEffectVolume(vol
);
832 this->SetWidgetDirty(widget
);
833 SetWindowClassesDirty(WC_MUSIC_WINDOW
);
836 if (click_count
> 0) this->mouse_capture_widget
= widget
;
840 case WID_GO_BASE_MUSIC_JUKEBOX
: {
845 case WID_GO_BASE_GRF_OPEN_URL
:
846 if (BaseGraphics::GetUsedSet() == nullptr || BaseGraphics::GetUsedSet()->url
.empty()) return;
847 OpenBrowser(BaseGraphics::GetUsedSet()->url
);
850 case WID_GO_BASE_SFX_OPEN_URL
:
851 if (BaseSounds::GetUsedSet() == nullptr || BaseSounds::GetUsedSet()->url
.empty()) return;
852 OpenBrowser(BaseSounds::GetUsedSet()->url
);
855 case WID_GO_BASE_MUSIC_OPEN_URL
:
856 if (BaseMusic::GetUsedSet() == nullptr || BaseMusic::GetUsedSet()->url
.empty()) return;
857 OpenBrowser(BaseMusic::GetUsedSet()->url
);
860 case WID_GO_BASE_GRF_CONTENT_DOWNLOAD
:
861 ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_BASE_GRAPHICS
);
864 case WID_GO_BASE_SFX_CONTENT_DOWNLOAD
:
865 ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_BASE_SOUNDS
);
868 case WID_GO_BASE_MUSIC_CONTENT_DOWNLOAD
:
869 ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_BASE_MUSIC
);
872 case WID_GO_CURRENCY_DROPDOWN
:
873 case WID_GO_AUTOSAVE_DROPDOWN
:
874 case WID_GO_LANG_DROPDOWN
:
875 case WID_GO_RESOLUTION_DROPDOWN
:
876 case WID_GO_REFRESH_RATE_DROPDOWN
:
877 case WID_GO_BASE_GRF_DROPDOWN
:
878 case WID_GO_BASE_SFX_DROPDOWN
:
879 case WID_GO_BASE_MUSIC_DROPDOWN
: {
881 DropDownList list
= this->BuildDropDownList(widget
, &selected
);
883 ShowDropDownList(this, std::move(list
), selected
, widget
);
885 if (widget
== WID_GO_RESOLUTION_DROPDOWN
) ShowErrorMessage(STR_ERROR_RESOLUTION_LIST_FAILED
, INVALID_STRING_ID
, WL_ERROR
);
892 void OnMouseLoop() override
894 if (_left_button_down
|| this->gui_scale
== _gui_scale
) return;
896 _gui_scale_cfg
= this->gui_scale
;
898 if (AdjustGUIZoom(false)) {
899 ReInitAllWindows(true);
900 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO
, false);
905 void OnDropdownSelect(WidgetID widget
, int index
) override
908 case WID_GO_CURRENCY_DROPDOWN
: // Currency
909 if (index
== CURRENCY_CUSTOM
) ShowCustCurrency();
910 this->opt
->locale
.currency
= index
;
911 ReInitAllWindows(false);
914 case WID_GO_AUTOSAVE_DROPDOWN
: // Autosave options
915 _settings_client
.gui
.autosave_interval
= _autosave_dropdown_to_minutes
[index
];
916 ChangeAutosaveFrequency(false);
920 case WID_GO_LANG_DROPDOWN
: // Change interface language
921 ReadLanguagePack(&_languages
[index
]);
922 CloseWindowByClass(WC_QUERY_STRING
);
923 CheckForMissingGlyphs();
924 ClearAllCachedNames();
925 UpdateAllVirtCoords();
927 ReInitAllWindows(false);
930 case WID_GO_RESOLUTION_DROPDOWN
: // Change resolution
931 if ((uint
)index
< _resolutions
.size() && ChangeResInGame(_resolutions
[index
].width
, _resolutions
[index
].height
)) {
936 case WID_GO_REFRESH_RATE_DROPDOWN
: {
937 _settings_client
.gui
.refresh_rate
= *std::next(_refresh_rates
.begin(), index
);
938 if (_settings_client
.gui
.refresh_rate
> 60) {
939 /* Show warning to the user that this refresh rate might not be suitable on
940 * larger maps with many NewGRFs and vehicles. */
941 ShowErrorMessage(STR_GAME_OPTIONS_REFRESH_RATE_WARNING
, INVALID_STRING_ID
, WL_INFO
);
946 case WID_GO_BASE_GRF_DROPDOWN
:
947 if (_game_mode
== GM_MENU
) {
948 CloseWindowByClass(WC_GRF_PARAMETERS
);
949 auto set
= BaseGraphics::GetSet(index
);
950 BaseGraphics::SetSet(set
);
952 this->InvalidateData();
956 case WID_GO_BASE_SFX_DROPDOWN
:
957 ChangeSoundSet(index
);
960 case WID_GO_BASE_MUSIC_DROPDOWN
:
961 ChangeMusicSet(index
);
967 * Some data on this window has become invalid.
968 * @param data Information about the changed data. @see GameOptionsInvalidationData
969 * @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.
971 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
973 if (!gui_scope
) return;
974 this->SetWidgetLoweredState(WID_GO_SURVEY_PARTICIPATE_BUTTON
, _settings_client
.network
.participate_survey
== PS_YES
);
975 this->SetWidgetLoweredState(WID_GO_FULLSCREEN_BUTTON
, _fullscreen
);
976 this->SetWidgetLoweredState(WID_GO_VIDEO_ACCEL_BUTTON
, _video_hw_accel
);
977 this->SetWidgetDisabledState(WID_GO_REFRESH_RATE_DROPDOWN
, _video_vsync
);
980 this->SetWidgetLoweredState(WID_GO_VIDEO_VSYNC_BUTTON
, _video_hw_accel
&& _video_vsync
);
981 this->SetWidgetDisabledState(WID_GO_VIDEO_VSYNC_BUTTON
, !_video_hw_accel
);
984 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_AUTO
, _gui_scale_cfg
== -1);
985 this->SetWidgetLoweredState(WID_GO_GUI_SCALE_BEVEL_BUTTON
, _settings_client
.gui
.scale_bevels
);
986 #ifdef HAS_TRUETYPE_FONT
987 this->SetWidgetLoweredState(WID_GO_GUI_FONT_SPRITE
, _fcsettings
.prefer_sprite
);
988 this->SetWidgetLoweredState(WID_GO_GUI_FONT_AA
, _fcsettings
.global_aa
);
989 this->SetWidgetDisabledState(WID_GO_GUI_FONT_AA
, _fcsettings
.prefer_sprite
);
990 #endif /* HAS_TRUETYPE_FONT */
992 this->SetWidgetDisabledState(WID_GO_BASE_GRF_DROPDOWN
, _game_mode
!= GM_MENU
);
994 this->SetWidgetDisabledState(WID_GO_BASE_GRF_PARAMETERS
, BaseGraphics::GetUsedSet() == nullptr || !BaseGraphics::GetUsedSet()->IsConfigurable());
996 this->SetWidgetDisabledState(WID_GO_BASE_GRF_OPEN_URL
, BaseGraphics::GetUsedSet() == nullptr || BaseGraphics::GetUsedSet()->url
.empty());
997 this->SetWidgetDisabledState(WID_GO_BASE_SFX_OPEN_URL
, BaseSounds::GetUsedSet() == nullptr || BaseSounds::GetUsedSet()->url
.empty());
998 this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_OPEN_URL
, BaseMusic::GetUsedSet() == nullptr || BaseMusic::GetUsedSet()->url
.empty());
1000 for (TextfileType tft
= TFT_CONTENT_BEGIN
; tft
< TFT_CONTENT_END
; tft
++) {
1001 this->SetWidgetDisabledState(WID_GO_BASE_GRF_TEXTFILE
+ tft
, BaseGraphics::GetUsedSet() == nullptr || !BaseGraphics::GetUsedSet()->GetTextfile(tft
).has_value());
1002 this->SetWidgetDisabledState(WID_GO_BASE_SFX_TEXTFILE
+ tft
, BaseSounds::GetUsedSet() == nullptr || !BaseSounds::GetUsedSet()->GetTextfile(tft
).has_value());
1003 this->SetWidgetDisabledState(WID_GO_BASE_MUSIC_TEXTFILE
+ tft
, BaseMusic::GetUsedSet() == nullptr || !BaseMusic::GetUsedSet()->GetTextfile(tft
).has_value());
1006 this->SetWidgetsDisabledState(!_network_available
, WID_GO_BASE_GRF_CONTENT_DOWNLOAD
, WID_GO_BASE_SFX_CONTENT_DOWNLOAD
, WID_GO_BASE_MUSIC_CONTENT_DOWNLOAD
);
1010 static constexpr NWidgetPart _nested_game_options_widgets
[] = {
1011 NWidget(NWID_HORIZONTAL
),
1012 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
1013 NWidget(WWT_CAPTION
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
1015 NWidget(WWT_PANEL
, COLOUR_GREY
),
1016 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
), SetPadding(WidgetDimensions::unscaled
.sparse
),
1017 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),
1018 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),
1019 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),
1020 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),
1023 NWidget(WWT_PANEL
, COLOUR_GREY
),
1024 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_GO_TAB_SELECTION
),
1026 NWidget(NWID_VERTICAL
), SetPadding(WidgetDimensions::unscaled
.sparse
), SetPIP(0, WidgetDimensions::unscaled
.vsep_wide
, 0),
1027 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_LANGUAGE
, STR_NULL
),
1028 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),
1031 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_AUTOSAVE_FRAME
, STR_NULL
),
1032 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),
1035 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_CURRENCY_UNITS_FRAME
, STR_NULL
),
1036 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),
1039 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_GO_SURVEY_SEL
),
1040 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY_FRAME
, STR_NULL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_sparse
, 0),
1041 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_normal
, 0),
1042 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_PARTICIPATE_SURVEY
, STR_NULL
),
1043 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_GO_SURVEY_PARTICIPATE_BUTTON
), SetAspect(WidgetDimensions::ASPECT_SETTINGS_BUTTON
), SetDataTip(STR_EMPTY
, STR_GAME_OPTIONS_PARTICIPATE_SURVEY_TOOLTIP
),
1045 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
1046 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
),
1047 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
),
1054 NWidget(NWID_VERTICAL
), SetPadding(WidgetDimensions::unscaled
.sparse
), SetPIP(0, WidgetDimensions::unscaled
.vsep_wide
, 0),
1055 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_GUI_SCALE_FRAME
, STR_NULL
),
1056 NWidget(NWID_VERTICAL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_normal
, 0),
1057 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
),
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_SCALE_AUTO
, STR_NULL
),
1060 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_GO_GUI_SCALE_AUTO
), SetAspect(WidgetDimensions::ASPECT_SETTINGS_BUTTON
), SetDataTip(STR_EMPTY
, STR_GAME_OPTIONS_GUI_SCALE_AUTO_TOOLTIP
),
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_SCALE_BEVELS
, STR_NULL
),
1064 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_GO_GUI_SCALE_BEVEL_BUTTON
), SetAspect(WidgetDimensions::ASPECT_SETTINGS_BUTTON
), SetDataTip(STR_EMPTY
, STR_GAME_OPTIONS_GUI_SCALE_BEVELS_TOOLTIP
),
1066 #ifdef HAS_TRUETYPE_FONT
1067 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_normal
, 0),
1068 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_GUI_FONT_SPRITE
, STR_NULL
),
1069 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_GO_GUI_FONT_SPRITE
), SetAspect(WidgetDimensions::ASPECT_SETTINGS_BUTTON
), SetDataTip(STR_EMPTY
, STR_GAME_OPTIONS_GUI_FONT_SPRITE_TOOLTIP
),
1071 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_normal
, 0),
1072 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_GUI_FONT_AA
, STR_NULL
),
1073 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_GO_GUI_FONT_AA
), SetAspect(WidgetDimensions::ASPECT_SETTINGS_BUTTON
), SetDataTip(STR_EMPTY
, STR_GAME_OPTIONS_GUI_FONT_AA_TOOLTIP
),
1075 #endif /* HAS_TRUETYPE_FONT */
1079 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_GRAPHICS
, STR_NULL
),
1080 NWidget(NWID_VERTICAL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_normal
, 0),
1081 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_normal
, 0),
1082 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_RESOLUTION
, STR_NULL
),
1083 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_GO_RESOLUTION_DROPDOWN
), SetMinimalSize(100, 12), SetDataTip(STR_JUST_STRING2
, STR_GAME_OPTIONS_RESOLUTION_TOOLTIP
),
1085 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_normal
, 0),
1086 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_REFRESH_RATE
, STR_NULL
),
1087 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
),
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_FULLSCREEN
, STR_NULL
),
1091 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_GO_FULLSCREEN_BUTTON
), SetAspect(WidgetDimensions::ASPECT_SETTINGS_BUTTON
), SetDataTip(STR_EMPTY
, STR_GAME_OPTIONS_FULLSCREEN_TOOLTIP
),
1093 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_normal
, 0),
1094 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_VIDEO_ACCELERATION
, STR_NULL
),
1095 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_GO_VIDEO_ACCEL_BUTTON
), SetAspect(WidgetDimensions::ASPECT_SETTINGS_BUTTON
), SetDataTip(STR_EMPTY
, STR_GAME_OPTIONS_VIDEO_ACCELERATION_TOOLTIP
),
1098 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_normal
, 0),
1099 NWidget(WWT_TEXT
, COLOUR_GREY
), SetMinimalSize(0, 12), SetFill(1, 0), SetDataTip(STR_GAME_OPTIONS_VIDEO_VSYNC
, STR_NULL
),
1100 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_GO_VIDEO_VSYNC_BUTTON
), SetAspect(WidgetDimensions::ASPECT_SETTINGS_BUTTON
), SetDataTip(STR_EMPTY
, STR_GAME_OPTIONS_VIDEO_VSYNC_TOOLTIP
),
1103 NWidget(NWID_HORIZONTAL
),
1104 NWidget(WWT_EMPTY
, INVALID_COLOUR
, WID_GO_VIDEO_DRIVER_INFO
), SetMinimalTextLines(1, 0), SetFill(1, 0),
1109 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_BASE_GRF
, STR_NULL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_sparse
, 0), SetFill(1, 0),
1110 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
1111 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),
1112 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_GO_BASE_GRF_PARAMETERS
), SetDataTip(STR_NEWGRF_SETTINGS_SET_PARAMETERS
, STR_NULL
),
1113 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_GO_BASE_GRF_CONTENT_DOWNLOAD
), SetDataTip(STR_GAME_OPTIONS_ONLINE_CONTENT
, STR_GAME_OPTIONS_ONLINE_CONTENT_TOOLTIP
),
1115 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),
1116 NWidget(NWID_VERTICAL
),
1117 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
1118 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
),
1119 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
),
1121 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
1122 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
),
1123 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
),
1129 /* Sound/Music tab */
1130 NWidget(NWID_VERTICAL
), SetPadding(WidgetDimensions::unscaled
.sparse
), SetPIP(0, WidgetDimensions::unscaled
.vsep_wide
, 0),
1131 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_VOLUME
, STR_NULL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_wide
, 0),
1132 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
1133 NWidget(WWT_TEXT
, COLOUR_GREY
, WID_GO_TEXT_SFX_VOLUME
), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_SFX_VOLUME
, STR_NULL
),
1134 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
),
1136 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
1137 NWidget(WWT_TEXT
, COLOUR_GREY
, WID_GO_TEXT_MUSIC_VOLUME
), SetMinimalSize(0, 12), SetDataTip(STR_GAME_OPTIONS_MUSIC_VOLUME
, STR_NULL
),
1138 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
),
1142 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_BASE_SFX
, STR_NULL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_sparse
, 0),
1143 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
1144 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),
1145 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_GO_BASE_SFX_CONTENT_DOWNLOAD
), SetDataTip(STR_GAME_OPTIONS_ONLINE_CONTENT
, STR_GAME_OPTIONS_ONLINE_CONTENT_TOOLTIP
),
1147 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),
1148 NWidget(NWID_VERTICAL
),
1149 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
1150 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
),
1151 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
),
1153 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
1154 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
),
1155 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
),
1160 NWidget(WWT_FRAME
, COLOUR_GREY
), SetDataTip(STR_GAME_OPTIONS_BASE_MUSIC
, STR_NULL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_sparse
, 0),
1161 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
1162 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),
1163 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_GO_BASE_MUSIC_CONTENT_DOWNLOAD
), SetDataTip(STR_GAME_OPTIONS_ONLINE_CONTENT
, STR_GAME_OPTIONS_ONLINE_CONTENT_TOOLTIP
),
1165 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
1166 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),
1167 NWidget(NWID_VERTICAL
), SetPIPRatio(0, 0, 1),
1168 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_GO_BASE_MUSIC_JUKEBOX
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_MUSIC
, STR_TOOLBAR_TOOLTIP_SHOW_SOUND_MUSIC_WINDOW
),
1171 NWidget(NWID_VERTICAL
),
1172 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
1173 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
),
1174 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
),
1176 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
1177 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
),
1178 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
),
1185 NWidget(NWID_VERTICAL
), SetPadding(WidgetDimensions::unscaled
.sparse
), SetPIP(0, WidgetDimensions::unscaled
.vsep_wide
, 0),
1186 NWidgetFunction(MakeNWidgetSocialPlugins
),
1192 static WindowDesc
_game_options_desc(
1193 WDP_CENTER
, nullptr, 0, 0,
1194 WC_GAME_OPTIONS
, WC_NONE
,
1196 _nested_game_options_widgets
1199 /** Open the game options window. */
1200 void ShowGameOptions()
1202 CloseWindowByClass(WC_GAME_OPTIONS
);
1203 new GameOptionsWindow(_game_options_desc
);
1206 static int SETTING_HEIGHT
= 11; ///< Height of a single setting in the tree view in pixels
1209 * Flags for #SettingEntry
1210 * @note The #SEF_BUTTONS_MASK matches expectations of the formal parameter 'state' of #DrawArrowButtons
1212 enum SettingEntryFlags
{
1213 SEF_LEFT_DEPRESSED
= 0x01, ///< Of a numeric setting entry, the left button is depressed
1214 SEF_RIGHT_DEPRESSED
= 0x02, ///< Of a numeric setting entry, the right button is depressed
1215 SEF_BUTTONS_MASK
= (SEF_LEFT_DEPRESSED
| SEF_RIGHT_DEPRESSED
), ///< Bit-mask for button flags
1217 SEF_LAST_FIELD
= 0x04, ///< This entry is the last one in a (sub-)page
1218 SEF_FILTERED
= 0x08, ///< Entry is hidden by the string filter
1221 /** How the list of advanced settings is filtered. */
1222 enum RestrictionMode
{
1223 RM_BASIC
, ///< Display settings associated to the "basic" list.
1224 RM_ADVANCED
, ///< Display settings associated to the "advanced" list.
1225 RM_ALL
, ///< List all settings regardless of the default/newgame/... values.
1226 RM_CHANGED_AGAINST_DEFAULT
, ///< Show only settings which are different compared to default values.
1227 RM_CHANGED_AGAINST_NEW
, ///< Show only settings which are different compared to the user's new game setting values.
1228 RM_END
, ///< End for iteration.
1230 DECLARE_POSTFIX_INCREMENT(RestrictionMode
)
1232 /** Filter for settings list. */
1233 struct SettingFilter
{
1234 StringFilter string
; ///< Filter string.
1235 RestrictionMode min_cat
; ///< Minimum category needed to display all filtered strings (#RM_BASIC, #RM_ADVANCED, or #RM_ALL).
1236 bool type_hides
; ///< Whether the type hides filtered strings.
1237 RestrictionMode mode
; ///< Filter based on category.
1238 SettingType type
; ///< Filter based on type.
1241 /** Data structure describing a single setting in a tab */
1242 struct BaseSettingEntry
{
1243 uint8_t flags
; ///< Flags of the setting entry. @see SettingEntryFlags
1244 uint8_t level
; ///< Nesting level of this setting entry
1246 BaseSettingEntry() : flags(0), level(0) {}
1247 virtual ~BaseSettingEntry() = default;
1249 virtual void Init(uint8_t level
= 0);
1250 virtual void FoldAll() {}
1251 virtual void UnFoldAll() {}
1252 virtual void ResetAll() = 0;
1255 * Set whether this is the last visible entry of the parent node.
1256 * @param last_field Value to set
1258 void SetLastField(bool last_field
) { if (last_field
) SETBITS(this->flags
, SEF_LAST_FIELD
); else CLRBITS(this->flags
, SEF_LAST_FIELD
); }
1260 virtual uint
Length() const = 0;
1261 virtual void GetFoldingState([[maybe_unused
]] bool &all_folded
, [[maybe_unused
]] bool &all_unfolded
) const {}
1262 virtual bool IsVisible(const BaseSettingEntry
*item
) const;
1263 virtual BaseSettingEntry
*FindEntry(uint row
, uint
*cur_row
);
1264 virtual uint
GetMaxHelpHeight([[maybe_unused
]] int maxw
) { return 0; }
1267 * Check whether an entry is hidden due to filters
1268 * @return true if hidden.
1270 bool IsFiltered() const { return (this->flags
& SEF_FILTERED
) != 0; }
1272 virtual bool UpdateFilterState(SettingFilter
&filter
, bool force_visible
) = 0;
1274 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;
1277 virtual void DrawSetting(GameSettings
*settings_ptr
, int left
, int right
, int y
, bool highlight
) const = 0;
1280 /** Standard setting */
1281 struct SettingEntry
: BaseSettingEntry
{
1282 const char *name
; ///< Name of the setting
1283 const IntSettingDesc
*setting
; ///< Setting description of the setting
1285 SettingEntry(const char *name
);
1287 void Init(uint8_t level
= 0) override
;
1288 void ResetAll() override
;
1289 uint
Length() const override
;
1290 uint
GetMaxHelpHeight(int maxw
) override
;
1291 bool UpdateFilterState(SettingFilter
&filter
, bool force_visible
) override
;
1293 void SetButtons(uint8_t new_val
);
1296 void DrawSetting(GameSettings
*settings_ptr
, int left
, int right
, int y
, bool highlight
) const override
;
1299 bool IsVisibleByRestrictionMode(RestrictionMode mode
) const;
1302 /** Containers for BaseSettingEntry */
1303 struct SettingsContainer
{
1304 typedef std::vector
<BaseSettingEntry
*> EntryVector
;
1305 EntryVector entries
; ///< Settings on this page
1307 template<typename T
>
1310 this->entries
.push_back(item
);
1314 void Init(uint8_t level
= 0);
1319 uint
Length() const;
1320 void GetFoldingState(bool &all_folded
, bool &all_unfolded
) const;
1321 bool IsVisible(const BaseSettingEntry
*item
) const;
1322 BaseSettingEntry
*FindEntry(uint row
, uint
*cur_row
);
1323 uint
GetMaxHelpHeight(int maxw
);
1325 bool UpdateFilterState(SettingFilter
&filter
, bool force_visible
);
1327 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;
1330 /** Data structure describing one page of settings in the settings window. */
1331 struct SettingsPage
: BaseSettingEntry
, SettingsContainer
{
1332 StringID title
; ///< Title of the sub-page
1333 bool folded
; ///< Sub-page is folded (not visible except for its title)
1335 SettingsPage(StringID title
);
1337 void Init(uint8_t level
= 0) override
;
1338 void ResetAll() override
;
1339 void FoldAll() override
;
1340 void UnFoldAll() override
;
1342 uint
Length() const override
;
1343 void GetFoldingState(bool &all_folded
, bool &all_unfolded
) const override
;
1344 bool IsVisible(const BaseSettingEntry
*item
) const override
;
1345 BaseSettingEntry
*FindEntry(uint row
, uint
*cur_row
) override
;
1346 uint
GetMaxHelpHeight(int maxw
) override
{ return SettingsContainer::GetMaxHelpHeight(maxw
); }
1348 bool UpdateFilterState(SettingFilter
&filter
, bool force_visible
) override
;
1350 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
;
1353 void DrawSetting(GameSettings
*settings_ptr
, int left
, int right
, int y
, bool highlight
) const override
;
1356 /* == BaseSettingEntry methods == */
1359 * Initialization of a setting entry
1360 * @param level Page nesting level of this entry
1362 void BaseSettingEntry::Init(uint8_t level
)
1364 this->level
= level
;
1368 * Check whether an entry is visible and not folded or filtered away.
1369 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1370 * @param item Entry to search for.
1371 * @return true if entry is visible.
1373 bool BaseSettingEntry::IsVisible(const BaseSettingEntry
*item
) const
1375 if (this->IsFiltered()) return false;
1376 return this == item
;
1380 * Find setting entry at row \a row_num
1381 * @param row_num Index of entry to return
1382 * @param cur_row Current row number
1383 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
1385 BaseSettingEntry
*BaseSettingEntry::FindEntry(uint row_num
, uint
*cur_row
)
1387 if (this->IsFiltered()) return nullptr;
1388 if (row_num
== *cur_row
) return this;
1394 * Draw a row in the settings panel.
1396 * The scrollbar uses rows of the page, while the page data structure is a tree of #SettingsPage and #SettingEntry objects.
1397 * 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.
1398 * Then it enables drawing rows while traversing until \a max_row is reached, at which point drawing is terminated.
1400 * The \a parent_last parameter ensures that the vertical lines at the left are
1401 * only drawn when another entry follows, that it prevents output like
1408 * The left-most vertical line is not wanted. It is prevented by setting the
1409 * appropriate bit in the \a parent_last parameter.
1411 * @param settings_ptr Pointer to current values of all settings
1412 * @param left Left-most position in window/panel to start drawing \a first_row
1413 * @param right Right-most x position to draw strings at.
1414 * @param y Upper-most position in window/panel to start drawing \a first_row
1415 * @param first_row First row number to draw
1416 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1417 * @param selected Selected entry by the user.
1418 * @param cur_row Current row number (internal variable)
1419 * @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)
1420 * @return Row number of the next row to draw
1422 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
1424 if (this->IsFiltered()) return cur_row
;
1425 if (cur_row
>= max_row
) return cur_row
;
1427 bool rtl
= _current_text_dir
== TD_RTL
;
1428 int offset
= (rtl
? -(int)_circle_size
.width
: (int)_circle_size
.width
) / 2;
1429 int level_width
= rtl
? -WidgetDimensions::scaled
.hsep_indent
: WidgetDimensions::scaled
.hsep_indent
;
1431 int x
= rtl
? right
: left
;
1432 if (cur_row
>= first_row
) {
1433 int colour
= GetColourGradient(COLOUR_ORANGE
, SHADE_NORMAL
);
1434 y
+= (cur_row
- first_row
) * SETTING_HEIGHT
; // Compute correct y start position
1436 /* Draw vertical for parent nesting levels */
1437 for (uint lvl
= 0; lvl
< this->level
; lvl
++) {
1438 if (!HasBit(parent_last
, lvl
)) GfxDrawLine(x
+ offset
, y
, x
+ offset
, y
+ SETTING_HEIGHT
- 1, colour
);
1441 /* draw own |- prefix */
1442 int halfway_y
= y
+ SETTING_HEIGHT
/ 2;
1443 int bottom_y
= (flags
& SEF_LAST_FIELD
) ? halfway_y
: y
+ SETTING_HEIGHT
- 1;
1444 GfxDrawLine(x
+ offset
, y
, x
+ offset
, bottom_y
, colour
);
1445 /* Small horizontal line from the last vertical line */
1446 GfxDrawLine(x
+ offset
, halfway_y
, x
+ level_width
- (rtl
? -WidgetDimensions::scaled
.hsep_normal
: WidgetDimensions::scaled
.hsep_normal
), halfway_y
, colour
);
1449 this->DrawSetting(settings_ptr
, rtl
? left
: x
, rtl
? x
: right
, y
, this == selected
);
1456 /* == SettingEntry methods == */
1459 * Constructor for a single setting in the 'advanced settings' window
1460 * @param name Name of the setting in the setting table
1462 SettingEntry::SettingEntry(const char *name
)
1465 this->setting
= nullptr;
1469 * Initialization of a setting entry
1470 * @param level Page nesting level of this entry
1472 void SettingEntry::Init(uint8_t level
)
1474 BaseSettingEntry::Init(level
);
1475 this->setting
= GetSettingFromName(this->name
)->AsIntSetting();
1478 /* Sets the given setting entry to its default value */
1479 void SettingEntry::ResetAll()
1481 SetSettingValue(this->setting
, this->setting
->def
);
1485 * Set the button-depressed flags (#SEF_LEFT_DEPRESSED and #SEF_RIGHT_DEPRESSED) to a specified value
1486 * @param new_val New value for the button flags
1487 * @see SettingEntryFlags
1489 void SettingEntry::SetButtons(uint8_t new_val
)
1491 assert((new_val
& ~SEF_BUTTONS_MASK
) == 0); // Should not touch any flags outside the buttons
1492 this->flags
= (this->flags
& ~SEF_BUTTONS_MASK
) | new_val
;
1495 /** Return number of rows needed to display the (filtered) entry */
1496 uint
SettingEntry::Length() const
1498 return this->IsFiltered() ? 0 : 1;
1502 * Get the biggest height of the help text(s), if the width is at least \a maxw. Help text gets wrapped if needed.
1503 * @param maxw Maximal width of a line help text.
1504 * @return Biggest height needed to display any help text of this node (and its descendants).
1506 uint
SettingEntry::GetMaxHelpHeight(int maxw
)
1508 return GetStringHeight(this->setting
->GetHelp(), maxw
);
1512 * Checks whether an entry shall be made visible based on the restriction mode.
1513 * @param mode The current status of the restriction drop down box.
1514 * @return true if the entry shall be visible.
1516 bool SettingEntry::IsVisibleByRestrictionMode(RestrictionMode mode
) const
1518 /* There shall not be any restriction, i.e. all settings shall be visible. */
1519 if (mode
== RM_ALL
) return true;
1521 const IntSettingDesc
*sd
= this->setting
;
1523 if (mode
== RM_BASIC
) return (this->setting
->cat
& SC_BASIC_LIST
) != 0;
1524 if (mode
== RM_ADVANCED
) return (this->setting
->cat
& SC_ADVANCED_LIST
) != 0;
1526 /* Read the current value. */
1527 const void *object
= ResolveObject(&GetGameSettings(), sd
);
1528 int64_t current_value
= sd
->Read(object
);
1529 int64_t filter_value
;
1531 if (mode
== RM_CHANGED_AGAINST_DEFAULT
) {
1532 /* This entry shall only be visible, if the value deviates from its default value. */
1534 /* Read the default value. */
1535 filter_value
= sd
->def
;
1537 assert(mode
== RM_CHANGED_AGAINST_NEW
);
1538 /* This entry shall only be visible, if the value deviates from
1539 * its value is used when starting a new game. */
1541 /* Make sure we're not comparing the new game settings against itself. */
1542 assert(&GetGameSettings() != &_settings_newgame
);
1544 /* Read the new game's value. */
1545 filter_value
= sd
->Read(ResolveObject(&_settings_newgame
, sd
));
1548 return current_value
!= filter_value
;
1552 * Update the filter state.
1553 * @param filter Filter
1554 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1555 * @return true if item remains visible
1557 bool SettingEntry::UpdateFilterState(SettingFilter
&filter
, bool force_visible
)
1559 CLRBITS(this->flags
, SEF_FILTERED
);
1561 bool visible
= true;
1563 const IntSettingDesc
*sd
= this->setting
;
1564 if (!force_visible
&& !filter
.string
.IsEmpty()) {
1565 /* Process the search text filter for this item. */
1566 filter
.string
.ResetState();
1568 SetDParam(0, STR_EMPTY
);
1569 filter
.string
.AddLine(sd
->GetTitle());
1570 filter
.string
.AddLine(sd
->GetHelp());
1572 visible
= filter
.string
.GetState();
1576 if (filter
.type
!= ST_ALL
&& sd
->GetType() != filter
.type
) {
1577 filter
.type_hides
= true;
1580 if (!this->IsVisibleByRestrictionMode(filter
.mode
)) {
1581 while (filter
.min_cat
< RM_ALL
&& (filter
.min_cat
== filter
.mode
|| !this->IsVisibleByRestrictionMode(filter
.min_cat
))) filter
.min_cat
++;
1586 if (!visible
) SETBITS(this->flags
, SEF_FILTERED
);
1590 static const void *ResolveObject(const GameSettings
*settings_ptr
, const IntSettingDesc
*sd
)
1592 if ((sd
->flags
& SF_PER_COMPANY
) != 0) {
1593 if (Company::IsValidID(_local_company
) && _game_mode
!= GM_MENU
) {
1594 return &Company::Get(_local_company
)->settings
;
1596 return &_settings_client
.company
;
1598 return settings_ptr
;
1602 * Function to draw setting value (button + text + current value)
1603 * @param settings_ptr Pointer to current values of all settings
1604 * @param left Left-most position in window/panel to start drawing
1605 * @param right Right-most position in window/panel to draw
1606 * @param y Upper-most position in window/panel to start drawing
1607 * @param highlight Highlight entry.
1609 void SettingEntry::DrawSetting(GameSettings
*settings_ptr
, int left
, int right
, int y
, bool highlight
) const
1611 const IntSettingDesc
*sd
= this->setting
;
1612 int state
= this->flags
& SEF_BUTTONS_MASK
;
1614 bool rtl
= _current_text_dir
== TD_RTL
;
1615 uint buttons_left
= rtl
? right
+ 1 - SETTING_BUTTON_WIDTH
: left
;
1616 uint text_left
= left
+ (rtl
? 0 : SETTING_BUTTON_WIDTH
+ WidgetDimensions::scaled
.hsep_wide
);
1617 uint text_right
= right
- (rtl
? SETTING_BUTTON_WIDTH
+ WidgetDimensions::scaled
.hsep_wide
: 0);
1618 uint button_y
= y
+ (SETTING_HEIGHT
- SETTING_BUTTON_HEIGHT
) / 2;
1620 /* We do not allow changes of some items when we are a client in a networkgame */
1621 bool editable
= sd
->IsEditable();
1623 SetDParam(0, STR_CONFIG_SETTING_VALUE
);
1624 int32_t value
= sd
->Read(ResolveObject(settings_ptr
, sd
));
1625 if (sd
->IsBoolSetting()) {
1626 /* Draw checkbox for boolean-value either on/off */
1627 DrawBoolButton(buttons_left
, button_y
, value
!= 0, editable
);
1628 } else if ((sd
->flags
& SF_GUI_DROPDOWN
) != 0) {
1629 /* Draw [v] button for settings of an enum-type */
1630 DrawDropDownButton(buttons_left
, button_y
, COLOUR_YELLOW
, state
!= 0, editable
);
1632 /* Draw [<][>] boxes for settings of an integer-type */
1633 DrawArrowButtons(buttons_left
, button_y
, COLOUR_YELLOW
, state
,
1634 editable
&& value
!= (sd
->flags
& SF_GUI_0_IS_SPECIAL
? 0 : sd
->min
), editable
&& (uint32_t)value
!= sd
->max
);
1636 sd
->SetValueDParams(1, value
);
1637 DrawString(text_left
, text_right
, y
+ (SETTING_HEIGHT
- GetCharacterHeight(FS_NORMAL
)) / 2, sd
->GetTitle(), highlight
? TC_WHITE
: TC_LIGHT_BLUE
);
1640 /* == SettingsContainer methods == */
1643 * Initialization of an entire setting page
1644 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1646 void SettingsContainer::Init(uint8_t level
)
1648 for (auto &it
: this->entries
) {
1653 /** Resets all settings to their default values */
1654 void SettingsContainer::ResetAll()
1656 for (auto settings_entry
: this->entries
) {
1657 settings_entry
->ResetAll();
1661 /** Recursively close all folds of sub-pages */
1662 void SettingsContainer::FoldAll()
1664 for (auto &it
: this->entries
) {
1669 /** Recursively open all folds of sub-pages */
1670 void SettingsContainer::UnFoldAll()
1672 for (auto &it
: this->entries
) {
1678 * Recursively accumulate the folding state of the tree.
1679 * @param[in,out] all_folded Set to false, if one entry is not folded.
1680 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1682 void SettingsContainer::GetFoldingState(bool &all_folded
, bool &all_unfolded
) const
1684 for (auto &it
: this->entries
) {
1685 it
->GetFoldingState(all_folded
, all_unfolded
);
1690 * Update the filter state.
1691 * @param filter Filter
1692 * @param force_visible Whether to force all items visible, no matter what
1693 * @return true if item remains visible
1695 bool SettingsContainer::UpdateFilterState(SettingFilter
&filter
, bool force_visible
)
1697 bool visible
= false;
1698 bool first_visible
= true;
1699 for (EntryVector::reverse_iterator it
= this->entries
.rbegin(); it
!= this->entries
.rend(); ++it
) {
1700 visible
|= (*it
)->UpdateFilterState(filter
, force_visible
);
1701 (*it
)->SetLastField(first_visible
);
1702 if (visible
&& first_visible
) first_visible
= false;
1709 * Check whether an entry is visible and not folded or filtered away.
1710 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1711 * @param item Entry to search for.
1712 * @return true if entry is visible.
1714 bool SettingsContainer::IsVisible(const BaseSettingEntry
*item
) const
1716 for (const auto &it
: this->entries
) {
1717 if (it
->IsVisible(item
)) return true;
1722 /** Return number of rows needed to display the whole page */
1723 uint
SettingsContainer::Length() const
1726 for (const auto &it
: this->entries
) {
1727 length
+= it
->Length();
1733 * Find the setting entry at row number \a row_num
1734 * @param row_num Index of entry to return
1735 * @param cur_row Variable used for keeping track of the current row number. Should point to memory initialized to \c 0 when first called.
1736 * @return The requested setting entry or \c nullptr if it does not exist
1738 BaseSettingEntry
*SettingsContainer::FindEntry(uint row_num
, uint
*cur_row
)
1740 BaseSettingEntry
*pe
= nullptr;
1741 for (const auto &it
: this->entries
) {
1742 pe
= it
->FindEntry(row_num
, cur_row
);
1743 if (pe
!= nullptr) {
1751 * Get the biggest height of the help texts, if the width is at least \a maxw. Help text gets wrapped if needed.
1752 * @param maxw Maximal width of a line help text.
1753 * @return Biggest height needed to display any help text of this (sub-)tree.
1755 uint
SettingsContainer::GetMaxHelpHeight(int maxw
)
1758 for (const auto &it
: this->entries
) {
1759 biggest
= std::max(biggest
, it
->GetMaxHelpHeight(maxw
));
1766 * Draw a row in the settings panel.
1768 * @param settings_ptr Pointer to current values of all settings
1769 * @param left Left-most position in window/panel to start drawing \a first_row
1770 * @param right Right-most x position to draw strings at.
1771 * @param y Upper-most position in window/panel to start drawing \a first_row
1772 * @param first_row First row number to draw
1773 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1774 * @param selected Selected entry by the user.
1775 * @param cur_row Current row number (internal variable)
1776 * @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)
1777 * @return Row number of the next row to draw
1779 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
1781 for (const auto &it
: this->entries
) {
1782 cur_row
= it
->Draw(settings_ptr
, left
, right
, y
, first_row
, max_row
, selected
, cur_row
, parent_last
);
1783 if (cur_row
>= max_row
) break;
1788 /* == SettingsPage methods == */
1791 * Constructor for a sub-page in the 'advanced settings' window
1792 * @param title Title of the sub-page
1794 SettingsPage::SettingsPage(StringID title
)
1796 this->title
= title
;
1797 this->folded
= true;
1801 * Initialization of an entire setting page
1802 * @param level Nesting level of this page (internal variable, do not provide a value for it when calling)
1804 void SettingsPage::Init(uint8_t level
)
1806 BaseSettingEntry::Init(level
);
1807 SettingsContainer::Init(level
+ 1);
1810 /** Resets all settings to their default values */
1811 void SettingsPage::ResetAll()
1813 for (auto settings_entry
: this->entries
) {
1814 settings_entry
->ResetAll();
1818 /** Recursively close all (filtered) folds of sub-pages */
1819 void SettingsPage::FoldAll()
1821 if (this->IsFiltered()) return;
1822 this->folded
= true;
1824 SettingsContainer::FoldAll();
1827 /** Recursively open all (filtered) folds of sub-pages */
1828 void SettingsPage::UnFoldAll()
1830 if (this->IsFiltered()) return;
1831 this->folded
= false;
1833 SettingsContainer::UnFoldAll();
1837 * Recursively accumulate the folding state of the (filtered) tree.
1838 * @param[in,out] all_folded Set to false, if one entry is not folded.
1839 * @param[in,out] all_unfolded Set to false, if one entry is folded.
1841 void SettingsPage::GetFoldingState(bool &all_folded
, bool &all_unfolded
) const
1843 if (this->IsFiltered()) return;
1846 all_unfolded
= false;
1851 SettingsContainer::GetFoldingState(all_folded
, all_unfolded
);
1855 * Update the filter state.
1856 * @param filter Filter
1857 * @param force_visible Whether to force all items visible, no matter what (due to filter text; not affected by restriction drop down box).
1858 * @return true if item remains visible
1860 bool SettingsPage::UpdateFilterState(SettingFilter
&filter
, bool force_visible
)
1862 if (!force_visible
&& !filter
.string
.IsEmpty()) {
1863 filter
.string
.ResetState();
1864 filter
.string
.AddLine(this->title
);
1865 force_visible
= filter
.string
.GetState();
1868 bool visible
= SettingsContainer::UpdateFilterState(filter
, force_visible
);
1870 CLRBITS(this->flags
, SEF_FILTERED
);
1872 SETBITS(this->flags
, SEF_FILTERED
);
1878 * Check whether an entry is visible and not folded or filtered away.
1879 * Note: This does not consider the scrolling range; it might still require scrolling to make the setting really visible.
1880 * @param item Entry to search for.
1881 * @return true if entry is visible.
1883 bool SettingsPage::IsVisible(const BaseSettingEntry
*item
) const
1885 if (this->IsFiltered()) return false;
1886 if (this == item
) return true;
1887 if (this->folded
) return false;
1889 return SettingsContainer::IsVisible(item
);
1892 /** Return number of rows needed to display the (filtered) entry */
1893 uint
SettingsPage::Length() const
1895 if (this->IsFiltered()) return 0;
1896 if (this->folded
) return 1; // Only displaying the title
1898 return 1 + SettingsContainer::Length();
1902 * Find setting entry at row \a row_num
1903 * @param row_num Index of entry to return
1904 * @param cur_row Current row number
1905 * @return The requested setting entry or \c nullptr if it not found (folded or filtered)
1907 BaseSettingEntry
*SettingsPage::FindEntry(uint row_num
, uint
*cur_row
)
1909 if (this->IsFiltered()) return nullptr;
1910 if (row_num
== *cur_row
) return this;
1912 if (this->folded
) return nullptr;
1914 return SettingsContainer::FindEntry(row_num
, cur_row
);
1918 * Draw a row in the settings panel.
1920 * @param settings_ptr Pointer to current values of all settings
1921 * @param left Left-most position in window/panel to start drawing \a first_row
1922 * @param right Right-most x position to draw strings at.
1923 * @param y Upper-most position in window/panel to start drawing \a first_row
1924 * @param first_row First row number to draw
1925 * @param max_row Row-number to stop drawing (the row-number of the row below the last row to draw)
1926 * @param selected Selected entry by the user.
1927 * @param cur_row Current row number (internal variable)
1928 * @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)
1929 * @return Row number of the next row to draw
1931 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
1933 if (this->IsFiltered()) return cur_row
;
1934 if (cur_row
>= max_row
) return cur_row
;
1936 cur_row
= BaseSettingEntry::Draw(settings_ptr
, left
, right
, y
, first_row
, max_row
, selected
, cur_row
, parent_last
);
1938 if (!this->folded
) {
1939 if (this->flags
& SEF_LAST_FIELD
) {
1940 assert(this->level
< 8 * sizeof(parent_last
));
1941 SetBit(parent_last
, this->level
); // Add own last-field state
1944 cur_row
= SettingsContainer::Draw(settings_ptr
, left
, right
, y
, first_row
, max_row
, selected
, cur_row
, parent_last
);
1951 * Function to draw setting value (button + text + current value)
1952 * @param left Left-most position in window/panel to start drawing
1953 * @param right Right-most position in window/panel to draw
1954 * @param y Upper-most position in window/panel to start drawing
1956 void SettingsPage::DrawSetting(GameSettings
*, int left
, int right
, int y
, bool) const
1958 bool rtl
= _current_text_dir
== TD_RTL
;
1959 DrawSprite((this->folded
? SPR_CIRCLE_FOLDED
: SPR_CIRCLE_UNFOLDED
), PAL_NONE
, rtl
? right
- _circle_size
.width
: left
, y
+ (SETTING_HEIGHT
- _circle_size
.height
) / 2);
1960 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
);
1963 /** Construct settings tree */
1964 static SettingsContainer
&GetSettingsTree()
1966 static SettingsContainer
*main
= nullptr;
1968 if (main
== nullptr)
1970 /* Build up the dynamic settings-array only once per OpenTTD session */
1971 main
= new SettingsContainer();
1973 SettingsPage
*localisation
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_LOCALISATION
));
1975 localisation
->Add(new SettingEntry("locale.units_velocity"));
1976 localisation
->Add(new SettingEntry("locale.units_velocity_nautical"));
1977 localisation
->Add(new SettingEntry("locale.units_power"));
1978 localisation
->Add(new SettingEntry("locale.units_weight"));
1979 localisation
->Add(new SettingEntry("locale.units_volume"));
1980 localisation
->Add(new SettingEntry("locale.units_force"));
1981 localisation
->Add(new SettingEntry("locale.units_height"));
1982 localisation
->Add(new SettingEntry("gui.date_format_in_default_names"));
1985 SettingsPage
*graphics
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_GRAPHICS
));
1987 graphics
->Add(new SettingEntry("gui.zoom_min"));
1988 graphics
->Add(new SettingEntry("gui.zoom_max"));
1989 graphics
->Add(new SettingEntry("gui.sprite_zoom_min"));
1990 graphics
->Add(new SettingEntry("gui.smallmap_land_colour"));
1991 graphics
->Add(new SettingEntry("gui.linkgraph_colours"));
1992 graphics
->Add(new SettingEntry("gui.graph_line_thickness"));
1995 SettingsPage
*sound
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_SOUND
));
1997 sound
->Add(new SettingEntry("sound.click_beep"));
1998 sound
->Add(new SettingEntry("sound.confirm"));
1999 sound
->Add(new SettingEntry("sound.news_ticker"));
2000 sound
->Add(new SettingEntry("sound.news_full"));
2001 sound
->Add(new SettingEntry("sound.new_year"));
2002 sound
->Add(new SettingEntry("sound.disaster"));
2003 sound
->Add(new SettingEntry("sound.vehicle"));
2004 sound
->Add(new SettingEntry("sound.ambient"));
2007 SettingsPage
*interface
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE
));
2009 SettingsPage
*general
= interface
->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_GENERAL
));
2011 general
->Add(new SettingEntry("gui.osk_activation"));
2012 general
->Add(new SettingEntry("gui.hover_delay_ms"));
2013 general
->Add(new SettingEntry("gui.errmsg_duration"));
2014 general
->Add(new SettingEntry("gui.window_snap_radius"));
2015 general
->Add(new SettingEntry("gui.window_soft_limit"));
2016 general
->Add(new SettingEntry("gui.right_click_wnd_close"));
2019 SettingsPage
*viewports
= interface
->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_VIEWPORTS
));
2021 viewports
->Add(new SettingEntry("gui.auto_scrolling"));
2022 viewports
->Add(new SettingEntry("gui.scroll_mode"));
2023 viewports
->Add(new SettingEntry("gui.smooth_scroll"));
2024 /* While the horizontal scrollwheel scrolling is written as general code, only
2025 * the cocoa (OSX) driver generates input for it.
2026 * Since it's also able to completely disable the scrollwheel will we display it on all platforms anyway */
2027 viewports
->Add(new SettingEntry("gui.scrollwheel_scrolling"));
2028 viewports
->Add(new SettingEntry("gui.scrollwheel_multiplier"));
2030 /* We might need to emulate a right mouse button on mac */
2031 viewports
->Add(new SettingEntry("gui.right_mouse_btn_emulation"));
2033 viewports
->Add(new SettingEntry("gui.population_in_label"));
2034 viewports
->Add(new SettingEntry("gui.liveries"));
2035 viewports
->Add(new SettingEntry("construction.train_signal_side"));
2036 viewports
->Add(new SettingEntry("gui.measure_tooltip"));
2037 viewports
->Add(new SettingEntry("gui.loading_indicators"));
2038 viewports
->Add(new SettingEntry("gui.show_track_reservation"));
2041 SettingsPage
*construction
= interface
->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_CONSTRUCTION
));
2043 construction
->Add(new SettingEntry("gui.link_terraform_toolbar"));
2044 construction
->Add(new SettingEntry("gui.persistent_buildingtools"));
2045 construction
->Add(new SettingEntry("gui.default_rail_type"));
2046 construction
->Add(new SettingEntry("gui.semaphore_build_before"));
2047 construction
->Add(new SettingEntry("gui.signal_gui_mode"));
2048 construction
->Add(new SettingEntry("gui.cycle_signal_types"));
2049 construction
->Add(new SettingEntry("gui.drag_signals_fixed_distance"));
2050 construction
->Add(new SettingEntry("gui.auto_remove_signals"));
2053 interface
->Add(new SettingEntry("gui.toolbar_pos"));
2054 interface
->Add(new SettingEntry("gui.statusbar_pos"));
2055 interface
->Add(new SettingEntry("gui.prefer_teamchat"));
2056 interface
->Add(new SettingEntry("gui.advanced_vehicle_list"));
2057 interface
->Add(new SettingEntry("gui.timetable_mode"));
2058 interface
->Add(new SettingEntry("gui.timetable_arrival_departure"));
2059 interface
->Add(new SettingEntry("gui.show_newgrf_name"));
2060 interface
->Add(new SettingEntry("gui.show_cargo_in_vehicle_lists"));
2063 SettingsPage
*advisors
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_ADVISORS
));
2065 advisors
->Add(new SettingEntry("gui.coloured_news_year"));
2066 advisors
->Add(new SettingEntry("news_display.general"));
2067 advisors
->Add(new SettingEntry("news_display.new_vehicles"));
2068 advisors
->Add(new SettingEntry("news_display.accident"));
2069 advisors
->Add(new SettingEntry("news_display.accident_other"));
2070 advisors
->Add(new SettingEntry("news_display.company_info"));
2071 advisors
->Add(new SettingEntry("news_display.acceptance"));
2072 advisors
->Add(new SettingEntry("news_display.arrival_player"));
2073 advisors
->Add(new SettingEntry("news_display.arrival_other"));
2074 advisors
->Add(new SettingEntry("news_display.advice"));
2075 advisors
->Add(new SettingEntry("gui.order_review_system"));
2076 advisors
->Add(new SettingEntry("gui.vehicle_income_warn"));
2077 advisors
->Add(new SettingEntry("gui.lost_vehicle_warn"));
2078 advisors
->Add(new SettingEntry("gui.old_vehicle_warn"));
2079 advisors
->Add(new SettingEntry("gui.show_finances"));
2080 advisors
->Add(new SettingEntry("news_display.economy"));
2081 advisors
->Add(new SettingEntry("news_display.subsidies"));
2082 advisors
->Add(new SettingEntry("news_display.open"));
2083 advisors
->Add(new SettingEntry("news_display.close"));
2084 advisors
->Add(new SettingEntry("news_display.production_player"));
2085 advisors
->Add(new SettingEntry("news_display.production_other"));
2086 advisors
->Add(new SettingEntry("news_display.production_nobody"));
2089 SettingsPage
*company
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_COMPANY
));
2091 company
->Add(new SettingEntry("gui.starting_colour"));
2092 company
->Add(new SettingEntry("gui.starting_colour_secondary"));
2093 company
->Add(new SettingEntry("company.engine_renew"));
2094 company
->Add(new SettingEntry("company.engine_renew_months"));
2095 company
->Add(new SettingEntry("company.engine_renew_money"));
2096 company
->Add(new SettingEntry("vehicle.servint_ispercent"));
2097 company
->Add(new SettingEntry("vehicle.servint_trains"));
2098 company
->Add(new SettingEntry("vehicle.servint_roadveh"));
2099 company
->Add(new SettingEntry("vehicle.servint_ships"));
2100 company
->Add(new SettingEntry("vehicle.servint_aircraft"));
2103 SettingsPage
*accounting
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_ACCOUNTING
));
2105 accounting
->Add(new SettingEntry("difficulty.infinite_money"));
2106 accounting
->Add(new SettingEntry("economy.inflation"));
2107 accounting
->Add(new SettingEntry("difficulty.initial_interest"));
2108 accounting
->Add(new SettingEntry("difficulty.max_loan"));
2109 accounting
->Add(new SettingEntry("difficulty.subsidy_multiplier"));
2110 accounting
->Add(new SettingEntry("difficulty.subsidy_duration"));
2111 accounting
->Add(new SettingEntry("economy.feeder_payment_share"));
2112 accounting
->Add(new SettingEntry("economy.infrastructure_maintenance"));
2113 accounting
->Add(new SettingEntry("difficulty.vehicle_costs"));
2114 accounting
->Add(new SettingEntry("difficulty.construction_cost"));
2117 SettingsPage
*vehicles
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES
));
2119 SettingsPage
*physics
= vehicles
->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_PHYSICS
));
2121 physics
->Add(new SettingEntry("vehicle.train_acceleration_model"));
2122 physics
->Add(new SettingEntry("vehicle.train_slope_steepness"));
2123 physics
->Add(new SettingEntry("vehicle.wagon_speed_limits"));
2124 physics
->Add(new SettingEntry("vehicle.freight_trains"));
2125 physics
->Add(new SettingEntry("vehicle.roadveh_acceleration_model"));
2126 physics
->Add(new SettingEntry("vehicle.roadveh_slope_steepness"));
2127 physics
->Add(new SettingEntry("vehicle.smoke_amount"));
2128 physics
->Add(new SettingEntry("vehicle.plane_speed"));
2131 SettingsPage
*routing
= vehicles
->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ROUTING
));
2133 routing
->Add(new SettingEntry("vehicle.road_side"));
2134 routing
->Add(new SettingEntry("difficulty.line_reverse_mode"));
2135 routing
->Add(new SettingEntry("pf.reverse_at_signals"));
2136 routing
->Add(new SettingEntry("pf.forbid_90_deg"));
2139 SettingsPage
*orders
= vehicles
->Add(new SettingsPage(STR_CONFIG_SETTING_VEHICLES_ORDERS
));
2141 orders
->Add(new SettingEntry("gui.new_nonstop"));
2142 orders
->Add(new SettingEntry("gui.quick_goto"));
2143 orders
->Add(new SettingEntry("gui.stop_location"));
2147 SettingsPage
*limitations
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_LIMITATIONS
));
2149 limitations
->Add(new SettingEntry("construction.command_pause_level"));
2150 limitations
->Add(new SettingEntry("construction.autoslope"));
2151 limitations
->Add(new SettingEntry("construction.extra_dynamite"));
2152 limitations
->Add(new SettingEntry("construction.map_height_limit"));
2153 limitations
->Add(new SettingEntry("construction.max_bridge_length"));
2154 limitations
->Add(new SettingEntry("construction.max_bridge_height"));
2155 limitations
->Add(new SettingEntry("construction.max_tunnel_length"));
2156 limitations
->Add(new SettingEntry("station.never_expire_airports"));
2157 limitations
->Add(new SettingEntry("vehicle.never_expire_vehicles"));
2158 limitations
->Add(new SettingEntry("vehicle.max_trains"));
2159 limitations
->Add(new SettingEntry("vehicle.max_roadveh"));
2160 limitations
->Add(new SettingEntry("vehicle.max_aircraft"));
2161 limitations
->Add(new SettingEntry("vehicle.max_ships"));
2162 limitations
->Add(new SettingEntry("vehicle.max_train_length"));
2163 limitations
->Add(new SettingEntry("station.station_spread"));
2164 limitations
->Add(new SettingEntry("station.distant_join_stations"));
2165 limitations
->Add(new SettingEntry("station.modified_catchment"));
2166 limitations
->Add(new SettingEntry("construction.road_stop_on_town_road"));
2167 limitations
->Add(new SettingEntry("construction.road_stop_on_competitor_road"));
2168 limitations
->Add(new SettingEntry("construction.crossing_with_competitor"));
2169 limitations
->Add(new SettingEntry("vehicle.disable_elrails"));
2170 limitations
->Add(new SettingEntry("order.station_length_loading_penalty"));
2173 SettingsPage
*disasters
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_ACCIDENTS
));
2175 disasters
->Add(new SettingEntry("difficulty.disasters"));
2176 disasters
->Add(new SettingEntry("difficulty.economy"));
2177 disasters
->Add(new SettingEntry("vehicle.plane_crashes"));
2178 disasters
->Add(new SettingEntry("difficulty.vehicle_breakdowns"));
2179 disasters
->Add(new SettingEntry("order.no_servicing_if_no_breakdowns"));
2180 disasters
->Add(new SettingEntry("order.serviceathelipad"));
2183 SettingsPage
*genworld
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_GENWORLD
));
2185 genworld
->Add(new SettingEntry("game_creation.landscape"));
2186 genworld
->Add(new SettingEntry("game_creation.land_generator"));
2187 genworld
->Add(new SettingEntry("difficulty.terrain_type"));
2188 genworld
->Add(new SettingEntry("game_creation.tgen_smoothness"));
2189 genworld
->Add(new SettingEntry("game_creation.variety"));
2190 genworld
->Add(new SettingEntry("game_creation.snow_coverage"));
2191 genworld
->Add(new SettingEntry("game_creation.snow_line_height"));
2192 genworld
->Add(new SettingEntry("game_creation.desert_coverage"));
2193 genworld
->Add(new SettingEntry("game_creation.amount_of_rivers"));
2196 SettingsPage
*environment
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT
));
2198 SettingsPage
*time
= environment
->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TIME
));
2200 time
->Add(new SettingEntry("economy.timekeeping_units"));
2201 time
->Add(new SettingEntry("economy.minutes_per_calendar_year"));
2202 time
->Add(new SettingEntry("game_creation.ending_year"));
2203 time
->Add(new SettingEntry("gui.pause_on_newgame"));
2204 time
->Add(new SettingEntry("gui.fast_forward_speed_limit"));
2207 SettingsPage
*authorities
= environment
->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_AUTHORITIES
));
2209 authorities
->Add(new SettingEntry("difficulty.town_council_tolerance"));
2210 authorities
->Add(new SettingEntry("economy.bribe"));
2211 authorities
->Add(new SettingEntry("economy.exclusive_rights"));
2212 authorities
->Add(new SettingEntry("economy.fund_roads"));
2213 authorities
->Add(new SettingEntry("economy.fund_buildings"));
2214 authorities
->Add(new SettingEntry("economy.station_noise_level"));
2217 SettingsPage
*towns
= environment
->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TOWNS
));
2219 towns
->Add(new SettingEntry("economy.town_cargo_scale"));
2220 towns
->Add(new SettingEntry("economy.town_growth_rate"));
2221 towns
->Add(new SettingEntry("economy.allow_town_roads"));
2222 towns
->Add(new SettingEntry("economy.allow_town_level_crossings"));
2223 towns
->Add(new SettingEntry("economy.found_town"));
2224 towns
->Add(new SettingEntry("economy.town_layout"));
2225 towns
->Add(new SettingEntry("economy.larger_towns"));
2226 towns
->Add(new SettingEntry("economy.initial_city_size"));
2227 towns
->Add(new SettingEntry("economy.town_cargogen_mode"));
2230 SettingsPage
*industries
= environment
->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_INDUSTRIES
));
2232 industries
->Add(new SettingEntry("economy.industry_cargo_scale"));
2233 industries
->Add(new SettingEntry("difficulty.industry_density"));
2234 industries
->Add(new SettingEntry("construction.raw_industry_construction"));
2235 industries
->Add(new SettingEntry("construction.industry_platform"));
2236 industries
->Add(new SettingEntry("economy.multiple_industry_per_town"));
2237 industries
->Add(new SettingEntry("game_creation.oil_refinery_limit"));
2238 industries
->Add(new SettingEntry("economy.type"));
2239 industries
->Add(new SettingEntry("station.serve_neutral_industries"));
2242 SettingsPage
*cdist
= environment
->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_CARGODIST
));
2244 cdist
->Add(new SettingEntry("linkgraph.recalc_time"));
2245 cdist
->Add(new SettingEntry("linkgraph.recalc_interval"));
2246 cdist
->Add(new SettingEntry("linkgraph.distribution_pax"));
2247 cdist
->Add(new SettingEntry("linkgraph.distribution_mail"));
2248 cdist
->Add(new SettingEntry("linkgraph.distribution_armoured"));
2249 cdist
->Add(new SettingEntry("linkgraph.distribution_default"));
2250 cdist
->Add(new SettingEntry("linkgraph.accuracy"));
2251 cdist
->Add(new SettingEntry("linkgraph.demand_distance"));
2252 cdist
->Add(new SettingEntry("linkgraph.demand_size"));
2253 cdist
->Add(new SettingEntry("linkgraph.short_path_saturation"));
2256 SettingsPage
*trees
= environment
->Add(new SettingsPage(STR_CONFIG_SETTING_ENVIRONMENT_TREES
));
2258 trees
->Add(new SettingEntry("game_creation.tree_placer"));
2259 trees
->Add(new SettingEntry("construction.extra_tree_placement"));
2263 SettingsPage
*ai
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_AI
));
2265 SettingsPage
*npc
= ai
->Add(new SettingsPage(STR_CONFIG_SETTING_AI_NPC
));
2267 npc
->Add(new SettingEntry("script.script_max_opcode_till_suspend"));
2268 npc
->Add(new SettingEntry("script.script_max_memory_megabytes"));
2269 npc
->Add(new SettingEntry("difficulty.competitor_speed"));
2270 npc
->Add(new SettingEntry("ai.ai_in_multiplayer"));
2271 npc
->Add(new SettingEntry("ai.ai_disable_veh_train"));
2272 npc
->Add(new SettingEntry("ai.ai_disable_veh_roadveh"));
2273 npc
->Add(new SettingEntry("ai.ai_disable_veh_aircraft"));
2274 npc
->Add(new SettingEntry("ai.ai_disable_veh_ship"));
2277 ai
->Add(new SettingEntry("economy.give_money"));
2280 SettingsPage
*network
= main
->Add(new SettingsPage(STR_CONFIG_SETTING_NETWORK
));
2282 network
->Add(new SettingEntry("network.use_relay_service"));
2290 static const StringID _game_settings_restrict_dropdown
[] = {
2291 STR_CONFIG_SETTING_RESTRICT_BASIC
, // RM_BASIC
2292 STR_CONFIG_SETTING_RESTRICT_ADVANCED
, // RM_ADVANCED
2293 STR_CONFIG_SETTING_RESTRICT_ALL
, // RM_ALL
2294 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_DEFAULT
, // RM_CHANGED_AGAINST_DEFAULT
2295 STR_CONFIG_SETTING_RESTRICT_CHANGED_AGAINST_NEW
, // RM_CHANGED_AGAINST_NEW
2297 static_assert(lengthof(_game_settings_restrict_dropdown
) == RM_END
);
2299 /** Warnings about hidden search results. */
2300 enum WarnHiddenResult
{
2301 WHR_NONE
, ///< Nothing was filtering matches away.
2302 WHR_CATEGORY
, ///< Category setting filtered matches away.
2303 WHR_TYPE
, ///< Type setting filtered matches away.
2304 WHR_CATEGORY_TYPE
, ///< Both category and type settings filtered matches away.
2308 * Callback function for the reset all settings button
2309 * @param w Window which is calling this callback
2310 * @param confirmed boolean value, true when yes was clicked, false otherwise
2312 static void ResetAllSettingsConfirmationCallback(Window
*w
, bool confirmed
)
2315 GetSettingsTree().ResetAll();
2316 GetSettingsTree().FoldAll();
2317 w
->InvalidateData();
2321 /** Window to edit settings of the game. */
2322 struct GameSettingsWindow
: Window
{
2323 static GameSettings
*settings_ptr
; ///< Pointer to the game settings being displayed and modified.
2325 SettingEntry
*valuewindow_entry
; ///< If non-nullptr, pointer to setting for which a value-entering window has been opened.
2326 SettingEntry
*clicked_entry
; ///< If non-nullptr, pointer to a clicked numeric setting (with a depressed left or right button).
2327 SettingEntry
*last_clicked
; ///< If non-nullptr, pointer to the last clicked setting.
2328 SettingEntry
*valuedropdown_entry
; ///< If non-nullptr, pointer to the value for which a dropdown window is currently opened.
2329 bool closing_dropdown
; ///< True, if the dropdown list is currently closing.
2331 SettingFilter filter
; ///< Filter for the list.
2332 QueryString filter_editbox
; ///< Filter editbox;
2333 bool manually_changed_folding
; ///< Whether the user expanded/collapsed something manually.
2334 WarnHiddenResult warn_missing
; ///< Whether and how to warn about missing search results.
2335 int warn_lines
; ///< Number of lines used for warning about missing search results.
2339 GameSettingsWindow(WindowDesc
&desc
) : Window(desc
), filter_editbox(50)
2341 this->warn_missing
= WHR_NONE
;
2342 this->warn_lines
= 0;
2343 this->filter
.mode
= (RestrictionMode
)_settings_client
.gui
.settings_restriction_mode
;
2344 this->filter
.min_cat
= RM_ALL
;
2345 this->filter
.type
= ST_ALL
;
2346 this->filter
.type_hides
= false;
2347 this->settings_ptr
= &GetGameSettings();
2349 GetSettingsTree().FoldAll(); // Close all sub-pages
2351 this->valuewindow_entry
= nullptr; // No setting entry for which a entry window is opened
2352 this->clicked_entry
= nullptr; // No numeric setting buttons are depressed
2353 this->last_clicked
= nullptr;
2354 this->valuedropdown_entry
= nullptr;
2355 this->closing_dropdown
= false;
2356 this->manually_changed_folding
= false;
2358 this->CreateNestedTree();
2359 this->vscroll
= this->GetScrollbar(WID_GS_SCROLLBAR
);
2360 this->FinishInitNested(WN_GAME_OPTIONS_GAME_SETTINGS
);
2362 this->querystrings
[WID_GS_FILTER
] = &this->filter_editbox
;
2363 this->filter_editbox
.cancel_button
= QueryString::ACTION_CLEAR
;
2364 this->SetFocusedWidget(WID_GS_FILTER
);
2366 this->InvalidateData();
2369 void OnInit() override
2371 _circle_size
= maxdim(GetSpriteSize(SPR_CIRCLE_FOLDED
), GetSpriteSize(SPR_CIRCLE_UNFOLDED
));
2374 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
2377 case WID_GS_OPTIONSPANEL
:
2378 resize
.height
= SETTING_HEIGHT
= std::max({(int)_circle_size
.height
, SETTING_BUTTON_HEIGHT
, GetCharacterHeight(FS_NORMAL
)}) + WidgetDimensions::scaled
.vsep_normal
;
2381 size
.height
= 5 * resize
.height
+ WidgetDimensions::scaled
.framerect
.Vertical();
2384 case WID_GS_HELP_TEXT
: {
2385 static const StringID setting_types
[] = {
2386 STR_CONFIG_SETTING_TYPE_CLIENT
,
2387 STR_CONFIG_SETTING_TYPE_COMPANY_MENU
, STR_CONFIG_SETTING_TYPE_COMPANY_INGAME
,
2388 STR_CONFIG_SETTING_TYPE_GAME_MENU
, STR_CONFIG_SETTING_TYPE_GAME_INGAME
,
2390 for (const auto &setting_type
: setting_types
) {
2391 SetDParam(0, setting_type
);
2392 size
.width
= std::max(size
.width
, GetStringBoundingBox(STR_CONFIG_SETTING_TYPE
).width
+ padding
.width
);
2394 size
.height
= 2 * GetCharacterHeight(FS_NORMAL
) + WidgetDimensions::scaled
.vsep_normal
+
2395 std::max(size
.height
, GetSettingsTree().GetMaxHelpHeight(size
.width
));
2399 case WID_GS_RESTRICT_CATEGORY
:
2400 case WID_GS_RESTRICT_TYPE
:
2401 size
.width
= std::max(GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_CATEGORY
).width
, GetStringBoundingBox(STR_CONFIG_SETTING_RESTRICT_TYPE
).width
);
2409 void OnPaint() override
2411 if (this->closing_dropdown
) {
2412 this->closing_dropdown
= false;
2413 assert(this->valuedropdown_entry
!= nullptr);
2414 this->valuedropdown_entry
->SetButtons(0);
2415 this->valuedropdown_entry
= nullptr;
2418 /* Reserve the correct number of lines for the 'some search results are hidden' notice in the central settings display panel. */
2419 const Rect panel
= this->GetWidget
<NWidgetBase
>(WID_GS_OPTIONSPANEL
)->GetCurrentRect().Shrink(WidgetDimensions::scaled
.frametext
);
2420 StringID warn_str
= STR_CONFIG_SETTING_CATEGORY_HIDES
- 1 + this->warn_missing
;
2422 if (this->warn_missing
== WHR_NONE
) {
2425 SetDParam(0, _game_settings_restrict_dropdown
[this->filter
.min_cat
]);
2426 new_warn_lines
= GetStringLineCount(warn_str
, panel
.Width());
2428 if (this->warn_lines
!= new_warn_lines
) {
2429 this->vscroll
->SetCount(this->vscroll
->GetCount() - this->warn_lines
+ new_warn_lines
);
2430 this->warn_lines
= new_warn_lines
;
2433 this->DrawWidgets();
2435 /* Draw the 'some search results are hidden' notice. */
2436 if (this->warn_missing
!= WHR_NONE
) {
2437 SetDParam(0, _game_settings_restrict_dropdown
[this->filter
.min_cat
]);
2438 DrawStringMultiLine(panel
.WithHeight(this->warn_lines
* GetCharacterHeight(FS_NORMAL
)), warn_str
, TC_FROMSTRING
, SA_CENTER
);
2442 void SetStringParameters(WidgetID widget
) const override
2445 case WID_GS_RESTRICT_DROPDOWN
:
2446 SetDParam(0, _game_settings_restrict_dropdown
[this->filter
.mode
]);
2449 case WID_GS_TYPE_DROPDOWN
:
2450 switch (this->filter
.type
) {
2451 case ST_GAME
: SetDParam(0, _game_mode
== GM_MENU
? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU
: STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME
); break;
2452 case ST_COMPANY
: SetDParam(0, _game_mode
== GM_MENU
? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU
: STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME
); break;
2453 case ST_CLIENT
: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT
); break;
2454 default: SetDParam(0, STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL
); break;
2460 DropDownList
BuildDropDownList(WidgetID widget
) const
2464 case WID_GS_RESTRICT_DROPDOWN
:
2465 for (int mode
= 0; mode
!= RM_END
; mode
++) {
2466 /* If we are in adv. settings screen for the new game's settings,
2467 * we don't want to allow comparing with new game's settings. */
2468 bool disabled
= mode
== RM_CHANGED_AGAINST_NEW
&& settings_ptr
== &_settings_newgame
;
2470 list
.push_back(MakeDropDownListStringItem(_game_settings_restrict_dropdown
[mode
], mode
, disabled
));
2474 case WID_GS_TYPE_DROPDOWN
:
2475 list
.push_back(MakeDropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_ALL
, ST_ALL
));
2476 list
.push_back(MakeDropDownListStringItem(_game_mode
== GM_MENU
? STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_MENU
: STR_CONFIG_SETTING_TYPE_DROPDOWN_GAME_INGAME
, ST_GAME
));
2477 list
.push_back(MakeDropDownListStringItem(_game_mode
== GM_MENU
? STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_MENU
: STR_CONFIG_SETTING_TYPE_DROPDOWN_COMPANY_INGAME
, ST_COMPANY
));
2478 list
.push_back(MakeDropDownListStringItem(STR_CONFIG_SETTING_TYPE_DROPDOWN_CLIENT
, ST_CLIENT
));
2484 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
2487 case WID_GS_OPTIONSPANEL
: {
2488 Rect tr
= r
.Shrink(WidgetDimensions::scaled
.frametext
, WidgetDimensions::scaled
.framerect
);
2489 tr
.top
+= this->warn_lines
* SETTING_HEIGHT
;
2490 uint last_row
= this->vscroll
->GetPosition() + this->vscroll
->GetCapacity() - this->warn_lines
;
2491 int next_row
= GetSettingsTree().Draw(settings_ptr
, tr
.left
, tr
.right
, tr
.top
,
2492 this->vscroll
->GetPosition(), last_row
, this->last_clicked
);
2493 if (next_row
== 0) DrawString(tr
, STR_CONFIG_SETTINGS_NONE
);
2497 case WID_GS_HELP_TEXT
:
2498 if (this->last_clicked
!= nullptr) {
2499 const IntSettingDesc
*sd
= this->last_clicked
->setting
;
2502 switch (sd
->GetType()) {
2503 case ST_COMPANY
: SetDParam(0, _game_mode
== GM_MENU
? STR_CONFIG_SETTING_TYPE_COMPANY_MENU
: STR_CONFIG_SETTING_TYPE_COMPANY_INGAME
); break;
2504 case ST_CLIENT
: SetDParam(0, STR_CONFIG_SETTING_TYPE_CLIENT
); break;
2505 case ST_GAME
: SetDParam(0, _game_mode
== GM_MENU
? STR_CONFIG_SETTING_TYPE_GAME_MENU
: STR_CONFIG_SETTING_TYPE_GAME_INGAME
); break;
2506 default: NOT_REACHED();
2508 DrawString(tr
, STR_CONFIG_SETTING_TYPE
);
2509 tr
.top
+= GetCharacterHeight(FS_NORMAL
);
2511 int32_t def_val
= sd
->get_def_cb
!= nullptr ? sd
->get_def_cb() : sd
->def
;
2512 sd
->SetValueDParams(0, def_val
);
2513 DrawString(tr
, STR_CONFIG_SETTING_DEFAULT_VALUE
);
2514 tr
.top
+= GetCharacterHeight(FS_NORMAL
) + WidgetDimensions::scaled
.vsep_normal
;
2516 DrawStringMultiLine(tr
, sd
->GetHelp(), TC_WHITE
);
2526 * Set the entry that should have its help text displayed, and mark the window dirty so it gets repainted.
2527 * @param pe Setting to display help text of, use \c nullptr to stop displaying help of the currently displayed setting.
2529 void SetDisplayedHelpText(SettingEntry
*pe
)
2531 if (this->last_clicked
!= pe
) this->SetDirty();
2532 this->last_clicked
= pe
;
2535 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
2538 case WID_GS_EXPAND_ALL
:
2539 this->manually_changed_folding
= true;
2540 GetSettingsTree().UnFoldAll();
2541 this->InvalidateData();
2544 case WID_GS_COLLAPSE_ALL
:
2545 this->manually_changed_folding
= true;
2546 GetSettingsTree().FoldAll();
2547 this->InvalidateData();
2550 case WID_GS_RESET_ALL
:
2552 STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_CAPTION
,
2553 STR_CONFIG_SETTING_RESET_ALL_CONFIRMATION_DIALOG_TEXT
,
2555 ResetAllSettingsConfirmationCallback
2559 case WID_GS_RESTRICT_DROPDOWN
: {
2560 DropDownList list
= this->BuildDropDownList(widget
);
2561 if (!list
.empty()) {
2562 ShowDropDownList(this, std::move(list
), this->filter
.mode
, widget
);
2567 case WID_GS_TYPE_DROPDOWN
: {
2568 DropDownList list
= this->BuildDropDownList(widget
);
2569 if (!list
.empty()) {
2570 ShowDropDownList(this, std::move(list
), this->filter
.type
, widget
);
2576 if (widget
!= WID_GS_OPTIONSPANEL
) return;
2578 int32_t btn
= this->vscroll
->GetScrolledRowFromWidget(pt
.y
, this, WID_GS_OPTIONSPANEL
, WidgetDimensions::scaled
.framerect
.top
);
2579 if (btn
== INT32_MAX
|| btn
< this->warn_lines
) return;
2580 btn
-= this->warn_lines
;
2583 BaseSettingEntry
*clicked_entry
= GetSettingsTree().FindEntry(btn
, &cur_row
);
2585 if (clicked_entry
== nullptr) return; // Clicked below the last setting of the page
2587 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
2588 if (x
< 0) return; // Clicked left of the entry
2590 SettingsPage
*clicked_page
= dynamic_cast<SettingsPage
*>(clicked_entry
);
2591 if (clicked_page
!= nullptr) {
2592 this->SetDisplayedHelpText(nullptr);
2593 clicked_page
->folded
= !clicked_page
->folded
; // Flip 'folded'-ness of the sub-page
2595 this->manually_changed_folding
= true;
2597 this->InvalidateData();
2601 SettingEntry
*pe
= dynamic_cast<SettingEntry
*>(clicked_entry
);
2602 assert(pe
!= nullptr);
2603 const IntSettingDesc
*sd
= pe
->setting
;
2605 /* return if action is only active in network, or only settable by server */
2606 if (!sd
->IsEditable()) {
2607 this->SetDisplayedHelpText(pe
);
2611 int32_t value
= sd
->Read(ResolveObject(settings_ptr
, sd
));
2613 /* clicked on the icon on the left side. Either scroller, bool on/off or dropdown */
2614 if (x
< SETTING_BUTTON_WIDTH
&& (sd
->flags
& SF_GUI_DROPDOWN
)) {
2615 this->SetDisplayedHelpText(pe
);
2617 if (this->valuedropdown_entry
== pe
) {
2618 /* unclick the dropdown */
2619 this->CloseChildWindows(WC_DROPDOWN_MENU
);
2620 this->closing_dropdown
= false;
2621 this->valuedropdown_entry
->SetButtons(0);
2622 this->valuedropdown_entry
= nullptr;
2624 if (this->valuedropdown_entry
!= nullptr) this->valuedropdown_entry
->SetButtons(0);
2625 this->closing_dropdown
= false;
2627 const NWidgetBase
*wid
= this->GetWidget
<NWidgetBase
>(WID_GS_OPTIONSPANEL
);
2628 int rel_y
= (pt
.y
- wid
->pos_y
- WidgetDimensions::scaled
.framerect
.top
) % wid
->resize_y
;
2631 wi_rect
.left
= pt
.x
- (_current_text_dir
== TD_RTL
? SETTING_BUTTON_WIDTH
- 1 - x
: x
);
2632 wi_rect
.right
= wi_rect
.left
+ SETTING_BUTTON_WIDTH
- 1;
2633 wi_rect
.top
= pt
.y
- rel_y
+ (SETTING_HEIGHT
- SETTING_BUTTON_HEIGHT
) / 2;
2634 wi_rect
.bottom
= wi_rect
.top
+ SETTING_BUTTON_HEIGHT
- 1;
2636 /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
2637 if (pt
.y
>= wi_rect
.top
&& pt
.y
<= wi_rect
.bottom
) {
2638 this->valuedropdown_entry
= pe
;
2639 this->valuedropdown_entry
->SetButtons(SEF_LEFT_DEPRESSED
);
2642 for (int i
= sd
->min
; i
<= (int)sd
->max
; i
++) {
2643 sd
->SetValueDParams(0, i
);
2644 list
.push_back(MakeDropDownListStringItem(STR_JUST_STRING2
, i
));
2647 ShowDropDownListAt(this, std::move(list
), value
, WID_GS_SETTING_DROPDOWN
, wi_rect
, COLOUR_ORANGE
);
2651 } else if (x
< SETTING_BUTTON_WIDTH
) {
2652 this->SetDisplayedHelpText(pe
);
2653 int32_t oldvalue
= value
;
2655 if (sd
->IsBoolSetting()) {
2658 /* Add a dynamic step-size to the scroller. In a maximum of
2659 * 50-steps you should be able to get from min to max,
2660 * unless specified otherwise in the 'interval' variable
2661 * of the current setting. */
2662 uint32_t step
= (sd
->interval
== 0) ? ((sd
->max
- sd
->min
) / 50) : sd
->interval
;
2663 if (step
== 0) step
= 1;
2665 /* don't allow too fast scrolling */
2666 if ((this->flags
& WF_TIMEOUT
) && this->timeout_timer
> 1) {
2667 _left_button_clicked
= false;
2671 /* Increase or decrease the value and clamp it to extremes */
2672 if (x
>= SETTING_BUTTON_WIDTH
/ 2) {
2675 assert((int32_t)sd
->max
>= 0);
2676 if (value
> (int32_t)sd
->max
) value
= (int32_t)sd
->max
;
2678 if ((uint32_t)value
> sd
->max
) value
= (int32_t)sd
->max
;
2680 if (value
< sd
->min
) value
= sd
->min
; // skip between "disabled" and minimum
2683 if (value
< sd
->min
) value
= (sd
->flags
& SF_GUI_0_IS_SPECIAL
) ? 0 : sd
->min
;
2686 /* Set up scroller timeout for numeric values */
2687 if (value
!= oldvalue
) {
2688 if (this->clicked_entry
!= nullptr) { // Release previous buttons if any
2689 this->clicked_entry
->SetButtons(0);
2691 this->clicked_entry
= pe
;
2692 this->clicked_entry
->SetButtons((x
>= SETTING_BUTTON_WIDTH
/ 2) != (_current_text_dir
== TD_RTL
) ? SEF_RIGHT_DEPRESSED
: SEF_LEFT_DEPRESSED
);
2694 _left_button_clicked
= false;
2698 if (value
!= oldvalue
) {
2699 SetSettingValue(sd
, value
);
2703 /* Only open editbox if clicked for the second time, and only for types where it is sensible for. */
2704 if (this->last_clicked
== pe
&& !sd
->IsBoolSetting() && !(sd
->flags
& SF_GUI_DROPDOWN
)) {
2705 int64_t value64
= value
;
2706 /* Show the correct currency-translated value */
2707 if (sd
->flags
& SF_GUI_CURRENCY
) value64
*= GetCurrency().rate
;
2709 CharSetFilter charset_filter
= CS_NUMERAL
; //default, only numeric input allowed
2710 if (sd
->min
< 0) charset_filter
= CS_NUMERAL_SIGNED
; // special case, also allow '-' sign for negative input
2712 this->valuewindow_entry
= pe
;
2713 SetDParam(0, value64
);
2714 /* Limit string length to 14 so that MAX_INT32 * max currency rate doesn't exceed MAX_INT64. */
2715 ShowQueryString(STR_JUST_INT
, STR_CONFIG_SETTING_QUERY_CAPTION
, 15, this, charset_filter
, QSF_ENABLE_DEFAULT
);
2717 this->SetDisplayedHelpText(pe
);
2721 void OnTimeout() override
2723 if (this->clicked_entry
!= nullptr) { // On timeout, release any depressed buttons
2724 this->clicked_entry
->SetButtons(0);
2725 this->clicked_entry
= nullptr;
2730 void OnQueryTextFinished(std::optional
<std::string
> str
) override
2732 /* The user pressed cancel */
2733 if (!str
.has_value()) return;
2735 assert(this->valuewindow_entry
!= nullptr);
2736 const IntSettingDesc
*sd
= this->valuewindow_entry
->setting
;
2739 if (!str
->empty()) {
2740 long long llvalue
= atoll(str
->c_str());
2742 /* Save the correct currency-translated value */
2743 if (sd
->flags
& SF_GUI_CURRENCY
) llvalue
/= GetCurrency().rate
;
2745 value
= ClampTo
<int32_t>(llvalue
);
2746 } else if (sd
->get_def_cb
!= nullptr) {
2747 value
= sd
->get_def_cb();
2752 SetSettingValue(this->valuewindow_entry
->setting
, value
);
2756 void OnDropdownSelect(WidgetID widget
, int index
) override
2759 case WID_GS_RESTRICT_DROPDOWN
:
2760 this->filter
.mode
= (RestrictionMode
)index
;
2761 if (this->filter
.mode
== RM_CHANGED_AGAINST_DEFAULT
||
2762 this->filter
.mode
== RM_CHANGED_AGAINST_NEW
) {
2764 if (!this->manually_changed_folding
) {
2765 /* Expand all when selecting 'changes'. Update the filter state first, in case it becomes less restrictive in some cases. */
2766 GetSettingsTree().UpdateFilterState(this->filter
, false);
2767 GetSettingsTree().UnFoldAll();
2770 /* Non-'changes' filter. Save as default. */
2771 _settings_client
.gui
.settings_restriction_mode
= this->filter
.mode
;
2773 this->InvalidateData();
2776 case WID_GS_TYPE_DROPDOWN
:
2777 this->filter
.type
= (SettingType
)index
;
2778 this->InvalidateData();
2781 case WID_GS_SETTING_DROPDOWN
:
2782 /* Deal with drop down boxes on the panel. */
2783 assert(this->valuedropdown_entry
!= nullptr);
2784 const IntSettingDesc
*sd
= this->valuedropdown_entry
->setting
;
2785 assert(sd
->flags
& SF_GUI_DROPDOWN
);
2787 SetSettingValue(sd
, index
);
2793 void OnDropdownClose(Point pt
, WidgetID widget
, int index
, bool instant_close
) override
2795 if (widget
!= WID_GS_SETTING_DROPDOWN
) {
2796 /* Normally the default implementation of OnDropdownClose() takes care of
2797 * a few things. We want that behaviour here too, but only for
2798 * "normal" dropdown boxes. The special dropdown boxes added for every
2799 * setting that needs one can't have this call. */
2800 Window::OnDropdownClose(pt
, widget
, index
, instant_close
);
2802 /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
2803 * the same dropdown button was clicked again, and then not open the dropdown again.
2804 * So, we only remember that it was closed, and process it on the next OnPaint, which is
2806 assert(this->valuedropdown_entry
!= nullptr);
2807 this->closing_dropdown
= true;
2812 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
2814 if (!gui_scope
) return;
2816 /* Update which settings are to be visible. */
2817 RestrictionMode min_level
= (this->filter
.mode
<= RM_ALL
) ? this->filter
.mode
: RM_BASIC
;
2818 this->filter
.min_cat
= min_level
;
2819 this->filter
.type_hides
= false;
2820 GetSettingsTree().UpdateFilterState(this->filter
, false);
2822 if (this->filter
.string
.IsEmpty()) {
2823 this->warn_missing
= WHR_NONE
;
2824 } else if (min_level
< this->filter
.min_cat
) {
2825 this->warn_missing
= this->filter
.type_hides
? WHR_CATEGORY_TYPE
: WHR_CATEGORY
;
2827 this->warn_missing
= this->filter
.type_hides
? WHR_TYPE
: WHR_NONE
;
2829 this->vscroll
->SetCount(GetSettingsTree().Length() + this->warn_lines
);
2831 if (this->last_clicked
!= nullptr && !GetSettingsTree().IsVisible(this->last_clicked
)) {
2832 this->SetDisplayedHelpText(nullptr);
2835 bool all_folded
= true;
2836 bool all_unfolded
= true;
2837 GetSettingsTree().GetFoldingState(all_folded
, all_unfolded
);
2838 this->SetWidgetDisabledState(WID_GS_EXPAND_ALL
, all_unfolded
);
2839 this->SetWidgetDisabledState(WID_GS_COLLAPSE_ALL
, all_folded
);
2842 void OnEditboxChanged(WidgetID wid
) override
2844 if (wid
== WID_GS_FILTER
) {
2845 this->filter
.string
.SetFilterTerm(this->filter_editbox
.text
.buf
);
2846 if (!this->filter
.string
.IsEmpty() && !this->manually_changed_folding
) {
2847 /* User never expanded/collapsed single pages and entered a filter term.
2848 * Expand everything, to save weird expand clicks, */
2849 GetSettingsTree().UnFoldAll();
2851 this->InvalidateData();
2855 void OnResize() override
2857 this->vscroll
->SetCapacityFromWidget(this, WID_GS_OPTIONSPANEL
, WidgetDimensions::scaled
.framerect
.Vertical());
2861 GameSettings
*GameSettingsWindow::settings_ptr
= nullptr;
2863 static constexpr NWidgetPart _nested_settings_selection_widgets
[] = {
2864 NWidget(NWID_HORIZONTAL
),
2865 NWidget(WWT_CLOSEBOX
, COLOUR_MAUVE
),
2866 NWidget(WWT_CAPTION
, COLOUR_MAUVE
), SetDataTip(STR_CONFIG_SETTING_TREE_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
2867 NWidget(WWT_DEFSIZEBOX
, COLOUR_MAUVE
),
2869 NWidget(WWT_PANEL
, COLOUR_MAUVE
),
2870 NWidget(NWID_VERTICAL
), SetPIP(WidgetDimensions::unscaled
.frametext
.top
, WidgetDimensions::unscaled
.vsep_normal
, WidgetDimensions::unscaled
.frametext
.bottom
),
2871 NWidget(NWID_HORIZONTAL
), SetPIP(WidgetDimensions::unscaled
.frametext
.left
, WidgetDimensions::unscaled
.hsep_wide
, WidgetDimensions::unscaled
.frametext
.right
),
2872 NWidget(WWT_TEXT
, COLOUR_MAUVE
, WID_GS_RESTRICT_CATEGORY
), SetDataTip(STR_CONFIG_SETTING_RESTRICT_CATEGORY
, STR_NULL
),
2873 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),
2875 NWidget(NWID_HORIZONTAL
), SetPIP(WidgetDimensions::unscaled
.frametext
.left
, WidgetDimensions::unscaled
.hsep_wide
, WidgetDimensions::unscaled
.frametext
.right
),
2876 NWidget(WWT_TEXT
, COLOUR_MAUVE
, WID_GS_RESTRICT_TYPE
), SetDataTip(STR_CONFIG_SETTING_RESTRICT_TYPE
, STR_NULL
),
2877 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),
2879 NWidget(NWID_HORIZONTAL
), SetPIP(WidgetDimensions::unscaled
.frametext
.left
, WidgetDimensions::unscaled
.hsep_wide
, WidgetDimensions::unscaled
.frametext
.right
),
2880 NWidget(WWT_TEXT
, COLOUR_MAUVE
), SetFill(0, 1), SetDataTip(STR_CONFIG_SETTING_FILTER_TITLE
, STR_NULL
),
2881 NWidget(WWT_EDITBOX
, COLOUR_MAUVE
, WID_GS_FILTER
), SetDataTip(STR_LIST_FILTER_OSKTITLE
, STR_LIST_FILTER_TOOLTIP
), SetFill(1, 0), SetResize(1, 0),
2885 NWidget(NWID_HORIZONTAL
),
2886 NWidget(WWT_PANEL
, COLOUR_MAUVE
, WID_GS_OPTIONSPANEL
), SetMinimalSize(400, 174), SetScrollbar(WID_GS_SCROLLBAR
), EndContainer(),
2887 NWidget(NWID_VSCROLLBAR
, COLOUR_MAUVE
, WID_GS_SCROLLBAR
),
2889 NWidget(WWT_PANEL
, COLOUR_MAUVE
),
2890 NWidget(WWT_EMPTY
, INVALID_COLOUR
, WID_GS_HELP_TEXT
), SetMinimalSize(300, 25), SetFill(1, 1), SetResize(1, 0),
2891 SetPadding(WidgetDimensions::unscaled
.frametext
),
2893 NWidget(NWID_HORIZONTAL
),
2894 NWidget(WWT_PUSHTXTBTN
, COLOUR_MAUVE
, WID_GS_EXPAND_ALL
), SetDataTip(STR_CONFIG_SETTING_EXPAND_ALL
, STR_NULL
),
2895 NWidget(WWT_PUSHTXTBTN
, COLOUR_MAUVE
, WID_GS_COLLAPSE_ALL
), SetDataTip(STR_CONFIG_SETTING_COLLAPSE_ALL
, STR_NULL
),
2896 NWidget(WWT_PUSHTXTBTN
, COLOUR_MAUVE
, WID_GS_RESET_ALL
), SetDataTip(STR_CONFIG_SETTING_RESET_ALL
, STR_NULL
),
2897 NWidget(WWT_PANEL
, COLOUR_MAUVE
), SetFill(1, 0), SetResize(1, 0),
2899 NWidget(WWT_RESIZEBOX
, COLOUR_MAUVE
),
2903 static WindowDesc
_settings_selection_desc(
2904 WDP_CENTER
, "settings", 510, 450,
2905 WC_GAME_OPTIONS
, WC_NONE
,
2907 _nested_settings_selection_widgets
2910 /** Open advanced settings window. */
2911 void ShowGameSettings()
2913 CloseWindowByClass(WC_GAME_OPTIONS
);
2914 new GameSettingsWindow(_settings_selection_desc
);
2919 * Draw [<][>] boxes.
2920 * @param x the x position to draw
2921 * @param y the y position to draw
2922 * @param button_colour the colour of the button
2923 * @param state 0 = none clicked, 1 = first clicked, 2 = second clicked
2924 * @param clickable_left is the left button clickable?
2925 * @param clickable_right is the right button clickable?
2927 void DrawArrowButtons(int x
, int y
, Colours button_colour
, uint8_t state
, bool clickable_left
, bool clickable_right
)
2929 int colour
= GetColourGradient(button_colour
, SHADE_DARKER
);
2930 Dimension dim
= NWidgetScrollbar::GetHorizontalDimension();
2932 Rect lr
= {x
, y
, x
+ (int)dim
.width
- 1, y
+ (int)dim
.height
- 1};
2933 Rect rr
= {x
+ (int)dim
.width
, y
, x
+ (int)dim
.width
* 2 - 1, y
+ (int)dim
.height
- 1};
2935 DrawFrameRect(lr
, button_colour
, (state
== 1) ? FR_LOWERED
: FR_NONE
);
2936 DrawFrameRect(rr
, button_colour
, (state
== 2) ? FR_LOWERED
: FR_NONE
);
2937 DrawSpriteIgnorePadding(SPR_ARROW_LEFT
, PAL_NONE
, lr
, SA_CENTER
);
2938 DrawSpriteIgnorePadding(SPR_ARROW_RIGHT
, PAL_NONE
, rr
, SA_CENTER
);
2940 /* Grey out the buttons that aren't clickable */
2941 bool rtl
= _current_text_dir
== TD_RTL
;
2942 if (rtl
? !clickable_right
: !clickable_left
) {
2943 GfxFillRect(lr
.Shrink(WidgetDimensions::scaled
.bevel
), colour
, FILLRECT_CHECKER
);
2945 if (rtl
? !clickable_left
: !clickable_right
) {
2946 GfxFillRect(rr
.Shrink(WidgetDimensions::scaled
.bevel
), colour
, FILLRECT_CHECKER
);
2951 * Draw a dropdown button.
2952 * @param x the x position to draw
2953 * @param y the y position to draw
2954 * @param button_colour the colour of the button
2955 * @param state true = lowered
2956 * @param clickable is the button clickable?
2958 void DrawDropDownButton(int x
, int y
, Colours button_colour
, bool state
, bool clickable
)
2960 int colour
= GetColourGradient(button_colour
, SHADE_DARKER
);
2962 Rect r
= {x
, y
, x
+ SETTING_BUTTON_WIDTH
- 1, y
+ SETTING_BUTTON_HEIGHT
- 1};
2964 DrawFrameRect(r
, button_colour
, state
? FR_LOWERED
: FR_NONE
);
2965 DrawSpriteIgnorePadding(SPR_ARROW_DOWN
, PAL_NONE
, r
, SA_CENTER
);
2968 GfxFillRect(r
.Shrink(WidgetDimensions::scaled
.bevel
), colour
, FILLRECT_CHECKER
);
2973 * Draw a toggle button.
2974 * @param x the x position to draw
2975 * @param y the y position to draw
2976 * @param state true = lowered
2977 * @param clickable is the button clickable?
2979 void DrawBoolButton(int x
, int y
, bool state
, bool clickable
)
2981 static const Colours _bool_ctabs
[2][2] = {{COLOUR_CREAM
, COLOUR_RED
}, {COLOUR_DARK_GREEN
, COLOUR_GREEN
}};
2983 Rect r
= {x
, y
, x
+ SETTING_BUTTON_WIDTH
- 1, y
+ SETTING_BUTTON_HEIGHT
- 1};
2984 DrawFrameRect(r
, _bool_ctabs
[state
][clickable
], state
? FR_LOWERED
: FR_NONE
);
2987 struct CustomCurrencyWindow
: Window
{
2990 CustomCurrencyWindow(WindowDesc
&desc
) : Window(desc
)
2997 void SetButtonState()
2999 this->SetWidgetDisabledState(WID_CC_RATE_DOWN
, GetCustomCurrency().rate
== 1);
3000 this->SetWidgetDisabledState(WID_CC_RATE_UP
, GetCustomCurrency().rate
== UINT16_MAX
);
3001 this->SetWidgetDisabledState(WID_CC_YEAR_DOWN
, GetCustomCurrency().to_euro
== CF_NOEURO
);
3002 this->SetWidgetDisabledState(WID_CC_YEAR_UP
, GetCustomCurrency().to_euro
== CalendarTime::MAX_YEAR
);
3005 void SetStringParameters(WidgetID widget
) const override
3008 case WID_CC_RATE
: SetDParam(0, 1); SetDParam(1, 1); break;
3009 case WID_CC_SEPARATOR
: SetDParamStr(0, GetCustomCurrency().separator
); break;
3010 case WID_CC_PREFIX
: SetDParamStr(0, GetCustomCurrency().prefix
); break;
3011 case WID_CC_SUFFIX
: SetDParamStr(0, GetCustomCurrency().suffix
); break;
3013 SetDParam(0, (GetCustomCurrency().to_euro
!= CF_NOEURO
) ? STR_CURRENCY_SWITCH_TO_EURO
: STR_CURRENCY_SWITCH_TO_EURO_NEVER
);
3014 SetDParam(1, GetCustomCurrency().to_euro
);
3017 case WID_CC_PREVIEW
:
3018 SetDParam(0, 10000);
3023 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
3026 /* Set the appropriate width for the up/down buttons. */
3027 case WID_CC_RATE_DOWN
:
3028 case WID_CC_RATE_UP
:
3029 case WID_CC_YEAR_DOWN
:
3030 case WID_CC_YEAR_UP
:
3031 size
= maxdim(size
, {(uint
)SETTING_BUTTON_WIDTH
/ 2, (uint
)SETTING_BUTTON_HEIGHT
});
3034 /* Set the appropriate width for the edit buttons. */
3035 case WID_CC_SEPARATOR_EDIT
:
3036 case WID_CC_PREFIX_EDIT
:
3037 case WID_CC_SUFFIX_EDIT
:
3038 size
= maxdim(size
, {(uint
)SETTING_BUTTON_WIDTH
, (uint
)SETTING_BUTTON_HEIGHT
});
3041 /* Make sure the window is wide enough for the widest exchange rate */
3044 SetDParam(1, INT32_MAX
);
3045 size
= GetStringBoundingBox(STR_CURRENCY_EXCHANGE_RATE
);
3050 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
3055 CharSetFilter afilter
= CS_ALPHANUMERAL
;
3058 case WID_CC_RATE_DOWN
:
3059 if (GetCustomCurrency().rate
> 1) GetCustomCurrency().rate
--;
3060 if (GetCustomCurrency().rate
== 1) this->DisableWidget(WID_CC_RATE_DOWN
);
3061 this->EnableWidget(WID_CC_RATE_UP
);
3064 case WID_CC_RATE_UP
:
3065 if (GetCustomCurrency().rate
< UINT16_MAX
) GetCustomCurrency().rate
++;
3066 if (GetCustomCurrency().rate
== UINT16_MAX
) this->DisableWidget(WID_CC_RATE_UP
);
3067 this->EnableWidget(WID_CC_RATE_DOWN
);
3071 SetDParam(0, GetCustomCurrency().rate
);
3075 afilter
= CS_NUMERAL
;
3078 case WID_CC_SEPARATOR_EDIT
:
3079 case WID_CC_SEPARATOR
:
3080 SetDParamStr(0, GetCustomCurrency().separator
);
3081 str
= STR_JUST_RAW_STRING
;
3083 line
= WID_CC_SEPARATOR
;
3086 case WID_CC_PREFIX_EDIT
:
3088 SetDParamStr(0, GetCustomCurrency().prefix
);
3089 str
= STR_JUST_RAW_STRING
;
3091 line
= WID_CC_PREFIX
;
3094 case WID_CC_SUFFIX_EDIT
:
3096 SetDParamStr(0, GetCustomCurrency().suffix
);
3097 str
= STR_JUST_RAW_STRING
;
3099 line
= WID_CC_SUFFIX
;
3102 case WID_CC_YEAR_DOWN
:
3103 GetCustomCurrency().to_euro
= (GetCustomCurrency().to_euro
<= MIN_EURO_YEAR
) ? CF_NOEURO
: GetCustomCurrency().to_euro
- 1;
3104 if (GetCustomCurrency().to_euro
== CF_NOEURO
) this->DisableWidget(WID_CC_YEAR_DOWN
);
3105 this->EnableWidget(WID_CC_YEAR_UP
);
3108 case WID_CC_YEAR_UP
:
3109 GetCustomCurrency().to_euro
= Clamp(GetCustomCurrency().to_euro
+ 1, MIN_EURO_YEAR
, CalendarTime::MAX_YEAR
);
3110 if (GetCustomCurrency().to_euro
== CalendarTime::MAX_YEAR
) this->DisableWidget(WID_CC_YEAR_UP
);
3111 this->EnableWidget(WID_CC_YEAR_DOWN
);
3115 SetDParam(0, GetCustomCurrency().to_euro
);
3119 afilter
= CS_NUMERAL
;
3124 this->query_widget
= line
;
3125 ShowQueryString(str
, STR_CURRENCY_CHANGE_PARAMETER
, len
+ 1, this, afilter
, QSF_NONE
);
3132 void OnQueryTextFinished(std::optional
<std::string
> str
) override
3134 if (!str
.has_value()) return;
3136 switch (this->query_widget
) {
3138 GetCustomCurrency().rate
= Clamp(atoi(str
->c_str()), 1, UINT16_MAX
);
3141 case WID_CC_SEPARATOR
: // Thousands separator
3142 GetCustomCurrency().separator
= std::move(*str
);
3146 GetCustomCurrency().prefix
= std::move(*str
);
3150 GetCustomCurrency().suffix
= std::move(*str
);
3153 case WID_CC_YEAR
: { // Year to switch to euro
3154 TimerGameCalendar::Year val
= atoi(str
->c_str());
3156 GetCustomCurrency().to_euro
= (val
< MIN_EURO_YEAR
? CF_NOEURO
: std::min(val
, CalendarTime::MAX_YEAR
));
3160 MarkWholeScreenDirty();
3164 void OnTimeout() override
3170 static constexpr NWidgetPart _nested_cust_currency_widgets
[] = {
3171 NWidget(NWID_HORIZONTAL
),
3172 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
3173 NWidget(WWT_CAPTION
, COLOUR_GREY
), SetDataTip(STR_CURRENCY_WINDOW
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
3175 NWidget(WWT_PANEL
, COLOUR_GREY
),
3176 NWidget(NWID_VERTICAL
), SetPIP(0, WidgetDimensions::unscaled
.vsep_wide
, 0), SetPadding(WidgetDimensions::unscaled
.sparse
),
3177 NWidget(NWID_VERTICAL
, NC_EQUALSIZE
), SetPIP(0, WidgetDimensions::unscaled
.vsep_normal
, 0),
3178 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
3179 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
3180 NWidget(WWT_PUSHARROWBTN
, COLOUR_YELLOW
, WID_CC_RATE_DOWN
), SetDataTip(AWV_DECREASE
, STR_CURRENCY_DECREASE_EXCHANGE_RATE_TOOLTIP
),
3181 NWidget(WWT_PUSHARROWBTN
, COLOUR_YELLOW
, WID_CC_RATE_UP
), SetDataTip(AWV_INCREASE
, STR_CURRENCY_INCREASE_EXCHANGE_RATE_TOOLTIP
),
3183 NWidget(WWT_TEXT
, COLOUR_BLUE
, WID_CC_RATE
), SetDataTip(STR_CURRENCY_EXCHANGE_RATE
, STR_CURRENCY_SET_EXCHANGE_RATE_TOOLTIP
), SetFill(1, 0),
3185 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
3186 NWidget(WWT_PUSHBTN
, COLOUR_DARK_BLUE
, WID_CC_SEPARATOR_EDIT
), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP
), SetFill(0, 1),
3187 NWidget(WWT_TEXT
, COLOUR_BLUE
, WID_CC_SEPARATOR
), SetDataTip(STR_CURRENCY_SEPARATOR
, STR_CURRENCY_SET_CUSTOM_CURRENCY_SEPARATOR_TOOLTIP
), SetFill(1, 0),
3189 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
3190 NWidget(WWT_PUSHBTN
, COLOUR_DARK_BLUE
, WID_CC_PREFIX_EDIT
), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP
), SetFill(0, 1),
3191 NWidget(WWT_TEXT
, COLOUR_BLUE
, WID_CC_PREFIX
), SetDataTip(STR_CURRENCY_PREFIX
, STR_CURRENCY_SET_CUSTOM_CURRENCY_PREFIX_TOOLTIP
), SetFill(1, 0),
3193 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
3194 NWidget(WWT_PUSHBTN
, COLOUR_DARK_BLUE
, WID_CC_SUFFIX_EDIT
), SetDataTip(0x0, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP
), SetFill(0, 1),
3195 NWidget(WWT_TEXT
, COLOUR_BLUE
, WID_CC_SUFFIX
), SetDataTip(STR_CURRENCY_SUFFIX
, STR_CURRENCY_SET_CUSTOM_CURRENCY_SUFFIX_TOOLTIP
), SetFill(1, 0),
3197 NWidget(NWID_HORIZONTAL
), SetPIP(0, WidgetDimensions::unscaled
.hsep_wide
, 0),
3198 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
3199 NWidget(WWT_PUSHARROWBTN
, COLOUR_YELLOW
, WID_CC_YEAR_DOWN
), SetDataTip(AWV_DECREASE
, STR_CURRENCY_DECREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP
),
3200 NWidget(WWT_PUSHARROWBTN
, COLOUR_YELLOW
, WID_CC_YEAR_UP
), SetDataTip(AWV_INCREASE
, STR_CURRENCY_INCREASE_CUSTOM_CURRENCY_TO_EURO_TOOLTIP
),
3202 NWidget(WWT_TEXT
, COLOUR_BLUE
, WID_CC_YEAR
), SetDataTip(STR_JUST_STRING1
, STR_CURRENCY_SET_CUSTOM_CURRENCY_TO_EURO_TOOLTIP
), SetFill(1, 0),
3205 NWidget(WWT_LABEL
, COLOUR_BLUE
, WID_CC_PREVIEW
),
3206 SetDataTip(STR_CURRENCY_PREVIEW
, STR_CURRENCY_CUSTOM_CURRENCY_PREVIEW_TOOLTIP
),
3211 static WindowDesc
_cust_currency_desc(
3212 WDP_CENTER
, nullptr, 0, 0,
3213 WC_CUSTOM_CURRENCY
, WC_NONE
,
3215 _nested_cust_currency_widgets
3218 /** Open custom currency window. */
3219 static void ShowCustCurrency()
3221 CloseWindowById(WC_CUSTOM_CURRENCY
, 0);
3222 new CustomCurrencyWindow(_cust_currency_desc
);