diff --git a/src/common/FBBackendSDL2.hxx b/src/common/FBBackendSDL2.hxx index 99536924b..bf30f687a 100644 --- a/src/common/FBBackendSDL2.hxx +++ b/src/common/FBBackendSDL2.hxx @@ -273,9 +273,6 @@ class FBBackendSDL2 : public FBBackend // Center setting of current window bool myCenter{false}; - // Flag for bezel mode - bool myShowBezel{false}; - // Does the renderer support render targets? bool myRenderTargetSupport{false}; diff --git a/src/common/PNGLibrary.cxx b/src/common/PNGLibrary.cxx index 37e21732c..421fd9984 100644 --- a/src/common/PNGLibrary.cxx +++ b/src/common/PNGLibrary.cxx @@ -78,19 +78,25 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface, VariantLi // byte into separate bytes (useful for paletted and grayscale images). png_set_packing(png_ptr); - // Only normal RBG(A) images are supported (without the alpha channel) + // Alpha channel is supported if(color_type == PNG_COLOR_TYPE_RGBA) { hasAlpha = true; - //png_set_strip_alpha(png_ptr); } else if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + // TODO: preserve alpha png_set_gray_to_rgb(png_ptr); } else if(color_type == PNG_COLOR_TYPE_PALETTE) { - png_set_palette_to_rgb(png_ptr); + if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + { + png_set_tRNS_to_alpha(png_ptr); + hasAlpha = true; + } + else + png_set_palette_to_rgb(png_ptr); } else if(color_type != PNG_COLOR_TYPE_RGB) { @@ -385,7 +391,7 @@ void PNGLibrary::takeSnapshot(uInt32 number) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool PNGLibrary::allocateStorage(size_t width, size_t height, bool hasAlpha) { - // Create space for the entire image (3 bytes per pixel in RGB format) + // Create space for the entire image (3(4) bytes per pixel in RGB(A) format) const size_t req_buffer_size = width * height * (hasAlpha ? 4 : 3); if(req_buffer_size > ReadInfo.buffer.capacity()) ReadInfo.buffer.reserve(req_buffer_size * 1.5); @@ -427,7 +433,7 @@ void PNGLibrary::loadImagetoSurface(FBSurface& surface, bool hasAlpha) uInt32* s_ptr = s_buf; if(hasAlpha) for(uInt32 icol = 0; icol < ReadInfo.width; ++icol, i_ptr += 4) - *s_ptr++ = fb.mapRGBA(*i_ptr, *(i_ptr+1), *(i_ptr+2), 85/* *(i_ptr+3)*/); + *s_ptr++ = fb.mapRGBA(*i_ptr, *(i_ptr+1), *(i_ptr+2), *(i_ptr+3)); else for(uInt32 icol = 0; icol < ReadInfo.width; ++icol, i_ptr += 3) *s_ptr++ = fb.mapRGB(*i_ptr, *(i_ptr+1), *(i_ptr+2)); diff --git a/src/common/VideoModeHandler.cxx b/src/common/VideoModeHandler.cxx index fcc1275dd..f87634c2a 100644 --- a/src/common/VideoModeHandler.cxx +++ b/src/common/VideoModeHandler.cxx @@ -33,10 +33,9 @@ void VideoModeHandler::setDisplaySize(const Common::Size& display, Int32 fsIndex // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const VideoModeHandler::Mode& - VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode) + VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode, bool showBezel) { const bool windowedRequested = myFSIndex == -1; - const bool showBezel = inTIAMode&& settings.getBool("showbezel"); // TIA mode allows zooming at non-integral factors in most cases if(inTIAMode) @@ -57,7 +56,9 @@ const VideoModeHandler::Mode& const float overscan = 1 - settings.getInt("tia.fs_overscan") / 100.0; // First calculate maximum zoom that keeps aspect ratio - const float scaleX = myImage.w / myDisplay.w, + // Note: We are assuming a 16:9 bezel image here + const float bezelScaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1; + const float scaleX = myImage.w / (myDisplay.w / bezelScaleW), scaleY = static_cast(myImage.h) / myDisplay.h; float zoom = 1.F / std::max(scaleX, scaleY); @@ -114,22 +115,21 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, zoom{zoomLevel}, fsIndex{fsindex} { - const float scaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1; - //const Int32 imageW = iw * scaleW; - //screenS.w = screenS.w * scaleW; + // Note: We are assuming a 16:9 bezel image here + const float bezelScaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1; // Now resize based on windowed/fullscreen mode and stretch factor if(fsIndex != -1) // fullscreen mode { switch(stretch) { case Stretch::Preserve: - iw *= overscan / scaleW; + iw *= overscan; ih *= overscan; break; case Stretch::Fill: // Scale to all available space - iw = screenS.w * (overscan / scaleW); + iw = screenS.w * (overscan / bezelScaleW); ih = screenS.h * overscan; break; @@ -148,7 +148,7 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, { case Stretch::Preserve: case Stretch::Fill: - screenS.w = iw * scaleW; + screenS.w = iw * bezelScaleW; screenS.h = ih; break; diff --git a/src/common/VideoModeHandler.hxx b/src/common/VideoModeHandler.hxx index 5ff15dfba..702acc15d 100644 --- a/src/common/VideoModeHandler.hxx +++ b/src/common/VideoModeHandler.hxx @@ -95,7 +95,7 @@ class VideoModeHandler @return A video mode based on the given criteria */ const VideoModeHandler::Mode& buildMode(const Settings& settings, - bool inTIAMode); + bool inTIAMode, bool showBezel); private: Common::Size myImage, myDisplay; diff --git a/src/common/sdl_blitter/BilinearBlitter.cxx b/src/common/sdl_blitter/BilinearBlitter.cxx index eb65b7242..9235a62b0 100644 --- a/src/common/sdl_blitter/BilinearBlitter.cxx +++ b/src/common/sdl_blitter/BilinearBlitter.cxx @@ -125,14 +125,13 @@ void BilinearBlitter::recreateTexturesIfNecessary() SDL_UpdateTexture(myTexture, nullptr, myStaticData->pixels, myStaticData->pitch); } - if (myAttributes.blending) { - const auto blendAlpha = static_cast(myAttributes.blendalpha * 2.55); + const std::array textures = { myTexture, mySecondaryTexture }; + for (SDL_Texture* texture: textures) { + if (!texture) continue; - const std::array textures = { myTexture, mySecondaryTexture }; - for (SDL_Texture* texture: textures) { - if (!texture) continue; - - SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); + if (myAttributes.blending) { + const auto blendAlpha = static_cast(myAttributes.blendalpha * 2.55); SDL_SetTextureAlphaMod(texture, blendAlpha); } } diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index d33d70df9..3fd33239e 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -71,8 +71,6 @@ FrameBuffer::FrameBuffer(OSystem& osystem) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FrameBuffer::~FrameBuffer() // NOLINT (we need an empty d'tor) { - if(myBezelSurface) - deallocateSurface(myBezelSurface); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -396,10 +394,10 @@ void FrameBuffer::update(UpdateMode mode) { myPausedCount = static_cast(7 * myOSystem.frameRate()); showTextMessage("Paused", MessagePosition::MiddleCenter); - myTIASurface->render(shade); + renderTIA(shade, false); } if(rerender) - myTIASurface->render(shade); + renderTIA(shade, false); break; // EventHandlerState::PAUSE } @@ -410,14 +408,12 @@ void FrameBuffer::update(UpdateMode mode) redraw |= myOSystem.optionsMenu().needsRedraw(); if(redraw) { - clear(); - myTIASurface->render(true); + renderTIA(true); myOSystem.optionsMenu().draw(forceRedraw); } else if(rerender) { - clear(); - myTIASurface->render(true); + renderTIA(true); myOSystem.optionsMenu().render(); } break; // EventHandlerState::OPTIONSMENU @@ -429,14 +425,12 @@ void FrameBuffer::update(UpdateMode mode) redraw |= myOSystem.commandMenu().needsRedraw(); if(redraw) { - clear(); - myTIASurface->render(true); + renderTIA(true); myOSystem.commandMenu().draw(forceRedraw); } else if(rerender) { - clear(); - myTIASurface->render(true); + renderTIA(true); myOSystem.commandMenu().render(); } break; // EventHandlerState::CMDMENU @@ -448,14 +442,12 @@ void FrameBuffer::update(UpdateMode mode) redraw |= myOSystem.highscoresMenu().needsRedraw(); if(redraw) { - clear(); - myTIASurface->render(true); + renderTIA(true); myOSystem.highscoresMenu().draw(forceRedraw); } else if(rerender) { - clear(); - myTIASurface->render(true); + renderTIA(true); myOSystem.highscoresMenu().render(); } break; // EventHandlerState::HIGHSCORESMENU @@ -467,8 +459,7 @@ void FrameBuffer::update(UpdateMode mode) redraw |= myOSystem.messageMenu().needsRedraw(); if(redraw) { - clear(); - myTIASurface->render(true); + renderTIA(true); myOSystem.messageMenu().draw(forceRedraw); } break; // EventHandlerState::MESSAGEMENU @@ -480,8 +471,7 @@ void FrameBuffer::update(UpdateMode mode) redraw |= myOSystem.plusRomsMenu().needsRedraw(); if(redraw) { - clear(); - myTIASurface->render(true); + renderTIA(true); myOSystem.plusRomsMenu().draw(forceRedraw); } break; // EventHandlerState::PLUSROMSMENU @@ -493,14 +483,12 @@ void FrameBuffer::update(UpdateMode mode) redraw |= myOSystem.timeMachine().needsRedraw(); if(redraw) { - clear(); - myTIASurface->render(); + renderTIA(); myOSystem.timeMachine().draw(forceRedraw); } else if(rerender) { - clear(); - myTIASurface->render(); + renderTIA(); myOSystem.timeMachine().render(); } break; // EventHandlerState::TIMEMACHINE @@ -532,7 +520,7 @@ void FrameBuffer::update(UpdateMode mode) } redraw |= success; if(redraw) - myTIASurface->render(); + renderTIA(false, false); // Stop playback mode at the end of the state buffer // and switch to Time Machine or Pause mode @@ -594,8 +582,7 @@ void FrameBuffer::updateInEmulationMode(float framesPerSecond) // We don't worry about selective rendering here; the rendering // always happens at the full framerate - clear(); // TODO - test this: it may cause slowdowns on older systems - myTIASurface->render(); + renderTIA(); // Show frame statistics if(myStatsMsg.enabled) @@ -608,7 +595,6 @@ void FrameBuffer::updateInEmulationMode(float framesPerSecond) if(myMsg.enabled) drawMessage(); - myBezelSurface->render(); // Push buffers to screen myBackend->renderToScreen(); } @@ -966,6 +952,17 @@ void FrameBuffer::resetSurfaces() update(UpdateMode::REDRAW); // force full update } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBuffer::renderTIA(bool shade, bool doClear) +{ + if(doClear) + clear(); // TODO - test this: it may cause slowdowns on older systems + + myTIASurface->render(shade); + if(myBezelSurface) + myBezelSurface->render(); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void FrameBuffer::setTIAPalette(const PaletteArray& rgb_palette) { @@ -1273,9 +1270,14 @@ FBInitStatus FrameBuffer::applyVideoMode() myVidModeHandler.setDisplaySize(myAbsDesktopSize[display]); const bool inTIAMode = myOSystem.eventHandler().inTIAMode(); +#ifdef IMAGE_SUPPORT + const bool showBezel = inTIAMode && myOSystem.settings().getBool("showbezel") && checkBezel(); +#else + const bool showBezel = false; +#endif // Build the new mode based on current settings - const VideoModeHandler::Mode& mode = myVidModeHandler.buildMode(s, inTIAMode); + const VideoModeHandler::Mode& mode = myVidModeHandler.buildMode(s, inTIAMode, showBezel); if(mode.imageR.size() > mode.screenS) return FBInitStatus::FailTooLarge; @@ -1307,22 +1309,12 @@ FBInitStatus FrameBuffer::applyVideoMode() myOSystem.settings().setValue("tia.zoom", myActiveVidMode.zoom); } - if(inTIAMode) - { - if(myBezelSurface) - deallocateSurface(myBezelSurface); - - myBezelSurface = allocateSurface( - myActiveVidMode.screenS.w, - myActiveVidMode.screenS.h); - - // Get a valid filename representing a snapshot file for this rom and load the snapshot - const string& path = myOSystem.snapshotLoadDir().getPath(); - - //loadBezel(path + "Atari-2600.png"); - loadBezel(path + "Combat (USA).png"); - //loadBezel(path + "Asteroids (USA).png"); - } +#ifdef IMAGE_SUPPORT + if(myBezelSurface) + deallocateSurface(myBezelSurface); + if(showBezel) + loadBezel(); +#endif resetSurfaces(); setCursorState(); @@ -1337,48 +1329,63 @@ FBInitStatus FrameBuffer::applyVideoMode() return status; } +#ifdef IMAGE_SUPPORT // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool FrameBuffer::loadBezel(const string& fileName) +bool FrameBuffer::checkBezel() { - try + const string& path = myOSystem.bezelDir().getPath(); + const string& cartName = myOSystem.console().properties().get(PropType::Cart_Name); + FSNode node(path + cartName + ".png"); + + if(!node.exists()) { - VariantList metaData; - myOSystem.png().loadImage(fileName, *myBezelSurface, metaData); - - // Scale surface to available image area - const Common::Rect& src = myBezelSurface->srcRect(); - const float scale = std::min( - static_cast(myActiveVidMode.screenS.w) / src.w(), - static_cast(myActiveVidMode.screenS.h) / src.h() - ) * myOSystem.frameBuffer().hidpiScaleFactor(); - myBezelSurface->setDstSize( - static_cast(std::round(src.w() * scale)), - static_cast(round(src.h() * scale))); - //myActiveVidMode.screenS.w, - //myActiveVidMode.screenS.h); - myBezelSurface->setScalingInterpolation(ScalingInterpolation::sharp); - - //Int32 w = round(src.w() * scale); - //Int32 h = round(src.h() * scale); - //cerr << scale << ": " << w << " x " << h << endl; - - //// temp workaround: - //FBSurface::Attributes& attr = myBezelSurface->attributes(); - //attr.blendalpha = 50; // 0..100 - //attr.blending = true; - //myBezelSurface->applyAttributes(); - ////SDL_SetSurfaceBlendMode(myBezelSurface, SDL_BLENDMODE_BLEND); - - if(myBezelSurface) - myBezelSurface->setVisible(true); - } - catch(const runtime_error&) - { - return false; + FSNode defaultNode(path + "default.png"); + return defaultNode.exists(); } return true; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool FrameBuffer::loadBezel() +{ + const string& path = myOSystem.bezelDir().getPath(); + const string& cartName = myOSystem.console().properties().get(PropType::Cart_Name); + bool isValid = true; + + myBezelSurface = allocateSurface(myActiveVidMode.screenS.w, myActiveVidMode.screenS.h); + try + { + VariantList metaData; + myOSystem.png().loadImage(path + cartName + ".png", *myBezelSurface, metaData); + } + catch(const runtime_error&) + { + try + { + VariantList metaData; + myOSystem.png().loadImage(path + "default.png", *myBezelSurface, metaData); + } + catch(const runtime_error&) + { + isValid = false; + } + } + + if(isValid) + { + // Scale bezel to fullscreen (preserve or stretch) or window size + const uInt32 bezelH = std::min(myActiveVidMode.screenS.h, + myActiveVidMode.imageR.h()); + myBezelSurface->setDstSize(myActiveVidMode.screenS.w, bezelH); + myBezelSurface->setDstPos(0, (myActiveVidMode.screenS.h - bezelH) / 2); // center vertically + myBezelSurface->setScalingInterpolation(ScalingInterpolation::sharp); + } + if(myBezelSurface) + myBezelSurface->setVisible(isValid); + return isValid; +} +#endif + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - float FrameBuffer::maxWindowZoom() const { diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index d0deb2cd7..f2df5146c 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -410,6 +410,14 @@ class FrameBuffer */ void resetSurfaces(); + /** + Renders TIA and overlaying, optional bezel surface + + @param shade Shade the TIA surface after rendering + @param doClear Clear the framebuffer before rendering + */ + void renderTIA(bool shade = false, bool doClear = true); + #ifdef GUI_SUPPORT /** Helps to create a basic message onscreen. @@ -454,10 +462,21 @@ class FrameBuffer */ FBInitStatus applyVideoMode(); + #ifdef IMAGE_SUPPORT /** - Load the bezel for the given ROM filename. + Check if a bezel for the current ROM name exists. + + @return Whether the bezel was found or not */ - bool loadBezel(const string& romFileName); + bool checkBezel(); + + /** + Load the bezel for the current ROM. + + @return Whether the bezel was loaded or not + */ + bool loadBezel(); + #endif /** Calculate the maximum level by which the base window can be zoomed and @@ -537,7 +556,7 @@ class FrameBuffer // The TIASurface class takes responsibility for TIA rendering shared_ptr myTIASurface; - // The BezelSurface class takes responsibility for TIA rendering + // The BezelSurface which blends over the TIA surface shared_ptr myBezelSurface; // Used for onscreen messages and frame statistics diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 035cbefa7..607c6f281 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -308,6 +308,14 @@ void OSystem::setConfigPaths() mySnapshotLoadDir = FSNode(ssLoadDir); if(!mySnapshotLoadDir.isDirectory()) mySnapshotLoadDir.makeDir(); + + const string_view bezelDir = mySettings->getString("bezeldir"); + if(bezelDir == EmptyString) + myBezelDir = userDir(); + else + myBezelDir = FSNode(bezelDir); + if(!myBezelDir.isDirectory()) + myBezelDir.makeDir(); #endif myCheatFile = myBaseDir; myCheatFile /= "stella.cht"; diff --git a/src/emucore/OSystem.hxx b/src/emucore/OSystem.hxx index 0aa895689..5873abe76 100644 --- a/src/emucore/OSystem.hxx +++ b/src/emucore/OSystem.hxx @@ -303,11 +303,12 @@ class OSystem #ifdef IMAGE_SUPPORT /** - Return the full/complete path name for saving and loading - PNG snapshots. + Return the full/complete path name for saving snapshots, loading + launcher images and loading bezels. */ const FSNode& snapshotSaveDir() const { return mySnapshotSaveDir; } const FSNode& snapshotLoadDir() const { return mySnapshotLoadDir; } + const FSNode& bezelDir() const { return myBezelDir; } #endif /** @@ -599,7 +600,7 @@ class OSystem private: FSNode myBaseDir, myStateDir, mySnapshotSaveDir, mySnapshotLoadDir, - myNVRamDir, myCfgDir, myHomeDir, myUserDir; + myNVRamDir, myCfgDir, myHomeDir, myUserDir, myBezelDir; FSNode myCheatFile, myPaletteFile; FSNode myRomFile; string myRomMD5; diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index 4c7c32214..b9da0d67e 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -161,6 +161,7 @@ Settings::Settings() setPermanent("romdir", ""); setPermanent("userdir", ""); setPermanent("saveuserdir", "false"); + setPermanent("bezeldir", ""); // ROM browser options setPermanent("exitlauncher", "false"); @@ -539,6 +540,7 @@ void Settings::usage() << " -turbo <1|0> Enable 'Turbo' mode for maximum emulation speed\n" << " -uimessages <1|0> Show onscreen UI messages for different events\n" << " -pausedim <1|0> Enable emulation dimming in pause mode\n" + << " -showbezel <1|0> Show bezel left and right of emulation\n" << endl #ifdef SOUND_SUPPORT << " -audio.enabled <1|0> Enable audio\n" @@ -656,6 +658,7 @@ void Settings::usage() << " -followlauncher <0|1> Default ROM path follows launcher navigation\n" << " -userdir Set the path to save user files to\n" << " -saveuserdir <0|1> Update user path when navigating in browser\n" + << " -bezeldir Set the path to load bezels from\n" << " -lastrom Last played ROM, automatically selected in\n" << " launcher\n" << " -romloadcount Number of ROM to load next from multicard\n" diff --git a/src/gui/UIDialog.cxx b/src/gui/UIDialog.cxx index bdebd6eef..fd1831574 100644 --- a/src/gui/UIDialog.cxx +++ b/src/gui/UIDialog.cxx @@ -299,7 +299,7 @@ UIDialog::UIDialog(OSystem& osystem, DialogContainer& parent, bwidth = font.getStringWidth("Image path" + ELLIPSIS) + fontWidth * 2 + 1; myOpenBrowserButton = new ButtonWidget(myTab, font, xpos, ypos, bwidth, buttonHeight, "Image path" + ELLIPSIS, kChooseSnapLoadDirCmd); - myOpenBrowserButton->setToolTip("Select path for snapshot images used in Launcher."); + myOpenBrowserButton->setToolTip("Select path for images used in Launcher."); wid.push_back(myOpenBrowserButton); mySnapLoadPath = new EditTextWidget(myTab, font, HBORDER + lwidth, diff --git a/src/gui/VideoAudioDialog.cxx b/src/gui/VideoAudioDialog.cxx index 1efb69bae..5de52435f 100644 --- a/src/gui/VideoAudioDialog.cxx +++ b/src/gui/VideoAudioDialog.cxx @@ -23,6 +23,7 @@ #include "Cart.hxx" #include "CartDPC.hxx" #include "Dialog.hxx" +#include "BrowserDialog.hxx" #include "OSystem.hxx" #include "EditTextWidget.hxx" #include "PopUpWidget.hxx" @@ -79,6 +80,7 @@ VideoAudioDialog::VideoAudioDialog(OSystem& osystem, DialogContainer& parent, addDisplayTab(); addPaletteTab(); addTVEffectsTab(); + addBezelTab(); addAudioTab(); // Add Defaults, OK and Cancel buttons @@ -351,7 +353,7 @@ void VideoAudioDialog::addTVEffectsTab() int pwidth = _font.getStringWidth("Bad adjust "); WidgetArray wid; VariantList items; - const int tabID = myTab->addTab(" TV Effects ", TabWidget::AUTO_WIDTH); + const int tabID = myTab->addTab("TV Effects", TabWidget::AUTO_WIDTH); items.clear(); VarList::push_back(items, "Disabled", static_cast(NTSCFilter::Preset::OFF)); @@ -438,6 +440,47 @@ void VideoAudioDialog::addTVEffectsTab() myTab->parentWidget(tabID)->setHelpAnchor("VideoAudioEffects"); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void VideoAudioDialog::addBezelTab() +{ + const int lineHeight = Dialog::lineHeight(), + buttonHeight = Dialog::buttonHeight(), + fontWidth = Dialog::fontWidth(), + VBORDER = Dialog::vBorder(), + HBORDER = Dialog::hBorder(), + VGAP = Dialog::vGap(); + const int INDENT = CheckboxWidget::prefixSize(_font); + int xpos = HBORDER, + ypos = VBORDER; + WidgetArray wid; + const int tabID = myTab->addTab(" Bezels ", TabWidget::AUTO_WIDTH); + + // Enable bezels + myBezelEnableCheckbox = new CheckboxWidget(myTab, _font, xpos, ypos, + "Enable bezels", kBezelEnableChanged); + //myBezelEnableCheckbox->setToolTip(Event::BezelToggle); + wid.push_back(myBezelEnableCheckbox); + xpos += INDENT; + ypos += lineHeight + VGAP; + + // Bezel path + int bwidth = _font.getStringWidth("Bezel path" + ELLIPSIS) + fontWidth * 2 + 1; + myOpenBrowserButton = new ButtonWidget(myTab, _font, xpos, ypos, bwidth, buttonHeight, + "Bezel path" + ELLIPSIS, kChooseBezelDirCmd); + myOpenBrowserButton->setToolTip("Select path for bezels."); + wid.push_back(myOpenBrowserButton); + + myBezelPath = new EditTextWidget(myTab, _font, xpos + bwidth + fontWidth, + ypos + (buttonHeight - lineHeight) / 2 - 1, + _w - xpos - bwidth - fontWidth - HBORDER - 2, lineHeight, ""); + wid.push_back(myBezelPath); + + // Add items for tab 4 + addToFocusList(wid, myTab, tabID); + + myTab->parentWidget(tabID)->setHelpAnchor("TODO???"); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void VideoAudioDialog::addAudioTab() { @@ -450,7 +493,7 @@ void VideoAudioDialog::addAudioTab() int lwidth = _font.getStringWidth("Volume "), pwidth = 0; WidgetArray wid; VariantList items; - const int tabID = myTab->addTab(" Audio ", TabWidget::AUTO_WIDTH); + const int tabID = myTab->addTab(" Audio ", TabWidget::AUTO_WIDTH); int xpos = HBORDER, ypos = VBORDER; @@ -460,7 +503,7 @@ void VideoAudioDialog::addAudioTab() mySoundEnableCheckbox->setToolTip(Event::SoundToggle); wid.push_back(mySoundEnableCheckbox); ypos += lineHeight + VGAP; - xpos += CheckboxWidget::prefixSize(_font); + xpos += INDENT; // Volume myVolumeSlider = new SliderWidget(myTab, _font, xpos, ypos, @@ -563,7 +606,7 @@ void VideoAudioDialog::addAudioTab() myDpcPitch->setTickmarkIntervals(2); wid.push_back(myDpcPitch); - // Add items for tab 4 + // Add items for tab 5 addToFocusList(wid, myTab, tabID); myTab->parentWidget(tabID)->setHelpAnchor("VideoAudioAudio"); @@ -679,6 +722,12 @@ void VideoAudioDialog::loadConfig() myTVScanIntense->setValue(settings.getInt("tv.scanlines")); myTVScanMask->setSelected(settings.getString("tv.scanmask"), TIASurface::SETTING_STANDARD); + ///////////////////////////////////////////////////////////////////////////// + // Bezel tab + myBezelEnableCheckbox->setState(settings.getBool("showbezel")); + myBezelPath->setText(settings.getString("bezeldir")); + handleBezelChange(); + ///////////////////////////////////////////////////////////////////////////// // Audio tab AudioSettings& audioSettings = instance().audioSettings(); @@ -709,7 +758,7 @@ void VideoAudioDialog::loadConfig() updateSettingsWithPreset(instance().audioSettings()); - updateEnabledState(); + updateAudioEnabledState(); myTab->loadConfig(); } @@ -803,6 +852,12 @@ void VideoAudioDialog::saveConfig() settings.setValue("tv.scanlines", myTVScanIntense->getValueLabel()); settings.setValue("tv.scanmask", myTVScanMask->getSelectedTag()); + ///////////////////////////////////////////////////////////////////////////// + // Bezel tab + settings.setValue("showbezel", myBezelEnableCheckbox->getState()); + settings.setValue("bezeldir", myBezelPath->getText()); + + // Note: The following has to happen after all video related setting have been saved if(instance().hasConsole()) { instance().console().setTIAProperties(); @@ -936,7 +991,13 @@ void VideoAudioDialog::setDefaults() loadTVAdjustables(NTSCFilter::Preset::CUSTOM); break; } - case 3: // Audio + case 3: // Bezels + myBezelEnableCheckbox->setState(true); + myBezelPath->setText(instance().userDir().getShortPath()); + handleBezelChange(); + break; + + case 4: // Audio mySoundEnableCheckbox->setState(AudioSettings::DEFAULT_ENABLED); myVolumeSlider->setValue(AudioSettings::DEFAULT_VOLUME); myDevicePopup->setSelected(AudioSettings::DEFAULT_DEVICE); @@ -953,7 +1014,7 @@ void VideoAudioDialog::setDefaults() } else updatePreset(); - updateEnabledState(); + updateAudioEnabledState(); break; default: // satisfy compiler @@ -1098,6 +1159,13 @@ void VideoAudioDialog::handlePhosphorChange() myTVPhosLevel->setEnabled(myTVPhosphor->getState()); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void VideoAudioDialog::handleBezelChange() +{ + myOpenBrowserButton->setEnabled(myBezelEnableCheckbox->getState()); + myBezelPath->setEnabled(myBezelEnableCheckbox->getState()); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void VideoAudioDialog::handleCommand(CommandSender* sender, int cmd, int data, int id) @@ -1213,13 +1281,26 @@ void VideoAudioDialog::handleCommand(CommandSender* sender, int cmd, myTVPhosLevel->setValueUnit("%"); break; + case kBezelEnableChanged: + handleBezelChange(); + break; + + case kChooseBezelDirCmd: + BrowserDialog::show(this, _font, "Select Bezel Directory", + myBezelPath->getText(), + BrowserDialog::Mode::Directories, + [this](bool OK, const FSNode& node) { + if(OK) myBezelPath->setText(node.getShortPath()); + }); + break; + case kSoundEnableChanged: - updateEnabledState(); + updateAudioEnabledState(); break; case kModeChanged: updatePreset(); - updateEnabledState(); + updateAudioEnabledState(); break; case kHeadroomChanged: @@ -1300,7 +1381,7 @@ void VideoAudioDialog::colorPalette() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void VideoAudioDialog::updateEnabledState() +void VideoAudioDialog::updateAudioEnabledState() { const bool active = mySoundEnableCheckbox->getState(); const auto preset = static_cast diff --git a/src/gui/VideoAudioDialog.hxx b/src/gui/VideoAudioDialog.hxx index 6ca482b16..99ac0dec6 100644 --- a/src/gui/VideoAudioDialog.hxx +++ b/src/gui/VideoAudioDialog.hxx @@ -26,6 +26,7 @@ class PopUpWidget; class RadioButtonGroup; class SliderWidget; class StaticTextWidget; +class EditTextWidget; class TabWidget; class OSystem; @@ -49,7 +50,9 @@ class VideoAudioDialog : public Dialog void addDisplayTab(); void addPaletteTab(); void addTVEffectsTab(); + void addBezelTab(); void addAudioTab(); + void handleTVModeChange(NTSCFilter::Preset); void loadTVAdjustables(NTSCFilter::Preset preset); void handleRendererChanged(); @@ -59,11 +62,15 @@ class VideoAudioDialog : public Dialog void handleFullScreenChange(); void handleOverscanChange(); void handlePhosphorChange(); + void handleBezelChange(); + void handleCommand(CommandSender* sender, int cmd, int data, int id) override; + void addPalette(int x, int y, int w, int h); void colorPalette(); + void updatePreset(); - void updateEnabledState(); + void updateAudioEnabledState(); void updateSettingsWithPreset(AudioSettings&); private: @@ -123,6 +130,11 @@ class VideoAudioDialog : public Dialog std::array myColorLbl{nullptr}; ColorWidget* myColor[16][8]{{nullptr}}; + // Bezels + CheckboxWidget* myBezelEnableCheckbox{nullptr}; + ButtonWidget* myOpenBrowserButton{nullptr}; + EditTextWidget* myBezelPath{nullptr}; + // Audio CheckboxWidget* mySoundEnableCheckbox{nullptr}; SliderWidget* myVolumeSlider{nullptr}; @@ -163,6 +175,9 @@ class VideoAudioDialog : public Dialog kPhosBlendChanged = 'VDbl', kScanlinesChanged = 'VDsc', + kBezelEnableChanged = 'BZen', + kChooseBezelDirCmd = 'BZsl', + kSoundEnableChanged = 'ADse', kDeviceChanged = 'ADdc', kModeChanged = 'ADmc', diff --git a/test/bezels/Combat (1977) (Atari).png b/test/bezels/Combat (1977) (Atari).png new file mode 100644 index 000000000..3bb2d65ef Binary files /dev/null and b/test/bezels/Combat (1977) (Atari).png differ diff --git a/test/bezels/default.png b/test/bezels/default.png new file mode 100644 index 000000000..e9b52df45 Binary files /dev/null and b/test/bezels/default.png differ