From 59616558f48f30b70545ad9f9e5b9ee51dcd6dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Ker=C3=A4nen?= Date: Wed, 30 Mar 2016 10:13:41 +0300 Subject: [PATCH] UI|Home: Load game title pictures from the WAD files of each game --- .../client/include/resource/idtech1image.h | 61 +++++++++++++++ .../apps/client/src/resource/idtech1image.cpp | 74 +++++++++++++++++++ .../client/src/ui/home/gamecolumnwidget.cpp | 2 +- .../src/ui/home/gamepanelbuttonwidget.cpp | 67 +++++++++++++---- .../apps/client/src/ui/home/homewidget.cpp | 8 +- 5 files changed, 191 insertions(+), 21 deletions(-) create mode 100644 doomsday/apps/client/include/resource/idtech1image.h create mode 100644 doomsday/apps/client/src/resource/idtech1image.cpp diff --git a/doomsday/apps/client/include/resource/idtech1image.h b/doomsday/apps/client/include/resource/idtech1image.h new file mode 100644 index 0000000000..eacac8516f --- /dev/null +++ b/doomsday/apps/client/include/resource/idtech1image.h @@ -0,0 +1,61 @@ +/** @file idtech1image.h + * + * @authors Copyright (c) 2016 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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, see: + * http://www.gnu.org/licenses + */ + +#ifndef DENG_CLIENT_RESOURCE_IDTECH1IMAGE_H +#define DENG_CLIENT_RESOURCE_IDTECH1IMAGE_H + +#include + +/** + * Image that imports its content using Id Tech 1 graphics formats. + */ +class IdTech1Image : public de::Image +{ +public: + enum Format { + Automatic, + RawVGAScreen, + Patch + }; + +public: + /** + * Constructs a new Id Tech 1 image. The Image object gets initialized with the + * RGBA_8888 contents of the image. + * + * @param data Image data. Format detected automatically. + * @param palette RGB palette containing color triplets. The size of the palette + * must be big enough to contain all the color indices used in the + * image data. + */ + IdTech1Image(de::IByteArray const &data, de::IByteArray const &palette, + Format format = Automatic); + + de::Vector2i origin() const; + + /** + * Size of the image data as declared in its metadata. May not match the actual + * image size. + */ + Size nominalSize() const; + +private: + DENG2_PRIVATE(d) +}; + +#endif // DENG_CLIENT_RESOURCE_IDTECH1IMAGE_H diff --git a/doomsday/apps/client/src/resource/idtech1image.cpp b/doomsday/apps/client/src/resource/idtech1image.cpp new file mode 100644 index 0000000000..28e5ce0985 --- /dev/null +++ b/doomsday/apps/client/src/resource/idtech1image.cpp @@ -0,0 +1,74 @@ +/** @file idtech1image.cpp + * + * @authors Copyright (c) 2016 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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, see: + * http://www.gnu.org/licenses + */ + +#include "resource/idtech1image.h" + +#include + +using namespace de; +using namespace res; + +DENG2_PIMPL_NOREF(IdTech1Image) +{ + Vector2i origin; + Size nominalSize; +}; + +IdTech1Image::IdTech1Image(IByteArray const &data, IByteArray const &palette, Format format) + : d(new Instance) +{ + Size const rawSize(320, 200); + + if(format == Automatic) + { + // Try to guess which format the data uses. + if(data.size() == rawSize.x * rawSize.y) + { + format = RawVGAScreen; + } + else + { + format = Patch; + } + } + + if(format == RawVGAScreen) + { + d->nominalSize = rawSize; + Image::operator = (fromIndexedData(rawSize, data, palette)); + } + else + { + auto const metadata = Patch::loadMetadata(data); + d->nominalSize = metadata.logicalDimensions; + d->origin = metadata.origin; + + Image::operator = (Image::fromMaskedIndexedData(metadata.dimensions, + Patch::load(data), palette)); + } +} + +Vector2i IdTech1Image::origin() const +{ + return d->origin; +} + +Image::Size IdTech1Image::nominalSize() const +{ + return d->nominalSize; +} diff --git a/doomsday/apps/client/src/ui/home/gamecolumnwidget.cpp b/doomsday/apps/client/src/ui/home/gamecolumnwidget.cpp index a87255fd5a..9c098b7468 100644 --- a/doomsday/apps/client/src/ui/home/gamecolumnwidget.cpp +++ b/doomsday/apps/client/src/ui/home/gamecolumnwidget.cpp @@ -347,7 +347,7 @@ DENG_GUI_PIMPL(GameColumnWidget) auto *heading = LabelWidget::newWithText(tr("Custom Profiles")); heading->setSizePolicy(ui::Filled, ui::Expand); heading->setFont("heading"); - heading->setTextColor("accent"); + //heading->setTextColor("accent"); heading->setAlignment(ui::AlignLeft); heading->margins().setLeftRight(""); return heading; diff --git a/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp b/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp index 47d8d2b82b..ae7bdabdf9 100644 --- a/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp +++ b/doomsday/apps/client/src/ui/home/gamepanelbuttonwidget.cpp @@ -21,11 +21,15 @@ #include "ui/savedsessionlistdata.h" #include "ui/dialogs/packagesdialog.h" #include "ui/widgets/packagesbuttonwidget.h" +#include "resource/idtech1image.h" #include "dd_main.h" #include -#include -#include +#include +#include +#include +#include + #include #include #include @@ -43,12 +47,16 @@ DENG_GUI_PIMPL(GamePanelButtonWidget) PackagesButtonWidget *packagesButton; ButtonWidget *playButton; ButtonWidget *deleteSaveButton; + res::LumpCatalog catalog; Instance(Public *i, GameProfile &profile, SavedSessionListData const &savedItems) : Base(i) , gameProfile(profile) , savedItems(savedItems) { + self.icon().setImageFit(ui::FitToHeight | ui::OriginalAspectRatio); + self.icon().setBehavior(ContentClipping); + packagesButton = new PackagesButtonWidget; packagesButton->setDialogTitle(profile.name()); self.addButton(packagesButton); @@ -60,6 +68,10 @@ DENG_GUI_PIMPL(GamePanelButtonWidget) StringList pkgs; for(auto const &i : ids) pkgs << i; gameProfile.setPackages(pkgs); + if(catalog.setPackages(gameProfile.allRequiredPackages())) + { + updateGameTitleImage(); + } }); playButton = new ButtonWidget; @@ -113,20 +125,6 @@ DENG_GUI_PIMPL(GamePanelButtonWidget) } } - /// Action that deletes a savegame folder. - struct DeleteAction : public Action - { - GamePanelButtonWidget *widget; - String savePath; - DeleteAction(GamePanelButtonWidget *wgt, String const &path) - : widget(wgt), savePath(path) {} - void trigger() { - widget->unselectSave(); - App::rootFolder().removeFile(savePath); - App::fileSystem().refresh(); - } - }; - void deleteButtonPressed() { DENG2_ASSERT(saves->selectedPos() != ui::Data::InvalidPos); @@ -151,6 +149,39 @@ DENG_GUI_PIMPL(GamePanelButtonWidget) pop->open(); } + void updateGameTitleImage() + { + if(!game().isPlayable()) + { + // Use a generic logo, some files are missing. + QImage img(64, 64, QImage::Format_ARGB32); + img.fill(Qt::white); + self.icon().setImage(img); + return; + } + + try + { + 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); + + // Apply VGA aspect correction while downscaling 50%. + self.icon().setImage(img.toQImage().scaled(img.width()/2, + img.height()/2 * 1.2f, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + } + catch(Error const &er) + { + LOG_RES_WARNING("Failed to load title picture for game profile \"%s\": %s") + << gameProfile.name() + << er.asText(); + } + } + //- ChildWidgetOrganizer::IFilter --------------------------------------------- bool isItemAccepted(ChildWidgetOrganizer const &, @@ -217,6 +248,10 @@ void GamePanelButtonWidget::updateContent() .arg(meta)); d->packagesButton->setPackages(d->gameProfile.packages()); + if(d->catalog.setPackages(d->gameProfile.allRequiredPackages())) + { + d->updateGameTitleImage(); + } } void GamePanelButtonWidget::unselectSave() diff --git a/doomsday/apps/client/src/ui/home/homewidget.cpp b/doomsday/apps/client/src/ui/home/homewidget.cpp index b62a4130bf..1cdc2fcb2d 100644 --- a/doomsday/apps/client/src/ui/home/homewidget.cpp +++ b/doomsday/apps/client/src/ui/home/homewidget.cpp @@ -154,7 +154,7 @@ DENG_GUI_PIMPL(HomeWidget) { // Edge navigation buttons are only visible when hoving on them. button.set(Background(style().colors().colorf("text"))); - button.setImageColor(style().colors().colorf("inverted.text")); + button.setImageColor(style().colors().colorf("inverted.text") + Vector4f(0, 0, 0, 2)); button.setOverrideImageSize(style().fonts().font("default").height().value() * 2); button.setOpacity(0); button.setBehavior(Widget::Focusable, UnsetFlags); // only for the mouse @@ -162,7 +162,7 @@ DENG_GUI_PIMPL(HomeWidget) button.audienceForStateChange() += this; button.rule() - .setInput(Rule::Width, style().fonts().font("default").height() * 2) + .setInput(Rule::Width, style().fonts().font("default").height() * 1.5f) .setInput(Rule::Bottom, self.rule().bottom()) .setInput(Rule::Top, tabs->rule().bottom()); } @@ -172,11 +172,11 @@ DENG_GUI_PIMPL(HomeWidget) // Hide navigation buttons when they are not being used. if(state == ButtonWidget::Down) { - button.setOpacity(.4f); + button.setOpacity(.3f); } else { - button.setOpacity(state == ButtonWidget::Up? 0 : .2f, 0.25); + button.setOpacity(state == ButtonWidget::Up? 0 : .1f, 0.25); } }