From 9ea4679739b38819306f0ebf02fdac449f0c4402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= Date: Sun, 15 Jan 2017 11:49:57 +0200 Subject: [PATCH] UI|Home: Starting a game with package; show WAD title picture in dialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Any package can now be easily used in any profile via the package info dialog. The “Play in…” button uses an ad-hoc game profile. If a WAD contains a TITLE/TITLEPIC lump, and the appropriate game can be identified, the title picture is shown in the package info dialog. --- .../client/include/resource/idtech1image.h | 31 ++++++ .../include/ui/widgets/homeitemwidget.h | 26 ----- .../apps/client/src/resource/idtech1image.cpp | 51 ++++++++++ .../client/src/ui/dialogs/packagesdialog.cpp | 5 +- .../src/ui/home/gamepanelbuttonwidget.cpp | 2 +- .../ui/home/multiplayerpanelbuttonwidget.cpp | 3 +- .../client/src/ui/widgets/homeitemwidget.cpp | 41 -------- .../src/ui/widgets/packageinfodialog.cpp | 94 +++++++++++++++---- .../include/doomsday/doomsdayapp.h | 2 + .../include/doomsday/gameprofiles.h | 2 + doomsday/apps/libdoomsday/src/doomsdayapp.cpp | 6 ++ .../apps/libdoomsday/src/gameprofiles.cpp | 11 +++ 12 files changed, 184 insertions(+), 90 deletions(-) diff --git a/doomsday/apps/client/include/resource/idtech1image.h b/doomsday/apps/client/include/resource/idtech1image.h index eacac8516f..ebc4d9ab4f 100644 --- a/doomsday/apps/client/include/resource/idtech1image.h +++ b/doomsday/apps/client/include/resource/idtech1image.h @@ -20,6 +20,9 @@ #define DENG_CLIENT_RESOURCE_IDTECH1IMAGE_H #include +#include + +class Game; /** * Image that imports its content using Id Tech 1 graphics formats. @@ -54,8 +57,36 @@ class IdTech1Image : public de::Image */ Size nominalSize() const; +public: + enum LogoFlag + { + UnmodifiedAppearance = 0, + ColorizedByFamily = 0x1, + Downscale50Percent = 0x2, + NullImageIfFails = 0x4, // by default returns a small fallback image + + DefaultLogoFlags = ColorizedByFamily | Downscale50Percent, + }; + Q_DECLARE_FLAGS(LogoFlags, LogoFlag) + + /** + * Prepares a game logo image to be used in items. The image is based on the + * game's title screen image in its WAD file(s). + * + * @param game Game. + * @param catalog Catalog of selected lumps. + * + * @return Image. + * + * @todo This could be moved to a better location / other class. -jk + */ + static de::Image makeGameLogo(Game const &game, res::LumpCatalog const &catalog, + LogoFlags flags = DefaultLogoFlags); + private: DENG2_PRIVATE(d) }; +Q_DECLARE_OPERATORS_FOR_FLAGS(IdTech1Image::LogoFlags) + #endif // DENG_CLIENT_RESOURCE_IDTECH1IMAGE_H diff --git a/doomsday/apps/client/include/ui/widgets/homeitemwidget.h b/doomsday/apps/client/include/ui/widgets/homeitemwidget.h index 09887c60d1..4b447f3d90 100644 --- a/doomsday/apps/client/include/ui/widgets/homeitemwidget.h +++ b/doomsday/apps/client/include/ui/widgets/homeitemwidget.h @@ -75,31 +75,6 @@ class HomeItemWidget : public de::GuiWidget, public de::IAssetGroup virtual void itemRightClicked(); -public: - enum LogoFlag - { - UnmodifiedAppearance = 0, - ColorizedByFamily = 0x1, - Downscale50Percent = 0x2, - - DefaultLogoFlags = ColorizedByFamily | Downscale50Percent, - }; - Q_DECLARE_FLAGS(LogoFlags, LogoFlag) - - /** - * Prepares a game logo image to be used in items. The image is based on the - * game's title screen image in its WAD file(s). - * - * @param game Game. - * @param catalog Catalog of selected lumps. - * - * @return Image. - * - * @todo This could be moved to a better location / other class. -jk - */ - static de::Image makeGameLogo(Game const &game, res::LumpCatalog const &catalog, - LogoFlags flags = DefaultLogoFlags); - signals: void mouseActivity(); void doubleClicked(); @@ -112,6 +87,5 @@ class HomeItemWidget : public de::GuiWidget, public de::IAssetGroup }; Q_DECLARE_OPERATORS_FOR_FLAGS(HomeItemWidget::Flags) -Q_DECLARE_OPERATORS_FOR_FLAGS(HomeItemWidget::LogoFlags) #endif // DENG_CLIENT_UI_HOME_HOMEITEMWIDGET_H diff --git a/doomsday/apps/client/src/resource/idtech1image.cpp b/doomsday/apps/client/src/resource/idtech1image.cpp index e20fce708c..faf8c28b30 100644 --- a/doomsday/apps/client/src/resource/idtech1image.cpp +++ b/doomsday/apps/client/src/resource/idtech1image.cpp @@ -19,6 +19,9 @@ #include "resource/idtech1image.h" #include +#include + +#include using namespace de; using namespace res; @@ -72,3 +75,51 @@ Image::Size IdTech1Image::nominalSize() const { return d->nominalSize; } + +Image IdTech1Image::makeGameLogo(Game const &game, + res::LumpCatalog const &catalog, + LogoFlags flags) +{ + try + { + if (game.isPlayable()) + { + Block const playPal = catalog.read("PLAYPAL"); + Block const title = catalog.read("TITLE"); + Block const titlePic = catalog.read("TITLEPIC"); + + IdTech1Image img(title.isEmpty()? titlePic : title, playPal); + + float const scaleFactor = flags.testFlag(Downscale50Percent)? .5f : 1.f; + Image::Size const finalSize(img.width() * scaleFactor, + img.height() * scaleFactor * 1.2f); // VGA aspect + + Image logoImage(img.toQImage().scaled(finalSize.x, finalSize.y, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + if (flags & ColorizedByFamily) + { + String const colorId = "home.icon." + + (game.family().isEmpty()? "other" : game.family()); + return logoImage.colorized(Style::get().colors().color(colorId)); + } + return logoImage; + } + } + catch (Error const &er) + { + if (flags & NullImageIfFails) return Image(); + + LOG_RES_WARNING("Failed to load title picture for game \"%s\": %s") + << game.title() + << er.asText(); + } + if (flags & NullImageIfFails) + { + return Image(); + } + // Use a generic logo, some files are missing. + QImage img(64, 64, QImage::Format_ARGB32); + img.fill(Qt::black); + return img; +} diff --git a/doomsday/apps/client/src/ui/dialogs/packagesdialog.cpp b/doomsday/apps/client/src/ui/dialogs/packagesdialog.cpp index b0b732bf6e..cb5c733ad4 100644 --- a/doomsday/apps/client/src/ui/dialogs/packagesdialog.cpp +++ b/doomsday/apps/client/src/ui/dialogs/packagesdialog.cpp @@ -21,6 +21,7 @@ #include "ui/widgets/homeitemwidget.h" #include "ui/widgets/homemenuwidget.h" #include "ui/widgets/packageinfodialog.h" +#include "resource/idtech1image.h" #include "ui/clientwindow.h" #include "clientapp.h" @@ -277,8 +278,8 @@ DENG_GUI_PIMPL(PackagesDialog) { if (game && catalog.setPackages(requiredPackages + selectedPackages)) { - gameTitle->setImage(HomeItemWidget::makeGameLogo(*game, catalog, - HomeItemWidget::UnmodifiedAppearance)); + gameTitle->setImage(IdTech1Image::makeGameLogo(*game, catalog, + IdTech1Image::UnmodifiedAppearance)); // List of the native required files. StringList dataFiles; for (String packageId : requiredPackages) diff --git a/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp b/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp index 1297f0fbf2..f4d3ab09e1 100644 --- a/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp +++ b/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp @@ -238,7 +238,7 @@ DENG_GUI_PIMPL(GamePanelButtonWidget) void updateGameTitleImage() { - self().icon().setImage(self().makeGameLogo(game(), catalog)); + self().icon().setImage(IdTech1Image::makeGameLogo(game(), catalog)); } void profileChanged(Profiles::AbstractProfile &) diff --git a/doomsday/apps/client/src/ui/home/multiplayerpanelbuttonwidget.cpp b/doomsday/apps/client/src/ui/home/multiplayerpanelbuttonwidget.cpp index ec51ca9813..f6c7f1b5cc 100644 --- a/doomsday/apps/client/src/ui/home/multiplayerpanelbuttonwidget.cpp +++ b/doomsday/apps/client/src/ui/home/multiplayerpanelbuttonwidget.cpp @@ -20,6 +20,7 @@ #include "ui/clientwindow.h" #include "network/net_main.h" #include "network/serverlink.h" +#include "resource/idtech1image.h" #include "clientapp.h" #include "dd_main.h" @@ -218,7 +219,7 @@ void MultiplayerPanelButtonWidget::updateContent(shell::ServerInfo const &info) /// @todo The server info should include the list of packages. if (d->catalog.setPackages(game.requiredPackages())) { - icon().setImage(makeGameLogo(game, d->catalog)); + icon().setImage(IdTech1Image::makeGameLogo(game, d->catalog)); } } else diff --git a/doomsday/apps/client/src/ui/widgets/homeitemwidget.cpp b/doomsday/apps/client/src/ui/widgets/homeitemwidget.cpp index 5ec0d56464..d6041bb687 100644 --- a/doomsday/apps/client/src/ui/widgets/homeitemwidget.cpp +++ b/doomsday/apps/client/src/ui/widgets/homeitemwidget.cpp @@ -389,47 +389,6 @@ void HomeItemWidget::focusLost() void HomeItemWidget::itemRightClicked() {} -Image HomeItemWidget::makeGameLogo(Game const &game, res::LumpCatalog const &catalog, - LogoFlags flags) -{ - try - { - if (game.isPlayable()) - { - Block const playPal = catalog.read("PLAYPAL"); - Block const title = catalog.read("TITLE"); - Block const titlePic = catalog.read("TITLEPIC"); - - IdTech1Image img(title.isEmpty()? titlePic : title, playPal); - - float const scaleFactor = flags.testFlag(Downscale50Percent)? .5f : 1.f; - Image::Size const finalSize(img.width() * scaleFactor, - img.height() * scaleFactor * 1.2f); // VGA aspect - - Image logoImage(img.toQImage().scaled(finalSize.x, finalSize.y, - Qt::IgnoreAspectRatio, - Qt::SmoothTransformation)); - if (flags.testFlag(ColorizedByFamily)) - { - String const colorId = "home.icon." + - (game.family().isEmpty()? "other" : game.family()); - return logoImage.colorized(Style::get().colors().color(colorId)); - } - return logoImage; - } - } - catch (Error const &er) - { - LOG_RES_WARNING("Failed to load title picture for game \"%s\": %s") - << game.title() - << er.asText(); - } - // Use a generic logo, some files are missing. - QImage img(64, 64, QImage::Format_ARGB32); - img.fill(Qt::black); - return img; -} - void HomeItemWidget::addButton(GuiWidget *widget) { // Common styling. diff --git a/doomsday/apps/client/src/ui/widgets/packageinfodialog.cpp b/doomsday/apps/client/src/ui/widgets/packageinfodialog.cpp index db2a5bf6a4..62edaa86f3 100644 --- a/doomsday/apps/client/src/ui/widgets/packageinfodialog.cpp +++ b/doomsday/apps/client/src/ui/widgets/packageinfodialog.cpp @@ -18,10 +18,13 @@ #include "ui/widgets/packageinfodialog.h" #include "ui/widgets/packagecontentoptionswidget.h" +#include "resource/idtech1image.h" +#include "dd_main.h" #include #include #include +#include #include #include @@ -31,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +54,7 @@ DENG_GUI_PIMPL(PackageInfoDialog) LabelWidget *metaInfo; IndirectRule *targetHeight; String packageId; + String compatibleGame; // guessed NativePath nativePath; SafeWidgetPtr configurePopup; SafeWidgetPtr profileMenu; @@ -133,6 +138,39 @@ DENG_GUI_PIMPL(PackageInfoDialog) *targetHeight); } + void setPackageIcon(Image const &iconImage) + { + icon->setImage(iconImage); + icon->setImageColor(Vector4f(1, 1, 1, 1)); + icon->setImageFit(ui::FitToHeight | ui::OriginalAspectRatio); + icon->setImageScale(1); + icon->setBehavior(ContentClipping, true); + } + + bool useGameTitlePicture(DataBundle const &bundle) + { + auto *lumpDir = bundle.lumpDirectory(); + if (!lumpDir || (!lumpDir->has("TITLEPIC") && !lumpDir->has("TITLE"))) + { + return false; + } + + Game const &game = Games::get()[compatibleGame]; + + res::LumpCatalog catalog; + catalog.setPackages(game.requiredPackages() + StringList({ packageId })); + Image img = IdTech1Image::makeGameLogo(game, + catalog, + IdTech1Image::NullImageIfFails | + IdTech1Image::UnmodifiedAppearance); + if (!img.isNull()) + { + setPackageIcon(img); + return true; + } + return false; + } + void useDefaultIcon() { icon->setStyleImage("package.large"); @@ -158,11 +196,7 @@ DENG_GUI_PIMPL(PackageInfoDialog) throw Error("PackageInfoDialog::useIconFile", "Icon file " + img->description() + " is too large (max 512x512)"); } - icon->setImage(iconImage); - icon->setImageColor(Vector4f(1, 1, 1, 1)); - icon->setImageFit(ui::FitToHeight | ui::OriginalAspectRatio); - icon->setImageScale(1); - icon->setBehavior(ContentClipping, true); + setPackageIcon(iconImage); return; } } @@ -193,8 +227,13 @@ DENG_GUI_PIMPL(PackageInfoDialog) String format; if (DataBundle const *bundle = file->target().maybeAs()) { - format = bundle->formatAsText().upperFirstChar(); - useDefaultIcon(); + format = bundle->formatAsText().upperFirstChar(); + compatibleGame = bundle->guessCompatibleGame(); + + if (!useGameTitlePicture(*bundle)) + { + useDefaultIcon(); + } if (bundle->format() == DataBundle::Collection) { @@ -238,7 +277,15 @@ DENG_GUI_PIMPL(PackageInfoDialog) metaInfo->setText(metaMsg); // Description text. - String msg = "Description of the package."; + String msg; + if (compatibleGame.isEmpty()) + { + msg = "Not enough information to determine which game this package is for."; + } + else + { + msg = String("This package is probably meant for %1.").arg(compatibleGame); + } if (meta.has("notes")) { @@ -341,6 +388,10 @@ DENG_GUI_PIMPL(PackageInfoDialog) { label = _E(C) + label + _E(.) " " _E(s)_E(b)_E(D) + tr("ADDED"); } + else if (!compatibleGame.isEmpty() && prof->gameId() == compatibleGame) + { + label = _E(1) + label; + } items << new ui::ActionItem(label, new CallbackAction([this, prof] () { @@ -356,18 +407,23 @@ DENG_GUI_PIMPL(PackageInfoDialog) { switch (menuMode) { - case AddToProfile: { - StringList pkgs = profile.packages(); - if (!pkgs.contains(packageId)) - { - pkgs << packageId; - profile.setPackages(pkgs); - } - break; } - - case PlayInProfile: - + case AddToProfile: + profile.appendPackage(packageId); break; + + case PlayInProfile: { + auto &prof = DoomsdayApp::app().adhocProfile(); + prof = profile; + prof.appendPackage(packageId); + Loop::timer(0.1, [] { + // Switch the game. + GLWindow::main().glActivate(); + BusyMode_FreezeGameForBusyMode(); + DoomsdayApp::app().changeGame(DoomsdayApp::app().adhocProfile(), + DD_ActivateGameWorker); + }); + self().accept(); + break; } } } }; diff --git a/doomsday/apps/libdoomsday/include/doomsday/doomsdayapp.h b/doomsday/apps/libdoomsday/include/doomsday/doomsdayapp.h index a975da1e4c..2ca274f303 100644 --- a/doomsday/apps/libdoomsday/include/doomsday/doomsdayapp.h +++ b/doomsday/apps/libdoomsday/include/doomsday/doomsdayapp.h @@ -129,6 +129,8 @@ class LIBDOOMSDAY_PUBLIC DoomsdayApp void setDoomsdayBasePath(de::NativePath const &path); std::string const &doomsdayBasePath() const; + GameProfile &adhocProfile(); + /** * Checks if the currently loaded packages are compatible with the provided list. * If the user does not cancel, and the correct packages can be loaded, calls the diff --git a/doomsday/apps/libdoomsday/include/doomsday/gameprofiles.h b/doomsday/apps/libdoomsday/include/doomsday/gameprofiles.h index 7b1cb12f69..b71693539e 100644 --- a/doomsday/apps/libdoomsday/include/doomsday/gameprofiles.h +++ b/doomsday/apps/libdoomsday/include/doomsday/gameprofiles.h @@ -49,6 +49,8 @@ class LIBDOOMSDAY_PUBLIC GameProfiles : public de::Profiles void setUserCreated(bool userCreated); void setUseGameRequirements(bool useGameRequirements); + bool appendPackage(de::String const &id); + de::String gameId() const; Game &game() const; de::StringList packages() const; diff --git a/doomsday/apps/libdoomsday/src/doomsdayapp.cpp b/doomsday/apps/libdoomsday/src/doomsdayapp.cpp index 628916d0bd..18c9b9e296 100644 --- a/doomsday/apps/libdoomsday/src/doomsdayapp.cpp +++ b/doomsday/apps/libdoomsday/src/doomsdayapp.cpp @@ -83,6 +83,7 @@ DENG2_PIMPL(DoomsdayApp) Plugins plugins; Games games; Game *currentGame = nullptr; + GameProfile adhocProfile; GameProfile const *currentProfile = nullptr; StringList preGamePackages; GameProfiles gameProfiles; @@ -619,6 +620,11 @@ std::string const &DoomsdayApp::doomsdayBasePath() const return d->ddBasePath; } +GameProfile &DoomsdayApp::adhocProfile() +{ + return d->adhocProfile; +} + void DoomsdayApp::setDoomsdayBasePath(NativePath const &path) { NativePath cleaned = App::commandLine().startupPath() / path; // In case it's relative. diff --git a/doomsday/apps/libdoomsday/src/gameprofiles.cpp b/doomsday/apps/libdoomsday/src/gameprofiles.cpp index 5300233e3f..405a937d49 100644 --- a/doomsday/apps/libdoomsday/src/gameprofiles.cpp +++ b/doomsday/apps/libdoomsday/src/gameprofiles.cpp @@ -235,6 +235,17 @@ void GameProfiles::Profile::setUseGameRequirements(bool useGameRequirements) } } +bool GameProfiles::Profile::appendPackage(String const &id) +{ + if (!d->packages.contains(id)) + { + d->packages << id; + notifyChange(); + return true; + } + return false; +} + Game &GameProfiles::Profile::game() const { return DoomsdayApp::games()[d->gameId];