bezels working now (TODO: testing, doc)

This commit is contained in:
thrust26 2023-08-19 18:12:32 +02:00
parent 88e737c6f4
commit 53b3d0901c
15 changed files with 259 additions and 123 deletions

View File

@ -273,9 +273,6 @@ class FBBackendSDL2 : public FBBackend
// Center setting of current window // Center setting of current window
bool myCenter{false}; bool myCenter{false};
// Flag for bezel mode
bool myShowBezel{false};
// Does the renderer support render targets? // Does the renderer support render targets?
bool myRenderTargetSupport{false}; bool myRenderTargetSupport{false};

View File

@ -78,19 +78,25 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface, VariantLi
// byte into separate bytes (useful for paletted and grayscale images). // byte into separate bytes (useful for paletted and grayscale images).
png_set_packing(png_ptr); 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) if(color_type == PNG_COLOR_TYPE_RGBA)
{ {
hasAlpha = true; hasAlpha = true;
//png_set_strip_alpha(png_ptr);
} }
else if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) 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); png_set_gray_to_rgb(png_ptr);
} }
else if(color_type == PNG_COLOR_TYPE_PALETTE) 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) 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) 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); const size_t req_buffer_size = width * height * (hasAlpha ? 4 : 3);
if(req_buffer_size > ReadInfo.buffer.capacity()) if(req_buffer_size > ReadInfo.buffer.capacity())
ReadInfo.buffer.reserve(req_buffer_size * 1.5); ReadInfo.buffer.reserve(req_buffer_size * 1.5);
@ -427,7 +433,7 @@ void PNGLibrary::loadImagetoSurface(FBSurface& surface, bool hasAlpha)
uInt32* s_ptr = s_buf; uInt32* s_ptr = s_buf;
if(hasAlpha) if(hasAlpha)
for(uInt32 icol = 0; icol < ReadInfo.width; ++icol, i_ptr += 4) 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 else
for(uInt32 icol = 0; icol < ReadInfo.width; ++icol, i_ptr += 3) for(uInt32 icol = 0; icol < ReadInfo.width; ++icol, i_ptr += 3)
*s_ptr++ = fb.mapRGB(*i_ptr, *(i_ptr+1), *(i_ptr+2)); *s_ptr++ = fb.mapRGB(*i_ptr, *(i_ptr+1), *(i_ptr+2));

View File

