1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2010-2016, The OpenClonk Team and contributors
6 * Copyright (c) 2018-2021, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18// Credits screen
19
20#include "C4GuiListBox.h"
21#include "C4GuiResource.h"
22#include "C4Include.h"
23#include "C4StartupAboutDlg.h"
24
25#include "C4Version.h"
26#include "C4GraphicsResource.h"
27#include "C4UpdateDlg.h"
28
29#include <initializer_list>
30#include <sstream>
31#include <utility>
32
33enum
34{
35 PERSONLIST_NOCAPTION = 1 << 0,
36 PERSONLIST_NONEWLINE = 1 << 1
37};
38
39struct PersonList
40{
41 struct Entry
42 {
43 const char *name, *nick;
44 };
45 const char *title;
46 virtual void WriteTo(C4GUI::TextWindow *textbox, CStdFont &font, bool newline = true) = 0;
47 virtual std::string ToString(bool newline = true, bool with_color = false) = 0;
48 virtual ~PersonList() { }
49};
50
51static struct DeveloperList : public PersonList
52{
53 std::vector<Entry> developers;
54
55 DeveloperList(std::initializer_list<Entry> l) : developers(l) { }
56
57 void WriteTo(C4GUI::TextWindow *textbox, CStdFont &font, bool newline = true) override
58 {
59 if (!newline)
60 {
61 textbox->AddTextLine(szText: ToString(newline: false, with_color: true).c_str(), pFont: &font, C4GUI_MessageFontClr, fDoUpdate: false, fMakeReadableOnBlack: true);
62 return;
63 }
64
65 for (auto &p : developers)
66 {
67 textbox->AddTextLine(szText: p.nick ? (p.name ? std::format(fmt: "{} <c f7f76f>({})</c>", args&: p.name, args&: p.nick).c_str() : std::format(fmt: "<c f7f76f>{}</c>", args&: p.nick).c_str()) : p.name, pFont: &font, C4GUI_MessageFontClr, fDoUpdate: false, fMakeReadableOnBlack: true);
68 }
69 }
70
71 std::string ToString(bool newline = true, bool with_color = false) override
72 {
73 const char *opening_tag = with_color ? "<c f7f76f>" : "";
74 const char *closing_tag = with_color ? "</c>" : "";
75 std::stringstream out;
76 for (auto &p : developers)
77 {
78 if (p.name)
79 {
80 out << p.name;
81 if (p.nick)
82 {
83 out << opening_tag << " (" << p.nick << ")" << closing_tag;
84 }
85 }
86 else
87 {
88 out << opening_tag << p.nick << closing_tag;
89 }
90 out << (newline ? "\n" : ", ");
91 }
92 return out.str();
93 }
94}
95
96gameDesign =
97{
98 {.name: "Matthes Bender", .nick: "matthes"},
99},
100code =
101{
102 {.name: "Sven Eberhardt", .nick: "Sven2"},
103 {.name: "Peter Wortmann", .nick: "PeterW"},
104 {.name: "G\xfcnther Brammer", .nick: "G\xfcnther"},
105 {.name: "Armin Burgmeier", .nick: "Clonk-Karl"},
106 {.name: "Julian Raschke", .nick: "survivor"},
107 {.name: "Alexander Post", .nick: "qualle"},
108 {.name: "Jan Heberer", .nick: "Jan"},
109 {.name: "Markus Mittendrein", .nick: "Der Tod"},
110 {.name: "Dominik Bayerl", .nick: "Kanibal"},
111 {.name: "George Tokmaji", .nick: "Fulgen"},
112 {.name: "Martin Plicht", .nick: "Mortimer"},
113 {.name: "Matthias Brehmer", .nick: "Bratkartoffl"},
114 {.name: "Tim Kuhrt", .nick: "TLK"}
115},
116scripting =
117{
118 {.name: "Felix Wagner", .nick: "Clonkonaut"},
119 {.name: "Richard Gerum", .nick: "Randrian"},
120 {.name: "Markus Hoppe", .nick: "Shamino"},
121 {.name: "David Dormagen", .nick: "Zapper"},
122 {.name: "Florian Gro\xdf", .nick: "flgr"},
123 {.name: "Tobias Zwick", .nick: "Newton"},
124 {.name: "Bernhard Bonigl", .nick: "boni"},
125 {.name: "Viktor Yuschuk", .nick: "Viktor"},
126 {.name: nullptr, .nick: "Raven"}
127},
128additionalArt =
129{
130 {.name: "Erik Nitzschke", .nick: "DukeAufDune"},
131 {.name: "Merten Ehmig", .nick: "pluto"},
132 {.name: "Matthias Rottl\xe4nder", .nick: "Matthi"},
133 {.name: "Christopher Reimann", .nick: "Benzol"},
134 {.name: "Jonathan Veit", .nick: "AniProGuy"},
135 {.name: "Arthur M\xf6ller", .nick: "Aqua"},
136 {.name: "Tobias Zwick", .nick: "Newton"},
137 {.name: nullptr, .nick: "Raven"}
138},
139music =
140{
141 {.name: "Hans-Christan K\xfchl", .nick: "HCK"},
142 {.name: "Sebastian Burkhart", .nick: "hypo"},
143 {.name: "Florian Boos", .nick: "Flobby"},
144 {.name: "Martin Strohmeier", .nick: "K-Pone"}
145},
146voice =
147{
148 {.name: "Klemens K\xf6hring", .nick: nullptr}
149},
150web =
151{
152 {.name: "Markus Wichitill", .nick: "mawic"},
153 {.name: "Martin Schuster", .nick: "knight_k"},
154 {.name: "Arne Bochem", .nick: "ArneB"},
155 {.name: "Lukas Werling", .nick: "Luchs"},
156 {.name: "Florian Graier", .nick: "Nachtfalter"},
157 {.name: "Benedict Etzel", .nick: "B_E"}
158};
159
160template<int32_t left, int32_t top, int32_t right, int32_t bottom>
161class CustomMarginTextWindow : public C4GUI::TextWindow
162{
163public:
164 CustomMarginTextWindow(C4Rect &rtBounds, size_t iPicWdt = 0, size_t iPicHgt = 0, size_t iPicPadding = 0, size_t iMaxLines = 100, size_t iMaxTextLen = 4096, const char *szIndentChars = " ", bool fAutoGrow = false, const C4Facet *pOverlayPic = nullptr, int iOverlayBorder = 0, bool fMarkup = false) : C4GUI::TextWindow{rtBounds, iPicWdt, iPicHgt, iPicPadding, iMaxLines, iMaxTextLen, szIndentChars, fAutoGrow, pOverlayPic, iOverlayBorder, fMarkup}
165 {
166 UpdateSize();
167 }
168
169 virtual int32_t GetMarginTop() override { return top; }
170 virtual int32_t GetMarginLeft() override { return left; }
171 virtual int32_t GetMarginRight() override { return right; }
172 virtual int32_t GetMarginBottom() override { return bottom; }
173};
174
175namespace
176{
177 void AddTextWindowLines(C4GUI::TextWindow *textbox, const std::string &text)
178 {
179 CStdFont &rUseFont = C4GUI::GetRes()->TextFont;
180 std::stringstream str{text};
181 for (std::string line; std::getline(in&: str, str&: line, delim: '\n'); )
182 {
183 textbox->AddTextLine(szText: line.c_str(), pFont: &rUseFont, C4GUI_MessageFontClr, fDoUpdate: false, fMakeReadableOnBlack: true);
184 }
185 textbox->UpdateHeight();
186 }
187
188 struct License
189 {
190 std::string title;
191 std::string licenseTitle;
192 std::string text;
193 };
194
195 std::vector<License> licenses
196 {
197 #include "generated/licenses.h"
198 };
199
200 class LicenseWindow : public C4GUI::Window
201 {
202 public:
203 LicenseWindow(const C4Rect &bounds, const std::vector<License> &licenses)
204 {
205 SetBounds(bounds);
206 C4GUI::ComponentAligner aligner{GetClientRect(), 0, 10};
207 tabList = new C4GUI::ListBox{aligner.GetFromLeft(iWdt: aligner.GetWidth() / 5)};
208 tabList->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler<LicenseWindow>{this, &LicenseWindow::ChangeTab});
209 AddElement(pChild: tabList);
210
211 textWindow = new C4GUI::TextWindow{aligner.GetAll(), 0, 0, 0, 1000, 50000, ""};
212 AddElement(pChild: textWindow);
213
214 for (const auto &license : licenses)
215 {
216 tabList->AddElement(pChild: new LicenseTab{license.title, license.licenseTitle, license.text});
217 }
218 tabList->SelectFirstEntry(fByUser: false);
219 }
220
221 private:
222 void ChangeTab(C4GUI::Element *element)
223 {
224 if (!element) return;
225
226 const auto *const tab = static_cast<LicenseTab *>(element);
227 std::string title{tab->GetTitle()};
228 const auto &licenseTitle = tab->GetLicenseTitle();
229 if (!licenseTitle.empty())
230 {
231 title += " (";
232 title += licenseTitle;
233 title += ")";
234 }
235 textWindow->ClearText(fDoUpdate: false);
236 textWindow->AddTextLine(szText: title.c_str(), pFont: &C4GUI::GetRes()->TitleFont, C4GUI_Caption2FontClr, fDoUpdate: false, fMakeReadableOnBlack: false);
237 AddTextWindowLines(textbox: textWindow, text: tab->GetLicenseText());
238 }
239
240 C4GUI::ListBox *tabList;
241 C4GUI::TextWindow *textWindow;
242
243 class LicenseTab : public C4GUI::Label
244 {
245 public:
246 LicenseTab(std::string title, std::string licenseTitle, std::string licenseText)
247 : C4GUI::Label{title.c_str(), 0, 0}, title{std::move(title)}, licenseTitle{std::move(licenseTitle)}, licenseText{std::move(licenseText)} {}
248 const std::string &GetTitle() const noexcept { return title; }
249 const std::string &GetLicenseTitle() const noexcept { return licenseTitle; }
250 const std::string &GetLicenseText() const noexcept { return licenseText; }
251
252 private:
253 std::string title;
254 std::string licenseTitle;
255 std::string licenseText;
256 };
257 };
258}
259
260// C4StartupAboutDlg
261
262C4StartupAboutDlg::C4StartupAboutDlg() : C4StartupDlg(LoadResStr(id: C4ResStrTableKey::IDS_DLG_ABOUT))
263{
264 // ctor
265 UpdateSize();
266
267 CStdFont &rTrademarkFont = C4GUI::GetRes()->MiniFont;
268 C4Rect rcClient = GetContainedClientRect();
269 // bottom line buttons and copyright messages
270 C4GUI::ComponentAligner caMain(rcClient, 0,0, true);
271 C4GUI::ComponentAligner caButtons(caMain.GetFromBottom(iHgt: caMain.GetHeight()*1/8), 0,0, false);
272 C4GUI::CallbackButton<C4StartupAboutDlg> *btn;
273
274 AddElement(pChild: new C4GUI::Label(FANPROJECTTEXT " " TRADEMARKTEXT,
275 caButtons.GetFromBottom(iHgt: rTrademarkFont.GetLineHeight()), ARight, 0xffffffff, &rTrademarkFont));
276
277 int32_t iButtonWidth = caButtons.GetInnerWidth() / 4;
278 AddElement(pChild: btn = new C4GUI::CallbackButton<C4StartupAboutDlg>(LoadResStr(id: C4ResStrTableKey::IDS_BTN_BACK), caButtons.GetGridCell(iSectX: 0,iSectXMax: 3,iSectY: 0,iSectYMax: 1,iSectSizeX: iButtonWidth,C4GUI_ButtonHgt,fCenterPos: true), &C4StartupAboutDlg::OnBackBtn));
279 btn->SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_DLGTIP_BACKMAIN));
280 AddElement(pChild: btn = new C4GUI::CallbackButton<C4StartupAboutDlg>(LoadResStr(id: C4ResStrTableKey::IDS_BTN_CHECKFORUPDATES), caButtons.GetGridCell(iSectX: 2,iSectXMax: 4,iSectY: 0,iSectYMax: 1,iSectSizeX: iButtonWidth,C4GUI_ButtonHgt,fCenterPos: true), &C4StartupAboutDlg::OnUpdateBtn));
281 btn->SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_DESC_CHECKONLINEFORNEWVERSIONS));
282 AddElement(pChild: btnAdvance = new C4GUI::CallbackButton<C4StartupAboutDlg>(LoadResStr(id: C4ResStrTableKey::IDS_BTN_LICENSES),
283 caButtons.GetGridCell(iSectX: 3,iSectXMax: 4,iSectY: 0,iSectYMax: 1,iSectSizeX: iButtonWidth,C4GUI_ButtonHgt,fCenterPos: true), &C4StartupAboutDlg::OnAdvanceButton));
284
285 using ElementVector = decltype(aboutPages)::value_type;
286 ElementVector page1;
287
288 C4GUI::ComponentAligner caDevelopers(caMain.GetAll(), 0,0, false);
289
290 C4GUI::ComponentAligner caDevelopersCol1(caDevelopers.GetFromLeft(iWdt: caMain.GetWidth()*1/3), 0, 0, false);
291 DrawPersonList(page&: page1, gameDesign, title: "Game Design", rect&: caDevelopersCol1.GetFromTop(iHgt: caDevelopersCol1.GetHeight()*1/5));
292 DrawPersonList(page&: page1, code, title: "Engine and Tools", rect&: caDevelopersCol1.GetAll());
293
294 C4GUI::ComponentAligner caDevelopersCol2(caDevelopers.GetFromLeft(iWdt: caMain.GetWidth()*1/3), 0,0, false);
295 DrawPersonList(page&: page1, scripting, title: "Scripting", rect&: caDevelopersCol2.GetFromTop(iHgt: caDevelopersCol2.GetHeight()*1/2));
296 DrawPersonList(page&: page1, additionalArt, title: "Additional Art", rect&: caDevelopersCol2.GetAll());
297
298 C4GUI::ComponentAligner caDevelopersCol3(caDevelopers.GetFromLeft(iWdt: caMain.GetWidth()*1/3), 0,0, false);
299 DrawPersonList(page&: page1, music, title: "Music", rect&: caDevelopersCol3.GetFromTop(iHgt: caDevelopersCol3.GetHeight()*1/3));
300 DrawPersonList(page&: page1, voice, title: "Voice", rect&: caDevelopersCol3.GetFromTop(iHgt: caDevelopersCol3.GetHeight()*3/10));
301 DrawPersonList(page&: page1, web, title: "Web", rect&: caDevelopersCol3.GetAll());
302
303 aboutPages.emplace_back(args: std::move(page1));
304
305 auto licenseWindow = new LicenseWindow{caMain.GetAll(), licenses};
306 AddElement(pChild: licenseWindow);
307 aboutPages.push_back(x: {licenseWindow});
308
309 for (uint32_t page = 1; page < aboutPages.size(); ++page)
310 {
311 SetPageVisibility(number: page, visible: false);
312 }
313}
314
315void C4StartupAboutDlg::AddLicense(std::string title, std::string licenseTitle, std::string licenseText)
316{
317 licenses.push_back(x: {.title: std::move(title), .licenseTitle: std::move(licenseTitle), .text: std::move(licenseText)});
318}
319
320void C4StartupAboutDlg::CreateTextWindowWithText(std::vector<C4GUI::Element *> &page, C4Rect &rect, const std::string &text, const std::string &title)
321{
322 CStdFont &captionFont = C4GUI::GetRes()->TitleFont;
323 if (!title.empty())
324 {
325 int height = captionFont.GetLineHeight();
326 page.push_back(x: DrawCaption(rect, title.c_str()));
327 rect.y += height; rect.Hgt -= height;
328 }
329
330 auto textbox = new CustomMarginTextWindow<0, 8, 0, 8>(rect, 0, 0, 0, 100, 8000, "", true, nullptr, 0, true);
331 AddElement(pChild: textbox);
332 textbox->SetDecoration(fDrawBG: false, fDrawFrame: false, pToGfx: nullptr, fAutoScroll: true);
333
334 AddTextWindowLines(textbox, text);
335 page.push_back(x: textbox);
336}
337
338void C4StartupAboutDlg::DrawPersonList(std::vector<C4GUI::Element *> &page, PersonList &persons, const char *title, C4Rect &rect, uint8_t flags)
339{
340 CreateTextWindowWithText(page, rect, text: persons.ToString(newline: !(flags & PERSONLIST_NONEWLINE), with_color: true), title: flags & PERSONLIST_NOCAPTION ? "" : title);
341}
342
343C4GUI::Label *C4StartupAboutDlg::DrawCaption(C4Rect &rect, const char *text)
344{
345 CStdFont &captionFont = C4GUI::GetRes()->CaptionFont;
346 auto caption = new C4GUI::Label(text, rect, ALeft, C4GUI_Caption2FontClr, &captionFont);
347 AddElement(pChild: caption);
348 return caption;
349}
350
351void C4StartupAboutDlg::DoBack()
352{
353 C4Startup::Get()->SwitchDialog(eToDlg: C4Startup::SDID_Main);
354}
355
356void C4StartupAboutDlg::DrawElement(C4FacetEx &cgo)
357{
358 DrawBackground(cgo, rFromFct&: C4Startup::Get()->Graphics.fctAboutBG);
359}
360
361void C4StartupAboutDlg::SwitchPage(uint32_t number)
362{
363 SetPageVisibility(number: currentPage, visible: false);
364 currentPage = number;
365 SetPageVisibility(number: currentPage, visible: true);
366 btnAdvance->SetVisibility(currentPage != aboutPages.size() - 1);
367}
368
369void C4StartupAboutDlg::SetPageVisibility(uint32_t number, bool visible)
370{
371 for (auto *element : aboutPages[number])
372 {
373 element->SetVisibility(visible);
374 }
375}
376
377void C4StartupAboutDlg::OnUpdateBtn(C4GUI::Control *btn)
378{
379 C4UpdateDlg::CheckForUpdates(pScreen: GetScreen());
380}
381