diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bbd185..86d2b62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -501,8 +501,21 @@ endif() ## OBS Studio - Frontend/Qt if(HAVE_OBS_FRONTEND) list(APPEND PROJECT_UI + "ui/streamfx.qrc" + "ui/about.ui" + "ui/about-entry.ui" ) list(APPEND PROJECT_PRIVATE_SOURCE + "source/ui/ui-common.hpp" + "source/ui/ui.hpp" + "source/ui/ui.cpp" + "source/ui/ui-about.hpp" + "source/ui/ui-about.cpp" + "source/ui/ui-about-entry.hpp" + "source/ui/ui-about-entry.cpp" + ) + list(APPEND PROJECT_DATA + ) list(APPEND PROJECT_INCLUDE_DIRS "source/ui" diff --git a/source/plugin.cpp b/source/plugin.cpp index abf50cf..1224d81 100644 --- a/source/plugin.cpp +++ b/source/plugin.cpp @@ -18,6 +18,7 @@ */ #include "plugin.hpp" +#include #include #include "configuration.hpp" #include "obs/obs-source-tracker.hpp" @@ -62,6 +63,10 @@ #include "transitions/transition-shader.hpp" #endif +#ifdef ENABLE_FRONTEND +#include "ui/ui.hpp" +#endif + static std::shared_ptr _threadpool; MODULE_EXPORT bool obs_module_load(void) @@ -133,6 +138,11 @@ try { #endif } + // Frontend +#ifdef ENABLE_FRONTEND + streamfx::ui::handler::initialize(); +#endif + LOG_INFO("Loaded Version %s", STREAMFX_VERSION_STRING); return true; } catch (...) { @@ -144,6 +154,11 @@ MODULE_EXPORT void obs_module_unload(void) try { LOG_INFO("Unloading Version %s", STREAMFX_VERSION_STRING); + // Frontend +#ifdef ENABLE_FRONTEND + streamfx::ui::handler::finalize(); +#endif + // Transitions { using namespace streamfx::transition; diff --git a/source/ui/ui-about-entry.cpp b/source/ui/ui-about-entry.cpp new file mode 100644 index 0000000..aba1a69 --- /dev/null +++ b/source/ui/ui-about-entry.cpp @@ -0,0 +1,130 @@ +/* + * Modern effects for a modern Streamer + * Copyright (C) 2020 Michael Fabian Dirks + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "ui-about-entry.hpp" + +constexpr std::string_view i18n_role_contributor = "UI.About.Role.Contributor"; +constexpr std::string_view i18n_role_translator = "UI.About.Role.Translator"; +constexpr std::string_view i18n_role_family = "UI.About.Role.Family"; +constexpr std::string_view i18n_role_friend = "UI.About.Role.Friend"; +constexpr std::string_view i18n_role_supporter_patreon = "UI.About.Role.Supporter.Patreon"; +constexpr std::string_view i18n_role_supporter_github = "UI.About.Role.Supporter.Github"; +constexpr std::string_view i18n_role_supporter_twitch = "UI.About.Role.Supporter.Twitch"; +constexpr std::string_view i18n_role_creator = "UI.About.Role.Creator"; + +streamfx::ui::about_entry::about_entry(QWidget* parent, streamfx::ui::about::entry& entry) + : QWidget(parent), _last_click(std::chrono::high_resolution_clock::now()), _link1_url(), _link2_url() +{ + setupUi(this); + + name->setText(QString::fromStdString(entry.name)); + switch (entry.role) { + case streamfx::ui::about::role_type::NONE: + title->setText(QString::fromStdString(entry.role_custom)); + break; + case streamfx::ui::about::role_type::CONTRIBUTOR: + title->setText(D_TRANSLATE(i18n_role_contributor.data())); + break; + case streamfx::ui::about::role_type::TRANSLATOR: + title->setText(D_TRANSLATE(i18n_role_translator.data())); + break; + case streamfx::ui::about::role_type::FAMILY: { + const char* txt = D_TRANSLATE(i18n_role_family.data()); + std::vector buf(2048); + snprintf(buf.data(), buf.size(), txt, entry.role_custom.c_str()); + title->setText(QString::fromUtf8(buf.data())); + break; + } + case streamfx::ui::about::role_type::FRIEND: { + const char* txt = D_TRANSLATE(i18n_role_friend.data()); + std::vector buf(2048); + snprintf(buf.data(), buf.size(), txt, entry.role_custom.c_str()); + title->setText(QString::fromUtf8(buf.data())); + break; + } + case streamfx::ui::about::role_type::PATREON_SUPPORTER: + title->setText(D_TRANSLATE(i18n_role_supporter_patreon.data())); + break; + case streamfx::ui::about::role_type::GITHUB_SUPPORTER: + title->setText(D_TRANSLATE(i18n_role_supporter_github.data())); + break; + case streamfx::ui::about::role_type::TWITCH_SUPPORTER: + title->setText(D_TRANSLATE(i18n_role_supporter_twitch.data())); + break; + case streamfx::ui::about::role_type::CREATOR: + title->setText(D_TRANSLATE(i18n_role_creator.data())); + break; + } + + std::tuple els[]{ + {entry.link1_type, entry.link1_address, entry.link1_text, link1, _link1_url}, + {entry.link2_type, entry.link2_address, entry.link2_text, link2, _link2_url}, + }; + for (auto el : els) { + switch (std::get<0>(el)) { + case streamfx::ui::about::link_type::NONE: + std::get<3>(el)->setHidden(true); + break; + case streamfx::ui::about::link_type::GENERIC: + std::get<3>(el)->setIcon(QIcon(":/linktype/generic")); + break; + case streamfx::ui::about::link_type::SOCIAL_TWITCH: + std::get<3>(el)->setIcon(QIcon(":/linktype/twitch")); + break; + case streamfx::ui::about::link_type::SOCIAL_YOUTUBE: + std::get<3>(el)->setIcon(QIcon(":/linktype/youtube")); + break; + case streamfx::ui::about::link_type::SOCIAL_DISCORD: + std::get<3>(el)->setIcon(QIcon(":/linktype/discord")); + break; + case streamfx::ui::about::link_type::SOCIAL_TWITTER: + std::get<3>(el)->setIcon(QIcon(":/linktype/twitter")); + break; + case streamfx::ui::about::link_type::SOCIAL_FACEBOOK: + std::get<3>(el)->setIcon(QIcon(":/linktype/facebook")); + break; + } + std::get<3>(el)->setText(QString::fromUtf8(std::get<2>(el).c_str())); + std::get<4>(el).setUrl(QString::fromStdString(std::get<1>(el))); + } + connect(link1, &QPushButton::pressed, this, &streamfx::ui::about_entry::on_link1_clicked); + connect(link2, &QPushButton::pressed, this, &streamfx::ui::about_entry::on_link2_clicked); + + // Don't free up space when hidden. + /*if (!(link1->isVisible() || link2->isVisible())) { + QSizePolicy qsp = link1->sizePolicy(); + qsp.setRetainSizeWhenHidden(true); + link1->setSizePolicy(qsp); + }*/ +} + +streamfx::ui::about_entry::~about_entry() {} + +void streamfx::ui::about_entry::on_link1_clicked() +{ + // FIXME! Button clicks twice? + for (size_t attempt = 0; (attempt < 10) && (!QDesktopServices::openUrl(_link1_url)); attempt++) { + } +} + +void streamfx::ui::about_entry::on_link2_clicked() +{ + for (size_t attempt = 0; (attempt < 10) && (!QDesktopServices::openUrl(_link2_url)); attempt++) { + } +} diff --git a/source/ui/ui-about-entry.hpp b/source/ui/ui-about-entry.hpp new file mode 100644 index 0000000..6d161ef --- /dev/null +++ b/source/ui/ui-about-entry.hpp @@ -0,0 +1,51 @@ +/* + * Modern effects for a modern Streamer + * Copyright (C) 2020 Michael Fabian Dirks + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#pragma once +#include +#include "ui-about.hpp" +#include "ui-common.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4365 4371 4619 4946) +#endif +#include "ui_about-entry.h" +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace streamfx::ui { + class about_entry : public QWidget, public Ui::AboutEntry { + Q_OBJECT + + private: + std::chrono::high_resolution_clock::time_point _last_click; + QUrl _link1_url; + QUrl _link2_url; + + public: + about_entry(QWidget* parent, ui::about::entry& entry); + ~about_entry(); + + public slots: + void on_link1_clicked(); + void on_link2_clicked(); + }; +} // namespace streamfx::ui diff --git a/source/ui/ui-about.cpp b/source/ui/ui-about.cpp new file mode 100644 index 0000000..662c3f1 --- /dev/null +++ b/source/ui/ui-about.cpp @@ -0,0 +1,204 @@ +/* + * Modern effects for a modern Streamer + * Copyright (C) 2020 Michael Fabian Dirks + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "ui-about.hpp" +#include +#include +#include "ui-about-entry.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4365 4371 4619 4946) +#endif +#include +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +constexpr std::string_view text_social_facebook = "Facebook"; +constexpr std::string_view text_social_twitch = "Twitch"; +constexpr std::string_view text_social_twitter = "Twitter"; +constexpr std::string_view text_social_youtube = "YouTube"; + +static const std::list _entries = { + // Contributers + streamfx::ui::about::entry{"Michael \"Xaymar\" Dirks", streamfx::ui::about::role_type::CONTRIBUTOR, "", + streamfx::ui::about::link_type::GENERIC, "https://xaymar.com", "Blog & Stuff", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://twitch.tv/xaymar", + text_social_twitch.data()}, + + // Translators + streamfx::ui::about::entry{"FrozenNortherner", streamfx::ui::about::role_type::TRANSLATOR, "", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://twitch.tv/frozennortherner", + text_social_twitch.data(), streamfx::ui::about::link_type::NONE, "", ""}, + streamfx::ui::about::entry{"HANAWINS", streamfx::ui::about::role_type::TRANSLATOR, "", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://www.twitch.tv/hanawins", + text_social_twitch.data(), streamfx::ui::about::link_type::NONE, "", ""}, + streamfx::ui::about::entry{"hydargos", streamfx::ui::about::role_type::TRANSLATOR, "", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://www.youtube.com/hydargos", + text_social_youtube.data(), streamfx::ui::about::link_type::NONE, "", ""}, + streamfx::ui::about::entry{"Monsteer", streamfx::ui::about::role_type::TRANSLATOR, "", + streamfx::ui::about::link_type::SOCIAL_TWITTER, "https://twitter.com/cooliguay", + text_social_twitter.data(), streamfx::ui::about::link_type::NONE, "", ""}, + streamfx::ui::about::entry{"Nanito", streamfx::ui::about::role_type::TRANSLATOR, "", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://twitch.tv/nanito", + text_social_twitch.data(), streamfx::ui::about::link_type::SOCIAL_FACEBOOK, + "https://facebook.com/nanitotv", text_social_facebook.data()}, + + // Separator + streamfx::ui::about::entry{"", streamfx::ui::about::role_type::NONE, "", streamfx::ui::about::link_type::NONE, "", + "", streamfx::ui::about::link_type::NONE, "", ""}, + + // Supporters + streamfx::ui::about::entry{"GranDroidTonight", streamfx::ui::about::role_type::PATREON_SUPPORTER, "", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://www.twitch.tv/GranDroidTonight", + text_social_twitch.data(), streamfx::ui::about::link_type::SOCIAL_YOUTUBE, + "https://youtube.com/channel/UCGoT2XFPpeKaL1QuY_NPDuA", text_social_youtube.data()}, + streamfx::ui::about::entry{"chi11estpanda", streamfx::ui::about::role_type::PATREON_SUPPORTER, "", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://www.twitch.tv/chi11estpanda", + "Gaming, Life and Laughs", streamfx::ui::about::link_type::NONE, "", ""}, + streamfx::ui::about::entry{"DandiDoesIt", streamfx::ui::about::role_type::PATREON_SUPPORTER, "", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://www.twitch.tv/DandiDoesIt", + "Twitch Channel", streamfx::ui::about::link_type::NONE, "", ""}, + streamfx::ui::about::entry{"iamresist", streamfx::ui::about::role_type::PATREON_SUPPORTER, "", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://twitch.tv/iamresist", + text_social_twitch.data(), streamfx::ui::about::link_type::NONE, "", ""}, + streamfx::ui::about::entry{"Nordern", streamfx::ui::about::role_type::PATREON_SUPPORTER, "", + streamfx::ui::about::link_type::SOCIAL_YOUTUBE, "https://youtube.com/nordern", + text_social_youtube.data(), streamfx::ui::about::link_type::SOCIAL_TWITCH, + "https://www.twitch.tv/thenordern", text_social_twitch.data()}, + + // Separator + streamfx::ui::about::entry{"", streamfx::ui::about::role_type::NONE, "", streamfx::ui::about::link_type::NONE, "", + "", streamfx::ui::about::link_type::NONE, "", ""}, + + // Family + streamfx::ui::about::entry{"Andrea Stenschke", streamfx::ui::about::role_type::FAMILY, "Xaymar", + streamfx::ui::about::link_type::NONE, "", "", streamfx::ui::about::link_type::NONE, "", + ""}, + streamfx::ui::about::entry{"Carsten Dirks", streamfx::ui::about::role_type::FAMILY, "Xaymar", + streamfx::ui::about::link_type::NONE, "", "", streamfx::ui::about::link_type::NONE, "", + ""}, + streamfx::ui::about::entry{"Christian \"Azekil\" Dirks", streamfx::ui::about::role_type::FAMILY, "Xaymar", + streamfx::ui::about::link_type::NONE, "", "", streamfx::ui::about::link_type::NONE, "", + ""}, + streamfx::ui::about::entry{"Gabriele Rantfl", streamfx::ui::about::role_type::FAMILY, "Xaymar", + streamfx::ui::about::link_type::NONE, "", "", streamfx::ui::about::link_type::NONE, "", + ""}, + streamfx::ui::about::entry{"Reiner Rantfl", streamfx::ui::about::role_type::FAMILY, "Xaymar", + streamfx::ui::about::link_type::NONE, "", "", streamfx::ui::about::link_type::NONE, "", + ""}, + streamfx::ui::about::entry{"René \"Dex\" Dirks", streamfx::ui::about::role_type::FAMILY, "Xaymar", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://twitch.tv/vektordex", + text_social_twitch.data(), streamfx::ui::about::link_type::GENERIC, + "https://worldofdex.de", "Website"}, + + // Friends + streamfx::ui::about::entry{"Axelle", streamfx::ui::about::role_type::FRIEND, "Xaymar", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://www.twitch.tv/axelle123", + text_social_twitch.data(), streamfx::ui::about::link_type::SOCIAL_TWITTER, + "https://twitter.com/AxellesNobody", text_social_twitter.data()}, + + // Separator + streamfx::ui::about::entry{"", streamfx::ui::about::role_type::NONE, "", streamfx::ui::about::link_type::NONE, "", + "", streamfx::ui::about::link_type::NONE, "", ""}, + + // Creators + streamfx::ui::about::entry{"EposVox", streamfx::ui::about::role_type::CREATOR, "", + streamfx::ui::about::link_type::SOCIAL_TWITCH, "https://www.twitch.tv/EposVox", + text_social_twitch.data(), streamfx::ui::about::link_type::SOCIAL_YOUTUBE, + "https://youtube.com/c/EposVox", text_social_youtube.data()}, + +}; + +streamfx::ui::about::about() : QDialog(reinterpret_cast(obs_frontend_get_main_window())) +{ + setupUi(this); + + // Remove some extra styling. + setWindowFlag(Qt::WindowContextHelpButtonHint, false); // Remove the unimplemented help button. + + // Thank every single helper. + bool column_selector = false; + size_t row_selector = 0; + QGridLayout* content_layout = dynamic_cast(content->layout()); + for (auto entry : _entries) { + if (entry.name.size() == 0) { + row_selector += 2; + column_selector = 0; + + // Add a separator line. + auto separator = new QFrame(content); + { + auto sp = separator->sizePolicy(); + sp.setVerticalPolicy(QSizePolicy::Fixed); + separator->setSizePolicy(sp); + } + separator->setFrameShape(QFrame::HLine); + separator->setFrameShadow(QFrame::Sunken); + separator->setMaximumHeight(1); + separator->setMinimumHeight(1); + separator->setFixedHeight(1); + separator->setLineWidth(1); + content_layout->addWidget(separator, static_cast(row_selector - 1), 0, 1, 2); + content_layout->setRowStretch(static_cast(row_selector - 1), 0); + } else { + streamfx::ui::about_entry* v = new streamfx::ui::about_entry(content, entry); + + content_layout->addWidget(v, static_cast(row_selector), column_selector ? 1 : 0); + content_layout->setRowStretch(static_cast(row_selector), 0); + + if (column_selector) { + row_selector++; + } + column_selector = !column_selector; + } + } + { + row_selector++; + auto padder = new QFrame(content); + { + auto sp = padder->sizePolicy(); + sp.setVerticalPolicy(QSizePolicy::Minimum); + sp.setVerticalStretch(1); + padder->setSizePolicy(sp); + } + padder->setObjectName("PaddleMeDaddy"); + padder->setMaximumHeight(QWIDGETSIZE_MAX); + padder->setMinimumHeight(1); + padder->setFrameShape(QFrame::NoFrame); + content_layout->addWidget(padder, static_cast(row_selector), 0, 1, 2); + content_layout->setRowStretch(static_cast(row_selector), 9999); + } + + content_layout->setColumnStretch(0, 1); + content_layout->setColumnStretch(1, 1); + content->setSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Maximum); + + // Make the OK button do things. + connect(buttonBox, &QDialogButtonBox::accepted, this, &streamfx::ui::about::on_ok); +} + +streamfx::ui::about::~about() {} + +void streamfx::ui::about::on_ok() +{ + hide(); +} diff --git a/source/ui/ui-about.hpp b/source/ui/ui-about.hpp new file mode 100644 index 0000000..d3d7c9d --- /dev/null +++ b/source/ui/ui-about.hpp @@ -0,0 +1,83 @@ +/* + * Modern effects for a modern Streamer + * Copyright (C) 2020 Michael Fabian Dirks + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#pragma once +#include "common.hpp" +#include "ui-common.hpp" + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4365 4371 4619 4946) +#endif +#include "ui_about.h" +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +namespace streamfx::ui { + class about : public QDialog, public Ui::About { + Q_OBJECT + + public: + enum class role_type : std::int32_t { + NONE, + CONTRIBUTOR, + TRANSLATOR, + FAMILY, + FRIEND, + PATREON_SUPPORTER, + GITHUB_SUPPORTER, + TWITCH_SUPPORTER, + CREATOR, + }; + enum class link_type : std::int32_t { + NONE, + GENERIC, + + // Social Links + SOCIAL_TWITCH = 2000, + SOCIAL_YOUTUBE, + SOCIAL_DISCORD, + SOCIAL_TWITTER, + SOCIAL_FACEBOOK, + }; + + struct entry { + std::string name; + ui::about::role_type role; + std::string role_custom; + + ui::about::link_type link1_type; + std::string link1_address; + std::string link1_text; + + ui::about::link_type link2_type; + std::string link2_address; + std::string link2_text; + }; + + public: + about(); + ~about(); + + public slots: + ; // Not having this breaks some linters. + void on_ok(); + }; +} // namespace streamfx::ui diff --git a/source/ui/ui-common.hpp b/source/ui/ui-common.hpp new file mode 100644 index 0000000..ee8373c --- /dev/null +++ b/source/ui/ui-common.hpp @@ -0,0 +1,43 @@ +/* + * Modern effects for a modern Streamer + * Copyright (C) 2017 Michael Fabian Dirks + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#pragma once + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251 4365 4371 4619 4946) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/source/ui/ui.cpp b/source/ui/ui.cpp new file mode 100644 index 0000000..18d7f2d --- /dev/null +++ b/source/ui/ui.cpp @@ -0,0 +1,200 @@ +/* + * Modern effects for a modern Streamer + * Copyright (C) 2020 Michael Fabian Dirks + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "ui.hpp" +#include "common.hpp" +#include "strings.hpp" +#include +#include "configuration.hpp" +#include "obs/obs-tools.hpp" +#include "plugin.hpp" + +#include + +// Translation Keys +constexpr std::string_view _i18n_prefix = "StreamFX::"; +constexpr std::string_view _i18n_menu = "UI.Menu"; +constexpr std::string_view _i18n_menu_report_issue = "UI.Menu.ReportIssue"; +constexpr std::string_view _i18n_menu_request_help = "UI.Menu.RequestHelp"; +constexpr std::string_view _i18n_menu_website = "UI.Menu.Website"; +constexpr std::string_view _i18n_menu_discord = "UI.Menu.Discord"; +constexpr std::string_view _i18n_menu_github = "UI.Menu.Github"; +constexpr std::string_view _i18n_menu_about = "UI.Menu.About"; + +// Configuration +constexpr std::string_view _cfg_have_shown_about = "UI.HaveShownAboutStreamFX"; + +// URLs +constexpr std::string_view _url_report_issue = "https://github.com/Xaymar/obs-StreamFX/issues/new?template=issue.md"; +constexpr std::string_view _url_request_help = "https://github.com/Xaymar/obs-StreamFX/issues/new?template=help.md"; +constexpr std::string_view _url_website = "https://streamfx.xaymar.com"; +constexpr std::string_view _url_discord = "https://discordapp.com/invite/DaeJg7M"; +constexpr std::string_view _url_github = "https://github.com/Xaymar/obs-StreamFX"; + +bool streamfx::ui::handler::have_shown_about_streamfx(bool shown) +{ + if (shown) { + obs_data_set_bool(streamfx::configuration::instance()->get().get(), _cfg_have_shown_about.data(), true); + } + if (streamfx::configuration::instance()->is_different_version()) { + return false; + } else { + return obs_data_get_bool(streamfx::configuration::instance()->get().get(), _cfg_have_shown_about.data()); + } +} + +streamfx::ui::handler::handler() + : QObject(), _menu_action(), _menu(), + + _report_issue(), _request_help(), + + _link_website(), _link_discord(), _link_github(), + + _about_action(), _about_dialog() +{ + // Qt Resources and Translators + Q_INIT_RESOURCE(streamfx); + QCoreApplication::installTranslator(new streamfx::ui::translator(this)); + + // Handle all frontend events. + obs_frontend_add_event_callback(frontend_event_handler, this); + + { // Build StreamFX menu. + _menu = new QMenu(reinterpret_cast(obs_frontend_get_main_window())); + + { // Github Issues + //_menu->addSeparator(); + _report_issue = _menu->addAction(QString::fromUtf8(D_TRANSLATE(_i18n_menu_report_issue.data()))); + _request_help = _menu->addAction(QString::fromUtf8(D_TRANSLATE(_i18n_menu_request_help.data()))); + connect(_report_issue, &QAction::triggered, this, &streamfx::ui::handler::on_action_report_issue); + connect(_request_help, &QAction::triggered, this, &streamfx::ui::handler::on_action_request_help); + } + + { // Official Links + _menu->addSeparator(); + _link_website = _menu->addAction(QString::fromUtf8(D_TRANSLATE(_i18n_menu_website.data()))); + _link_discord = _menu->addAction(QString::fromUtf8(D_TRANSLATE(_i18n_menu_discord.data()))); + _link_github = _menu->addAction(QString::fromUtf8(D_TRANSLATE(_i18n_menu_github.data()))); + connect(_link_website, &QAction::triggered, this, &streamfx::ui::handler::on_action_website); + connect(_link_discord, &QAction::triggered, this, &streamfx::ui::handler::on_action_discord); + connect(_link_github, &QAction::triggered, this, &streamfx::ui::handler::on_action_github); + } + + { // About StreamFX + _about_dialog = new streamfx::ui::about(); + _menu->addSeparator(); + _about_action = _menu->addAction(QString::fromUtf8(D_TRANSLATE(_i18n_menu_about.data()))); + connect(_about_action, &QAction::triggered, this, &streamfx::ui::handler::on_action_about); + if (!have_shown_about_streamfx()) { + // Automatically show it if it has not yet been shown. + _about_dialog->show(); + have_shown_about_streamfx(true); + } + } + } +} + +streamfx::ui::handler::~handler() +{ + // Handle all frontend events. + obs_frontend_remove_event_callback(frontend_event_handler, this); + + // Qt Resources and Translators + Q_CLEANUP_RESOURCE(streamfx); +} + +void streamfx::ui::handler::frontend_event_handler(obs_frontend_event event, void* private_data) +{ + streamfx::ui::handler* ptr = reinterpret_cast(private_data); + switch (event) { + case OBS_FRONTEND_EVENT_FINISHED_LOADING: + ptr->on_obs_loaded(); + break; + default: + break; + } +} + +void streamfx::ui::handler::on_obs_loaded() +{ + // Add StreamFX menu. + _menu_action = reinterpret_cast(obs_frontend_add_tools_menu_qaction(D_TRANSLATE(_i18n_menu.data()))); + _menu_action->setMenu(_menu); +} + +void streamfx::ui::handler::on_action_report_issue(bool) +{ + QDesktopServices::openUrl(QUrl(QString::fromUtf8(_url_report_issue.data()))); +} + +void streamfx::ui::handler::on_action_request_help(bool) +{ + QDesktopServices::openUrl(QUrl(QString::fromUtf8(_url_request_help.data()))); +} + +void streamfx::ui::handler::on_action_website(bool) +{ + QDesktopServices::openUrl(QUrl(QString::fromUtf8(_url_website.data()))); +} + +void streamfx::ui::handler::on_action_discord(bool) +{ + QDesktopServices::openUrl(QUrl(QString::fromUtf8(_url_discord.data()))); +} + +void streamfx::ui::handler::on_action_github(bool) +{ + QDesktopServices::openUrl(QUrl(QString::fromUtf8(_url_github.data()))); +} + +void streamfx::ui::handler::on_action_about(bool checked) +{ + _about_dialog->show(); +} + +static std::shared_ptr _handler_singleton; + +void streamfx::ui::handler::initialize() +{ + _handler_singleton = std::make_shared(); +} + +void streamfx::ui::handler::finalize() +{ + _handler_singleton.reset(); +} + +std::shared_ptr streamfx::ui::handler::get() +{ + return _handler_singleton; +} + +streamfx::ui::translator::translator(QObject* parent) {} + +streamfx::ui::translator::~translator() {} + +QString streamfx::ui::translator::translate(const char* context, const char* sourceText, const char* disambiguation, + int n) const +{ + std::string_view sourceView{sourceText}; + if (sourceView.substr(0, _i18n_prefix.length()) == _i18n_prefix) { + return QString::fromUtf8(D_TRANSLATE(sourceView.substr(_i18n_prefix.length()).data())); + } + return QString(); +} diff --git a/source/ui/ui.hpp b/source/ui/ui.hpp new file mode 100644 index 0000000..ce58dfc --- /dev/null +++ b/source/ui/ui.hpp @@ -0,0 +1,82 @@ +/* + * Modern effects for a modern Streamer + * Copyright (C) 2020 Michael Fabian Dirks + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#pragma once +#include "ui-about.hpp" +#include "ui-common.hpp" + +namespace streamfx::ui { + class handler : public QObject { + Q_OBJECT + + private: + QAction* _menu_action; + QMenu* _menu; + + // Bug Report, Help Request + QAction* _report_issue; + QAction* _request_help; + + // Website, Discord, Source + QAction* _link_website; + QAction* _link_discord; + QAction* _link_github; + + // About Dialog + QAction* _about_action; + ui::about* _about_dialog; + + public: + handler(); + ~handler(); + + bool have_shown_about_streamfx(bool shown = false); + + private: + static void frontend_event_handler(obs_frontend_event event, void* private_data); + + void on_obs_loaded(); + + public slots: + ; // Not having this breaks some linters. + void on_action_report_issue(bool); + void on_action_request_help(bool); + void on_action_website(bool); + void on_action_discord(bool); + void on_action_github(bool); + void on_action_about(bool); + + public /* Singleton */: + static void initialize(); + + static void finalize(); + + static std::shared_ptr get(); + }; + + class translator : public QTranslator { + public: + translator(QObject* parent = nullptr); + ~translator(); + + virtual QString translate(const char* context, const char* sourceText, const char* disambiguation = nullptr, + int n = -1) const override; + }; + +} // namespace streamfx::ui diff --git a/ui/about-entry.ui b/ui/about-entry.ui new file mode 100644 index 0000000..432d8e8 --- /dev/null +++ b/ui/about-entry.ui @@ -0,0 +1,243 @@ + + + AboutEntry + + + + 0 + 0 + 195 + 99 + + + + + 0 + 0 + + + + Form + + + QWidget { + background: hsl(0, 0%, 20%); + color: hsl(0, 0%, 100%); +} + +QPushButton, +QPushButton:default, +QPushButton:flat, +QPushButton:checked { + background: hsl(0, 0%, 25%); + border: 0; + padding: 5px; + box-shadow: 0; + cursor: pointer; + padding: 4px; +} + +QWidget[objectName="title"] { + color: hsl(200, 100%, 80%); +} + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 5 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + + 14 + 75 + true + PreferAntialias + + + + Supporter Name + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 10 + + + + Supporter Title + + + Qt::AlignCenter + + + + + + + 5 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 36 + + + + + 16777215 + 36 + + + + + 0 + 36 + + + + + 10 + + + + YouTube + + + + :/social/youtube:/social/youtube + + + + 32 + 32 + + + + + + + + + 0 + 36 + + + + + 16777215 + 36 + + + + + 0 + 36 + + + + + 10 + + + + Twitch + + + + :/social/twitch:/social/twitch + + + + 32 + 32 + + + + + + + + + + + + + + + + diff --git a/ui/about.ui b/ui/about.ui new file mode 100644 index 0000000..35d616e --- /dev/null +++ b/ui/about.ui @@ -0,0 +1,173 @@ + + + About + + + + 0 + 0 + 512 + 512 + + + + + 512 + 512 + + + + + 1024 + 16777215 + + + + StreamFX::UI.About.Title + + + QWidget { + background: transparent; + color: hsl(0, 0%, 100%); +} + +QWidget[objectName="About"], +QWidget[objectName="contentContainer"], +QWidget[objectName="content"] { + background: hsl(0, 0%, 10%); + border: 0; +} + +QWidget[objectName="PaddleMeDaddy"] { + background: hsl(0, 0%, 10%); +} + + + + 5 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + 0 + 0 + + + + + + + :/logos/streamfx_logo + + + false + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 12 + true + + + + <html><head/><body><p>StreamFX is made possible by all the supporters on <a href="https://patreon.com/Xaymar"><span style=" text-decoration: underline; color:#00aaff;">Patreon</span></a>, on <a href="https://github.com/sponsors/xaymar"><span style=" text-decoration: underline; color:#00aaff;">Github Sponsors</span></a>, and anyone donating through <a href="https://paypal.me/Xaymar"><span style=" text-decoration: underline; color:#00aaff;">PayPal</span></a>. Additional thanks go out to all the translators helping out with the localization on <a href="https://crowdin.com/project/obs-stream-effects"><span style=" text-decoration: underline; color:#00aaff;">Crowdin</span></a>. You all are amazing!</p></body></html> + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + 0 + 492 + 282 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 5 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + false + + + + + + + + + + diff --git a/ui/linktype_discord.png b/ui/linktype_discord.png new file mode 100644 index 0000000..f953ffe Binary files /dev/null and b/ui/linktype_discord.png differ diff --git a/ui/linktype_facebook.png b/ui/linktype_facebook.png new file mode 100644 index 0000000..ff15596 Binary files /dev/null and b/ui/linktype_facebook.png differ diff --git a/ui/linktype_generic.png b/ui/linktype_generic.png new file mode 100644 index 0000000..d0cab09 Binary files /dev/null and b/ui/linktype_generic.png differ diff --git a/ui/linktype_twitch.png b/ui/linktype_twitch.png new file mode 100644 index 0000000..32b95a1 Binary files /dev/null and b/ui/linktype_twitch.png differ diff --git a/ui/linktype_twitter.png b/ui/linktype_twitter.png new file mode 100644 index 0000000..ce4173f Binary files /dev/null and b/ui/linktype_twitter.png differ diff --git a/ui/linktype_youtube.png b/ui/linktype_youtube.png new file mode 100644 index 0000000..c66b7d2 Binary files /dev/null and b/ui/linktype_youtube.png differ diff --git a/ui/logo.png b/ui/logo.png new file mode 100644 index 0000000..4a8c683 Binary files /dev/null and b/ui/logo.png differ diff --git a/ui/streamfx.qrc b/ui/streamfx.qrc new file mode 100644 index 0000000..152443d --- /dev/null +++ b/ui/streamfx.qrc @@ -0,0 +1,13 @@ + + + logo.png + + + linktype_discord.png + linktype_facebook.png + linktype_generic.png + linktype_twitch.png + linktype_twitter.png + linktype_youtube.png + +