Merge remote-tracking branch 'remotes/origin/feature/bezels'

This commit is contained in:
thrust26 2023-08-26 14:06:31 +02:00
commit adc7a3ce5e
50 changed files with 4816 additions and 3802 deletions

View File

@ -24,6 +24,8 @@
* Added 2nd UI theme and hotkey for toggling UI theme.
* Added bezel support.
* Added optional type format detection based on colors used.
* Added Joy2B+ controller support.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -320,6 +320,7 @@
Blargg filtering</a>, including presets for several common TV outputs
(Composite, S-Video, RGB, etc.), and ability to fully customize
many attributes (contrast, brightness, saturation, gamma, etc.).</li>
<li>Bezel support</li>
<li>Built-in ROM database with information partially compiled by
<a href="http://www.atarimania.com/rom_collection_archive_atari_2600_roms.html">
RomHunter</a></li>
@ -1178,8 +1179,8 @@
</tr>
<tr>
<td>Toggle aspect ratio correct scaling</td>
<td>Control + C</td>
<td>Control + C</td>
<td>Shift-Control + C</td>
<td>Shift-Control + C</td>
</tr>
<tr>
<td><i>Decrease</i> vertical display size</td>
@ -1206,6 +1207,11 @@
<td>Control + I</td>
<td>Control + I</td>
</tr>
<tr>
<td>Toggle bezel display</td>
<td>Control + B</td>
<td>Control + B</td>
</tr>
<tr>
<td colspan="3"><center><font size="-1">
These settings can also be changed using <a href="#GlobalKeys"><b>Global Keys</a></font></center>
@ -2688,6 +2694,39 @@
<td>Enable or disable emulation dimming in pause mode.</td>
</tr>
<tr>
<td><pre>-bezel.show &lt;1|0&gt;</pre></td>
<td>Enable or disable bezel display.</td>
</tr>
<tr>
<td><pre>-bezel.dir &lt;path&gt;</pre></td>
<td>Specifies from where bezel images are loaded.</td>
</tr>
<tr>
<td><pre>-bezel.windowed &lt;1|0&gt;</pre></td>
<td>Enable bezels in windowed modes as well.</td>
</tr>
<tr>
<td><pre>-bezel.win.auto&lt;1|0&gt;</pre></td>
<td>Enable manually set bezel's emulation window position.</td>
</tr>
<tr>
<td><pre>-bezel.win.left&lt;1|0&gt;</pre></td>
<td>Set left position of bezel's emulation window.</td>
</tr>
<tr>
<td><pre>-bezel.win.right&lt;1|0&gt;</pre></td>
<td>Set right position of bezel's emulation window.</td>
</tr>
<tr>
<td><pre>-bezel.win.top&lt;1|0&gt;</pre></td>
<td>Set top position of bezel's emulation window.</td>
</tr>
<tr>
<td><pre>-bezel.win.bottom&lt;1|0&gt;</pre></td>
<td>Set bottom position of bezel's emulation window.</td>
</tr>
<tr>
<td><pre>-audio.enabled &lt;1|0&gt;</pre></td>
<td>Enable or disable sound generation.</td>
@ -3750,6 +3789,29 @@
</table>
<br>
<p><b>Video & Audio Settings</b> dialog <a name="VideoAudioEffects">(Bezels)</a>:</p>
<table style="border:hidden">
<tr>
<td valign="top"><img src="graphics/options_bezels.png"></td>
<td style="border:hidden">&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td valign="top">
<table border="1" cellpadding="4">
<tr><th>Item</th><th>Brief description</th><th>For more information,<br>see <a href="#CommandLine">Command Line</a></th></tr>
<tr><td>Enable bezels</td><td>Enables the bezel display, if a matching bezel image or a
default image (named 'default.png') can be found in the bezel path.</td><td>-bezel.show</td></tr>
<tr><td>Bezel path</td><td>Specifies the path from where bezel images are loaded.</td><td>-bezel.dir</td></tr>
<tr><td>Windowed modes</td><td>Enable bezels in windowed modes as well.</td><td>-bezel.windowed</td></tr>
<tr><td>Manual emulation window</td><td>Enable manually set bezel's emulation window position.</td><td>-bezel.win.auto</td></tr>
<tr><td>Left</td><td>Set left position of bezel's emulation window.</td><td>-bezel.win.left</td></tr>
<tr><td>Right</td><td>Set right position of bezel's emulation window.</td><td>-bezel.win.right</td></tr>
<tr><td>Top</td><td>Set top position of bezel's emulation window.</td><td>-bezel.win.top</td></tr>
<tr><td>Bottom</td><td>Set bottom position of bezel's emulation window.</td><td>-bezel.win.bottom</td></tr>
</table>
</td>
</tr>
</table>
<br>
<p><b>Video & Audio Settings</b> dialog <a name="VideoAudioAudio">(Audio)</a>:</p>
<table style="border:hidden">
<tr>
@ -5048,7 +5110,7 @@ Ms Pac-Man (Stella extended codes):
<tr><td>Paddles </td><td>Standard paddle controllers for use with games such as Breakout and Warlords. One pair of controller per connector (allows for 4-player Warlords).</td></tr>
<tr><td>Paddles_IAxis &#185</td><td>Same as Paddles, except the axes are inverted.</td></tr>
<tr><td>Paddles_IAxDr &#185</td><td>Same as Paddles, except both the axes and direction of movement are inverted.</td></tr>
<tr><td>Driving &#185 </td><td>Looks like a paddle, but allows 360<EFBFBD> movement. Only one unit per connector, unlike paddles which were sold in pairs.</td></tr>
<tr><td>Driving &#185 </td><td>Looks like a paddle, but allows 360° movement. Only one unit per connector, unlike paddles which were sold in pairs.</td></tr>
<tr><td>Keyboard</td><td>Also known as the Star Raiders controller, functionally identical to the Kid's Controller and Keyboard Controller. Game included an overlay with commands, for use with Star Raiders.</td></tr>
<tr><td>AmigaMouse</td><td>Commodore Amiga computer mouse.</td></tr>
<tr><td>AtariMouse</td><td>Atari ST computer mouse.</td></tr>
@ -5197,6 +5259,20 @@ Ms Pac-Man (Stella extended codes):
<td>Contains any special notes about playing the game.</td>
</tr>
<tr>
<td VALIGN="TOP"><i>Cart.Url</i></td>
<td>Defines a website link, e.g. for additional information or to the AtariAge store.
Click the ">>" button to open the website.</td>
</tr>
<tr>
<td VALIGN="TOP"><i>Bezel.Name</i></td>
<td>Defines the file name (without suffix) used for loading the bezel
image. If the name fails or is not defined, the cart name is used. If
that fails too, 'default.png' is tried. If no image can be found, no
bezel is displayed.
</br>Note: The bezel images should reside in their own directory, separate from the launcher images. </td>
</tr>
</table>
<h3><a name="HighScoreProps"><b>High Scores Properties</b></h3></a>

214
src/common/Bezel.cxx Normal file
View File

