| 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 | |
| 33 | enum |
| 34 | { |
| 35 | PERSONLIST_NOCAPTION = 1 << 0, |
| 36 | PERSONLIST_NONEWLINE = 1 << 1 |
| 37 | }; |
| 38 | |
| 39 | struct 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 | |
| 51 | static 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 | |
| 96 | gameDesign = |
| 97 | { |
| 98 | {.name: "Matthes Bender" , .nick: "matthes" }, |
| 99 | }, |
| 100 | code = |
| 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 | }, |
| 116 | scripting = |
| 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 | }, |
| 128 | additionalArt = |
| 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 | }, |
| 139 | music = |
| 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 | }, |
| 146 | voice = |
| 147 | { |
| 148 | {.name: "Klemens K\xf6hring" , .nick: nullptr} |
| 149 | }, |
| 150 | web = |
| 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 | |
| 160 | template<int32_t left, int32_t top, int32_t right, int32_t bottom> |
| 161 | class CustomMarginTextWindow : public C4GUI::TextWindow |
| 162 | { |
| 163 | public: |
| 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 | |
| 175 | namespace |
| 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 | |
| 262 | C4StartupAboutDlg::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 | |
| 315 | void 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 | |
| 320 | void 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 | |
| 338 | void 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 | |
| 343 | C4GUI::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 | |
| 351 | void C4StartupAboutDlg::DoBack() |
| 352 | { |
| 353 | C4Startup::Get()->SwitchDialog(eToDlg: C4Startup::SDID_Main); |
| 354 | } |
| 355 | |
| 356 | void C4StartupAboutDlg::DrawElement(C4FacetEx &cgo) |
| 357 | { |
| 358 | DrawBackground(cgo, rFromFct&: C4Startup::Get()->Graphics.fctAboutBG); |
| 359 | } |
| 360 | |
| 361 | void 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 | |
| 369 | void C4StartupAboutDlg::SetPageVisibility(uint32_t number, bool visible) |
| 370 | { |
| 371 | for (auto *element : aboutPages[number]) |
| 372 | { |
| 373 | element->SetVisibility(visible); |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | void C4StartupAboutDlg::OnUpdateBtn(C4GUI::Control *btn) |
| 378 | { |
| 379 | C4UpdateDlg::CheckForUpdates(pScreen: GetScreen()); |
| 380 | } |
| 381 | |