From 57593fb2129a77a3a246702610d526fc09e22f42 Mon Sep 17 00:00:00 2001 From: jacob1 Date: Wed, 16 Oct 2024 23:24:42 -0400 Subject: [PATCH] Add credits UI Lists all GitHub contributors and moderators, alongside the original credits (which were moved from the intro text to here) The UI itself is controlled with credits.json. This can be regenerated with resources/gencredits.py. --- .gitignore | 1 + resources/credits.json | 1 + resources/gencredits.py | 101 ++++++++++++++ resources/meson.build | 1 + src/gui/credits/Credits.cpp | 217 +++++++++++++++++++++++++++++ src/gui/credits/Credits.h | 23 +++ src/gui/credits/meson.build | 3 + src/gui/game/IntroText.h | 4 - src/gui/interface/AvatarButton.cpp | 14 +- src/gui/interface/AvatarButton.h | 6 +- src/gui/interface/Separator.cpp | 12 ++ src/gui/interface/Separator.h | 16 +++ src/gui/interface/meson.build | 1 + src/gui/meson.build | 1 + src/gui/options/OptionsView.cpp | 36 +++-- 15 files changed, 412 insertions(+), 25 deletions(-) create mode 100644 resources/credits.json create mode 100644 resources/gencredits.py create mode 100644 src/gui/credits/Credits.cpp create mode 100644 src/gui/credits/Credits.h create mode 100644 src/gui/credits/meson.build create mode 100644 src/gui/interface/Separator.cpp create mode 100644 src/gui/interface/Separator.h diff --git a/.gitignore b/.gitignore index 875974871..496b7abd5 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,7 @@ screenshot_* /.kdev4 # Other IDEs / misc +/.idea .vscode/ .vs/ *.sublime-* diff --git a/resources/credits.json b/resources/credits.json new file mode 100644 index 000000000..22ef1c8d9 --- /dev/null +++ b/resources/credits.json @@ -0,0 +1 @@ +{"GitHub": ["simtr", "jacob1", "LBPHacker", "jacksonmj", "Pilihp64", "mniip", "savask", "NoH", "triclops200", "SuperDoxin", "zc00gii", "krawthekrow", "wolfy1339", "QuanTech0", "catsoften", "Catelite", "antb", "moonheart08", "cracker1000", "Huulivoide", "ntoskrnl11", "me4502", "suve", "mmbob", "C7C8", "boxmein", "ief015", "jbot-42", "tridiaq", "perssphere07", "iczero", "SebastianMestre", "nixls", "yareky", "CapacitorSet", "ssccsscc", "JustinShurie", "BlueSyncLine", "Mailaender", "kroq-gar78", "nunom27", "amaank404", "nucular", "jombo23", "gamax92", "Ristovski", "Novocain1", "VelocityRa", "orbitcowboy", "TropicalBastos", "Bowserinator", "n1kolasM", "NoComplaintsEver", "Not-Super-Nova", "Onestay42", "RCAProduction", "SilentSpud", "Rebmiami", "RobertBScott", "ageofadz", "Vgr255", "jebbyk", "avevad", "cppxor2arr", "dxgldotorg", "grufkork", "rfht", "china-richway2", "yangbowen", "dreness", "andrewrk", "super7ramp", "Caeleron", "CanGonenc", "ChromicQuanta", "connor-create", "Departing", "AMDmi3", "EchoHowardLam", "BigWolfy1339", "Jakav-N", "JasonS05", "meyer9", "um3k", "Ksawi999", "Maticzpl", "Mrprocom", "handicraftsman"], "OrigCredits": [{"realname": "Stanislaw K Skowronek", "message": "Designed the original Powder Toy"}, {"realname": "Simon Robertshaw", "message": "Wrote the website, current server owner"}, {"realname": "Skresanov Savely", "message": ""}, {"realname": "Pilihp64", "message": ""}, {"realname": "Catelite", "message": ""}, {"realname": "Victoria Hoyle", "message": ""}, {"realname": "Nathan Cousins", "message": ""}, {"realname": "jacksonmj", "message": ""}, {"realname": "Felix Wallin", "message": ""}, {"realname": "Lieuwe Mosch", "message": ""}, {"realname": "Anthony Boot", "message": ""}, {"realname": "Me4502", "message": ""}, {"realname": "MaksProg", "message": ""}, {"realname": "jacob1", "message": ""}, {"realname": "mniip", "message": ""}, {"realname": "LBPHacker", "message": ""}], "Moderators": [{"username": "jacob1", "role": "Moderator"}, {"username": "LBPHacker", "role": "Moderator"}, {"username": "Sylvi", "role": "Moderator"}, {"username": "CCl2F2", "role": "Moderator"}, {"username": "catsoften", "role": "Moderator"}, {"username": "Denderth", "role": "Moderator"}, {"username": "Simon", "role": "Moderator"}, {"username": "Mrprocom", "role": "Moderator"}, {"username": "jacksonmj", "role": "Former Staff"}, {"username": "Pilihp64", "role": "Former Staff"}, {"username": "Catelite", "role": "Former Staff"}, {"username": "boxmein", "role": "Former Staff"}, {"username": "lolzy", "role": "Former Staff"}, {"username": "Xenocide", "role": "Former Staff"}, {"username": "triclops200", "role": "Former Staff"}, {"username": "devast8a", "role": "Former Staff"}, {"username": "HK6", "role": "Former Staff"}, {"username": "FrankBro", "role": "Former Staff"}, {"username": "doxin", "role": "Former Staff"}, {"username": "ief015", "role": "Former Staff"}, {"username": "ad", "role": "Former Staff"}]} \ No newline at end of file diff --git a/resources/gencredits.py b/resources/gencredits.py new file mode 100644 index 000000000..e0923d6bc --- /dev/null +++ b/resources/gencredits.py @@ -0,0 +1,101 @@ +import urllib.error +import urllib.request +import json + +def get_url(url : str) -> bytes | None: + try: + req = urllib.request.Request(url) + data = urllib.request.urlopen(req, timeout=10) + page = data.read() + + return page + except urllib.error.URLError as e: + print(f"{url} - {e}") + return None + +def fetch_gh_contributors() -> list[any]: + page = 1 + ret = [] + while True: + data = get_url(f"https://api.github.com/repos/The-Powder-Toy/The-Powder-Toy/contributors?page={page}") + contributors = json.loads(data) + if not len(contributors): + break + ret.extend(contributors) + page = page + 1 + + return ret + +def get_github_json(github_contributors : list[any]) -> list[str]: + ret = [] + for contributor in github_contributors: + ret.append(contributor["login"]) + + return ret + +def get_orig_json() -> list[dict[str, str | int]]: + """Credits that appeared in intro text in older versions""" + + return [ + { "realname" : "Stanislaw K Skowronek", "message" : "Designed the original Powder Toy"}, + { "realname" : "Simon Robertshaw", "message" : "Wrote the website, current server owner"}, + { "realname" : "Skresanov Savely", "message" : ""}, + { "realname" : "Pilihp64", "message" : ""}, + { "realname" : "Catelite", "message" : ""}, + { "realname" : "Victoria Hoyle", "message" : ""}, + { "realname" : "Nathan Cousins", "message" : ""}, + { "realname" : "jacksonmj", "message" : ""}, + { "realname" : "Felix Wallin", "message" : ""}, + { "realname" : "Lieuwe Mosch", "message" : ""}, + { "realname" : "Anthony Boot", "message" : ""}, + { "realname" : "Me4502", "message" : ""}, + { "realname" : "MaksProg", "message" : ""}, + { "realname" : "jacob1", "message" : ""}, + { "realname" : "mniip", "message" : ""}, + { "realname" : "LBPHacker", "message" : ""}, + ] + +def get_moderator_json() -> list[dict[str, str | int]]: + """Current and former moderators""" + + return [ + { "username" : "jacob1", "role" : "Moderator" }, + { "username" : "LBPHacker", "role" : "Moderator" }, + { "username" : "Sylvi", "role" : "Moderator" }, + { "username" : "CCl2F2", "role" : "Moderator" }, + { "username" : "catsoften", "role" : "Moderator" }, + { "username" : "Denderth", "role" : "Moderator" }, + { "username" : "Simon", "role" : "Moderator" }, + { "username" : "Mrprocom", "role" : "Moderator" }, + { "username" : "jacksonmj", "role" : "Former Staff" }, + { "username" : "Pilihp64", "role" : "Former Staff" }, + { "username" : "Catelite", "role" : "Former Staff" }, + { "username" : "boxmein", "role" : "Former Staff" }, + { "username" : "lolzy", "role" : "Former Staff" }, + { "username" : "Xenocide", "role" : "Former Staff" }, + { "username" : "triclops200", "role" : "Former Staff" }, + { "username" : "devast8a", "role" : "Former Staff" }, + { "username" : "HK6", "role" : "Former Staff" }, + { "username" : "FrankBro", "role" : "Former Staff" }, + { "username" : "doxin", "role" : "Former Staff" }, + { "username" : "ief015", "role" : "Former Staff" }, + { "username" : "ad", "role" : "Former Staff" }, + ] + +def process() -> any: + github_contributors = fetch_gh_contributors() + github = get_github_json(github_contributors) + + orig = get_orig_json() + mods = get_moderator_json() + + data = { + "GitHub" : github, + "OrigCredits" : orig, + "Moderators" : mods, + } + + with open("credits.json", "w") as f: + json.dump(data, f) + +process() diff --git a/resources/meson.build b/resources/meson.build index 95248123a..64faa9038 100644 --- a/resources/meson.build +++ b/resources/meson.build @@ -118,3 +118,4 @@ endif data_files += to_array.process('save_local.png', extra_args: 'save_local_png') data_files += to_array.process('save_online.png', extra_args: 'save_online_png') data_files += to_array.process('font.bz2', extra_args: 'compressed_font_data') +data_files += to_array.process('credits.json', extra_args: 'credits_json') diff --git a/src/gui/credits/Credits.cpp b/src/gui/credits/Credits.cpp new file mode 100644 index 000000000..12deca331 --- /dev/null +++ b/src/gui/credits/Credits.cpp @@ -0,0 +1,217 @@ +#include "Credits.h" + +#include + +#include "credits.json.h" +#include "gui/Style.h" + +#include "common/platform/Platform.h" +#include "gui/interface/AvatarButton.h" +#include "gui/interface/Button.h" +#include "gui/interface/Engine.h" +#include "gui/interface/Label.h" +#include "gui/interface/RichLabel.h" +#include "gui/interface/ScrollPanel.h" +#include "gui/interface/Separator.h" + +Credits::Credits(): + ui::Window(ui::Point(-1, -1), ui::Point(WINDOWW, WINDOWH)) +{ + Json::Value root; + Json::Reader reader; + auto credits = credits_json.AsCharSpan(); + if (bool parsed = reader.parse(credits.data(), credits.data() + credits.size(), root, false); !parsed) { + // Failure. Shouldn't ever happen. + return; + } + + auto *scrollPanel = new ui::ScrollPanel(ui::Point(0, 0), ui::Point(Size.X, Size.Y - 12)); + AddComponent(scrollPanel); + + int xPos = 0, yPos = 0, row = 0; + int nextY = 0; + + // Organize blocks of components of equal width into rows, and add them to the scroll panel + auto organizeComponents = [&xPos, &yPos, &nextY, &row, &scrollPanel](const auto& components, const int panelWidth) { + ui::Point blockSize = { 0, 0 }; + for (const auto &component : components) + { + blockSize.X = std::max(blockSize.X, component->Position.X + component->Size.X); + blockSize.Y = std::max(blockSize.Y, component->Position.Y + component->Size.Y); + } + + // New row, offset x position to ensure entire row is centered + if (xPos == 0) + xPos = (panelWidth % blockSize.X) / 2; + for (const auto &component : components) + component->Position += ui::Point({ xPos, yPos }); + + xPos += blockSize.X; + nextY = std::max(nextY, yPos + blockSize.Y); + if (xPos + blockSize.X > panelWidth) + { + xPos = 0; + yPos = nextY + 8; + row++; + } + + for (const auto &component : components) + scrollPanel->AddChild(component); + }; + + // Add header and separator for each section of credits + auto addHeader = [&xPos, &yPos, &nextY, &row, &scrollPanel](const String &text, const bool addSeparator = true) { + xPos = 0; + yPos = nextY + 10; + row = 0; + + if (addSeparator) + { + auto *separator = new ui::Separator(ui::Point(0, yPos), ui::Point(scrollPanel->Size.X, 1)); + scrollPanel->AddChild(separator); + yPos += 6; + } + + auto *label = new ui::RichLabel(ui::Point(4, yPos), ui::Point(scrollPanel->Size.X, 24), text); + label->SetTextColour(style::Colour::InformationTitle); + label->Appearance.HorizontalAlign = ui::Appearance::AlignCentre; + label->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; + scrollPanel->AddChild(label); + yPos += label->Size.Y + 8; + }; + + + addHeader("TPT is an open source project, developed by members of the community.\n" + "We'd like to thank everyone who contributed to our \bt{a:https://github.com/The-Powder-Toy/The-Powder-Toy|GitHub repo}\x0E:", false); + + auto GitHub = root["GitHub"]; + int grayscale = 255; + for (auto &item : GitHub) + { + ByteString username = item.asString(); + auto components = AddCredit(username.FromUtf8(), "", Small, "", false, grayscale); + organizeComponents(components, scrollPanel->Size.X); + if (grayscale > 180) + grayscale--; + } + + + addHeader("Staff - volunteers that run the community and keep the site running"); + + auto Moderators = root["Moderators"]; + for (auto &item : Moderators) + { + ByteString username = item["username"].asString(); + ByteString role = item["role"].asString(); + + if (role == "Moderator" || role == "HalfMod") + { + auto components = AddCredit(username.FromUtf8(), "", Large, GetProfileUri(username), true); + organizeComponents(components, scrollPanel->Size.X); + } + } + + + addHeader("Former Staff", false); + + for (auto &item : Moderators) + { + ByteString username = item["username"].asString(); + ByteString role = item["role"].asString(); + + if (role == "Former Staff") + { + auto components = AddCredit(username.FromUtf8(), "", Small, "", true); + organizeComponents(components, scrollPanel->Size.X); + } + } + + + addHeader("The following users have been credited in the intro text from the start.\n" + "Their contributions to the early beginnings of TPT were invaluable in shaping TPT into what it is today."); + + auto OrigCredits = root["OrigCredits"]; + for (auto &item : OrigCredits) + { + ByteString realname = item["realname"].asString(); + ByteString message = item["message"].asString(); + + auto components = AddCredit(realname.FromUtf8(), message.FromUtf8(), row == 0 ? Half : Small, ""); + organizeComponents(components, scrollPanel->Size.X); + } + + + scrollPanel->InnerSize = ui::Point(scrollPanel->Size.X, nextY); + + auto *closeButton = new ui::Button({ 0, Size.Y - 12 }, { Size.X, 12 }, "Close"); + closeButton->SetActionCallback({ + [this] { + CloseActiveWindow(); + } }); + AddComponent(closeButton); +} + +std::vector Credits::AddCredit(const String &name, const String &subheader, const CreditSize size, + const ByteString &uri, const bool includeAvatar, const int grayscale) +{ + std::vector components; + int creditBlockWidth = size == Small ? 100 : (size == Large ? 155 : 310); + int y = 0; + + if (includeAvatar) + { + int avatarWidth = size == Small ? 40 : 64; + int avatarSize = size == Small ? 40 : 256; + auto *avatarButton = new ui::AvatarButton(ui::Point((creditBlockWidth - avatarWidth) / 2, 0), ui::Point(avatarWidth, avatarWidth), name.ToUtf8(), avatarSize); + if (!uri.empty()) + { + avatarButton->SetActionCallback({[uri] { + Platform::OpenURI(uri); + } }); + } + components.push_back(avatarButton); + + y += avatarButton->Size.Y + 2; + } + + if (!name.empty()) + { + auto labelText = !uri.empty() ? GetRichLabelText(uri, name) : name; + auto *nameLabel = new ui::RichLabel(ui::Point(0, y), ui::Point(creditBlockWidth, 14), labelText); + nameLabel->Appearance.HorizontalAlign = ui::Appearance::AlignCentre; + nameLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; + nameLabel->SetTextColour(ui::Colour(grayscale, grayscale, grayscale)); + components.push_back(nameLabel); + + y += nameLabel->Size.Y + 2; + } + + if (!subheader.empty()) + { + auto *subheaderLabel = new ui::Label(ui::Point(0, y), ui::Point(creditBlockWidth, 14), subheader); + subheaderLabel->Appearance.HorizontalAlign = ui::Appearance::AlignCentre; + subheaderLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; + int col = (int)((float)grayscale * .67f); + subheaderLabel->SetTextColour(ui::Colour(col, col, col)); + components.push_back(subheaderLabel); + } + + return components; +} + +ByteString Credits::GetProfileUri(const ByteString &username) +{ + return "https://powdertoy.co.uk/User.html?Name=" + username; +} + +String Credits::GetRichLabelText(const ByteString &uri, const String &message) +{ + StringBuilder builder; + builder << "{a:" << uri.FromUtf8() << "|" << message << "}"; + return builder.Build(); +} + +void Credits::OnTryExit(ExitMethod method) +{ + ui::Engine::Ref().CloseWindow(); +} diff --git a/src/gui/credits/Credits.h b/src/gui/credits/Credits.h new file mode 100644 index 000000000..77c21baf6 --- /dev/null +++ b/src/gui/credits/Credits.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include "gui/interface/Window.h" + +class Credits : public ui::Window +{ + enum CreditSize + { + Small, + Large, + Half, + }; + + static std::vector AddCredit(const String &name, const String &subheader, CreditSize size, + const ByteString &uri, bool includeAvatar = false, int grayscale = 255); + static ByteString GetProfileUri(const ByteString &username); + static ByteString GetTptLabelText(const ByteString &tpt, const ByteString &github); + static String GetRichLabelText(const ByteString &uri, const String &message); +public: + Credits(); + + void OnTryExit(ExitMethod method) override; +}; diff --git a/src/gui/credits/meson.build b/src/gui/credits/meson.build new file mode 100644 index 000000000..1ca0cb68a --- /dev/null +++ b/src/gui/credits/meson.build @@ -0,0 +1,3 @@ +powder_files += files( + 'Credits.cpp', +) diff --git a/src/gui/game/IntroText.h b/src/gui/game/IntroText.h index 3da5eb585..5393ee89c 100644 --- a/src/gui/game/IntroText.h +++ b/src/gui/game/IntroText.h @@ -64,10 +64,6 @@ inline ByteString IntroText() "Use 'S' to save parts of the window as 'stamps'. 'L' loads the most recent stamp, 'K' shows a library of stamps you saved.\n" "Use 'P' to take a screenshot and save it into the current directory.\n" "Use 'H' to toggle the HUD. Use 'D' to toggle debug mode in the HUD.\n" - "\n" - "Contributors: \bgStanislaw K Skowronek (Designed the original Powder Toy),\n" - "\bgSimon Robertshaw, Skresanov Savely, Pilihp64, Catelite, Victoria Hoyle, Nathan Cousins, jacksonmj,\n" - "\bgFelix Wallin, Lieuwe Mosch, Anthony Boot, Me4502, MaksProg, jacob1, mniip, LBPHacker\n" "\n"; if constexpr (BETA) { diff --git a/src/gui/interface/AvatarButton.cpp b/src/gui/interface/AvatarButton.cpp index 741f2183a..2d3369095 100644 --- a/src/gui/interface/AvatarButton.cpp +++ b/src/gui/interface/AvatarButton.cpp @@ -10,9 +10,10 @@ namespace ui { -AvatarButton::AvatarButton(Point position, Point size, ByteString username): +AvatarButton::AvatarButton(Point position, Point size, ByteString username, int avatarSize): Component(position, size), name(username), + avatarSize(avatarSize), tried(false) { @@ -23,7 +24,14 @@ void AvatarButton::Tick(float dt) if (!avatar && !tried && name.size() > 0) { tried = true; - imageRequest = std::make_unique(ByteString::Build(STATICSERVER, "/avatars/", name, ".png"), Size); + ByteStringBuilder urlBuilder; + urlBuilder << STATICSERVER << "/avatars/" << name; + if (avatarSize) + { + urlBuilder << "." << avatarSize; + } + urlBuilder << ".png"; + imageRequest = std::make_unique(urlBuilder.Build(), Size); imageRequest->Start(); } @@ -45,7 +53,7 @@ void AvatarButton::Draw(const Point& screenPos) { Graphics * g = GetGraphics(); - if(avatar) + if (avatar) { auto *tex = avatar.get(); g->BlendImage(tex->Data(), 255, RectSized(screenPos, tex->Size())); diff --git a/src/gui/interface/AvatarButton.h b/src/gui/interface/AvatarButton.h index 3f1734f1c..7b9d67f44 100644 --- a/src/gui/interface/AvatarButton.h +++ b/src/gui/interface/AvatarButton.h @@ -3,7 +3,6 @@ #include "Component.h" #include "graphics/Graphics.h" -#include "gui/interface/Colour.h" #include "client/http/ImageRequest.h" #include @@ -15,6 +14,7 @@ class AvatarButton : public Component { std::unique_ptr avatar; ByteString name; + int avatarSize; bool tried; struct AvatarButtonAction @@ -26,7 +26,7 @@ class AvatarButton : public Component std::unique_ptr imageRequest; public: - AvatarButton(Point position, Point size, ByteString username); + AvatarButton(Point position, Point size, ByteString username, int avatarSize = 0); virtual ~AvatarButton() = default; void OnMouseClick(int x, int y, unsigned int button) override; @@ -46,6 +46,6 @@ public: ByteString GetUsername() { return name; } inline void SetActionCallback(AvatarButtonAction const &action) { actionCallback = action; }; protected: - bool isMouseInside, isButtonDown; + bool isMouseInside = false, isButtonDown = false; }; } diff --git a/src/gui/interface/Separator.cpp b/src/gui/interface/Separator.cpp new file mode 100644 index 000000000..c71ffec0f --- /dev/null +++ b/src/gui/interface/Separator.cpp @@ -0,0 +1,12 @@ +#include "Separator.h" +#include "graphics/Graphics.h" + +namespace ui +{ + +void Separator::Draw(const ui::Point& screenPos) +{ + GetGraphics()->BlendRect(RectSized(screenPos, Size), 0xFFFFFF_rgb .WithAlpha(180)); +} + +} \ No newline at end of file diff --git a/src/gui/interface/Separator.h b/src/gui/interface/Separator.h new file mode 100644 index 000000000..95d36a36b --- /dev/null +++ b/src/gui/interface/Separator.h @@ -0,0 +1,16 @@ +#pragma once +#include "Component.h" + +namespace ui +{ + +class Separator : public Component +{ +public: + Separator(ui::Point position, ui::Point size) : Component(position, size) + { } + + void Draw(const ui::Point& screenPos) override; +}; + +} \ No newline at end of file diff --git a/src/gui/interface/meson.build b/src/gui/interface/meson.build index 35997e327..4f5a68eff 100644 --- a/src/gui/interface/meson.build +++ b/src/gui/interface/meson.build @@ -23,4 +23,5 @@ powder_files += files( 'AvatarButton.cpp', 'RichLabel.cpp', 'SaveButton.cpp', + 'Separator.cpp', ) diff --git a/src/gui/meson.build b/src/gui/meson.build index bcee43e58..226aea1da 100644 --- a/src/gui/meson.build +++ b/src/gui/meson.build @@ -4,6 +4,7 @@ gui_files = files( subdir('colourpicker') subdir('console') +subdir('credits') subdir('dialogues') subdir('elementsearch') subdir('filebrowser') diff --git a/src/gui/options/OptionsView.cpp b/src/gui/options/OptionsView.cpp index b1a9e8968..0fcf2d665 100644 --- a/src/gui/options/OptionsView.cpp +++ b/src/gui/options/OptionsView.cpp @@ -10,6 +10,7 @@ #include "simulation/ElementDefs.h" #include "simulation/SimulationSettings.h" #include "client/Client.h" +#include "gui/credits/Credits.h" #include "gui/dialogues/ConfirmPrompt.h" #include "gui/dialogues/InformationMessage.h" #include "gui/interface/Button.h" @@ -17,6 +18,7 @@ #include "gui/interface/DropDown.h" #include "gui/interface/Engine.h" #include "gui/interface/Label.h" +#include "gui/interface/Separator.h" #include "gui/interface/Textbox.h" #include "gui/interface/DirectionSelector.h" #include "PowderToySDL.h" @@ -41,19 +43,7 @@ OptionsView::OptionsView() : ui::Window(ui::Point(-1, -1), ui::Point(320, 340)) AddComponent(label); } - class Separator : public ui::Component - { - public: - Separator(ui::Point position, ui::Point size) : Component(position, size){} - virtual ~Separator(){} - - void Draw(const ui::Point& screenPos) override - { - GetGraphics()->BlendRect(RectSized(screenPos, Size), 0xFFFFFF_rgb .WithAlpha(180)); - } - }; - - Separator *tmpSeparator = new Separator(ui::Point(0, 22), ui::Point(Size.X, 1)); + auto *tmpSeparator = new ui::Separator(ui::Point(0, 22), ui::Point(Size.X, 1)); AddComponent(tmpSeparator); scrollPanel = new ui::ScrollPanel(ui::Point(1, 23), ui::Point(Size.X-2, Size.Y-39)); @@ -103,7 +93,7 @@ OptionsView::OptionsView() : ui::Window(ui::Point(-1, -1), ui::Point(320, 340)) }; auto addSeparator = [this, ¤tY]() { currentY += 6; - auto *separator = new Separator(ui::Point(0, currentY), ui::Point(Size.X, 1)); + auto *separator = new ui::Separator(ui::Point(0, currentY), ui::Point(Size.X, 1)); scrollPanel->AddChild(separator); currentY += 11; }; @@ -180,7 +170,7 @@ OptionsView::OptionsView() : ui::Window(ui::Point(-1, -1), ui::Point(320, 340)) tempLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle; AddComponent(tempLabel); - Separator * tempSeparator = new Separator(ui::Point(0, 22), ui::Point(Size.X, 1)); + auto * tempSeparator = new ui::Separator(ui::Point(0, 22), ui::Point(Size.X, 1)); AddComponent(tempSeparator); labelValues = new ui::Label(ui::Point(0, (radius * 5 / 2) + 37), ui::Point(Size.X, 16), String::Build(Format::Precision(1), "X:", x, " Y:", y, " Total:", std::hypot(x, y))); @@ -364,6 +354,22 @@ OptionsView::OptionsView() : ui::Window(ui::Point(-1, -1), ui::Point(320, 340)) } currentY += 26; } + + { + addSeparator(); + + auto *creditsButton = new ui::Button(ui::Point(10, currentY), ui::Point(90, 16), "Credits"); + creditsButton->SetActionCallback({ [this] { + auto *credits = new Credits(); + ui::Engine::Ref().ShowWindow(credits); + } }); + scrollPanel->AddChild(creditsButton); + + addLabel(5, " - Find out who contributed to TPT"); + currentY += 13; + } + + { ui::Button *ok = new ui::Button(ui::Point(0, Size.Y-16), ui::Point(Size.X, 16), "OK"); ok->SetActionCallback({ [this] {