@ -0,0 +1,214 @@
//============================================================================
//
// 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-2023 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 <cmath>
#include "OSystem.hxx"
#include "Console.hxx"
#include "EventHandler.hxx"
#include "FBSurface.hxx"
#include "PNGLibrary.hxx"
#include "Bezel.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Bezel::Bezel(OSystem& osystem)
: myOSystem{osystem},
myFB{osystem.frameBuffer()}
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const string Bezel::getName(int& index) const
{
if(++index == 1)
return myOSystem.console().properties().get(PropType::Bezel_Name);
// Try to generate bezel name from cart name
const string& cartName = myOSystem.console().properties().get(PropType::Cart_Name);
size_t pos = cartName.find_first_of("(");
if(pos == std::string::npos)
pos = cartName.length() + 1;
if(index < 10 && pos != std::string::npos && pos > 0)
{
// The following suffixes are from "The Official No-Intro Convention",
// covering all used combinations by "The Bezel Project" (except single ones)
// (Unl) = unlicensed (Homebrews)
const std::array<string, 8> suffixes = {
" (USA)", " (USA) (Proto)", " (USA) (Unl)", " (USA) (Hack)",
" (Europe)", " (Germany)", " (France) (Unl)", " (Australia)"
};
return cartName.substr(0, pos - 1) + suffixes[index - 2];
}
if(index == 10)
{
index = -1;
return "default";
}
return "";
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 Bezel::borderSize(uInt32 x, uInt32 y, uInt32 size, Int32 step) const
{
uInt32 *pixels{nullptr}, pitch;
uInt32 i;
mySurface->basePtr(pixels, pitch);
pixels += x + y * pitch;
for(i = 0; i < size; ++i, pixels += step)
{
uInt8 r, g, b, a;
myFB.getRGBA(*pixels, &r, &g, &b, &a);
if(a < 255)
return i;
}
return size - 1;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool Bezel::load()
{
bool isValid = false;
#ifdef IMAGE_SUPPORT
const bool isShown = myOSystem.eventHandler().inTIAMode() &&
myOSystem.settings().getBool("bezel.show") &&
(myFB.fullScreen() || myOSystem.settings().getBool("bezel.windowed"));
if(mySurface)
myFB.deallocateSurface(mySurface);
mySurface = nullptr;
if(isShown)
{
mySurface = myFB.allocateSurface(1, 1); // dummy size
try
{
const string& path = myOSystem.bezelDir().getPath();
string imageName;
VariantList metaData;
int index = 0;
do
{
const string& name = getName(index);
if(name != EmptyString)
{
// Note: JPG does not support transparency
imageName = path + name + ".png";
FSNode node(imageName);
if(node.exists())
{
isValid = true;
myOSystem.png().loadImage(imageName, *mySurface, metaData);
break;
}
}
} while(index != -1);
}
catch(const runtime_error&)
{
isValid = false;
}
}
#endif
if(isValid)
{
const Settings& settings = myOSystem.settings();
const Int32 w = mySurface->width();
const Int32 h = mySurface->height();
uInt32 top, bottom, left, right;
if(settings.getBool("bezel.win.auto"))
{
// Determine transparent window inside bezel image
uInt32 xCenter, yCenter;
xCenter = w >> 1;
top = borderSize(xCenter, 0, h, w);
bottom = h - 1 - borderSize(xCenter, h - 1, h, -w);
yCenter = (bottom + top) >> 1;
left = borderSize(0, yCenter, w, 1);
right = w - 1 - borderSize(w - 1, yCenter, w, -1);
}
else
{
// BP: 13, 13, 0, 0%
// HY: 12, 12, 0, 0%
// P1: 25, 25, 11, 22%
// P2: 23, 23, 7, 20%
left = std::min(w - 1, static_cast<Int32>(w * settings.getInt("bezel.win.left") / 100. + .5));
right = w - 1 - std::min(w - 1, static_cast<Int32>(w * settings.getInt("bezel.win.right") / 100. + .5));
top = std::min(h - 1, static_cast<Int32>(h * settings.getInt("bezel.win.top") / 100. + .5));
bottom = h - 1 - std::min(h - 1, static_cast<Int32>(h * settings.getInt("bezel.win.bottom") / 100. + .5));
}
//cerr << (int)(right - left + 1) << " x " << (int)(bottom - top + 1) << " = "
// << double((int)(right - left + 1)) / double((int)(bottom - top + 1));
// Disable bezel is no transparent window was found
if (left < right && top < bottom)
myInfo = Info(Common::Size(w, h), Common::Rect(left, top, right, bottom));
else
myInfo = Info();
}
else
myInfo = Info();
return isValid;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Bezel::apply()
{
if(isShown())
{
const uInt32 bezelW =
std::min(myFB.screenSize().w,
static_cast<uInt32>(std::round(myFB.imageRect().w() * myInfo.ratioW())));
const uInt32 bezelH =
std::min(myFB.screenSize().h,
static_cast<uInt32>(std::round(myFB.imageRect().h() * myInfo.ratioH())));
// Position and scale bezel
mySurface->setDstSize(bezelW, bezelH);
mySurface->setDstPos((myFB.screenSize().w - bezelW) / 2, // center
(myFB.screenSize().h - bezelH) / 2);
mySurface->setScalingInterpolation(ScalingInterpolation::sharp);
// Note: Variable bezel window positions are handled in VideoModeHandler::Mode
// Enable blending to allow overlaying the bezel over the TIA output
mySurface->attributes().blending = true;
mySurface->attributes().blendalpha = 100;
mySurface->applyAttributes();
mySurface->setVisible(true);
}
else
if(mySurface)
mySurface->setVisible(false);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Bezel::render()
{
if(mySurface)
mySurface->render();
}

140
src/common/Bezel.hxx Normal file
View File

@ -0,0 +1,140 @@
//============================================================================
//
// 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-2023 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 BEZEL_HXX
#define BEZEL_HXX
class OSystem;
class FBSurface;
class FrameBuffer;
#include "Rect.hxx"
/**
This class handles the bezels.
Bezels are loaded using a file name which is either a bezel name property or
is autogenerated from the cart name property. The bezels can be any size and
their transparent emulation window can be at any position. The position of
the window can be determined automatically.
+--------------------------------------+
| | display.h
+--------------------------------------+
| |
| +---------------+ |
| | window | |
| | | |
| | tia.h * zoom | |
| | | | bezel.h * zoom
| | | |
| +---------------+ |
| |
+--------------------------------------+ size
| |
+--------------------------------------+
The bezel and window sizes and their ratios are used for correct scaling.
@author Thomas Jentzsch
*/
class Bezel
{
public:
explicit Bezel(OSystem& osystem);
~Bezel() = default;
struct Info
{
private:
bool _isShown{false}; // Is bezel shown?
Common::Size _size{1, 1}; // Bezel size
Common::Rect _window{1, 1}; // Area of transparent TIA window inside bezel
public:
explicit Info() = default;
explicit Info(Common::Size size, Common::Rect window)
: _isShown{true}, _size{size}, _window{window} { }
bool isShown() const { return _isShown; }
Common::Size size() const { return _size; }
Common::Rect window() const { return _window; }
// Ratios between bezel sizes and TIA window sizes
double ratioW() const { return static_cast<double>(size().w) / window().w(); }
double ratioH() const { return static_cast<double>(size().h) / window().h(); }
};
// Structure access methods
const Info& info() const { return myInfo; }
bool isShown() const { return myInfo.isShown(); }
Common::Size size() const { return myInfo.size(); }
Common::Rect window() const { return myInfo.window(); }
// Ratio between bezel size and TIA window size
double ratioW() const { return myInfo.ratioW(); }
double ratioH() const { return myInfo.ratioH(); }
/*
Calculate size of a bezel border.
*/
uInt32 borderSize(uInt32 x, uInt32 y, uInt32 size, Int32 step) const;
/*
Load the bezel.
*/
bool load();
/*
Display scaled bezel.
*/
void apply();
/*
Render bezel surface
*/
void render();
private:
/*
Generate bezel file name.
*/
const string getName(int& index) const;
private:
// The parent system for the bezel
OSystem& myOSystem;
// Pointer to the FrameBuffer object
FrameBuffer& myFB;
// The bezel surface which blends over the TIA surface
shared_ptr<FBSurface> mySurface;
// Bezel info structure
Info myInfo;
private:
// Following constructors and assignment operators not supported
Bezel() = delete;
Bezel(const Bezel&) = delete;
Bezel(Bezel&&) = delete;
Bezel& operator=(const Bezel&) = delete;
Bezel& operator=(Bezel&&) = delete;
};
#endif

View File

@ -101,6 +101,18 @@ class FBBackendSDL2 : public FBBackend
FORCE_INLINE void getRGB(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b) const override
{ SDL_GetRGB(pixel, myPixelFormat, r, g, b); }
/**
This method is called to retrieve the R/G/B/A data from the given pixel.
@param pixel The pixel containing R/G/B data
@param r The red component of the color
@param g The green component of the color
@param b The blue component of the color
@param a The alpha component of the color.
*/
FORCE_INLINE void getRGBA(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b, uInt8* a) const override
{ SDL_GetRGBA(pixel, myPixelFormat, r, g, b, a); }
/**
This method is called to map a given R/G/B triple to the screen palette.
@ -111,6 +123,17 @@ class FBBackendSDL2 : public FBBackend
inline uInt32 mapRGB(uInt8 r, uInt8 g, uInt8 b) const override
{ return SDL_MapRGB(myPixelFormat, r, g, b); }
/**
This method is called to map a given R/G/B/A triple to the screen palette.
@param r The red component of the color.
@param g The green component of the color.
@param b The blue component of the color.
@param a The alpha component of the color.
*/
inline uInt32 mapRGBA(uInt8 r, uInt8 g, uInt8 b, uInt8 a) const override
{ return SDL_MapRGBA(myPixelFormat, r, g, b, a); }
/**
This method is called to get a copy of the specified ARGB data from the
viewable FrameBuffer area. Note that this isn't the same as any

View File

@ -48,6 +48,7 @@ FBSurfaceSDL2::FBSurfaceSDL2(FBBackendSDL2& backend,
: myBackend{backend},
myInterpolationMode{inter}
{
//cerr << width << " x " << height << endl;
createSurface(width, height, staticData);
}
@ -233,6 +234,7 @@ void FBSurfaceSDL2::createSurface(uInt32 width, uInt32 height,
mySurface = SDL_CreateRGBSurface(0, width, height,
pf.BitsPerPixel, pf.Rmask, pf.Gmask, pf.Bmask, pf.Amask);
//SDL_SetSurfaceBlendMode(mySurface, SDL_BLENDMODE_ADD); // default: SDL_BLENDMODE_BLEND
// We start out with the src and dst rectangles containing the same
// dimensions, indicating no scaling or re-positioning

View File

@ -623,6 +623,7 @@ PhysicalKeyboardHandler::DefaultCommonMapping = {
#endif
{ Event::OptionsMenuMode, KBDK_TAB },
{ Event::CmdMenuMode, KBDK_BACKSLASH },
{ Event::ToggleBezel, KBDK_B, KBDM_CTRL },
{ Event::TimeMachineMode, KBDK_T, KBDM_SHIFT },
{ Event::DebuggerMode, KBDK_GRAVE },
{ Event::PlusRomsSetupMode, KBDK_P, KBDM_SHIFT | KBDM_CTRL | MOD3 },
@ -641,7 +642,7 @@ PhysicalKeyboardHandler::DefaultCommonMapping = {
{ Event::VCenterIncrease, KBDK_PAGEDOWN, MOD3 },
{ Event::VSizeAdjustDecrease, KBDK_PAGEDOWN, KBDM_SHIFT | MOD3 },
{ Event::VSizeAdjustIncrease, KBDK_PAGEUP, KBDM_SHIFT | MOD3 },
{ Event::ToggleCorrectAspectRatio, KBDK_C, KBDM_CTRL },
{ Event::ToggleCorrectAspectRatio, KBDK_C, KBDM_SHIFT | KBDM_CTRL },
{ Event::VolumeDecrease, KBDK_LEFTBRACKET, MOD3 },
{ Event::VolumeIncrease, KBDK_RIGHTBRACKET, MOD3 },
{ Event::SoundToggle, KBDK_RIGHTBRACKET, KBDM_CTRL },

View File

@ -33,12 +33,14 @@ PNGLibrary::PNGLibrary(OSystem& osystem)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::loadImage(const string& filename, FBSurface& surface, VariantList& metaData)
void PNGLibrary::loadImage(const string& filename, FBSurface& surface,
VariantList& metaData)
{
png_structp png_ptr{nullptr};
png_infop info_ptr{nullptr};
png_uint_32 iwidth{0}, iheight{0};
int bit_depth{0}, color_type{0}, interlace_type{0};
bool hasAlpha = false;
const auto loadImageERROR = [&](string_view s) {
if(png_ptr)
@ -76,18 +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)
{
png_set_strip_alpha(png_ptr);
hasAlpha = true;
}
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)
{
@ -95,7 +104,7 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface, VariantLi
}
// Create/initialize storage area for the current image
if(!allocateStorage(iwidth, iheight))
if(!allocateStorage(iwidth, iheight, hasAlpha))
loadImageERROR("Not enough memory to read PNG image");
// The PNG read function expects an array of rows, not a single 1-D array
@ -112,7 +121,7 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface, VariantLi
readMetaData(png_ptr, info_ptr, metaData);
// Load image into the surface, setting the correct dimensions
loadImagetoSurface(surface);
loadImagetoSurface(surface, hasAlpha);
// Cleanup
if(png_ptr)
@ -380,10 +389,10 @@ void PNGLibrary::takeSnapshot(uInt32 number)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PNGLibrary::allocateStorage(size_t width, size_t height)
bool PNGLibrary::allocateStorage(size_t width, size_t height, bool hasAlpha)
{
// Create space for the entire image (3 bytes per pixel in RGB format)
const size_t req_buffer_size = width * height * 3;
// 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);
@ -393,13 +402,13 @@ bool PNGLibrary::allocateStorage(size_t width, size_t height)
ReadInfo.width = static_cast<png_uint_32>(width);
ReadInfo.height = static_cast<png_uint_32>(height);
ReadInfo.pitch = static_cast<png_uint_32>(width * 3);
ReadInfo.pitch = static_cast<png_uint_32>(width * (hasAlpha ? 4 : 3));
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::loadImagetoSurface(FBSurface& surface)
void PNGLibrary::loadImagetoSurface(FBSurface& surface, bool hasAlpha)
{
// First determine if we need to resize the surface
const uInt32 iw = ReadInfo.width, ih = ReadInfo.height;
@ -422,8 +431,12 @@ void PNGLibrary::loadImagetoSurface(FBSurface& surface)
{
const uInt8* i_ptr = i_buf;
uInt32* s_ptr = s_buf;
for(uInt32 icol = 0; icol < ReadInfo.width; ++icol, i_ptr += 3)
*s_ptr++ = fb.mapRGB(*i_ptr, *(i_ptr+1), *(i_ptr+2));
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), *(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));
}
}

View File

@ -153,7 +153,7 @@ class PNGLibrary
@param width The width of the PNG image
@param height The height of the PNG image
*/
static bool allocateStorage(size_t width, size_t height);
static bool allocateStorage(size_t width, size_t height, bool hasAlpha);
/** The actual method which saves a PNG image.
@ -173,7 +173,7 @@ class PNGLibrary
@param surface The FBSurface into which to place the PNG data
*/
void loadImagetoSurface(FBSurface& surface);
void loadImagetoSurface(FBSurface& surface, bool hasAlpha);
/**
Write PNG tEXt chunks to the image.

View File

@ -15,7 +15,11 @@
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <cmath>
#include "Settings.hxx"
#include "Bezel.hxx"
#include "VideoModeHandler.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -33,7 +37,7 @@ 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, Bezel::Info bezelInfo)
{
const bool windowedRequested = myFSIndex == -1;
@ -42,23 +46,24 @@ VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode)
{
if(windowedRequested)
{
const float zoom = settings.getFloat("tia.zoom");
const double zoom = settings.getFloat("tia.zoom");
ostringstream desc;
desc << (zoom * 100) << "%";
// 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);
myMode = Mode(myImage.w, myImage.h,
Mode::Stretch::Fill, myFSIndex,
desc.str(), zoom, bezelInfo);
}
else
{
const float overscan = 1 - settings.getInt("tia.fs_overscan") / 100.0;
const double overscan = 1 - settings.getInt("tia.fs_overscan") / 100.0;
// First calculate maximum zoom that keeps aspect ratio
const float scaleX = static_cast<float>(myImage.w) / myDisplay.w,
scaleY = static_cast<float>(myImage.h) / myDisplay.h;
float zoom = 1.F / std::max(scaleX, scaleY);
const double scaleX = static_cast<double>(myImage.w) / (myDisplay.w / bezelInfo.ratioW()),
scaleY = static_cast<double>(myImage.h) / (myDisplay.h / bezelInfo.ratioH());
double zoom = 1. / std::max(scaleX, scaleY);
// When aspect ratio correction is off, we want pixel-exact images,
// so we default to integer zooming
@ -67,17 +72,19 @@ VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode)
if(!settings.getBool("tia.fs_stretch")) // preserve aspect, use all space
{
myMode = Mode(myImage.w * zoom, myImage.h * zoom,
myMode = Mode(myImage.w, myImage.h,
myDisplay.w, myDisplay.h,
Mode::Stretch::Preserve, myFSIndex,
"Fullscreen: Preserve aspect, no stretch", zoom, overscan);
"Fullscreen: Preserve aspect, no stretch",
zoom, overscan, bezelInfo);
}
else // ignore aspect, use all space
{
myMode = Mode(myImage.w * zoom, myImage.h * zoom,
myMode = Mode(myImage.w, myImage.h,
myDisplay.w, myDisplay.h,
Mode::Stretch::Fill, myFSIndex,
"Fullscreen: Ignore aspect, full stretch", zoom, overscan);
"Fullscreen: Ignore aspect, full stretch",
zoom, overscan, bezelInfo);
}
}
}
@ -89,26 +96,25 @@ VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode)
myMode = Mode(myImage.w, myImage.h, myDisplay.w, myDisplay.h,
Mode::Stretch::None, myFSIndex);
}
return myMode;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, Stretch smode,
Int32 fsindex, string_view desc,
float zoomLevel)
: Mode(iw, ih, iw, ih, smode, fsindex, desc, zoomLevel)
double zoomLevel, Bezel::Info bezelInfo)
: Mode(iw, ih, iw, ih, smode, fsindex, desc, zoomLevel, 1., bezelInfo)
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
Stretch smode, Int32 fsindex, string_view desc,
float zoomLevel, float overscan)
double zoomLevel, double overscan, Bezel::Info bezelInfo)
: screenS{sw, sh},
stretch{smode},
description{desc},
zoom{zoomLevel},
zoom{zoomLevel}, //hZoom{zoomLevel}, vZoom{zoomLevel},
fsIndex{fsindex}
{
// Now resize based on windowed/fullscreen mode and stretch factor
@ -117,20 +123,21 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
switch(stretch)
{
case Stretch::Preserve:
iw *= overscan;
ih *= overscan;
iw = std::round(iw * overscan * zoomLevel);
ih = std::round(ih * overscan * zoomLevel);
break;
case Stretch::Fill:
{
// Scale to all available space
iw = screenS.w * overscan;
ih = screenS.h * overscan;
iw = std::round(screenS.w * overscan / bezelInfo.ratioW());
ih = std::round(screenS.h * overscan / bezelInfo.ratioH());
break;
case Stretch::None:
}
case Stretch::None: // UI Mode
// Don't do any scaling at all
iw = std::min(iw, screenS.w) * overscan;
ih = std::min(ih, screenS.h) * overscan;
iw = std::min(static_cast<uInt32>(iw * zoomLevel), screenS.w) * overscan;
ih = std::min(static_cast<uInt32>(ih * zoomLevel), screenS.h) * overscan;
break;
}
}
@ -142,11 +149,13 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
{
case Stretch::Preserve:
case Stretch::Fill:
screenS.w = iw;
screenS.h = ih;
iw *= zoomLevel;
ih *= zoomLevel;
screenS.w = std::round(iw * bezelInfo.ratioW());
screenS.h = std::round(ih * bezelInfo.ratioH());
break;
case Stretch::None:
case Stretch::None: // UI Mode
break; // Do not change image or screen rects whatsoever
}
}
@ -155,7 +164,17 @@ VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
iw = std::min(iw, screenS.w);
ih = std::min(ih, screenS.h);
imageR.moveTo((screenS.w - iw) >> 1, (screenS.h - ih) >> 1);
// Allow variable image positions in asymmetric bezels
// (works in case of no bezel too)
const uInt32 wx = bezelInfo.window().x() * iw / bezelInfo.window().w();
const uInt32 wy = bezelInfo.window().y() * ih / bezelInfo.window().h();
const uInt32 bezelW = std::min(screenS.w,
static_cast<uInt32>(std::round(iw * bezelInfo.ratioW())));
const uInt32 bezelH = std::min(screenS.h,
static_cast<uInt32>(std::round(ih * bezelInfo.ratioH())));
// Center image (no bezel) or move image relative to centered bezel
imageR.moveTo(((screenS.w - bezelW) >> 1) + wx, ((screenS.h - bezelH) >> 1) + wy);
imageR.setWidth(iw);
imageR.setHeight(ih);

View File

@ -22,6 +22,7 @@ class Settings;
#include "Rect.hxx"
#include "bspf.hxx"
#include "Bezel.hxx"
class VideoModeHandler
{
@ -44,15 +45,17 @@ class VideoModeHandler
Common::Size screenS;
Stretch stretch{Mode::Stretch::None};
string description;
float zoom{1.F};
double zoom{1.};
Int32 fsIndex{-1}; // -1 indicates windowed mode
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);
double zoomLevel = 1., double overscan = 1.,
Bezel::Info bezelInfo = Bezel::Info());
Mode(uInt32 iw, uInt32 ih, Stretch smode, Int32 fsindex = -1,
string_view desc = "", float zoomLevel = 1.F);
string_view desc = "", double zoomLevel = 1.,
Bezel::Info bezelInfo = Bezel::Info());
friend ostream& operator<<(ostream& os, const Mode& vm)
{
@ -94,8 +97,8 @@ class VideoModeHandler
@return A video mode based on the given criteria
*/
const VideoModeHandler::Mode& buildMode(const Settings& settings,
bool inTIAMode);
const VideoModeHandler::Mode& buildMode(const Settings& settings, bool inTIAMode,
Bezel::Info bezelInfo = Bezel::Info());
private:
Common::Size myImage, myDisplay;

View File

@ -386,6 +386,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(Event::Type, {
{Event::ToggleBits, "ToggleBits"},
{Event::ToggleFixedColors, "ToggleFixedColors"},
{Event::ToggleFrameStats, "ToggleFrameStats"},
{Event::ToggleBezel, "ToggleBezel"},
{Event::ExitGame, "ExitGame"},
{Event::SettingDecrease, "SettingDecrease"},
{Event::SettingIncrease, "SettingIncrease"},

View File

@ -4,6 +4,7 @@ MODULE_OBJS := \
src/common/AudioQueue.o \
src/common/AudioSettings.o \
src/common/Base.o \
src/common/Bezel.o \
src/common/DevSettingsHandler.o \
src/common/EventHandlerSDL2.o \
src/common/FBBackendSDL2.o \

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -126,7 +126,7 @@ class Event
ToggleBLCollision, ToggleBLBit, TogglePFCollision, TogglePFBit,
ToggleCollisions, ToggleBits, ToggleFixedColors,
ToggleFrameStats, ToggleSAPortOrder, ExitGame,
ToggleFrameStats, ToggleBezel, ToggleSAPortOrder, ExitGame,
SettingDecrease, SettingIncrease, PreviousSetting, NextSetting,
ToggleAdaptRefresh, PreviousMultiCartRom,
// add new (after Version 4) events from here to avoid that user remapped events get overwritten

View File

@ -1517,6 +1517,14 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated)
if(pressed && !repeated) myOSystem.toggleTimeMachine();
return;
case Event::ToggleBezel:
if(pressed && !repeated)
{
myOSystem.frameBuffer().toggleBezel();
myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::BEZEL);
}
return;
#ifdef IMAGE_SUPPORT
case Event::ToggleContSnapshots:
if(pressed && !repeated) myOSystem.png().toggleContinuousSnapshots(false);
@ -2827,6 +2835,7 @@ EventHandler::EmulActionList EventHandler::ourEmulActionList = { {
{ Event::ExitMode, "Exit current Stella menu/mode" },
{ Event::OptionsMenuMode, "Enter Options menu UI" },
{ Event::CmdMenuMode, "Toggle Commands menu UI" },
{ Event::ToggleBezel, "Toggle bezel display" },
{ Event::HighScoresMenuMode, "Toggle High Scores UI" },
{ Event::PlusRomsSetupMode, "Toggle PlusROMs setup UI" },
{ Event::TogglePauseMode, "Toggle Pause mode" },
@ -3148,7 +3157,7 @@ EventHandler::MenuActionList EventHandler::ourMenuActionList = { {
const Event::EventSet EventHandler::MiscEvents = {
Event::Quit, Event::ReloadConsole, Event::Fry, Event::StartPauseMode,
Event::TogglePauseMode, Event::OptionsMenuMode, Event::CmdMenuMode,
Event::PlusRomsSetupMode, Event::ExitMode,
Event::ToggleBezel, Event::PlusRomsSetupMode, Event::ExitMode,
Event::ToggleTurbo, Event::DecreaseSpeed, Event::IncreaseSpeed,
Event::TakeSnapshot, Event::ToggleContSnapshots, Event::ToggleContSnapshotsFrame,
// Event::MouseAxisXMove, Event::MouseAxisYMove,

View File

@ -537,7 +537,7 @@ class EventHandler
#else
REFRESH_SIZE = 0,
#endif
EMUL_ACTIONLIST_SIZE = 232 + PNG_SIZE + COMBO_SIZE + REFRESH_SIZE,
EMUL_ACTIONLIST_SIZE = 233 + PNG_SIZE + COMBO_SIZE + REFRESH_SIZE,
MENU_ACTIONLIST_SIZE = 20
;

View File

@ -128,6 +128,17 @@ class FBBackend
*/
virtual void getRGB(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b) const = 0;
/**
This method is called to retrieve the R/G/B/A data from the given pixel.
@param pixel The pixel containing R/G/B data
@param r The red component of the color
@param g The green component of the color
@param b The blue component of the color
@param a The alpha component of the color.
*/
virtual void getRGBA(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b, uInt8* a) const = 0;
/**
This method is called to map a given R/G/B triple to the screen palette.
@ -137,6 +148,16 @@ class FBBackend
*/
virtual uInt32 mapRGB(uInt8 r, uInt8 g, uInt8 b) const = 0;
/**
This method is called to map a given R/G/B triple to the screen palette.
@param r The red component of the color.
@param g The green component of the color.
@param b The blue component of the color.
@param a The alpha component of the color.
*/
virtual uInt32 mapRGBA(uInt8 r, uInt8 g, uInt8 b, uInt8 a) const = 0;
/**
This method is called to get the specified ARGB data from the viewable
FrameBuffer area. Note that this isn't the same as any internal

View File

@ -27,9 +27,11 @@
#include "Sound.hxx"
#include "AudioSettings.hxx"
#include "MediaFactory.hxx"
#include "PNGLibrary.hxx"
#include "FBSurface.hxx"
#include "TIASurface.hxx"
#include "Bezel.hxx"
#include "FrameBuffer.hxx"
#include "PaletteHandler.hxx"
#include "StateManager.hxx"
@ -125,6 +127,8 @@ void FrameBuffer::initialize()
// Create a TIA surface; we need it for rendering TIA images
myTIASurface = make_unique<TIASurface>(myOSystem);
// Create a bezel surface for TIA overlays
myBezel = make_unique<Bezel>(myOSystem);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -277,8 +281,10 @@ FBInitStatus FrameBuffer::createDisplay(string_view title, BufferType type,
if(myBufferType == BufferType::Emulator)
{
myBezel->load();
// Determine possible TIA windowed zoom levels
const float currentTIAZoom = myOSystem.settings().getFloat("tia.zoom");
const double currentTIAZoom = myOSystem.settings().getFloat("tia.zoom");
myOSystem.settings().setValue("tia.zoom",
BSPF::clampw(currentTIAZoom, supportedTIAMinZoom(), supportedTIAMaxZoom()));
}
@ -393,10 +399,10 @@ void FrameBuffer::update(UpdateMode mode)
{
myPausedCount = static_cast<uInt32>(7 * myOSystem.frameRate());
showTextMessage("Paused", MessagePosition::MiddleCenter);
myTIASurface->render(shade);
renderTIA(shade, false);
}
if(rerender)
myTIASurface->render(shade);
renderTIA(shade, false);
break; // EventHandlerState::PAUSE
}
@ -407,14 +413,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
@ -426,14 +430,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
@ -445,14 +447,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
@ -464,8 +464,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
@ -477,8 +476,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
@ -490,14 +488,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
@ -529,7 +525,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
@ -591,8 +587,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)
@ -758,7 +753,8 @@ void FrameBuffer::drawFrameStats(float framesPerSecond)
myStatsMsg.w, color, TextAlign::Left, 0, true, kBGColor);
}
myStatsMsg.surface->setDstPos(imageRect().x() + 10, imageRect().y() + 8);
myStatsMsg.surface->setDstPos(imageRect().x() + imageRect().w() / 64,
imageRect().y() + imageRect().h() / 64);
myStatsMsg.surface->setDstSize(myStatsMsg.w * hidpiScaleFactor(),
myStatsMsg.h * hidpiScaleFactor());
myStatsMsg.surface->render();
@ -832,7 +828,7 @@ inline bool FrameBuffer::drawMessage()
// Draw the bounded box and text
const Common::Rect& dst = myMsg.surface->dstRect();
const int fontWidth = font().getMaxCharWidth(),
fontHeight = font().getFontHeight();
fontHeight = font().getFontHeight();
const int VBORDER = fontHeight / 4;
const int HBORDER = fontWidth * 1.25 / 2.0;
constexpr int BORDER = 1;
@ -962,6 +958,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(myBezel)
myBezel->render();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::setTIAPalette(const PaletteArray& rgb_palette)
{
@ -1144,7 +1151,7 @@ void FrameBuffer::toggleFullscreen(bool toggle)
msg << "enabled (" << myBackend->refreshRate() << " Hz, ";
else
msg << "disabled (";
msg << "Zoom " << myActiveVidMode.zoom * 100 << "%)";
msg << "Zoom " << round(myActiveVidMode.zoom * 100) << "%)";
}
else
{
@ -1226,7 +1233,7 @@ void FrameBuffer::switchVideoMode(int direction)
if(!fullScreen())
{
// Windowed TIA modes support variable zoom levels
float zoom = myOSystem.settings().getFloat("tia.zoom");
double zoom = myOSystem.settings().getFloat("tia.zoom");
if(direction == +1) zoom += ZOOM_STEPS;
else if(direction == -1) zoom -= ZOOM_STEPS;
@ -1256,6 +1263,25 @@ void FrameBuffer::switchVideoMode(int direction)
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::toggleBezel(bool toggle)
{
bool enabled = myOSystem.settings().getBool("bezel.show");
if(toggle)
{
if(myBufferType == BufferType::Emulator &&
(fullScreen() || myOSystem.settings().getBool("bezel.windowed")))
{
enabled = !enabled;
myOSystem.settings().setValue("bezel.show", enabled);
myBezel->load();
applyVideoMode();
}
}
myOSystem.frameBuffer().showTextMessage(enabled ? "Bezel enabled" : "Bezel disabled");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FBInitStatus FrameBuffer::applyVideoMode()
{
@ -1271,7 +1297,8 @@ FBInitStatus FrameBuffer::applyVideoMode()
const bool inTIAMode = myOSystem.eventHandler().inTIAMode();
// Build the new mode based on current settings
const VideoModeHandler::Mode& mode = myVidModeHandler.buildMode(s, inTIAMode);
const VideoModeHandler::Mode& mode
= myVidModeHandler.buildMode(s, inTIAMode, myBezel->info());
if(mode.imageR.size() > mode.screenS)
return FBInitStatus::FailTooLarge;
@ -1295,7 +1322,11 @@ FBInitStatus FrameBuffer::applyVideoMode()
// Inform TIA surface about new mode, and update TIA settings
if(inTIAMode)
{
myTIASurface->initialize(myOSystem.console(), myActiveVidMode);
#ifdef IMAGE_SUPPORT
myBezel->apply();
#endif
myTIASurface->initialize(myOSystem.console(), myActiveVidMode);
if(fullScreen())
myOSystem.settings().setValue("tia.fs_stretch",
myActiveVidMode.stretch == VideoModeHandler::Mode::Stretch::Fill);
@ -1317,18 +1348,19 @@ FBInitStatus FrameBuffer::applyVideoMode()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
float FrameBuffer::maxWindowZoom() const
double FrameBuffer::maxWindowZoom() const
{
const int display = displayId(BufferType::Emulator);
float multiplier = 1;
double multiplier = 1;
for(;;)
{
// Figure out the zoomed size of the window
const uInt32 width = TIAConstants::viewableWidth * multiplier;
const uInt32 height = TIAConstants::viewableHeight * multiplier;
// Figure out the zoomed size of the window (incl. the bezel)
const uInt32 width = static_cast<double>(TIAConstants::viewableWidth) * myBezel->ratioW() * multiplier;
const uInt32 height = static_cast<double>(TIAConstants::viewableHeight) * myBezel->ratioH() * multiplier;
if((width > myAbsDesktopSize[display].w) || (height > myAbsDesktopSize[display].h))
if((width > myAbsDesktopSize[display].w) ||
(height > myAbsDesktopSize[display].h))
break;
multiplier += ZOOM_STEPS;

View File

@ -25,6 +25,7 @@ class Console;
class Settings;
class FBSurface;
class TIASurface;
class Bezel;
#ifdef GUI_SUPPORT
#include "Font.hxx"
@ -53,7 +54,7 @@ class FrameBuffer
{
public:
// Zoom level step interval
static constexpr float ZOOM_STEPS = 0.25;
static constexpr double ZOOM_STEPS = 0.25;
enum UpdateMode {
NONE = 0,
@ -219,8 +220,8 @@ class FrameBuffer
Get the minimum/maximum supported TIA zoom level (windowed mode)
for the framebuffer.
*/
float supportedTIAMinZoom() const { return myTIAMinZoom * hidpiScaleFactor(); }
float supportedTIAMaxZoom() const { return maxWindowZoom(); }
double supportedTIAMinZoom() const { return myTIAMinZoom * hidpiScaleFactor(); }
double supportedTIAMaxZoom() const { return maxWindowZoom(); }
/**
Get the TIA surface associated with the framebuffer.
@ -256,6 +257,11 @@ class FrameBuffer
*/
void switchVideoMode(int direction = +1);
/**
Toggles the bezel display.
*/
void toggleBezel(bool toggle = true);
/**
Sets the state of the cursor (hidden or grabbed) based on the
current mode.
@ -349,6 +355,19 @@ class FrameBuffer
myBackend->getRGB(pixel, r, g, b);
}
/**
This method is called to retrieve the R/G/B/A data from the given pixel.
@param pixel The pixel containing R/G/B data
@param r The red component of the color
@param g The green component of the color
@param b The blue component of the color
@param a The alpha component of the color.
*/
void getRGBA(uInt32 pixel, uInt8* r, uInt8* g, uInt8* b, uInt8* a) const {
myBackend->getRGBA(pixel, r, g, b, a);
}
/**
This method is called to map a given R/G/B triple to the screen palette.
@ -360,6 +379,18 @@ class FrameBuffer
return myBackend->mapRGB(r, g, b);
}
/**
This method is called to map a given R/G/B/A triple to the screen palette.
@param r The red component of the color.
@param g The green component of the color.
@param b The blue component of the color.
@param a The alpha component of the color.
*/
uInt32 mapRGBA(uInt8 r, uInt8 g, uInt8 b, uInt8 a) const {
return myBackend->mapRGBA(r, g, b, a);
}
/**
This method is called to get the specified ARGB data from the viewable
FrameBuffer area. Note that this isn't the same as any internal
@ -399,6 +430,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.
@ -447,7 +486,7 @@ class FrameBuffer
Calculate the maximum level by which the base window can be zoomed and
still fit in the desktop screen.
*/
float maxWindowZoom() const;
double maxWindowZoom() const;
/**
Enables/disables fullscreen mode.
@ -519,7 +558,10 @@ class FrameBuffer
#endif
// The TIASurface class takes responsibility for TIA rendering
unique_ptr<TIASurface> myTIASurface;
shared_ptr<TIASurface> myTIASurface;
// The BezelSurface which blends over the TIA surface
unique_ptr<Bezel> myBezel;
// Used for onscreen messages and frame statistics
// (scanline count and framerate)
@ -546,7 +588,7 @@ class FrameBuffer
vector<bool> myHiDPIEnabled;
// Minimum TIA zoom level that can be used for this framebuffer
float myTIAMinZoom{2.F};
double myTIAMinZoom{2.F};
// Holds a reference to all the surfaces that have been created
std::list<shared_ptr<FBSurface>> mySurfaceList;

View File

@ -208,6 +208,8 @@ bool GlobalKeyHandler::skipAVSetting() const
myOSystem.settings().getInt("tv.scanlines") > 0;
const bool isSoftwareRenderer =
myOSystem.settings().getString("video") == "software";
const bool allowBezel =
myOSystem.settings().getBool("bezel.windowed") || isFullScreen;
return (mySetting == Setting::OVERSCAN && !isFullScreen)
|| (mySetting == Setting::ZOOM && isFullScreen)
@ -223,7 +225,8 @@ bool GlobalKeyHandler::skipAVSetting() const
&& mySetting <= Setting::NTSC_BLEEDING
&& !isCustomFilter)
|| (mySetting == Setting::SCANLINE_MASK && !hasScanlines)
|| (mySetting == Setting::INTERPOLATION && isSoftwareRenderer);
|| (mySetting == Setting::INTERPOLATION && isSoftwareRenderer)
|| (mySetting == Setting::BEZEL && !allowBezel);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -391,6 +394,7 @@ GlobalKeyHandler::SettingData GlobalKeyHandler::getSettingData(const Setting set
{Setting::SCANLINES, {true, std::bind(&TIASurface::changeScanlineIntensity, &myOSystem.frameBuffer().tiaSurface(), _1)}},
{Setting::SCANLINE_MASK, {false, std::bind(&TIASurface::cycleScanlineMask, &myOSystem.frameBuffer().tiaSurface(), _1)}},
{Setting::INTERPOLATION, {false, std::bind(&Console::toggleInter, &myOSystem.console(), _1)}},
{Setting::BEZEL, {false, std::bind(&FrameBuffer::toggleBezel, &myOSystem.frameBuffer(), _1)}},
// *** Input group ***
{Setting::DIGITAL_DEADZONE, {true, std::bind(&PhysicalJoystickHandler::changeDigitalDeadZone, &joyHandler(), _1)}},
{Setting::ANALOG_DEADZONE, {true, std::bind(&PhysicalJoystickHandler::changeAnalogPaddleDeadZone, &joyHandler(), _1)}},

View File

@ -73,6 +73,7 @@ class GlobalKeyHandler
SCANLINES,
SCANLINE_MASK,
INTERPOLATION,
BEZEL,
// *** Input group ***
DIGITAL_DEADZONE,
ANALOG_DEADZONE,
@ -128,7 +129,7 @@ class GlobalKeyHandler
// *** Ranges ***
NUM_ADJ,
START_AV_ADJ = VOLUME,
END_AV_ADJ = INTERPOLATION,
END_AV_ADJ = BEZEL,
START_INPUT_ADJ = DIGITAL_DEADZONE,
END_INPUT_ADJ = MOUSE_RANGE,
START_DEBUG_ADJ = DEVELOPER,

View File

@ -292,24 +292,6 @@ void OSystem::setConfigPaths()
buildDirIfRequired(myCfgDir, myBaseDir, "cfg");
#endif
#ifdef IMAGE_SUPPORT
const string_view ssSaveDir = mySettings->getString("snapsavedir");
if(ssSaveDir == EmptyString)
mySnapshotSaveDir = userDir();
else
mySnapshotSaveDir = FSNode(ssSaveDir);
if(!mySnapshotSaveDir.isDirectory())
mySnapshotSaveDir.makeDir();
const string_view ssLoadDir = mySettings->getString("snaploaddir");
if(ssLoadDir == EmptyString)
mySnapshotLoadDir = userDir();
else
mySnapshotLoadDir = FSNode(ssLoadDir);
if(!mySnapshotLoadDir.isDirectory())
mySnapshotLoadDir.makeDir();
#endif
myCheatFile = myBaseDir; myCheatFile /= "stella.cht";
myPaletteFile = myBaseDir; myPaletteFile /= "stella.pal";
@ -325,11 +307,56 @@ void OSystem::setConfigPaths()
dbgPath("cfg dir ", myCfgDir);
dbgPath("ssave dir ", mySnapshotSaveDir);
dbgPath("sload dir ", mySnapshotLoadDir);
dbgPath("bezel dir ", myBezelDir);
dbgPath("cheat file", myCheatFile);
dbgPath("pal file ", myPaletteFile);
#endif
}
#ifdef IMAGE_SUPPORT
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const FSNode& OSystem::snapshotSaveDir()
{
const string_view ssSaveDir = mySettings->getString("snapsavedir");
if(ssSaveDir == EmptyString)
mySnapshotSaveDir = userDir();
else
mySnapshotSaveDir = FSNode(ssSaveDir);
if(!mySnapshotSaveDir.isDirectory())
mySnapshotSaveDir.makeDir();
return mySnapshotSaveDir;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const FSNode& OSystem::snapshotLoadDir()
{
const string_view ssLoadDir = mySettings->getString("snaploaddir");
if(ssLoadDir == EmptyString)
mySnapshotLoadDir = userDir();
else
mySnapshotLoadDir = FSNode(ssLoadDir);
if(!mySnapshotLoadDir.isDirectory())
mySnapshotLoadDir.makeDir();
return mySnapshotLoadDir;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const FSNode& OSystem::bezelDir()
{
const string_view bezelDir = mySettings->getString("bezel.dir");
if(bezelDir == EmptyString)
myBezelDir = userDir();
else
myBezelDir = FSNode(bezelDir);
if(!myBezelDir.isDirectory())
myBezelDir.makeDir();
return myBezelDir;
}
#endif
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void OSystem::setUserDir(string_view path)
{
@ -726,6 +753,7 @@ unique_ptr<Console> OSystem::openConsole(const FSNode& romfile, string& md5)
CMDLINE_PROPS_UPDATE("ppblend", PropType::Display_PPBlend);
CMDLINE_PROPS_UPDATE("pxcenter", PropType::Controller_PaddlesXCenter);
CMDLINE_PROPS_UPDATE("pycenter", PropType::Controller_PaddlesYCenter);
CMDLINE_PROPS_UPDATE("bezelname", PropType::Bezel_Name);
// Finally, create the cart with the correct properties
if(cart)

View File

@ -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& snapshotSaveDir();
const FSNode& snapshotLoadDir();
const FSNode& bezelDir();
#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;

View File

@ -181,7 +181,8 @@ void Properties::print() const
<< get(PropType::Display_Format) << "|"
<< get(PropType::Display_VCenter) << "|"
<< get(PropType::Display_Phosphor) << "|"
<< get(PropType::Display_PPBlend)
<< get(PropType::Display_PPBlend) << "|"
<< get(PropType::Bezel_Name)
<< endl;
}
@ -241,7 +242,8 @@ void Properties::printHeader()
<< "Display_Format|"
<< "Display_VCenter|"
<< "Display_Phosphor|"
<< "Display_PPBlend"
<< "Display_PPBlend|"
<< "Bezel_Name"
<< endl;
}
@ -276,7 +278,8 @@ std::array<string, Properties::NUM_PROPS> Properties::ourDefaultProperties =
"AUTO", // Display.Format
"0", // Display.VCenter
"NO", // Display.Phosphor
"0" // Display.PPBlend
"0", // Display.PPBlend
"" // Bezel.Name
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -310,5 +313,6 @@ std::array<string, Properties::NUM_PROPS> Properties::ourPropertyNames =
"Display.Format",
"Display.VCenter",
"Display.Phosphor",
"Display.PPBlend"
"Display.PPBlend",
"Bezel.Name"
};

View File

@ -51,6 +51,7 @@ enum class PropType : uInt8 {
Display_VCenter,
Display_Phosphor,
Display_PPBlend,
Bezel_Name,
NumTypes
};

View File

@ -62,6 +62,13 @@ Settings::Settings()
setPermanent("display", 0);
setPermanent("uimessages", "true");
setPermanent("pausedim", "true");
setPermanent("bezel.show", "true");
setPermanent("bezel.windowed", "false");
setPermanent("bezel.win.auto", "true");
setPermanent("bezel.win.left", "12");
setPermanent("bezel.win.right", "12");
setPermanent("bezel.win.top", "0");
setPermanent("bezel.win.bottom", "0");
// TIA specific options
setPermanent("tia.inter", "false");
setPermanent("tia.zoom", "3");
@ -160,6 +167,7 @@ Settings::Settings()
setPermanent("romdir", "");
setPermanent("userdir", "");
setPermanent("saveuserdir", "false");
setPermanent("bezel.dir", "");
// ROM browser options
setPermanent("exitlauncher", "false");
@ -534,10 +542,18 @@ void Settings::usage()
<< " -detectpal60 <1|0> Enable PAL-60 autodetection\n"
<< " -detectntsc50 <1|0> Enable NTSC-50 autodetection\n"
<< endl
<< " -speed <number> Run emulation at the given speed\n"
<< " -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"
<< " -speed <number> Run emulation at the given speed\n"
<< " -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"
<< endl
<< " -bezel.show <1|0> Show bezel around emulation window\n"
<< " -bezel.windowed <1|0> Show bezel in windowed modes\n"
<< " -bezel.win.auto <1|0> Automatically set bezel window position\n"
<< " -bezel.win.left <0-40> Set left bezel window position [%]\n"
<< " -bezel.win.right <0-40> Set right bezel window position [%]\n"
<< " -bezel.win.top <0-40> Set top bezel window position [%]\n"
<< " -bezel.win.bottom <0-40> Set bottom bezel window position [%]\n"
<< endl
#ifdef SOUND_SUPPORT
<< " -audio.enabled <1|0> Enable audio\n"
@ -655,6 +671,7 @@ void Settings::usage()
<< " -followlauncher <0|1> Default ROM path follows launcher navigation\n"
<< " -userdir <dir> Set the path to save user files to\n"
<< " -saveuserdir <0|1> Update user path when navigating in browser\n"
<< " -bezel.dir <dir> Set the path to load bezels from\n"
<< " -lastrom <name> Last played ROM, automatically selected in\n"
<< " launcher\n"
<< " -romloadcount <number> Number of ROM to load next from multicard\n"
@ -746,6 +763,7 @@ void Settings::usage()
<< " -vcenter <arg> Sets the 'Display.vcenter' property\n"
<< " -pp <arg> Sets the 'Display.Phosphor' property\n"
<< " -ppblend <arg> Sets the 'Display.PPBlend' property\n"
<< " -bezelname <arg> Sets the 'Bezel.Name' property\n"
<< endl
#endif

View File

@ -432,7 +432,7 @@ void TIASurface::createScanlineSurface()
mySLineSurface = myFB.allocateSurface(width, height,
interpolationModeFromSettings(myOSystem.settings()), data.data());
mySLineSurface->setSrcSize(mySLineSurface->width(), height);
//mySLineSurface->setSrcSize(mySLineSurface->width(), height);
mySLineSurface->setDstRect(myTiaSurface->dstRect());
updateSurfaceSettings();

View File

@ -2,7 +2,7 @@ MODULE := src/emucore
MODULE_OBJS := \
src/emucore/AtariVox.o \
src/emucore/Bankswitch.o \
src/emucore/Bankswitch.o \
src/emucore/Booster.o \
src/emucore/Cart.o \
src/emucore/CartARM.o \

File diff suppressed because it is too large Load Diff

View File

@ -1410,7 +1410,7 @@ void DeveloperDialog::handleDebugColours(int idx, int color)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DeveloperDialog::handleDebugColours(string_view colors)
{
for(int i = 0; i < DEBUG_COLORS; ++i)
for(int i = 0; i < DEBUG_COLORS && i < colors.length(); ++i)
{
switch(colors[i])
{

View File

@ -269,7 +269,7 @@ void DialogContainer::handleMouseButtonEvent(MouseButton b, bool pressed,
if(b == MouseButton::MIDDLE)
{
// Middle mouse button emulates lef mouse button double click
// Middle mouse button emulates left mouse button double click
myLastClick.count = 2;
b = MouseButton::LEFT;
}

View File

@ -482,6 +482,13 @@ void GameInfoDialog::addCartridgeTab()
buttonWidth(">>"), myUrl->getHeight(), ">>", kLinkPressed);
wid.push_back(myUrl);
ypos += lineHeight + VGAP;
new StaticTextWidget(myTab, _font, xpos, ypos + 1, lwidth, fontHeight, "Bezelname");
myBezelName = new EditTextWidget(myTab, _font, xpos + lwidth, ypos - 1,
fwidth, lineHeight, "");
myBezelName->setToolTip("Define the name of the bezel file.");
wid.push_back(myBezelName);
// Add items for tab 3
addToFocusList(wid, myTab, tabID);
@ -870,6 +877,7 @@ void GameInfoDialog::loadCartridgeProperties(const Properties& props)
myRarity->setText(props.get(PropType::Cart_Rarity));
myNote->setText(props.get(PropType::Cart_Note));
myUrl->setText(props.get(PropType::Cart_Url));
myBezelName->setText(props.get(PropType::Bezel_Name));
updateLink();
}
@ -986,6 +994,7 @@ void GameInfoDialog::saveProperties()
myGameProperties.set(PropType::Cart_Rarity, myRarity->getText());
myGameProperties.set(PropType::Cart_Note, myNote->getText());
myGameProperties.set(PropType::Cart_Url, myUrl->getText());
myGameProperties.set(PropType::Bezel_Name, myBezelName->getText());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -139,6 +139,7 @@ class GameInfoDialog : public Dialog, public CommandSender
EditTextWidget* myNote{nullptr};
ButtonWidget* myUrlButton{nullptr};
EditTextWidget* myUrl{nullptr};
EditTextWidget* myBezelName{nullptr};
// High Scores properties
CheckboxWidget* myHighScores{nullptr};

View File

@ -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,
@ -316,10 +316,10 @@ UIDialog::UIDialog(OSystem& osystem, DialogContainer& parent,
// Add message concerning usage
xpos = HBORDER;
ypos = myTab->getHeight() - fontHeight - ifont.getFontHeight() - VGAP - VBORDER;
lwidth = ifont.getStringWidth("(*) Changes require an application restart");
lwidth = ifont.getStringWidth("(*) Changes may require an application restart");
new StaticTextWidget(myTab, ifont, xpos, ypos,
std::min(lwidth, _w - HBORDER * 2), ifont.getFontHeight(),
"(*) Changes require an application restart");
"(*) Changes may require an application restart");
// Add items for tab 1
addToFocusList(wid, myTab, tabID);

View File

@ -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<uInt32>(NTSCFilter::Preset::OFF));
@ -438,6 +440,91 @@ 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);
ypos += lineHeight + VGAP * 3;
myBezelShowWindowed = new CheckboxWidget(myTab, _font, xpos, ypos,
"Windowed modes");
myBezelShowWindowed->setToolTip("Enable bezels in windowed modes as well.");
wid.push_back(myBezelShowWindowed);
// Disable auto borders
ypos += lineHeight + VGAP * 1;
myManualWindow = new CheckboxWidget(myTab, _font, xpos, ypos,
"Manual emulation window", kAutoWindowChanged);
myManualWindow->setToolTip("Enable if automatic window detection fails.");
wid.push_back(myManualWindow);
xpos += INDENT;
const int lWidth = _font.getStringWidth("Bottom ");
const int sWidth = myBezelPath->getRight() - xpos - lWidth - 4.5 * fontWidth; // _w - HBORDER - xpos - lwidth;
ypos += lineHeight + VGAP * 1;
myWinLeftSlider = new SliderWidget(myTab, _font, xpos, ypos, sWidth, lineHeight,
"Left ", 0, 0, 4 * fontWidth, "%");
myWinLeftSlider->setMinValue(0); myWinLeftSlider->setMaxValue(40);
myWinLeftSlider->setTickmarkIntervals(4);
wid.push_back(myWinLeftSlider);
ypos += lineHeight + VGAP * 1;
myWinRightSlider = new SliderWidget(myTab, _font, xpos, ypos, sWidth, lineHeight,
"Right ", 0, 0, 4 * fontWidth, "%");
myWinRightSlider->setMinValue(0); myWinRightSlider->setMaxValue(40);
myWinRightSlider->setTickmarkIntervals(4);
wid.push_back(myWinRightSlider);
ypos += lineHeight + VGAP * 1;
myWinTopSlider = new SliderWidget(myTab, _font, xpos, ypos, sWidth, lineHeight,
"Top ", 0, 0, 4 * fontWidth, "%");
myWinTopSlider->setMinValue(0); myWinTopSlider->setMaxValue(40);
myWinTopSlider->setTickmarkIntervals(4);
wid.push_back(myWinTopSlider);
ypos += lineHeight + VGAP;
myWinBottomSlider = new SliderWidget(myTab, _font, xpos, ypos, sWidth, lineHeight,
"Bottom ", 0, 0, 4 * fontWidth, "%");
myWinBottomSlider->setMinValue(0); myWinBottomSlider->setMaxValue(40);
myWinBottomSlider->setTickmarkIntervals(4);
wid.push_back(myWinBottomSlider);
// Add items for tab 4
addToFocusList(wid, myTab, tabID);
myTab->parentWidget(tabID)->setHelpAnchor("TODO???");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void VideoAudioDialog::addAudioTab()
{
@ -450,7 +537,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 +547,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 +650,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");
@ -630,7 +717,6 @@ void VideoAudioDialog::loadConfig()
myPhaseShift->setTickmarkIntervals(4);
myPhaseShift->setToolTip("Adjust PAL phase shift of 'Custom' palette.");
myPhaseShift->setValue(myPaletteAdj.phasePal);
}
else
{
@ -679,6 +765,18 @@ void VideoAudioDialog::loadConfig()
myTVScanIntense->setValue(settings.getInt("tv.scanlines"));
myTVScanMask->setSelected(settings.getString("tv.scanmask"), TIASurface::SETTING_STANDARD);
/////////////////////////////////////////////////////////////////////////////
// Bezel tab
myBezelEnableCheckbox->setState(settings.getBool("bezel.show"));
myBezelPath->setText(settings.getString("bezel.dir"));
myBezelShowWindowed->setState(settings.getBool("bezel.windowed"));
myManualWindow->setState(!settings.getBool("bezel.win.auto"));
myWinLeftSlider->setValue(settings.getInt("bezel.win.left"));
myWinRightSlider->setValue(settings.getInt("bezel.win.right"));
myWinTopSlider->setValue(settings.getInt("bezel.win.top"));
myWinBottomSlider->setValue(settings.getInt("bezel.win.bottom"));
handleBezelChange();
/////////////////////////////////////////////////////////////////////////////
// Audio tab
AudioSettings& audioSettings = instance().audioSettings();
@ -709,7 +807,7 @@ void VideoAudioDialog::loadConfig()
updateSettingsWithPreset(instance().audioSettings());
updateEnabledState();
updateAudioEnabledState();
myTab->loadConfig();
}
@ -794,8 +892,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());
@ -803,6 +900,18 @@ void VideoAudioDialog::saveConfig()
settings.setValue("tv.scanlines", myTVScanIntense->getValueLabel());
settings.setValue("tv.scanmask", myTVScanMask->getSelectedTag());
/////////////////////////////////////////////////////////////////////////////
// Bezel tab
settings.setValue("bezel.show", myBezelEnableCheckbox->getState());
settings.setValue("bezel.dir", myBezelPath->getText());
settings.setValue("bezel.windowed", myBezelShowWindowed->getState());
settings.setValue("bezel.win.auto", !myManualWindow->getState());
settings.setValue("bezel.win.left", myWinLeftSlider->getValueLabel());
settings.setValue("bezel.win.right", myWinRightSlider->getValueLabel());
settings.setValue("bezel.win.top", myWinTopSlider->getValueLabel());
settings.setValue("bezel.win.bottom", myWinBottomSlider->getValueLabel());
// Note: The following has to happen after all video related setting have been saved
if(instance().hasConsole())
{
instance().console().setTIAProperties();
@ -936,7 +1045,15 @@ void VideoAudioDialog::setDefaults()
loadTVAdjustables(NTSCFilter::Preset::CUSTOM);
break;
}
case 3: // Audio
case 3: // Bezels
myBezelEnableCheckbox->setState(true);
myBezelPath->setText(instance().userDir().getShortPath());
myBezelShowWindowed->setState(false);
myManualWindow->setState(false);
handleBezelChange();
break;
case 4: // Audio
mySoundEnableCheckbox->setState(AudioSettings::DEFAULT_ENABLED);
myVolumeSlider->setValue(AudioSettings::DEFAULT_VOLUME);
myDevicePopup->setSelected(AudioSettings::DEFAULT_DEVICE);
@ -953,7 +1070,7 @@ void VideoAudioDialog::setDefaults()
}
else updatePreset();
updateEnabledState();
updateAudioEnabledState();
break;
default: // satisfy compiler
@ -1098,6 +1215,21 @@ void VideoAudioDialog::handlePhosphorChange()
myTVPhosLevel->setEnabled(myTVPhosphor->getState());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void VideoAudioDialog::handleBezelChange()
{
const bool enable = myBezelEnableCheckbox->getState();
const bool nonAuto = myManualWindow->getState();
myOpenBrowserButton->setEnabled(enable);
myBezelPath->setEnabled(enable);
myBezelShowWindowed->setEnabled(enable);
myWinLeftSlider->setEnabled(enable && nonAuto);
myWinRightSlider->setEnabled(enable && nonAuto);
myWinTopSlider->setEnabled(enable && nonAuto);
myWinBottomSlider->setEnabled(enable && nonAuto);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void VideoAudioDialog::handleCommand(CommandSender* sender, int cmd,
int data, int id)
@ -1213,13 +1345,27 @@ void VideoAudioDialog::handleCommand(CommandSender* sender, int cmd,
myTVPhosLevel->setValueUnit("%");
break;
case kBezelEnableChanged:
case kAutoWindowChanged:
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 +1446,7 @@ void VideoAudioDialog::colorPalette()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void VideoAudioDialog::updateEnabledState()
void VideoAudioDialog::updateAudioEnabledState()
{
const bool active = mySoundEnableCheckbox->getState();
const auto preset = static_cast<AudioSettings::Preset>

View File

@ -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,17 @@ class VideoAudioDialog : public Dialog
std::array<StaticTextWidget*, 16> myColorLbl{nullptr};
ColorWidget* myColor[16][8]{{nullptr}};
// Bezels
CheckboxWidget* myBezelEnableCheckbox{nullptr};
ButtonWidget* myOpenBrowserButton{nullptr};
EditTextWidget* myBezelPath{nullptr};
CheckboxWidget* myBezelShowWindowed{nullptr};
CheckboxWidget* myManualWindow{nullptr};
SliderWidget* myWinLeftSlider{nullptr};
SliderWidget* myWinRightSlider{nullptr};
SliderWidget* myWinTopSlider{nullptr};
SliderWidget* myWinBottomSlider{nullptr};
// Audio
CheckboxWidget* mySoundEnableCheckbox{nullptr};
SliderWidget* myVolumeSlider{nullptr};
@ -163,6 +181,10 @@ class VideoAudioDialog : public Dialog
kPhosBlendChanged = 'VDbl',
kScanlinesChanged = 'VDsc',
kBezelEnableChanged = 'BZen',
kChooseBezelDirCmd = 'BZsl',
kAutoWindowChanged = 'BZab',
kSoundEnableChanged = 'ADse',
kDeviceChanged = 'ADdc',
kModeChanged = 'ADmc',

View File

@ -47,6 +47,9 @@ class FBBackendLIBRETRO : public FBBackend
uInt32 mapRGB(uInt8 r, uInt8 g, uInt8 b) const override {
return (r << 16) | (g << 8) | b;
}
uInt32 mapRGBA(uInt8 r, uInt8 g, uInt8 b, uInt8 a) const override {
return (a << 24) | (r << 16) | (g << 8) | b;
}
/**
This method is called to query and initialize the video hardware

View File

@ -734,6 +734,7 @@
<ClCompile Include="..\..\common\audio\LanczosResampler.cxx" />
<ClCompile Include="..\..\common\audio\SimpleResampler.cxx" />
<ClCompile Include="..\..\common\Base.cxx" />
<ClCompile Include="..\..\common\Bezel.cxx" />
<ClCompile Include="..\..\common\DevSettingsHandler.cxx" />
<ClCompile Include="..\..\common\EventHandlerSDL2.cxx" />
<ClCompile Include="..\..\common\FBBackendSDL2.cxx" />
@ -2050,6 +2051,7 @@
<ClInclude Include="..\..\common\audio\Resampler.hxx" />
<ClInclude Include="..\..\common\audio\SimpleResampler.hxx" />
<ClInclude Include="..\..\common\Base.hxx" />
<ClInclude Include="..\..\common\Bezel.hxx" />
<ClInclude Include="..\..\common\bspf.hxx" />
<ClInclude Include="..\..\common\DevSettingsHandler.hxx" />
<ClInclude Include="..\..\common\EventHandlerSDL2.hxx" />

View File

@ -1209,6 +1209,9 @@
<ClCompile Include="..\..\debugger\gui\Cart03E0Widget.cxx">
<Filter>Source Files\debugger\gui</Filter>
</ClCompile>
<ClCompile Include="..\..\common\Bezel.cxx">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\common\bspf.hxx">
@ -2465,6 +2468,9 @@
<ClInclude Include="..\..\debugger\gui\Cart03E0Widget.hxx">
<Filter>Header Files\debugger\gui</Filter>
</ClInclude>
<ClInclude Include="..\..\common\Bezel.hxx">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="stella.ico">

View File

@ -31,7 +31,8 @@ my %prop_type = (
"Display.Format" => 25,
"Display.VCenter" => 26,
"Display.Phosphor" => 27,
"Display.PPBlend" => 28
"Display.PPBlend" => 28,
"Bezel.Name" => 29
);
my @prop_type_as_string = (
"Cart.MD5",
@ -62,7 +63,8 @@ my @prop_type_as_string = (
"Display.Format",
"Display.VCenter",
"Display.Phosphor",
"Display.PPBlend"
"Display.PPBlend",
"Bezel.Name"
);
my @prop_defaults = (
@ -94,7 +96,8 @@ my @prop_defaults = (
"AUTO", # Display.Format
"0", # Display.VCenter
"NO", # Display.Phosphor
"0" # Display.PPBlend
"0", # Display.PPBlend
"" # Bezel.Name
);
# Load and parse a properties file into an hash table of property

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: 549 KiB