diff --git a/src/common/PNGLibrary.cxx b/src/common/PNGLibrary.cxx index db7b17ffc..6612000de 100644 --- a/src/common/PNGLibrary.cxx +++ b/src/common/PNGLibrary.cxx @@ -38,7 +38,7 @@ PNGLibrary::PNGLibrary(OSystem& osystem) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void PNGLibrary::loadImage(const string& filename, FBSurface& surface) +void PNGLibrary::loadImage(const string& filename, FBSurface& surface, VariantList& comments) { png_structp png_ptr{nullptr}; png_infop info_ptr{nullptr}; @@ -114,6 +114,9 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface) // We're finished reading png_read_end(png_ptr, info_ptr); + // Read the comments we got + readComments(png_ptr, info_ptr, comments); + // Load image into the surface, setting the correct dimensions loadImagetoSurface(surface); @@ -335,6 +338,7 @@ void PNGLibrary::takeSnapshot(uInt32 number) // Some text fields to add to the PNG snapshot VariantList comments; ostringstream version; + VarList::push_back(comments, "Title", "Snapshot"); version << "Stella " << STELLA_VERSION << " (Build " << STELLA_BUILD << ") [" << BSPF::ARCH << "]"; VarList::push_back(comments, "Software", version.str()); @@ -448,6 +452,23 @@ void PNGLibrary::writeComments(const png_structp png_ptr, png_infop info_ptr, png_set_text(png_ptr, info_ptr, text_ptr.data(), numComments); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void PNGLibrary::readComments(const png_structp png_ptr, png_infop info_ptr, + VariantList& comments) +{ + png_textp text_ptr; + int numComments = 0; + + // TODO: currently works only if comments are *before* the image data + png_get_text(png_ptr, info_ptr, &text_ptr, &numComments); + + comments.clear(); + for(int i = 0; i < numComments; ++i) + { + VarList::push_back(comments, text_ptr[i].key, text_ptr[i].text); + } +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void PNGLibrary::png_read_data(const png_structp ctx, png_bytep area, png_size_t size) { diff --git a/src/common/PNGLibrary.hxx b/src/common/PNGLibrary.hxx index 6770e7671..9d4d7f8a4 100644 --- a/src/common/PNGLibrary.hxx +++ b/src/common/PNGLibrary.hxx @@ -52,7 +52,7 @@ class PNGLibrary runtime_error is thrown containing a more detailed error message. */ - void loadImage(const string& filename, FBSurface& surface); + void loadImage(const string& filename, FBSurface& surface, VariantList& comments); /** Save the current FrameBuffer image to a PNG file. Note that in most @@ -183,6 +183,12 @@ class PNGLibrary void writeComments(const png_structp png_ptr, png_infop info_ptr, const VariantList& comments); + /** + Read PNG tEXt chunks from the image. + */ + void readComments(const png_structp png_ptr, png_infop info_ptr, + VariantList& comments); + /** PNG library callback functions */ static void png_read_data(const png_structp ctx, png_bytep area, png_size_t size); static void png_write_data(const png_structp ctx, png_bytep area, png_size_t size); diff --git a/src/common/bspf.hxx b/src/common/bspf.hxx index 1e77ae7d2..403f8a436 100644 --- a/src/common/bspf.hxx +++ b/src/common/bspf.hxx @@ -319,6 +319,65 @@ namespace BSPF return false; } + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inline size_t matchWithJoker(const string& str, const string& pattern) + { + if(str.length() >= pattern.length()) + { + // optimize a bit + if(pattern.find('?') != string::npos) + { + for(size_t pos = 0; pos < str.length() - pattern.length() + 1; ++pos) + { + bool found = true; + + for(size_t i = 0; found && i < pattern.length(); ++i) + if(pattern[i] != str[pos + i] && pattern[i] != '?') + found = false; + + if(found) + return pos; + } + } + else + return str.find(pattern); + } + return string::npos; + } + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + inline bool matchWithWildcards(const string& str, const string& pattern) + { + string pat = pattern; + + // remove leading and trailing '*' + size_t i = 0; + while(pat[i++] == '*'); + pat = pat.substr(i - 1); + + i = pat.length(); + while(pat[--i] == '*'); + pat.erase(i + 1); + + // Search for first '*' + const size_t pos = pat.find('*'); + + if(pos != string::npos) + { + // '*' found, split pattern into left and right part, search recursively + const string leftPat = pat.substr(0, pos); + const string rightPat = pat.substr(pos + 1); + const size_t posLeft = matchWithJoker(str, leftPat); + + if(posLeft != string::npos) + return matchWithWildcards(str.substr(pos + posLeft), rightPat); + else + return false; + } + // no further '*' found + return matchWithJoker(str, pat) != string::npos; + } + // Modify 'str', replacing all occurrences of 'from' with 'to' inline void replaceAll(string& str, const string& from, const string& to) { diff --git a/src/emucore/Bankswitch.cxx b/src/emucore/Bankswitch.cxx index c5aa628b0..6e618d8b3 100644 --- a/src/emucore/Bankswitch.cxx +++ b/src/emucore/Bankswitch.cxx @@ -97,13 +97,13 @@ Bankswitch::BSList = {{ { "AUTO" , "Auto-detect" }, { "0840" , "0840 (8K EconoBanking)" }, { "0FA0" , "0FA0 (8K Fotomania)" }, - { "2IN1" , "2IN1 Multicart (4-64K)" }, - { "4IN1" , "4IN1 Multicart (8-64K)" }, - { "8IN1" , "8IN1 Multicart (16-64K)" }, - { "16IN1" , "16IN1 Multicart (32-128K)" }, - { "32IN1" , "32IN1 Multicart (64/128K)" }, - { "64IN1" , "64IN1 Multicart (128/256K)" }, - { "128IN1" , "128IN1 Multicart (256/512K)" }, + { "2IN1" , "2in1 Multicart (4-64K)" }, + { "4IN1" , "4in1 Multicart (8-64K)" }, + { "8IN1" , "8in1 Multicart (16-64K)" }, + { "16IN1" , "16in1 Multicart (32-128K)" }, + { "32IN1" , "32in1 Multicart (64/128K)" }, + { "64IN1" , "64in1 Multicart (128/256K)" }, + { "128IN1" , "128in1 Multicart (256/512K)" }, { "2K" , "2K (32-2048 bytes Atari)" }, { "3E" , "3E (Tigervision, 32K RAM)" }, { "3EX" , "3EX (Tigervision, 256K RAM)" }, diff --git a/src/gui/LauncherDialog.cxx b/src/gui/LauncherDialog.cxx index 128f2eee2..10c36d4e1 100644 --- a/src/gui/LauncherDialog.cxx +++ b/src/gui/LauncherDialog.cxx @@ -45,10 +45,10 @@ #include "StellaKeys.hxx" #include "Props.hxx" #include "PropsSet.hxx" +#include "RomImageWidget.hxx" #include "RomInfoWidget.hxx" #include "TIAConstants.hxx" #include "Settings.hxx" -#include "Widget.hxx" #include "Font.hxx" #include "StellaFont.hxx" #include "ConsoleBFont.hxx" @@ -305,8 +305,8 @@ void LauncherDialog::addRomWidgets(int ypos) const int listHeight = _h - ypos - VBORDER - buttonHeight - VGAP * 3; const float imgZoom = getRomInfoZoom(listHeight); - const int romWidth = imgZoom * TIAConstants::viewableWidth; - const int listWidth = _w - (romWidth > 0 ? romWidth + fontWidth : 0) - HBORDER * 2; + const int imageWidth = imgZoom * TIAConstants::viewableWidth; + const int listWidth = _w - (imageWidth > 0 ? imageWidth + fontWidth : 0) - HBORDER * 2; // remember initial ROM directory for returning there via home button instance().settings().setValue("startromdir", getRomDir()); @@ -319,7 +319,7 @@ void LauncherDialog::addRomWidgets(int ypos) wid.push_back(myList); // Add ROM info area (if enabled) - if(romWidth > 0) + if(imageWidth > 0) { xpos += myList->getWidth() + fontWidth; @@ -327,12 +327,22 @@ void LauncherDialog::addRomWidgets(int ypos) const Common::Size imgSize(TIAConstants::viewableWidth * imgZoom, TIAConstants::viewableHeight * imgZoom); // Calculate font area, and in the process the font that can be used - const Common::Size fontArea(romWidth - fontWidth * 2, - myList->getHeight() - imgSize.h - VGAP * 3); + // Infofont is unknown yet, but used in image label too. Assuming maximum font height. + int imageHeight = imgSize.h + RomImageWidget::labelHeight(_font); + + const Common::Size fontArea(imageWidth - fontWidth * 2, + myList->getHeight() - imageHeight - VGAP * 4); setRomInfoFont(fontArea); + + // Now we have the correct font height + imageHeight = imgSize.h + RomImageWidget::labelHeight(*myROMInfoFont); + myRomImageWidget = new RomImageWidget(this, *myROMInfoFont, + xpos, ypos, imageWidth, imageHeight); + + const int yofs = imageHeight + VGAP * 2; myRomInfoWidget = new RomInfoWidget(this, *myROMInfoFont, - xpos, ypos, romWidth, myList->getHeight(), imgSize); + xpos, ypos + yofs, imageWidth, myList->getHeight() - yofs); } addToFocusList(wid); } @@ -503,7 +513,10 @@ void LauncherDialog::loadConfig() Dialog::setFocus(getFocusList()[mySelectedItem]); if(myRomInfoWidget) + { + myRomImageWidget->reloadProperties(currentNode()); myRomInfoWidget->reloadProperties(currentNode()); + } myList->clearFlags(Widget::FLAG_WANTS_RAWDATA); // always reset this } @@ -716,14 +729,20 @@ void LauncherDialog::setRomInfoFont(const Common::Size& area) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void LauncherDialog::loadRomInfo() { - if(!myRomInfoWidget) + if(!myRomImageWidget || !myROMInfoFont) return; const string& md5 = selectedRomMD5(); if(md5 != EmptyString) + { + myRomImageWidget->setProperties(currentNode(), md5); myRomInfoWidget->setProperties(currentNode(), md5); + } else + { + myRomImageWidget->clearProperties(); myRomInfoWidget->clearProperties(); + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -860,6 +879,14 @@ void LauncherDialog::handleKeyDown(StellaKey key, StellaMod mod, bool repeated) reload(); break; + case KBDK_LEFT: + myRomImageWidget->changeImage(-1); + break; + + case KBDK_RIGHT: + myRomImageWidget->changeImage(1); + break; + default: handled = false; break; diff --git a/src/gui/LauncherDialog.hxx b/src/gui/LauncherDialog.hxx index 289fa0f6e..193860676 100644 --- a/src/gui/LauncherDialog.hxx +++ b/src/gui/LauncherDialog.hxx @@ -27,6 +27,7 @@ class Properties; class EditTextWidget; class NavigationWidget; class LauncherFileListWidget; +class RomImageWidget; class RomInfoWidget; class StaticTextWidget; @@ -213,6 +214,7 @@ class LauncherDialog : public Dialog, CommandSender ButtonWidget* myOptionsButton{nullptr}; ButtonWidget* myQuitButton{nullptr}; + RomImageWidget* myRomImageWidget{nullptr}; RomInfoWidget* myRomInfoWidget{nullptr}; std::unordered_map myMD5List; diff --git a/src/gui/RomImageWidget.cxx b/src/gui/RomImageWidget.cxx new file mode 100644 index 000000000..ea8e5a4a4 --- /dev/null +++ b/src/gui/RomImageWidget.cxx @@ -0,0 +1,307 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2022 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "EventHandler.hxx" +#include "FrameBuffer.hxx" +#include "Dialog.hxx" +#include "FBSurface.hxx" +#include "Font.hxx" +#include "OSystem.hxx" +#include "PNGLibrary.hxx" +#include "Props.hxx" +#include "PropsSet.hxx" +#include "bspf.hxx" +#include "RomImageWidget.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +RomImageWidget::RomImageWidget(GuiObject* boss, const GUI::Font& font, + int x, int y, int w, int h) + : Widget(boss, font, x, y, w, h), + CommandSender(boss) +{ + _flags = Widget::FLAG_ENABLED | Widget::FLAG_TRACK_MOUSE; + _bgcolor = kDlgColor; + _bgcolorlo = kBGColorLo; + myImageHeight = _h - labelHeight(font); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RomImageWidget::setProperties(const FSNode& node, const string& md5) +{ + myHaveProperties = true; + + // Make sure to load a per-ROM properties entry, if one exists + instance().propSet().loadPerROM(node, md5); + + // And now get the properties for this ROM + instance().propSet().getMD5(md5, myProperties); + + // Decide whether the information should be shown immediately + if(instance().eventHandler().state() == EventHandlerState::LAUNCHER) + parseProperties(node); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RomImageWidget::clearProperties() +{ + myHaveProperties = mySurfaceIsValid = false; + if(mySurface) + mySurface->setVisible(mySurfaceIsValid); + + // Decide whether the information should be shown immediately + if(instance().eventHandler().state() == EventHandlerState::LAUNCHER) + setDirty(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RomImageWidget::reloadProperties(const FSNode& node) +{ + // The ROM may have changed since we were last in the browser, either + // by saving a different image or through a change in video renderer, + // so we reload the properties + if(myHaveProperties) + parseProperties(node); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RomImageWidget::parseProperties(const FSNode& node) +{ + if(myNavSurface == nullptr) + { + // Create navigation surface + myNavSurface = instance().frameBuffer().allocateSurface( + _w, myImageHeight); + + FBSurface::Attributes& attr = myNavSurface->attributes(); + + attr.blending = true; + attr.blendalpha = 60; + myNavSurface->applyAttributes(); + } + + // Check if a surface has ever been created; if so, we use it + // The surface will always be the maximum size, but sometimes we'll + // only draw certain parts of it + if(mySurface == nullptr) + { + mySurface = instance().frameBuffer().allocateSurface( + _w, myImageHeight, ScalingInterpolation::blur); + mySurface->applyAttributes(); + + dialog().addRenderCallback([this]() { + if(mySurfaceIsValid) + { + mySurface->render(); + if(isHighlighted()) + myNavSurface->render(); + } + }); + } + + // Initialize to empty properties entry + mySurfaceErrorMsg = ""; + mySurfaceIsValid = false; + +#ifdef PNG_SUPPORT + // TODO: RETRON_77 + + // Get a valid filename representing a snapshot file for this rom and load the snapshot + const string& path = instance().snapshotLoadDir().getPath(); + + myImageList.clear(); + myImageIdx = 0; + // 1. Try to load snapshots by property name + if(getImageList(path + myProperties.get(PropType::Cart_Name))) + mySurfaceIsValid = loadPng(myImageList[0].getPath()); + + // 2. Also try to load snapshot images by filename + if(getImageList(path + node.getNameWithExt(""))) + mySurfaceIsValid = loadPng(myImageList[0].getPath()); + + if(!mySurfaceIsValid) + { + // 3. If no ROM snapshots exist, try to load a default snapshot + mySurfaceIsValid = loadPng(path + "default_snapshot.png"); + } + +#else + mySurfaceErrorMsg = "PNG image loading not supported"; +#endif + if(mySurface) + mySurface->setVisible(mySurfaceIsValid); + + setDirty(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool RomImageWidget::changeImage(int direction) +{ + if(direction == -1 && myImageIdx) + return loadPng(myImageList[--myImageIdx].getPath()); + else if(direction == 1 && myImageIdx < myImageList.size() - 1) + return loadPng(myImageList[++myImageIdx].getPath()); + + return false; +} + +#ifdef PNG_SUPPORT +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool RomImageWidget::getImageList(const string& filename) +{ + FSNode::NameFilter filter = ([&](const FSNode& node) { + return (!node.isDirectory() && + (node.getPath() == filename + ".png" || + BSPF::matchWithWildcards(node.getPath(), filename + "#*.png"))); + }); + + FSNode node(instance().snapshotLoadDir().getPath()); + + node.getChildren(myImageList, FSNode::ListMode::FilesOnly, filter, false, false); + return myImageList.size() > 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool RomImageWidget::loadPng(const string& filename) +{ + try + { + VariantList comments; + instance().png().loadImage(filename, *mySurface, comments); + + // Scale surface to available image area + const Common::Rect& src = mySurface->srcRect(); + const float scale = std::min(float(_w) / src.w(), float(myImageHeight) / src.h()) * + instance().frameBuffer().hidpiScaleFactor(); + mySurface->setDstSize(static_cast(src.w() * scale), static_cast(src.h() * scale)); + + // Retrieve label for loaded image + myLabel = ""; + for(auto comment = comments.begin(); comment != comments.end(); ++comment) + { + if(comment->first == "Title") + { + myLabel = comment->second.toString(); + break; + } + if(comment->first == "Software" + && comment->second.toString().find("Stella") == 0) + myLabel = "Snapshot"; // default for Stella snapshots with missing "Title" comment + } + + setDirty(); + return true; + } + catch(const runtime_error& e) + { + mySurfaceErrorMsg = e.what(); + } + return false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RomImageWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount) +{ + if(isEnabled() && x >= 0 && x < _w && y >= 0 && y < myImageHeight) + changeImage(x < _w / 2 ? 1 : -1); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RomImageWidget::handleMouseMoved(int x, int y) +{ + if(x < _w / 2 != myMouseX < _w / 2) + setDirty(); + myMouseX = x; +} +#endif + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RomImageWidget::drawWidget(bool hilite) +{ + FBSurface& s = dialog().surface(); + const int yoff = myImageHeight; + + s.fillRect(_x+1, _y+1, _w-2, _h-1, _bgcolor); + s.frameRect(_x, _y, _w, myImageHeight, kColor); + + if(!myHaveProperties) + { + clearDirty(); + return; + } + + if(mySurfaceIsValid) + { + const Common::Rect& dst = mySurface->dstRect(); + const uInt32 scale = instance().frameBuffer().hidpiScaleFactor(); + const uInt32 x = _x * scale + ((_w * scale - dst.w()) >> 1); + const uInt32 y = _y * scale + ((myImageHeight * scale - dst.h()) >> 1); + + // Make sure when positioning the snapshot surface that we take + // the dialog surface position into account + const Common::Rect& s_dst = s.dstRect(); + mySurface->setDstPos(x + s_dst.x(), y + s_dst.y()); + + // Draw the image label and counter + ostringstream buf; + buf << myImageIdx + 1 << "/" << myImageList.size(); + const int yText = _y + myImageHeight + _font.getFontHeight() / 8; + const int wText = _font.getStringWidth(buf.str()); + + if(myLabel.length()) + s.drawString(_font, myLabel, _x, yText, _w - wText - _font.getMaxCharWidth() * 2, _textcolor); + if(myImageList.size()) + s.drawString(_font, buf.str(), _x + _w - wText, yText, wText, _textcolor); + + // Draw the navigation arrows + const bool leftArrow = myMouseX < _w / 2; + + myNavSurface->invalidate(); + if(isHighlighted() && + ((leftArrow && myImageIdx) || (!leftArrow && myImageIdx < myImageList.size() - 1)) || true) + { + const int w = _w / 64; + const int w2 = 1; // w / 2; + const int ax = leftArrow ? _w / 12 - w / 2 : _w - _w / 12 - w / 2; + const int ay = myImageHeight >> 1; + const int dx = (_w / 32) * (leftArrow ? 1 : -1); + const int dy = myImageHeight / 16; + + for(int i = 0; i < w; ++i) + { + myNavSurface->line(ax + dx + i + w2, ay - dy, ax + i + w2, ay, kBGColor); + myNavSurface->line(ax + dx + i + w2, ay + dy, ax + i + w2, ay, kBGColor); + myNavSurface->line(ax + dx + i, ay - dy + w2, ax + i, ay + w2, kBGColor); + myNavSurface->line(ax + dx + i, ay + dy + w2, ax + i, ay + w2, kBGColor); + myNavSurface->line(ax + dx + i + w2, ay - dy + w2, ax + i + w2, ay + w2, kBGColor); + myNavSurface->line(ax + dx + i + w2, ay + dy + w2, ax + i + w2, ay + w2, kBGColor); + } + for(int i = 0; i < w; ++i) + { + myNavSurface->line(ax + dx + i, ay - dy, ax + i, ay, kColorInfo); + myNavSurface->line(ax + dx + i, ay + dy, ax + i, ay, kColorInfo); + } + myNavSurface->setDstRect(mySurface->dstRect()); + } + } + else if(mySurfaceErrorMsg != "") + { + const uInt32 x = _x + ((_w - _font.getStringWidth(mySurfaceErrorMsg)) >> 1); + const uInt32 y = _y + ((yoff - _font.getLineHeight()) >> 1); + s.drawString(_font, mySurfaceErrorMsg, x, y, _w - 10, _textcolor); + } + clearDirty(); +} diff --git a/src/gui/RomImageWidget.hxx b/src/gui/RomImageWidget.hxx new file mode 100644 index 000000000..0d5b9e4dd --- /dev/null +++ b/src/gui/RomImageWidget.hxx @@ -0,0 +1,101 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2022 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef ROM_IMAGE_WIDGET_HXX +#define ROM_IMAGE_WIDGET_HXX + +class FBSurface; +class Properties; + +#include "Widget.hxx" +#include "bspf.hxx" + +class RomImageWidget : public Widget, public CommandSender +{ + public: + RomImageWidget(GuiObject *boss, const GUI::Font& font, + int x, int y, int w, int h); + ~RomImageWidget() override = default; + + static int labelHeight(const GUI::Font& font) + { + return font.getFontHeight() * 9 / 8; + } + + void setProperties(const FSNode& node, const string& md5); + void clearProperties(); + void reloadProperties(const FSNode& node); + bool changeImage(int direction = 1); + + protected: + void drawWidget(bool hilite) override; +#ifdef PNG_SUPPORT + void handleMouseUp(int x, int y, MouseButton b, int clickCount) override; + void handleMouseMoved(int x, int y) override; +#endif + + private: + void parseProperties(const FSNode& node); + #ifdef PNG_SUPPORT + bool getImageList(const string& filename); + bool loadPng(const string& filename); + #endif + + private: + // Surface pointer holding the PNG image + shared_ptr mySurface; + + // Surface pointer holding the navigation elements + shared_ptr myNavSurface; + + // Whether the surface should be redrawn by drawWidget() + bool mySurfaceIsValid{false}; + + // The properties for the currently selected ROM + Properties myProperties; + + // Indicates if the current properties should actually be used + bool myHaveProperties{false}; + + // Indicates if an error occurred in creating/displaying the surface + string mySurfaceErrorMsg; + + // Height of the image area + int myImageHeight{0}; + + // Contains the list of image names for the current ROM + FSList myImageList; + + // Index of currently displayed image + int myImageIdx{0}; + + // Current x-position of the mouse + int myMouseX{0}; + + // Label for the loaded image + string myLabel; + + private: + // Following constructors and assignment operators not supported + RomImageWidget() = delete; + RomImageWidget(const RomImageWidget&) = delete; + RomImageWidget(RomImageWidget&&) = delete; + RomImageWidget& operator=(const RomImageWidget&) = delete; + RomImageWidget& operator=(RomImageWidget&&) = delete; +}; + +#endif diff --git a/src/gui/RomInfoWidget.cxx b/src/gui/RomInfoWidget.cxx index 4ead37fb3..3638b19dd 100644 --- a/src/gui/RomInfoWidget.cxx +++ b/src/gui/RomInfoWidget.cxx @@ -24,21 +24,15 @@ #include "ControllerDetector.hxx" #include "Bankswitch.hxx" #include "CartDetector.hxx" -#include "Logger.hxx" #include "Props.hxx" -#include "PNGLibrary.hxx" #include "PropsSet.hxx" -#include "Rect.hxx" -#include "Widget.hxx" #include "RomInfoWidget.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RomInfoWidget::RomInfoWidget(GuiObject* boss, const GUI::Font& font, - int x, int y, int w, int h, - const Common::Size& imgSize) + int x, int y, int w, int h) : Widget(boss, font, x, y, w, h), - CommandSender(boss), - myAvail{imgSize} + CommandSender(boss) { _flags = Widget::FLAG_ENABLED; _bgcolor = kDlgColor; @@ -64,9 +58,7 @@ void RomInfoWidget::setProperties(const FSNode& node, const string& md5) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void RomInfoWidget::clearProperties() { - myHaveProperties = mySurfaceIsValid = false; - if(mySurface) - mySurface->setVisible(mySurfaceIsValid); + myHaveProperties = false; // Decide whether the information should be shown immediately if(instance().eventHandler().state() == EventHandlerState::LAUNCHER) @@ -87,51 +79,9 @@ void RomInfoWidget::reloadProperties(const FSNode& node) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void RomInfoWidget::parseProperties(const FSNode& node) { - // Check if a surface has ever been created; if so, we use it - // The surface will always be the maximum size, but sometimes we'll - // only draw certain parts of it - if(mySurface == nullptr) - { - mySurface = instance().frameBuffer().allocateSurface( - myAvail.w, myAvail.h, ScalingInterpolation::blur); - mySurface->applyAttributes(); - - dialog().addRenderCallback([this]() { - if(mySurfaceIsValid) - mySurface->render(); - } - ); - } - // Initialize to empty properties entry - mySurfaceErrorMsg = ""; - mySurfaceIsValid = false; myRomInfo.clear(); -#ifdef PNG_SUPPORT - // Get a valid filename representing a snapshot file for this rom and load the snapshot - const string& path = instance().snapshotLoadDir().getPath(); - - // 1. Try to load snapshot by property name - mySurfaceIsValid = loadPng(path + myProperties.get(PropType::Cart_Name) + ".png"); - - if(!mySurfaceIsValid) - { - // 2. If no snapshot with property name exists, try to load snapshot image by filename - mySurfaceIsValid = loadPng(path + node.getNameWithExt("") + ".png"); - - if(!mySurfaceIsValid) - { - // 3. If no ROM snapshot exists, try to load a default snapshot - mySurfaceIsValid = loadPng(path + "default_snapshot.png"); - } - } -#else - mySurfaceErrorMsg = "PNG image loading not supported"; -#endif - if(mySurface) - mySurface->setVisible(mySurfaceIsValid); - myUrl = myProperties.get(PropType::Cart_Url); // Now add some info for the message box below the image @@ -207,36 +157,13 @@ void RomInfoWidget::parseProperties(const FSNode& node) setDirty(); } -#ifdef PNG_SUPPORT -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool RomInfoWidget::loadPng(const string& filename) -{ - try - { - instance().png().loadImage(filename, *mySurface); - - // Scale surface to available image area - const Common::Rect& src = mySurface->srcRect(); - const float scale = std::min(float(myAvail.w) / src.w(), float(myAvail.h) / src.h()) * - instance().frameBuffer().hidpiScaleFactor(); - mySurface->setDstSize(static_cast(src.w() * scale), static_cast(src.h() * scale)); - - return true; - } - catch(const runtime_error& e) - { - mySurfaceErrorMsg = e.what(); - } - return false; -} -#endif - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void RomInfoWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount) { - if(isEnabled() && x >= 0 && x < _w && y >= 0 && y < _h) + if(isEnabled() && isHighlighted() + && x >= 0 && x < _w && y >= 0 && y < _h) { - clearFlags(Widget::FLAG_HILITED); + clearFlags(Widget::FLAG_HILITED); // avoid double clicks and opened URLs sendCommand(kClickedCmd, 0, _id); } } @@ -245,11 +172,9 @@ void RomInfoWidget::handleMouseUp(int x, int y, MouseButton b, int clickCount) void RomInfoWidget::drawWidget(bool hilite) { FBSurface& s = dialog().surface(); - const int yoff = myAvail.h + _font.getFontHeight() / 2; s.fillRect(_x+2, _y+2, _w-4, _h-4, _bgcolor); - s.frameRect(_x, _y, _w, myAvail.h, kColor); - s.frameRect(_x, _y+yoff, _w, _h-yoff, kColor); + s.frameRect(_x, _y, _w, _h, kColor); if(!myHaveProperties) { @@ -257,27 +182,8 @@ void RomInfoWidget::drawWidget(bool hilite) return; } - if(mySurfaceIsValid) - { - const Common::Rect& dst = mySurface->dstRect(); - const uInt32 scale = instance().frameBuffer().hidpiScaleFactor(); - const uInt32 x = _x * scale + ((_w * scale - dst.w()) >> 1); - const uInt32 y = _y * scale + ((myAvail.h * scale - dst.h()) >> 1); - - // Make sure when positioning the snapshot surface that we take - // the dialog surface position into account - const Common::Rect& s_dst = s.dstRect(); - mySurface->setDstPos(x + s_dst.x(), y + s_dst.y()); - } - else if(mySurfaceErrorMsg != "") - { - const uInt32 x = _x + ((_w - _font.getStringWidth(mySurfaceErrorMsg)) >> 1); - const uInt32 y = _y + ((yoff - _font.getLineHeight()) >> 1); - s.drawString(_font, mySurfaceErrorMsg, x, y, _w - 10, _textcolor); - } - const int xpos = _x + 8; - int ypos = _y + yoff + 5; + int ypos = _y + 5; for(const auto& info : myRomInfo) { if(info.length() * _font.getMaxCharWidth() <= static_cast(_w - 16)) diff --git a/src/gui/RomInfoWidget.hxx b/src/gui/RomInfoWidget.hxx index fc3b449e6..06cb977c9 100644 --- a/src/gui/RomInfoWidget.hxx +++ b/src/gui/RomInfoWidget.hxx @@ -20,9 +20,6 @@ class FBSurface; class Properties; -namespace Common { - struct Size; -} #include "Widget.hxx" #include "bspf.hxx" @@ -36,8 +33,7 @@ class RomInfoWidget : public Widget, public CommandSender public: RomInfoWidget(GuiObject *boss, const GUI::Font& font, - int x, int y, int w, int h, - const Common::Size& imgSize); + int x, int y, int w, int h); ~RomInfoWidget() override = default; void setProperties(const FSNode& node, const string& md5); @@ -52,17 +48,8 @@ class RomInfoWidget : public Widget, public CommandSender private: void parseProperties(const FSNode& node); - #ifdef PNG_SUPPORT - bool loadPng(const string& filename); - #endif private: - // Surface pointer holding the PNG image - shared_ptr mySurface; - - // Whether the surface should be redrawn by drawWidget() - bool mySurfaceIsValid{false}; - // Some ROM properties info, as well as 'tEXt' chunks from the PNG image StringList myRomInfo; @@ -75,12 +62,6 @@ class RomInfoWidget : public Widget, public CommandSender // Optional cart link URL string myUrl; - // Indicates if an error occurred in creating/displaying the surface - string mySurfaceErrorMsg; - - // How much space available for the PNG image - Common::Size myAvail; - private: // Following constructors and assignment operators not supported RomInfoWidget() = delete; diff --git a/src/gui/module.mk b/src/gui/module.mk index a31c859b7..f8e33c993 100644 --- a/src/gui/module.mk +++ b/src/gui/module.mk @@ -47,6 +47,7 @@ MODULE_OBJS := \ src/gui/R77HelpDialog.o \ src/gui/RadioButtonWidget.o \ src/gui/RomAuditDialog.o \ + src/gui/RomImageWidget.o \ src/gui/RomInfoWidget.o \ src/gui/ScrollBarWidget.o \ src/gui/SnapshotDialog.o \ diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj index 1fd59827a..e889ba1d6 100755 --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -929,6 +929,7 @@ + @@ -2146,6 +2147,7 @@ + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index d44dbcaa5..7e3371379 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -1146,6 +1146,9 @@ Source Files\debugger + + Source Files\gui + @@ -2372,6 +2375,9 @@ Header Files\debugger + + Header Files\gui +