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 bootstrap_gui.cpp Barely used user interface for bootstrapping OpenTTD, i.e. downloading the required content. */
11 #include "base_media_base.h"
12 #include "blitter/factory.hpp"
14 #if defined(WITH_FREETYPE) || defined(WITH_UNISCRIBE) || defined(WITH_COCOA)
16 #include "core/geometry_func.hpp"
18 #include "fontcache.h"
20 #include "network/network.h"
21 #include "network/network_content_gui.h"
23 #include "strings_func.h"
24 #include "video/video_driver.hpp"
25 #include "window_func.h"
27 #include "widgets/bootstrap_widget.h"
29 #include "table/strings.h"
31 #include "safeguards.h"
33 /** Widgets for the background window to prevent smearing. */
34 static const struct NWidgetPart _background_widgets
[] = {
35 NWidget(WWT_PANEL
, COLOUR_DARK_BLUE
, WID_BB_BACKGROUND
), SetResize(1, 1),
39 * Window description for the background window to prevent smearing.
41 static WindowDesc
_background_desc(
42 WDP_MANUAL
, nullptr, 0, 0,
43 WC_BOOTSTRAP
, WC_NONE
,
45 _background_widgets
, lengthof(_background_widgets
)
48 /** The background for the game. */
49 class BootstrapBackground
: public Window
{
51 BootstrapBackground() : Window(&_background_desc
)
54 CLRBITS(this->flags
, WF_WHITE_BORDER
);
55 ResizeWindow(this, _screen
.width
, _screen
.height
);
58 void DrawWidget(const Rect
&r
, int widget
) const override
60 GfxFillRect(r
.left
, r
.top
, r
.right
, r
.bottom
, 4, FILLRECT_OPAQUE
);
61 GfxFillRect(r
.left
, r
.top
, r
.right
, r
.bottom
, 0, FILLRECT_CHECKER
);
65 /** Nested widgets for the error window. */
66 static const NWidgetPart _nested_bootstrap_errmsg_widgets
[] = {
67 NWidget(NWID_HORIZONTAL
),
68 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_BEM_CAPTION
), SetDataTip(STR_MISSING_GRAPHICS_ERROR_TITLE
, STR_NULL
),
70 NWidget(WWT_PANEL
, COLOUR_GREY
),
71 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_BEM_MESSAGE
), EndContainer(),
72 NWidget(NWID_HORIZONTAL
),
73 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BEM_QUIT
), SetDataTip(STR_MISSING_GRAPHICS_ERROR_QUIT
, STR_NULL
), SetFill(1, 0),
78 /** Window description for the error window. */
79 static WindowDesc
_bootstrap_errmsg_desc(
80 WDP_CENTER
, nullptr, 0, 0,
81 WC_BOOTSTRAP
, WC_NONE
,
83 _nested_bootstrap_errmsg_widgets
, lengthof(_nested_bootstrap_errmsg_widgets
)
86 /** The window for a failed bootstrap. */
87 class BootstrapErrorWindow
: public Window
{
89 BootstrapErrorWindow() : Window(&_bootstrap_errmsg_desc
)
97 this->Window::Close();
100 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
102 if (widget
== WID_BEM_MESSAGE
) {
103 *size
= GetStringBoundingBox(STR_MISSING_GRAPHICS_ERROR
);
104 size
->height
= GetStringHeight(STR_MISSING_GRAPHICS_ERROR
, size
->width
- WD_FRAMETEXT_LEFT
- WD_FRAMETEXT_RIGHT
) + WD_FRAMETEXT_BOTTOM
+ WD_FRAMETEXT_TOP
;
108 void DrawWidget(const Rect
&r
, int widget
) const override
110 if (widget
== WID_BEM_MESSAGE
) {
111 DrawStringMultiLine(r
.left
+ WD_FRAMETEXT_LEFT
, r
.right
- WD_FRAMETEXT_RIGHT
, r
.top
+ WD_FRAMETEXT_TOP
, r
.bottom
- WD_FRAMETEXT_BOTTOM
, STR_MISSING_GRAPHICS_ERROR
, TC_FROMSTRING
, SA_CENTER
);
115 void OnClick(Point pt
, int widget
, int click_count
) override
117 if (widget
== WID_BEM_QUIT
) {
123 /** Nested widgets for the download window. */
124 static const NWidgetPart _nested_bootstrap_download_status_window_widgets
[] = {
125 NWidget(WWT_CAPTION
, COLOUR_GREY
), SetDataTip(STR_CONTENT_DOWNLOAD_TITLE
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
126 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_NCDS_BACKGROUND
),
127 NWidget(NWID_SPACER
), SetMinimalSize(350, 0), SetMinimalTextLines(3, WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
+ 30),
131 /** Window description for the download window */
132 static WindowDesc
_bootstrap_download_status_window_desc(
133 WDP_CENTER
, nullptr, 0, 0,
134 WC_NETWORK_STATUS_WINDOW
, WC_NONE
,
136 _nested_bootstrap_download_status_window_widgets
, lengthof(_nested_bootstrap_download_status_window_widgets
)
140 /** Window for showing the download status of content */
141 struct BootstrapContentDownloadStatusWindow
: public BaseNetworkContentDownloadStatusWindow
{
143 /** Simple call the constructor of the superclass. */
144 BootstrapContentDownloadStatusWindow() : BaseNetworkContentDownloadStatusWindow(&_bootstrap_download_status_window_desc
)
148 void Close() override
150 /* If we are not set to exit the game, it means the bootstrap failed. */
152 new BootstrapErrorWindow();
154 this->BaseNetworkContentDownloadStatusWindow::Close();
157 void OnDownloadComplete(ContentID cid
) override
159 /* We have completed downloading. We can trigger finding the right set now. */
160 BaseGraphics::FindSets();
162 /* And continue going into the menu. */
163 _game_mode
= GM_MENU
;
165 /* _exit_game is used to break out of the outer video driver's MainLoop. */
171 /** The widgets for the query. It has no close box as that sprite does not exist yet. */
172 static const NWidgetPart _bootstrap_query_widgets
[] = {
173 NWidget(NWID_HORIZONTAL
),
174 NWidget(WWT_CAPTION
, COLOUR_GREY
), SetDataTip(STR_MISSING_GRAPHICS_SET_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
176 NWidget(WWT_PANEL
, COLOUR_GREY
),
177 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_BAFD_QUESTION
), EndContainer(),
178 NWidget(NWID_HORIZONTAL
),
179 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BAFD_YES
), SetDataTip(STR_MISSING_GRAPHICS_YES_DOWNLOAD
, STR_NULL
),
180 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_BAFD_NO
), SetDataTip(STR_MISSING_GRAPHICS_NO_QUIT
, STR_NULL
),
185 /** The window description for the query. */
186 static WindowDesc
_bootstrap_query_desc(
187 WDP_CENTER
, nullptr, 0, 0,
188 WC_CONFIRM_POPUP_QUERY
, WC_NONE
,
190 _bootstrap_query_widgets
, lengthof(_bootstrap_query_widgets
)
193 /** The window for the query. It can't use the generic query window as that uses sprites that don't exist yet. */
194 class BootstrapAskForDownloadWindow
: public Window
, ContentCallback
{
195 Dimension button_size
; ///< The dimension of the button
198 /** Start listening to the content client events. */
199 BootstrapAskForDownloadWindow() : Window(&_bootstrap_query_desc
)
201 this->InitNested(WN_CONFIRM_POPUP_QUERY_BOOTSTRAP
);
202 _network_content_client
.AddCallback(this);
205 /** Stop listening to the content client events. */
206 void Close() override
208 _network_content_client
.RemoveCallback(this);
209 this->Window::Close();
212 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
214 /* We cache the button size. This is safe as no reinit can happen here. */
215 if (this->button_size
.width
== 0) {
216 this->button_size
= maxdim(GetStringBoundingBox(STR_MISSING_GRAPHICS_YES_DOWNLOAD
), GetStringBoundingBox(STR_MISSING_GRAPHICS_NO_QUIT
));
217 this->button_size
.width
+= WD_FRAMETEXT_LEFT
+ WD_FRAMETEXT_RIGHT
;
218 this->button_size
.height
+= WD_FRAMETEXT_TOP
+ WD_FRAMETEXT_BOTTOM
;
222 case WID_BAFD_QUESTION
:
223 /* The question is twice as wide as the buttons, and determine the height based on the width. */
224 size
->width
= this->button_size
.width
* 2;
225 size
->height
= GetStringHeight(STR_MISSING_GRAPHICS_SET_MESSAGE
, size
->width
- WD_FRAMETEXT_LEFT
- WD_FRAMETEXT_RIGHT
) + WD_FRAMETEXT_BOTTOM
+ WD_FRAMETEXT_TOP
;
230 *size
= this->button_size
;
235 void DrawWidget(const Rect
&r
, int widget
) const override
237 if (widget
!= 0) return;
239 DrawStringMultiLine(r
.left
+ WD_FRAMETEXT_LEFT
, r
.right
- WD_FRAMETEXT_RIGHT
, r
.top
+ WD_FRAMETEXT_TOP
, r
.bottom
- WD_FRAMETEXT_BOTTOM
, STR_MISSING_GRAPHICS_SET_MESSAGE
, TC_FROMSTRING
, SA_CENTER
);
242 void OnClick(Point pt
, int widget
, int click_count
) override
246 /* We got permission to connect! Yay! */
247 _network_content_client
.Connect();
259 void OnConnect(bool success
) override
261 /* Once connected, request the metadata. */
262 _network_content_client
.RequestContentList(CONTENT_TYPE_BASE_GRAPHICS
);
265 void OnReceiveContentInfo(const ContentInfo
*ci
) override
267 /* And once the meta data is received, start downloading it. */
268 _network_content_client
.Select(ci
->id
);
269 new BootstrapContentDownloadStatusWindow();
274 #endif /* defined(WITH_FREETYPE) */
277 * Handle all procedures for bootstrapping OpenTTD without a base graphics set.
278 * This requires all kinds of trickery that is needed to avoid the use of
279 * sprites from the base graphics set which are pretty interwoven.
280 * @return True if a base set exists, otherwise false.
282 bool HandleBootstrap()
284 if (BaseGraphics::GetUsedSet() != nullptr) return true;
286 /* No user interface, bail out with an error. */
287 if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) goto failure
;
289 /* If there is no network or no freetype, then there is nothing we can do. Go straight to failure. */
290 #if (defined(_WIN32) && defined(WITH_UNISCRIBE)) || (defined(WITH_FREETYPE) && (defined(WITH_FONTCONFIG) || defined(__APPLE__))) || defined(WITH_COCOA)
291 if (!_network_available
) goto failure
;
293 /* First tell the game we're bootstrapping. */
294 _game_mode
= GM_BOOTSTRAP
;
296 /* Initialise the freetype font code. */
297 InitializeUnicodeGlyphMap();
298 /* Next "force" finding a suitable freetype font as the local font is missing. */
299 CheckForMissingGlyphs(false);
301 /* Initialise the palette. The biggest step is 'faking' some recolour sprites.
302 * This way the mauve and gray colours work and we can show the user interface. */
304 static const int offsets
[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80, 0, 0, 0, 0x04, 0x08 };
305 for (uint i
= 0; i
!= 16; i
++) {
306 for (int j
= 0; j
< 8; j
++) {
307 _colour_gradient
[i
][j
] = offsets
[i
] + j
;
311 /* Finally ask the question. */
312 new BootstrapBackground();
313 new BootstrapAskForDownloadWindow();
315 /* Process the user events. */
316 VideoDriver::GetInstance()->MainLoop();
318 /* _exit_game is used to get out of the video driver's main loop.
319 * In case GM_BOOTSTRAP is still set we did not exit it via the
320 * "download complete" event, so it was a manual exit. Obey it. */
321 _exit_game
= _game_mode
== GM_BOOTSTRAP
;
322 if (_exit_game
) return false;
324 /* Try to probe the graphics. Should work this time. */
325 if (!BaseGraphics::SetSet({})) goto failure
;
327 /* Finally we can continue heading for the menu. */
328 _game_mode
= GM_MENU
;
332 /* Failure to get enough working to get a graphics set. */
334 usererror("Failed to find a graphics set. Please acquire a graphics set for OpenTTD. See section 1.4 of README.md.");