diff --git a/src/common/VideoModeHandler.cxx b/src/common/VideoModeHandler.cxx index f87634c2a..6651bba63 100644 --- a/src/common/VideoModeHandler.cxx +++ b/src/common/VideoModeHandler.cxx @@ -33,7 +33,7 @@ void VideoModeHandler::setDisplaySize(const Common::Size& display, Int32 fsIndex // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const VideoModeHandler::Mode& - VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode, bool showBezel) + VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode, Mode::BezelInfo bezelInfo) { const bool windowedRequested = myFSIndex == -1; @@ -49,7 +49,7 @@ const VideoModeHandler::Mode& // Image and screen (aka window) dimensions are the same // Overscan is not applicable in this mode myMode = Mode(myImage.w * zoom, myImage.h * zoom, Mode::Stretch::Fill, - myFSIndex, desc.str(), zoom, showBezel); + myFSIndex, desc.str(), zoom, bezelInfo); } else { @@ -57,7 +57,7 @@ const VideoModeHandler::Mode& // First calculate maximum zoom that keeps aspect ratio // 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 bezelScaleW = bezelInfo.enabled ? bezelInfo.scaleW() : 1; const float scaleX = myImage.w / (myDisplay.w / bezelScaleW), scaleY = static_cast(myImage.h) / myDisplay.h; float zoom = 1.F / std::max(scaleX, scaleY); @@ -73,7 +73,7 @@ const VideoModeHandler::Mode& myDisplay.w, myDisplay.h, Mode::Stretch::Preserve, myFSIndex, "Fullscreen: Preserve aspect, no stretch", - zoom, overscan, showBezel); + zoom, overscan, bezelInfo); } else // ignore aspect, use all space { @@ -81,7 +81,7 @@ const VideoModeHandler::Mode& myDisplay.w, myDisplay.h, Mode::Stretch::Fill, myFSIndex, "Fullscreen: Ignore aspect, full stretch", - zoom, overscan, showBezel); + zoom, overscan, bezelInfo); } } } @@ -100,15 +100,15 @@ const VideoModeHandler::Mode& // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, Stretch smode, Int32 fsindex, string_view desc, - float zoomLevel, bool showBezel) - : Mode(iw, ih, iw, ih, smode, fsindex, desc, zoomLevel, 1.F, showBezel) + float zoomLevel, BezelInfo bezelInfo) + : Mode(iw, ih, iw, ih, smode, fsindex, desc, zoomLevel, 1.F, bezelInfo) { } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, Stretch smode, Int32 fsindex, string_view desc, - float zoomLevel, float overscan, bool showBezel) + float zoomLevel, float overscan, BezelInfo bezelInfo) : screenS{sw, sh}, stretch{smode}, description{desc}, @@ -116,15 +116,17 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, fsIndex{fsindex} { // 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 bezelScaleW = bezelInfo.enabled ? bezelInfo.scaleW() : 1; // Now resize based on windowed/fullscreen mode and stretch factor if(fsIndex != -1) // fullscreen mode { switch(stretch) { case Stretch::Preserve: - iw *= overscan; - ih *= overscan; + iw = (iw - bezelInfo.hBorder() * zoomLevel) * overscan; + ih = (ih - bezelInfo.vBorder() * zoomLevel) * overscan; + //iw *= overscan; + //ih *= overscan; break; case Stretch::Fill: diff --git a/src/common/VideoModeHandler.hxx b/src/common/VideoModeHandler.hxx index 702acc15d..380ce17ba 100644 --- a/src/common/VideoModeHandler.hxx +++ b/src/common/VideoModeHandler.hxx @@ -38,6 +38,23 @@ class VideoModeHandler Fill, // Stretch to fill all available space None // No stretching (1x zoom) }; + struct BezelInfo + { + bool enabled{false}; + bool windowedMode{false}; + uInt32 topBorder{0}; + uInt32 bottomBorder{0}; + + BezelInfo() = default; + BezelInfo(bool _enabled, bool _windowedMode, uInt32 _topBorder, uInt32 _bottomBorder) + : enabled{_enabled}, windowedMode{_windowedMode}, + topBorder{_topBorder}, bottomBorder(_bottomBorder) { } + + uInt32 vBorder() { return topBorder + bottomBorder; } + uInt32 hBorder() { return (topBorder + bottomBorder) * 4.F / 3.F; } + // Scale width from 4:3 into 16:9 + float scaleW(float width = 1.F) { return width * (16.F / 9.F) / (4.F / 3.F); } + }; Common::Rect imageR; Common::Rect screenR; @@ -50,9 +67,13 @@ class VideoModeHandler Mode() = default; Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, Stretch smode, Int32 fsindex = -1, string_view desc = "", - float zoomLevel = 1.F, float overscan = 1.F, bool showBezel = false); + float zoomLevel = 1.F, float overscan = 1.F, + BezelInfo bezelInfo = BezelInfo()); + //bool showBezel = false, Int32 bezelBorder = 0); Mode(uInt32 iw, uInt32 ih, Stretch smode, Int32 fsindex = -1, - string_view desc = "", float zoomLevel = 1.F, bool showBezel = false); + string_view desc = "", float zoomLevel = 1.F, + BezelInfo bezelInfo = BezelInfo()); + //bool showBezel = false, Int32 bezelBorder = 0); friend ostream& operator<<(ostream& os, const Mode& vm) { @@ -94,8 +115,8 @@ class VideoModeHandler @return A video mode based on the given criteria */ - const VideoModeHandler::Mode& buildMode(const Settings& settings, - bool inTIAMode, bool showBezel); + const VideoModeHandler::Mode& buildMode(const Settings& settings, bool inTIAMode, + Mode::BezelInfo bezelInfo = Mode::BezelInfo()); private: Common::Size myImage, myDisplay; diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index 96fbbcb56..a83ceebe4 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -1279,9 +1279,13 @@ FBInitStatus FrameBuffer::applyVideoMode() #else const bool showBezel = false; #endif + VideoModeHandler::Mode::BezelInfo bezelInfo(showBezel, + myOSystem.settings().getBool("bezel.windowed"), + myOSystem.settings().getInt("bezel.topborder"), + myOSystem.settings().getInt("bezel.bottomborder")); // Build the new mode based on current settings - const VideoModeHandler::Mode& mode = myVidModeHandler.buildMode(s, inTIAMode, showBezel); + const VideoModeHandler::Mode& mode = myVidModeHandler.buildMode(s, inTIAMode, bezelInfo); if(mode.imageR.size() > mode.screenS) return FBInitStatus::FailTooLarge; @@ -1306,14 +1310,9 @@ FBInitStatus FrameBuffer::applyVideoMode() if(inTIAMode) { #ifdef IMAGE_SUPPORT - if(myBezelSurface) - deallocateSurface(myBezelSurface); - myBezelSurface = nullptr; - if(showBezel) - loadBezel(); + loadBezel(bezelInfo); #endif - - myTIASurface->initialize(myOSystem.console(), myActiveVidMode); + myTIASurface->initialize(myOSystem.console(), myActiveVidMode); if(fullScreen()) myOSystem.settings().setValue("tia.fs_stretch", myActiveVidMode.stretch == VideoModeHandler::Mode::Stretch::Fill); @@ -1385,63 +1384,87 @@ bool FrameBuffer::checkBezel() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool FrameBuffer::loadBezel() +bool FrameBuffer::loadBezel(VideoModeHandler::Mode::BezelInfo& info) { bool isValid = false; - double aspectRatio = 1; - myBezelSurface = allocateSurface(myActiveVidMode.screenS.w, myActiveVidMode.screenS.h); - try - { - const string& path = myOSystem.bezelDir().getPath(); - string imageName; - VariantList metaData; - int index = 0; - - do - { - const string& name = getBezelName(index); - if(name != EmptyString) - { - imageName = path + name + ".png"; - FSNode node(imageName); - if(node.exists()) - { - isValid = true; - break; - } - } - } while (index != -1); - if(isValid) - myOSystem.png().loadImage(imageName, *myBezelSurface, &aspectRatio, metaData); - } - catch(const runtime_error&) - { - isValid = false; - } - - if(isValid) - { - // Scale bezel to fullscreen (preserve or stretch) or window size - const uInt32 bezelW = std::min( - myActiveVidMode.screenS.w, - static_cast(myActiveVidMode.imageR.w() * (16.F / 9.F) / (4.F / 3.F))); - const uInt32 bezelH = std::min( - myActiveVidMode.screenS.h, - myActiveVidMode.imageR.h()); - //cerr << bezelW << " x " << bezelH << endl; - myBezelSurface->setDstSize(bezelW, bezelH); - myBezelSurface->setDstPos((myActiveVidMode.screenS.w - bezelW) / 2, - (myActiveVidMode.screenS.h - bezelH) / 2); // center - myBezelSurface->setScalingInterpolation(ScalingInterpolation::sharp); - - // Enable blending to allow overlaying the bezel over the TIA output - myBezelSurface->attributes().blending = true; - myBezelSurface->attributes().blendalpha = 100; - myBezelSurface->applyAttributes(); - } if(myBezelSurface) - myBezelSurface->setVisible(isValid); + deallocateSurface(myBezelSurface); + myBezelSurface = nullptr; + + if(info.enabled) + { + double aspectRatio = 1; + + myBezelSurface = allocateSurface(myActiveVidMode.screenS.w, myActiveVidMode.screenS.h); + try + { + const string& path = myOSystem.bezelDir().getPath(); + string imageName; + VariantList metaData; + int index = 0; + + do + { + const string& name = getBezelName(index); + if(name != EmptyString) + { + imageName = path + name + ".png"; + FSNode node(imageName); + if(node.exists()) + { + isValid = true; + break; + } + } + } while(index != -1); + if(isValid) + myOSystem.png().loadImage(imageName, *myBezelSurface, &aspectRatio, metaData); + } + catch(const runtime_error&) + { + isValid = false; + } + + if(isValid) + { + const float overscan = 1 - myOSystem.settings().getInt("tia.fs_overscan") / 100.0; + + uInt32 imageW, imageH; + if(fullScreen()) + { + const float hBorder = info.hBorder() * overscan * myActiveVidMode.zoom; + const float vBorder = info.vBorder() * overscan * myActiveVidMode.zoom; + imageW = info.scaleW(myActiveVidMode.imageR.w() + hBorder); + imageH = myActiveVidMode.imageR.h() + vBorder; + } + else + { + imageW = info.scaleW(myActiveVidMode.imageR.w()); + imageH = myActiveVidMode.imageR.h(); + } + + // Scale bezel to fullscreen (preserve or stretch) or window size + const uInt32 bezelW = std::min( + myActiveVidMode.screenS.w, imageW); + //static_cast(myActiveVidMode.imageR.w() * (16.F / 9.F) / (4.F / 3.F)) + static_cast(40 * myActiveVidMode.zoom)); + const uInt32 bezelH = std::min( + myActiveVidMode.screenS.h, imageH); + //myActiveVidMode.imageR.h() + static_cast(30 * myActiveVidMode.zoom)); + //cerr << bezelW << " x " << bezelH << endl; + myBezelSurface->setDstSize(bezelW, bezelH); + myBezelSurface->setDstPos((myActiveVidMode.screenS.w - bezelW) / 2, + (myActiveVidMode.screenS.h - bezelH) / 2); // center + myBezelSurface->setScalingInterpolation(ScalingInterpolation::sharp); + + // Enable blending to allow overlaying the bezel over the TIA output + myBezelSurface->attributes().blending = true; + myBezelSurface->attributes().blendalpha = 100; + myBezelSurface->applyAttributes(); + } + if(myBezelSurface) + myBezelSurface->setVisible(isValid); + } return isValid; } #endif diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index abdeaa57b..67bf0fe1c 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -485,7 +485,7 @@ class FrameBuffer @return Whether the bezel was loaded or not */ - bool loadBezel(); + bool loadBezel(VideoModeHandler::Mode::BezelInfo& info); #endif /** diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx index 83a6af1e2..4b518b51b 100644 --- a/src/emucore/Settings.cxx +++ b/src/emucore/Settings.cxx @@ -64,6 +64,8 @@ Settings::Settings() setPermanent("pausedim", "true"); setPermanent("bezel.show", "true"); setPermanent("bezel.windowed", "false"); + setPermanent("bezel.topborder", "0"); + setPermanent("bezel.bottomborder", "0"); // TIA specific options setPermanent("tia.inter", "false"); setPermanent("tia.zoom", "3"); diff --git a/src/gui/VideoAudioDialog.cxx b/src/gui/VideoAudioDialog.cxx index 3e6a1448f..f0a40c8cf 100644 --- a/src/gui/VideoAudioDialog.cxx +++ b/src/gui/VideoAudioDialog.cxx @@ -481,6 +481,22 @@ void VideoAudioDialog::addBezelTab() //myBezelEnableCheckbox->setToolTip(Event::BezelToggle); wid.push_back(myBezelShowWindowed); + ypos += lineHeight + VGAP * 1; + myTopBorderSlider = new SliderWidget(myTab, _font, xpos, ypos, + "Top border ", 0, 0, 6 * fontWidth, "px"); + myTopBorderSlider->setMinValue(0); myTopBorderSlider->setMaxValue(50); + myTopBorderSlider->setTickmarkIntervals(5); + //myTopBorderSlider->setToolTip(Event::VolumeDecrease, Event::VolumeIncrease); + wid.push_back(myTopBorderSlider); + + ypos += lineHeight + VGAP; + myBtmBorderSlider = new SliderWidget(myTab, _font, xpos, ypos, + "Bottom border ", 0, 0, 6 * fontWidth, "px"); + myBtmBorderSlider->setMinValue(0); myBtmBorderSlider->setMaxValue(50); + myBtmBorderSlider->setTickmarkIntervals(5); + //myBtmBorderSlider->setToolTip(Event::VolumeDecrease, Event::VolumeIncrease); + wid.push_back(myBtmBorderSlider); + // Add items for tab 4 addToFocusList(wid, myTab, tabID); @@ -679,7 +695,6 @@ void VideoAudioDialog::loadConfig() myPhaseShift->setTickmarkIntervals(4); myPhaseShift->setToolTip("Adjust PAL phase shift of 'Custom' palette."); myPhaseShift->setValue(myPaletteAdj.phasePal); - } else { @@ -733,6 +748,8 @@ void VideoAudioDialog::loadConfig() myBezelEnableCheckbox->setState(settings.getBool("bezel.show")); myBezelPath->setText(settings.getString("bezel.dir")); myBezelShowWindowed->setState(settings.getBool("bezel.windowed")); + myTopBorderSlider->setValue(settings.getInt("bezel.topborder")); + myBtmBorderSlider->setValue(settings.getInt("bezel.bottomborder")); handleBezelChange(); ///////////////////////////////////////////////////////////////////////////// @@ -850,8 +867,7 @@ void VideoAudioDialog::saveConfig() NTSCFilter::saveConfig(settings); // TV phosphor mode & blend - settings.setValue("tv.phosphor", - myTVPhosphor->getState() ? "always" : "byrom"); + settings.setValue("tv.phosphor", myTVPhosphor->getState() ? "always" : "byrom"); settings.setValue("tv.phosblend", myTVPhosLevel->getValueLabel() == "Off" ? "0" : myTVPhosLevel->getValueLabel()); @@ -864,6 +880,9 @@ void VideoAudioDialog::saveConfig() settings.setValue("bezel.show", myBezelEnableCheckbox->getState()); settings.setValue("bezel.dir", myBezelPath->getText()); settings.setValue("bezel.windowed", myBezelShowWindowed->getState()); + settings.setValue("bezel.topborder", myTopBorderSlider->getValueLabel()); + settings.setValue("bezel.bottomborder", myBtmBorderSlider->getValueLabel()); + cerr << myTopBorderSlider << endl; // Note: The following has to happen after all video related setting have been saved if(instance().hasConsole()) @@ -1003,6 +1022,8 @@ void VideoAudioDialog::setDefaults() myBezelEnableCheckbox->setState(true); myBezelPath->setText(instance().userDir().getShortPath()); myBezelShowWindowed->setState(false); + myTopBorderSlider->setValue(0); + myBtmBorderSlider->setValue(0); handleBezelChange(); break; @@ -1171,9 +1192,13 @@ void VideoAudioDialog::handlePhosphorChange() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void VideoAudioDialog::handleBezelChange() { - myOpenBrowserButton->setEnabled(myBezelEnableCheckbox->getState()); - myBezelPath->setEnabled(myBezelEnableCheckbox->getState()); - myBezelShowWindowed->setEnabled(myBezelEnableCheckbox->getState()); + const bool enable = myBezelEnableCheckbox->getState(); + + myOpenBrowserButton->setEnabled(enable); + myBezelPath->setEnabled(enable); + myBezelShowWindowed->setEnabled(enable); + myTopBorderSlider->setEnabled(enable); + myBtmBorderSlider->setEnabled(enable); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/gui/VideoAudioDialog.hxx b/src/gui/VideoAudioDialog.hxx index abca24941..af7053483 100644 --- a/src/gui/VideoAudioDialog.hxx +++ b/src/gui/VideoAudioDialog.hxx @@ -135,6 +135,8 @@ class VideoAudioDialog : public Dialog ButtonWidget* myOpenBrowserButton{nullptr}; EditTextWidget* myBezelPath{nullptr}; CheckboxWidget* myBezelShowWindowed{nullptr}; + SliderWidget* myTopBorderSlider{nullptr}; + SliderWidget* myBtmBorderSlider{nullptr}; // Audio CheckboxWidget* mySoundEnableCheckbox{nullptr};