@ -33,10 +33,9 @@ void VideoModeHandler::setDisplaySize(const Common::Size& display, Int32 fsIndex
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const VideoModeHandler::Mode& 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 windowedRequested = myFSIndex == -1;
const bool showBezel = inTIAMode&& settings.getBool("showbezel");
// TIA mode allows zooming at non-integral factors in most cases // TIA mode allows zooming at non-integral factors in most cases
if(inTIAMode) if(inTIAMode)
@ -57,7 +56,9 @@ const VideoModeHandler::Mode&
const float overscan = 1 - settings.getInt("tia.fs_overscan") / 100.0; const float overscan = 1 - settings.getInt("tia.fs_overscan") / 100.0;
// First calculate maximum zoom that keeps aspect ratio // 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<float>(myImage.h) / myDisplay.h; scaleY = static_cast<float>(myImage.h) / myDisplay.h;
float zoom = 1.F / std::max(scaleX, scaleY); 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}, zoom{zoomLevel},
fsIndex{fsindex} fsIndex{fsindex}
{ {
const float scaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1; // Note: We are assuming a 16:9 bezel image here
//const Int32 imageW = iw * scaleW; const float bezelScaleW = showBezel ? (16.F / 9.F) / (4.F / 3.F) : 1;
//screenS.w = screenS.w * scaleW;
// Now resize based on windowed/fullscreen mode and stretch factor // Now resize based on windowed/fullscreen mode and stretch factor
if(fsIndex != -1) // fullscreen mode if(fsIndex != -1) // fullscreen mode
{ {
switch(stretch) switch(stretch)
{ {
case Stretch::Preserve: case Stretch::Preserve:
iw *= overscan / scaleW; iw *= overscan;
ih *= overscan; ih *= overscan;
break; break;
case Stretch::Fill: case Stretch::Fill:
// Scale to all available space // Scale to all available space
iw = screenS.w * (overscan / scaleW); iw = screenS.w * (overscan / bezelScaleW);
ih = screenS.h * overscan; ih = screenS.h * overscan;
break; break;
@ -148,7 +148,7 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
{ {
case Stretch::Preserve: case Stretch::Preserve:
case Stretch::Fill: case Stretch::Fill:
screenS.w = iw * scaleW; screenS.w = iw * bezelScaleW;
screenS.h = ih; screenS.h = ih;
break; break;

View File

@ -95,7 +95,7 @@ class VideoModeHandler
@return A video mode based on the given criteria @return A video mode based on the given criteria
*/ */
const VideoModeHandler::Mode& buildMode(const Settings& settings, const VideoModeHandler::Mode& buildMode(const Settings& settings,
bool inTIAMode); bool inTIAMode, bool showBezel);
private: private:
Common::Size myImage, myDisplay; Common::Size myImage, myDisplay;

View File

@ -125,14 +125,13 @@ void BilinearBlitter::recreateTexturesIfNecessary()
SDL_UpdateTexture(myTexture, nullptr, myStaticData->pixels, myStaticData->pitch); SDL_UpdateTexture(myTexture, nullptr, myStaticData->pixels, myStaticData->pitch);
} }
if (myAttributes.blending) { const std::array<SDL_Texture*, 2> textures = { myTexture, mySecondaryTexture };
const auto blendAlpha = static_cast<uInt8>(myAttributes.blendalpha * 2.55); for (SDL_Texture* texture: textures) {
if (!texture) continue;
const std::array<SDL_Texture*, 2> textures = { myTexture, mySecondaryTexture }; SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
for (SDL_Texture* texture: textures) { if (myAttributes.blending) {
if (!texture) continue; const auto blendAlpha = static_cast<uInt8>(myAttributes.blendalpha * 2.55);
SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
SDL_SetTextureAlphaMod(texture, blendAlpha); SDL_SetTextureAlphaMod(texture, blendAlpha);
} }
} }

View File

@ -71,8 +71,6 @@ FrameBuffer::FrameBuffer(OSystem& osystem)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FrameBuffer::~FrameBuffer() // NOLINT (we need an empty d'tor) 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<uInt32>(7 * myOSystem.frameRate()); myPausedCount = static_cast<uInt32>(7 * myOSystem.frameRate());
showTextMessage("Paused", MessagePosition::MiddleCenter); showTextMessage("Paused", MessagePosition::MiddleCenter);
myTIASurface->render(shade); renderTIA(shade, false);
} }
if(rerender) if(rerender)
myTIASurface->render(shade); renderTIA(shade, false);
break; // EventHandlerState::PAUSE break; // EventHandlerState::PAUSE
} }
@ -410,14 +408,12 @@ void FrameBuffer::update(UpdateMode mode)
redraw |= myOSystem.optionsMenu().needsRedraw(); redraw |= myOSystem.optionsMenu().needsRedraw();
if(redraw) if(redraw)
{ {
clear(); renderTIA(true);
myTIASurface->render(true);
myOSystem.optionsMenu().draw(forceRedraw); myOSystem.optionsMenu().draw(forceRedraw);
} }
else if(rerender) else if(rerender)
{ {
clear(); renderTIA(true);
myTIASurface->render(true);
myOSystem.optionsMenu().render(); myOSystem.optionsMenu().render();
} }
break; // EventHandlerState::OPTIONSMENU break; // EventHandlerState::OPTIONSMENU
@ -429,14 +425,12 @@ void FrameBuffer::update(UpdateMode mode)
redraw |= myOSystem.commandMenu().needsRedraw(); redraw |= myOSystem.commandMenu().needsRedraw();
if(redraw) if(redraw)
{ {
clear(); renderTIA(true);
myTIASurface->render(true);
myOSystem.commandMenu().draw(forceRedraw); myOSystem.commandMenu().draw(forceRedraw);
} }
else if(rerender) else if(rerender)
{ {
clear(); renderTIA(true);
myTIASurface->render(true);
myOSystem.commandMenu().render(); myOSystem.commandMenu().render();
} }
break; // EventHandlerState::CMDMENU break; // EventHandlerState::CMDMENU
@ -448,14 +442,12 @@ void FrameBuffer::update(UpdateMode mode)
redraw |= myOSystem.highscoresMenu().needsRedraw(); redraw |= myOSystem.highscoresMenu().needsRedraw();
if(redraw) if(redraw)
{ {
clear(); renderTIA(true);
myTIASurface->render(true);
myOSystem.highscoresMenu().draw(forceRedraw); myOSystem.highscoresMenu().draw(forceRedraw);
} }
else if(rerender) else if(rerender)
{ {
clear(); renderTIA(true);
myTIASurface->render(true);
myOSystem.highscoresMenu().render(); myOSystem.highscoresMenu().render();
} }
break; // EventHandlerState::HIGHSCORESMENU break; // EventHandlerState::HIGHSCORESMENU
@ -467,8 +459,7 @@ void FrameBuffer::update(UpdateMode mode)
redraw |= myOSystem.messageMenu().needsRedraw(); redraw |= myOSystem.messageMenu().needsRedraw();
if(redraw) if(redraw)
{ {
clear(); renderTIA(true);
myTIASurface->render(true);
myOSystem.messageMenu().draw(forceRedraw); myOSystem.messageMenu().draw(forceRedraw);
} }
break; // EventHandlerState::MESSAGEMENU break; // EventHandlerState::MESSAGEMENU
@ -480,8 +471,7 @@ void FrameBuffer::update(UpdateMode mode)
redraw |= myOSystem.plusRomsMenu().needsRedraw(); redraw |= myOSystem.plusRomsMenu().needsRedraw();
if(redraw) if(redraw)
{ {
clear(); renderTIA(true);
myTIASurface->render(true);
myOSystem.plusRomsMenu().draw(forceRedraw); myOSystem.plusRomsMenu().draw(forceRedraw);
} }
break; // EventHandlerState::PLUSROMSMENU break; // EventHandlerState::PLUSROMSMENU
@ -493,14 +483,12 @@ void FrameBuffer::update(UpdateMode mode)
redraw |= myOSystem.timeMachine().needsRedraw(); redraw |= myOSystem.timeMachine().needsRedraw();
if(redraw) if(redraw)
{ {
clear(); renderTIA();
myTIASurface->render();
myOSystem.timeMachine().draw(forceRedraw); myOSystem.timeMachine().draw(forceRedraw);
} }
else if(rerender) else if(rerender)
{ {
clear(); renderTIA();
myTIASurface->render();
myOSystem.timeMachine().render(); myOSystem.timeMachine().render();
} }
break; // EventHandlerState::TIMEMACHINE break; // EventHandlerState::TIMEMACHINE
@ -532,7 +520,7 @@ void FrameBuffer::update(UpdateMode mode)
} }
redraw |= success; redraw |= success;
if(redraw) if(redraw)
myTIASurface->render(); renderTIA(false, false);
// Stop playback mode at the end of the state buffer // Stop playback mode at the end of the state buffer
// and switch to Time Machine or Pause mode // 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 // We don't worry about selective rendering here; the rendering
// always happens at the full framerate // always happens at the full framerate
clear(); // TODO - test this: it may cause slowdowns on older systems renderTIA();
myTIASurface->render();
// Show frame statistics // Show frame statistics
if(myStatsMsg.enabled) if(myStatsMsg.enabled)
@ -608,7 +595,6 @@ void FrameBuffer::updateInEmulationMode(float framesPerSecond)
if(myMsg.enabled) if(myMsg.enabled)
drawMessage(); drawMessage();
myBezelSurface->render();
// Push buffers to screen // Push buffers to screen
myBackend->renderToScreen(); myBackend->renderToScreen();
} }
@ -966,6 +952,17 @@ void FrameBuffer::resetSurfaces()
update(UpdateMode::REDRAW); // force full update 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) void FrameBuffer::setTIAPalette(const PaletteArray& rgb_palette)
{ {
@ -1273,9 +1270,14 @@ FBInitStatus FrameBuffer::applyVideoMode()
myVidModeHandler.setDisplaySize(myAbsDesktopSize[display]); myVidModeHandler.setDisplaySize(myAbsDesktopSize[display]);
const bool inTIAMode = myOSystem.eventHandler().inTIAMode(); 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 // 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) if(mode.imageR.size() > mode.screenS)
return FBInitStatus::FailTooLarge; return FBInitStatus::FailTooLarge;
@ -1307,22 +1309,12 @@ FBInitStatus FrameBuffer::applyVideoMode()
myOSystem.settings().setValue("tia.zoom", myActiveVidMode.zoom); myOSystem.settings().setValue("tia.zoom", myActiveVidMode.zoom);
} }
if(inTIAMode) #ifdef IMAGE_SUPPORT
{ if(myBezelSurface)
if(myBezelSurface) deallocateSurface(myBezelSurface);
deallocateSurface(myBezelSurface); if(showBezel)
loadBezel();
myBezelSurface = allocateSurface( #endif
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");
}
resetSurfaces(); resetSurfaces();
setCursorState(); setCursorState();
@ -1337,48 +1329,63 @@ FBInitStatus FrameBuffer::applyVideoMode()
return status; 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; FSNode defaultNode(path + "default.png");
myOSystem.png().loadImage(fileName, *myBezelSurface, metaData); return defaultNode.exists();
// Scale surface to available image area
const Common::Rect& src = myBezelSurface->srcRect();
const float scale = std::min(
static_cast<float>(myActiveVidMode.screenS.w) / src.w(),
static_cast<float>(myActiveVidMode.screenS.h) / src.h()
) * myOSystem.frameBuffer().hidpiScaleFactor();
myBezelSurface->setDstSize(
static_cast<uInt32>(std::round(src.w() * scale)),
static_cast<uInt32>(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;
} }
return true; 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 float FrameBuffer::maxWindowZoom() const
{ {

View File

@ -410,6 +410,14 @@ class FrameBuffer
*/ */
void resetSurfaces(); 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 #ifdef GUI_SUPPORT
/** /**
Helps to create a basic message onscreen. Helps to create a basic message onscreen.
@ -454,10 +462,21 @@ class FrameBuffer
*/ */
FBInitStatus applyVideoMode(); 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 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 // The TIASurface class takes responsibility for TIA rendering
shared_ptr<TIASurface> myTIASurface; shared_ptr<TIASurface> myTIASurface;
// The BezelSurface class takes responsibility for TIA rendering // The BezelSurface which blends over the TIA surface
shared_ptr<FBSurface> myBezelSurface; shared_ptr<FBSurface> myBezelSurface;
// Used for onscreen messages and frame statistics // Used for onscreen messages and frame statistics

View File

@ -308,6 +308,14 @@ void OSystem::setConfigPaths()
mySnapshotLoadDir = FSNode(ssLoadDir); mySnapshotLoadDir = FSNode(ssLoadDir);
if(!mySnapshotLoadDir.isDirectory()) if(!mySnapshotLoadDir.isDirectory())
mySnapshotLoadDir.makeDir(); mySnapshotLoadDir.makeDir();
const string_view bezelDir = mySettings->getString("bezeldir");
if(bezelDir == EmptyString)
myBezelDir = userDir();
else
myBezelDir = FSNode(bezelDir);
if(!myBezelDir.isDirectory())
myBezelDir.makeDir();
#endif #endif
myCheatFile = myBaseDir; myCheatFile /= "stella.cht"; myCheatFile = myBaseDir; myCheatFile /= "stella.cht";

View File

@ -303,11 +303,12 @@ class OSystem
#ifdef IMAGE_SUPPORT #ifdef IMAGE_SUPPORT
/** /**
Return the full/complete path name for saving and loading Return the full/complete path name for saving snapshots, loading
PNG snapshots. launcher images and loading bezels.
*/ */
const FSNode& snapshotSaveDir() const { return mySnapshotSaveDir; } const FSNode& snapshotSaveDir() const { return mySnapshotSaveDir; }
const FSNode& snapshotLoadDir() const { return mySnapshotLoadDir; } const FSNode& snapshotLoadDir() const { return mySnapshotLoadDir; }
const FSNode& bezelDir() const { return myBezelDir; }
#endif #endif
/** /**
@ -599,7 +600,7 @@ class OSystem
private: private:
FSNode myBaseDir, myStateDir, mySnapshotSaveDir, mySnapshotLoadDir, FSNode myBaseDir, myStateDir, mySnapshotSaveDir, mySnapshotLoadDir,
myNVRamDir, myCfgDir, myHomeDir, myUserDir; myNVRamDir, myCfgDir, myHomeDir, myUserDir, myBezelDir;
FSNode myCheatFile, myPaletteFile; FSNode myCheatFile, myPaletteFile;
FSNode myRomFile; string myRomMD5; FSNode myRomFile; string myRomMD5;

View File

@ -161,6 +161,7 @@ Settings::Settings()
setPermanent("romdir", ""); setPermanent("romdir", "");
setPermanent("userdir", ""); setPermanent("userdir", "");
setPermanent("saveuserdir", "false"); setPermanent("saveuserdir", "false");
setPermanent("bezeldir", "");
// ROM browser options // ROM browser options
setPermanent("exitlauncher", "false"); setPermanent("exitlauncher", "false");
@ -539,6 +540,7 @@ void Settings::usage()
<< " -turbo <1|0> Enable 'Turbo' mode for maximum emulation speed\n" << " -turbo <1|0> Enable 'Turbo' mode for maximum emulation speed\n"
<< " -uimessages <1|0> Show onscreen UI messages for different events\n" << " -uimessages <1|0> Show onscreen UI messages for different events\n"
<< " -pausedim <1|0> Enable emulation dimming in pause mode\n" << " -pausedim <1|0> Enable emulation dimming in pause mode\n"
<< " -showbezel <1|0> Show bezel left and right of emulation\n"
<< endl << endl
#ifdef SOUND_SUPPORT #ifdef SOUND_SUPPORT
<< " -audio.enabled <1|0> Enable audio\n" << " -audio.enabled <1|0> Enable audio\n"
@ -656,6 +658,7 @@ void Settings::usage()
<< " -followlauncher <0|1> Default ROM path follows launcher navigation\n" << " -followlauncher <0|1> Default ROM path follows launcher navigation\n"
<< " -userdir <dir> Set the path to save user files to\n" << " -userdir <dir> Set the path to save user files to\n"
<< " -saveuserdir <0|1> Update user path when navigating in browser\n" << " -saveuserdir <0|1> Update user path when navigating in browser\n"
<< " -bezeldir <dir> Set the path to load bezels from\n"
<< " -lastrom <name> Last played ROM, automatically selected in\n" << " -lastrom <name> Last played ROM, automatically selected in\n"
<< " launcher\n" << " launcher\n"
<< " -romloadcount <number> Number of ROM to load next from multicard\n" << " -romloadcount <number> Number of ROM to load next from multicard\n"

View File

@ -299,7 +299,7 @@ UIDialog::UIDialog(OSystem& osystem, DialogContainer& parent,
bwidth = font.getStringWidth("Image path" + ELLIPSIS) + fontWidth * 2 + 1; bwidth = font.getStringWidth("Image path" + ELLIPSIS) + fontWidth * 2 + 1;
myOpenBrowserButton = new ButtonWidget(myTab, font, xpos, ypos, bwidth, buttonHeight, myOpenBrowserButton = new ButtonWidget(myTab, font, xpos, ypos, bwidth, buttonHeight,
"Image path" + ELLIPSIS, kChooseSnapLoadDirCmd); "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); wid.push_back(myOpenBrowserButton);
mySnapLoadPath = new EditTextWidget(myTab, font, HBORDER + lwidth, mySnapLoadPath = new EditTextWidget(myTab, font, HBORDER + lwidth,

View File

@ -23,6 +23,7 @@
#include "Cart.hxx" #include "Cart.hxx"
#include "CartDPC.hxx" #include "CartDPC.hxx"
#include "Dialog.hxx" #include "Dialog.hxx"
#include "BrowserDialog.hxx"
#include "OSystem.hxx" #include "OSystem.hxx"
#include "EditTextWidget.hxx" #include "EditTextWidget.hxx"
#include "PopUpWidget.hxx" #include "PopUpWidget.hxx"
@ -79,6 +80,7 @@ VideoAudioDialog::VideoAudioDialog(OSystem& osystem, DialogContainer& parent,
addDisplayTab(); addDisplayTab();
addPaletteTab(); addPaletteTab();
addTVEffectsTab(); addTVEffectsTab();
addBezelTab();
addAudioTab(); addAudioTab();
// Add Defaults, OK and Cancel buttons // Add Defaults, OK and Cancel buttons
@ -351,7 +353,7 @@ void VideoAudioDialog::addTVEffectsTab()
int pwidth = _font.getStringWidth("Bad adjust "); int pwidth = _font.getStringWidth("Bad adjust ");
WidgetArray wid; WidgetArray wid;
VariantList items; VariantList items;
const int tabID = myTab->addTab(" TV Effects ", TabWidget::AUTO_WIDTH); const int tabID = myTab->addTab("TV Effects", TabWidget::AUTO_WIDTH);
items.clear(); items.clear();
VarList::push_back(items, "Disabled", static_cast<uInt32>(NTSCFilter::Preset::OFF)); VarList::push_back(items, "Disabled", static_cast<uInt32>(NTSCFilter::Preset::OFF));
@ -438,6 +440,47 @@ void VideoAudioDialog::addTVEffectsTab()
myTab->parentWidget(tabID)->setHelpAnchor("VideoAudioEffects"); 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() void VideoAudioDialog::addAudioTab()
{ {
@ -450,7 +493,7 @@ void VideoAudioDialog::addAudioTab()
int lwidth = _font.getStringWidth("Volume "), pwidth = 0; int lwidth = _font.getStringWidth("Volume "), pwidth = 0;
WidgetArray wid; WidgetArray wid;
VariantList items; 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; int xpos = HBORDER, ypos = VBORDER;
@ -460,7 +503,7 @@ void VideoAudioDialog::addAudioTab()
mySoundEnableCheckbox->setToolTip(Event::SoundToggle); mySoundEnableCheckbox->setToolTip(Event::SoundToggle);
wid.push_back(mySoundEnableCheckbox); wid.push_back(mySoundEnableCheckbox);
ypos += lineHeight + VGAP; ypos += lineHeight + VGAP;
xpos += CheckboxWidget::prefixSize(_font); xpos += INDENT;
// Volume // Volume
myVolumeSlider = new SliderWidget(myTab, _font, xpos, ypos, myVolumeSlider = new SliderWidget(myTab, _font, xpos, ypos,
@ -563,7 +606,7 @@ void VideoAudioDialog::addAudioTab()
myDpcPitch->setTickmarkIntervals(2); myDpcPitch->setTickmarkIntervals(2);
wid.push_back(myDpcPitch); wid.push_back(myDpcPitch);
// Add items for tab 4 // Add items for tab 5
addToFocusList(wid, myTab, tabID); addToFocusList(wid, myTab, tabID);
myTab->parentWidget(tabID)->setHelpAnchor("VideoAudioAudio"); myTab->parentWidget(tabID)->setHelpAnchor("VideoAudioAudio");
@ -679,6 +722,12 @@ void VideoAudioDialog::loadConfig()
myTVScanIntense->setValue(settings.getInt("tv.scanlines")); myTVScanIntense->setValue(settings.getInt("tv.scanlines"));
myTVScanMask->setSelected(settings.getString("tv.scanmask"), TIASurface::SETTING_STANDARD); myTVScanMask->setSelected(settings.getString("tv.scanmask"), TIASurface::SETTING_STANDARD);
/////////////////////////////////////////////////////////////////////////////
// Bezel tab
myBezelEnableCheckbox->setState(settings.getBool("showbezel"));
myBezelPath->setText(settings.getString("bezeldir"));
handleBezelChange();
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Audio tab // Audio tab
AudioSettings& audioSettings = instance().audioSettings(); AudioSettings& audioSettings = instance().audioSettings();
@ -709,7 +758,7 @@ void VideoAudioDialog::loadConfig()
updateSettingsWithPreset(instance().audioSettings()); updateSettingsWithPreset(instance().audioSettings());
updateEnabledState(); updateAudioEnabledState();
myTab->loadConfig(); myTab->loadConfig();
} }
@ -803,6 +852,12 @@ void VideoAudioDialog::saveConfig()
settings.setValue("tv.scanlines", myTVScanIntense->getValueLabel()); settings.setValue("tv.scanlines", myTVScanIntense->getValueLabel());
settings.setValue("tv.scanmask", myTVScanMask->getSelectedTag()); 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()) if(instance().hasConsole())
{ {
instance().console().setTIAProperties(); instance().console().setTIAProperties();
@ -936,7 +991,13 @@ void VideoAudioDialog::setDefaults()
loadTVAdjustables(NTSCFilter::Preset::CUSTOM); loadTVAdjustables(NTSCFilter::Preset::CUSTOM);
break; 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); mySoundEnableCheckbox->setState(AudioSettings::DEFAULT_ENABLED);
myVolumeSlider->setValue(AudioSettings::DEFAULT_VOLUME); myVolumeSlider->setValue(AudioSettings::DEFAULT_VOLUME);
myDevicePopup->setSelected(AudioSettings::DEFAULT_DEVICE); myDevicePopup->setSelected(AudioSettings::DEFAULT_DEVICE);
@ -953,7 +1014,7 @@ void VideoAudioDialog::setDefaults()
} }
else updatePreset(); else updatePreset();
updateEnabledState(); updateAudioEnabledState();
break; break;
default: // satisfy compiler default: // satisfy compiler
@ -1098,6 +1159,13 @@ void VideoAudioDialog::handlePhosphorChange()
myTVPhosLevel->setEnabled(myTVPhosphor->getState()); myTVPhosLevel->setEnabled(myTVPhosphor->getState());
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void VideoAudioDialog::handleBezelChange()
{
myOpenBrowserButton->setEnabled(myBezelEnableCheckbox->getState());
myBezelPath->setEnabled(myBezelEnableCheckbox->getState());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void VideoAudioDialog::handleCommand(CommandSender* sender, int cmd, void VideoAudioDialog::handleCommand(CommandSender* sender, int cmd,
int data, int id) int data, int id)
@ -1213,13 +1281,26 @@ void VideoAudioDialog::handleCommand(CommandSender* sender, int cmd,
myTVPhosLevel->setValueUnit("%"); myTVPhosLevel->setValueUnit("%");
break; 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: case kSoundEnableChanged:
updateEnabledState(); updateAudioEnabledState();
break; break;
case kModeChanged: case kModeChanged:
updatePreset(); updatePreset();
updateEnabledState(); updateAudioEnabledState();
break; break;
case kHeadroomChanged: case kHeadroomChanged:
@ -1300,7 +1381,7 @@ void VideoAudioDialog::colorPalette()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void VideoAudioDialog::updateEnabledState() void VideoAudioDialog::updateAudioEnabledState()
{ {
const bool active = mySoundEnableCheckbox->getState(); const bool active = mySoundEnableCheckbox->getState();
const auto preset = static_cast<AudioSettings::Preset> const auto preset = static_cast<AudioSettings::Preset>

View File

@ -26,6 +26,7 @@ class PopUpWidget;
class RadioButtonGroup; class RadioButtonGroup;
class SliderWidget; class SliderWidget;
class StaticTextWidget; class StaticTextWidget;
class EditTextWidget;
class TabWidget; class TabWidget;
class OSystem; class OSystem;
@ -49,7 +50,9 @@ class VideoAudioDialog : public Dialog
void addDisplayTab(); void addDisplayTab();
void addPaletteTab(); void addPaletteTab();
void addTVEffectsTab(); void addTVEffectsTab();
void addBezelTab();
void addAudioTab(); void addAudioTab();
void handleTVModeChange(NTSCFilter::Preset); void handleTVModeChange(NTSCFilter::Preset);
void loadTVAdjustables(NTSCFilter::Preset preset); void loadTVAdjustables(NTSCFilter::Preset preset);
void handleRendererChanged(); void handleRendererChanged();
@ -59,11 +62,15 @@ class VideoAudioDialog : public Dialog
void handleFullScreenChange(); void handleFullScreenChange();
void handleOverscanChange(); void handleOverscanChange();
void handlePhosphorChange(); void handlePhosphorChange();
void handleBezelChange();
void handleCommand(CommandSender* sender, int cmd, int data, int id) override; void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
void addPalette(int x, int y, int w, int h); void addPalette(int x, int y, int w, int h);
void colorPalette(); void colorPalette();
void updatePreset(); void updatePreset();
void updateEnabledState(); void updateAudioEnabledState();
void updateSettingsWithPreset(AudioSettings&); void updateSettingsWithPreset(AudioSettings&);
private: private:
@ -123,6 +130,11 @@ class VideoAudioDialog : public Dialog
std::array<StaticTextWidget*, 16> myColorLbl{nullptr}; std::array<StaticTextWidget*, 16> myColorLbl{nullptr};
ColorWidget* myColor[16][8]{{nullptr}}; ColorWidget* myColor[16][8]{{nullptr}};
// Bezels
CheckboxWidget* myBezelEnableCheckbox{nullptr};
ButtonWidget* myOpenBrowserButton{nullptr};
EditTextWidget* myBezelPath{nullptr};
// Audio // Audio
CheckboxWidget* mySoundEnableCheckbox{nullptr}; CheckboxWidget* mySoundEnableCheckbox{nullptr};
SliderWidget* myVolumeSlider{nullptr}; SliderWidget* myVolumeSlider{nullptr};
@ -163,6 +175,9 @@ class VideoAudioDialog : public Dialog
kPhosBlendChanged = 'VDbl', kPhosBlendChanged = 'VDbl',
kScanlinesChanged = 'VDsc', kScanlinesChanged = 'VDsc',
kBezelEnableChanged = 'BZen',
kChooseBezelDirCmd = 'BZsl',
kSoundEnableChanged = 'ADse', kSoundEnableChanged = 'ADse',
kDeviceChanged = 'ADdc', kDeviceChanged = 'ADdc',
kModeChanged = 'ADmc', kModeChanged = 'ADmc',

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

BIN
test/bezels/default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 KiB