mirror of https://github.com/stella-emu/stella.git
1285 lines
38 KiB
C++
1285 lines
38 KiB
C++
//============================================================================
|
|
//
|
|
// 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-2014 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.
|
|
//
|
|
// $Id$
|
|
//============================================================================
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
|
|
#include "bspf.hxx"
|
|
|
|
#include "CommandMenu.hxx"
|
|
#include "Console.hxx"
|
|
#include "EventHandler.hxx"
|
|
#include "Event.hxx"
|
|
#include "Font.hxx"
|
|
#include "StellaFont.hxx"
|
|
#include "StellaMediumFont.hxx"
|
|
#include "StellaLargeFont.hxx"
|
|
#include "ConsoleFont.hxx"
|
|
#include "Launcher.hxx"
|
|
#include "Menu.hxx"
|
|
#include "OSystem.hxx"
|
|
#include "Settings.hxx"
|
|
#include "TIA.hxx"
|
|
|
|
#include "FrameBuffer.hxx"
|
|
|
|
#ifdef DEBUGGER_SUPPORT
|
|
#include "Debugger.hxx"
|
|
#endif
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::FrameBuffer(OSystem* osystem)
|
|
: myOSystem(osystem),
|
|
myRedrawEntireFrame(true),
|
|
myUsePhosphor(false),
|
|
myPhosphorBlend(77),
|
|
myInitializedCount(0),
|
|
myPausedCount(0)
|
|
{
|
|
myMsg.surface = myStatsMsg.surface = NULL;
|
|
myMsg.enabled = myStatsMsg.enabled = false;
|
|
|
|
// Load NTSC filter settings
|
|
myNTSCFilter.loadConfig(myOSystem->settings());
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::~FrameBuffer(void)
|
|
{
|
|
delete myFont;
|
|
delete myInfoFont;
|
|
delete mySmallFont;
|
|
delete myLauncherFont;
|
|
|
|
// Free all allocated surfaces
|
|
while(!mySurfaceList.empty())
|
|
{
|
|
delete (*mySurfaceList.begin()).second;
|
|
mySurfaceList.erase(mySurfaceList.begin());
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool FrameBuffer::initialize()
|
|
{
|
|
// Get desktop resolution and supported renderers
|
|
uInt32 query_w, query_h;
|
|
queryHardware(query_w, query_h, myRenderers);
|
|
|
|
// Check the 'maxres' setting, which is an undocumented developer feature
|
|
// that specifies the desktop size (not normally set)
|
|
const GUI::Size& s = myOSystem->settings().getSize("maxres");
|
|
if(s.w > 0 && s.h > 0)
|
|
{
|
|
query_w = s.w;
|
|
query_h = s.h;
|
|
}
|
|
// Various parts of the codebase assume a minimum screen size
|
|
myDesktopSize.w = BSPF_max(query_w, (uInt32)kFBMinW);
|
|
myDesktopSize.h = BSPF_max(query_h, (uInt32)kFBMinH);
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Create fonts to draw text
|
|
// NOTE: the logic determining appropriate font sizes is done here,
|
|
// so that the UI classes can just use the font they expect,
|
|
// and not worry about it
|
|
// This logic should also take into account the size of the
|
|
// framebuffer, and try to be intelligent about font sizes
|
|
// We can probably add ifdefs to take care of corner cases,
|
|
// but that means we've failed to abstract it enough ...
|
|
////////////////////////////////////////////////////////////////////
|
|
bool smallScreen = myDesktopSize.w < (uInt32)kFBMinW ||
|
|
myDesktopSize.h < (uInt32)kFBMinH;
|
|
|
|
// This font is used in a variety of situations when a really small
|
|
// font is needed; we let the specific widget/dialog decide when to
|
|
// use it
|
|
mySmallFont = new GUI::Font(GUI::stellaDesc);
|
|
|
|
// The general font used in all UI elements
|
|
// This is determined by the size of the framebuffer
|
|
myFont = new GUI::Font(smallScreen ? GUI::stellaDesc : GUI::stellaMediumDesc);
|
|
|
|
// The info font used in all UI elements
|
|
// This is determined by the size of the framebuffer
|
|
myInfoFont = new GUI::Font(smallScreen ? GUI::stellaDesc : GUI::consoleDesc);
|
|
|
|
// The font used by the ROM launcher
|
|
// Normally, this is configurable by the user, except in the case of
|
|
// very small screens
|
|
if(!smallScreen)
|
|
{
|
|
const string& lf = myOSystem->settings().getString("launcherfont");
|
|
if(lf == "small")
|
|
myLauncherFont = new GUI::Font(GUI::consoleDesc);
|
|
else if(lf == "medium")
|
|
myLauncherFont = new GUI::Font(GUI::stellaMediumDesc);
|
|
else
|
|
myLauncherFont = new GUI::Font(GUI::stellaLargeDesc);
|
|
}
|
|
else
|
|
myLauncherFont = new GUI::Font(GUI::stellaDesc);
|
|
|
|
// Determine possible TIA windowed zoom levels
|
|
uInt32 maxZoom = maxWindowSizeForScreen((uInt32)kTIAMinW, (uInt32)kTIAMinH,
|
|
myDesktopSize.w, myDesktopSize.h);
|
|
|
|
// Figure our the smallest zoom level we can use
|
|
uInt32 firstZoom = smallScreen ? 1 : 2;
|
|
for(uInt32 zoom = firstZoom; zoom <= maxZoom; ++zoom)
|
|
{
|
|
ostringstream desc;
|
|
desc << "Zoom " << zoom << "x";
|
|
myTIAZoomLevels.push_back(desc.str(), zoom);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FBInitStatus FrameBuffer::createDisplay(const string& title,
|
|
uInt32 width, uInt32 height)
|
|
{
|
|
myInitializedCount++;
|
|
myScreenTitle = title;
|
|
|
|
// A 'windowed' system is defined as one where the window size can be
|
|
// larger than the screen size, as there's some sort of window manager
|
|
// that takes care of it (all current desktop systems fall in this category)
|
|
// However, some systems have no concept of windowing, and have hard limits
|
|
// on how large a window can be (ie, the size of the 'desktop' is the
|
|
// absolute upper limit on window size)
|
|
//
|
|
// If the WINDOWED_SUPPORT macro is defined, we treat the system as the
|
|
// former type; if not, as the latter type
|
|
|
|
bool useFullscreen = false;
|
|
#ifdef WINDOWED_SUPPORT
|
|
// We assume that a desktop of at least minimum acceptable size means that
|
|
// we're running on a 'large' system, and the window size requirements
|
|
// can be relaxed
|
|
// Otherwise, we treat the system as if WINDOWED_SUPPORT is not defined
|
|
if(myDesktopSize.w < (uInt32)kFBMinW &&
|
|
myDesktopSize.h < (uInt32)kFBMinH &&
|
|
(myDesktopSize.w < width || myDesktopSize.h < height))
|
|
return kFailTooLarge;
|
|
|
|
// FIXSDL - remove size limitations here?
|
|
if(myOSystem->settings().getString("fullscreen") == "1")
|
|
{
|
|
if(myDesktopSize.w < width || myDesktopSize.h < height)
|
|
return kFailTooLarge;
|
|
|
|
useFullscreen = true;
|
|
}
|
|
else
|
|
useFullscreen = false;
|
|
#else
|
|
// Make sure this mode is even possible
|
|
// We only really need to worry about it in non-windowed environments,
|
|
// where requesting a window that's too large will probably cause a crash
|
|
if(myDesktopSize.w < width || myDesktopSize.h < height)
|
|
return kFailTooLarge;
|
|
#endif
|
|
|
|
// Set the available video modes for this framebuffer
|
|
setAvailableVidModes(width, height);
|
|
|
|
// Initialize video subsystem (make sure we get a valid mode)
|
|
string pre_about = about();
|
|
const VideoMode& mode = getSavedVidMode(useFullscreen);
|
|
myImageRect = mode.image;
|
|
myScreenSize = mode.screen;
|
|
if(width <= myScreenSize.w && height <= myScreenSize.h)
|
|
{
|
|
if(setVideoMode(myScreenTitle, mode, useFullscreen))
|
|
{
|
|
// Did we get the requested fullscreen state?
|
|
myOSystem->settings().setValue("fullscreen", fullScreen());
|
|
setCursorState();
|
|
}
|
|
else
|
|
{
|
|
myOSystem->logMessage("ERROR: Couldn't initialize video subsystem", 0);
|
|
return kFailNotSupported;
|
|
}
|
|
}
|
|
else
|
|
return kFailTooLarge;
|
|
|
|
// Erase any messages from a previous run
|
|
myMsg.counter = 0;
|
|
|
|
// Create surfaces for TIA statistics and general messages
|
|
myStatsMsg.color = kBtnTextColor;
|
|
myStatsMsg.w = infoFont().getMaxCharWidth() * 24 + 2;
|
|
myStatsMsg.h = (infoFont().getFontHeight() + 2) * 2;
|
|
|
|
if(myStatsMsg.surface == NULL)
|
|
{
|
|
uInt32 surfaceID = allocateSurface(myStatsMsg.w, myStatsMsg.h);
|
|
myStatsMsg.surface = surface(surfaceID);
|
|
}
|
|
if(myMsg.surface == NULL)
|
|
{
|
|
uInt32 surfaceID = allocateSurface((uInt32)kFBMinW, font().getFontHeight()+10);
|
|
myMsg.surface = surface(surfaceID);
|
|
}
|
|
|
|
// Take care of some items that are only done once per framebuffer creation.
|
|
if(myInitializedCount == 1)
|
|
{
|
|
myOSystem->logMessage(about(), 1);
|
|
setUIPalette();
|
|
setWindowIcon();
|
|
}
|
|
else
|
|
{
|
|
string post_about = about();
|
|
if(post_about != pre_about)
|
|
myOSystem->logMessage(post_about, 1);
|
|
}
|
|
|
|
return kSuccess;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::update()
|
|
{
|
|
// Determine which mode we are in (from the EventHandler)
|
|
// Take care of S_EMULATE mode here, otherwise let the GUI
|
|
// figure out what to draw
|
|
switch(myOSystem->eventHandler().state())
|
|
{
|
|
case EventHandler::S_EMULATE:
|
|
{
|
|
// Run the console for one frame
|
|
// Note that the debugger can cause a breakpoint to occur, which changes
|
|
// the EventHandler state 'behind our back' - we need to check for that
|
|
myOSystem->console().tia().update();
|
|
#ifdef DEBUGGER_SUPPORT
|
|
if(myOSystem->eventHandler().state() != EventHandler::S_EMULATE) break;
|
|
#endif
|
|
if(myOSystem->eventHandler().frying())
|
|
myOSystem->console().fry();
|
|
|
|
// And update the screen
|
|
drawTIA(myRedrawEntireFrame);
|
|
|
|
// Show frame statistics
|
|
if(myStatsMsg.enabled)
|
|
{
|
|
const ConsoleInfo& info = myOSystem->console().about();
|
|
char msg[30];
|
|
BSPF_snprintf(msg, 30, "%3u @ %3.2ffps => %s",
|
|
myOSystem->console().tia().scanlines(),
|
|
myOSystem->console().getFramerate(), info.DisplayFormat.c_str());
|
|
myStatsMsg.surface->fillRect(0, 0, myStatsMsg.w, myStatsMsg.h, kBGColor);
|
|
myStatsMsg.surface->drawString(infoFont(),
|
|
msg, 1, 1, myStatsMsg.w, myStatsMsg.color, kTextAlignLeft);
|
|
myStatsMsg.surface->drawString(infoFont(),
|
|
info.BankSwitch, 1, 15, myStatsMsg.w, myStatsMsg.color, kTextAlignLeft);
|
|
myStatsMsg.surface->addDirtyRect(0, 0, 0, 0); // force a full draw
|
|
myStatsMsg.surface->setPos(myImageRect.x() + 1, myImageRect.y() + 1);
|
|
myStatsMsg.surface->update();
|
|
}
|
|
break; // S_EMULATE
|
|
}
|
|
|
|
case EventHandler::S_PAUSE:
|
|
{
|
|
// Only update the screen if it's been invalidated
|
|
if(myRedrawEntireFrame)
|
|
drawTIA(true);
|
|
|
|
// Show a pause message every 5 seconds
|
|
if(myPausedCount++ >= 7*myOSystem->frameRate())
|
|
{
|
|
myPausedCount = 0;
|
|
showMessage("Paused", kMiddleCenter);
|
|
}
|
|
break; // S_PAUSE
|
|
}
|
|
|
|
case EventHandler::S_MENU:
|
|
{
|
|
// When onscreen messages are enabled in double-buffer mode,
|
|
// a full redraw is required
|
|
myOSystem->menu().draw(myMsg.enabled && isDoubleBuffered());
|
|
break; // S_MENU
|
|
}
|
|
|
|
case EventHandler::S_CMDMENU:
|
|
{
|
|
// When onscreen messages are enabled in double-buffer mode,
|
|
// a full redraw is required
|
|
myOSystem->commandMenu().draw(myMsg.enabled && isDoubleBuffered());
|
|
break; // S_CMDMENU
|
|
}
|
|
|
|
case EventHandler::S_LAUNCHER:
|
|
{
|
|
// When onscreen messages are enabled in double-buffer mode,
|
|
// a full redraw is required
|
|
myOSystem->launcher().draw(myMsg.enabled && isDoubleBuffered());
|
|
break; // S_LAUNCHER
|
|
}
|
|
|
|
#ifdef DEBUGGER_SUPPORT
|
|
case EventHandler::S_DEBUGGER:
|
|
{
|
|
// When onscreen messages are enabled in double-buffer mode,
|
|
// a full redraw is required
|
|
myOSystem->debugger().draw(myMsg.enabled && isDoubleBuffered());
|
|
break; // S_DEBUGGER
|
|
}
|
|
#endif
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// Draw any pending messages
|
|
if(myMsg.enabled)
|
|
drawMessage();
|
|
|
|
// Do any post-frame stuff
|
|
postFrameUpdate();
|
|
|
|
// The frame doesn't need to be completely redrawn anymore
|
|
myRedrawEntireFrame = false;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::showMessage(const string& message, MessagePosition position,
|
|
bool force)
|
|
{
|
|
// Only show messages if they've been enabled
|
|
if(!(force || myOSystem->settings().getBool("uimessages")))
|
|
return;
|
|
|
|
// Erase old messages on the screen
|
|
if(myMsg.counter > 0)
|
|
{
|
|
myRedrawEntireFrame = true;
|
|
refresh();
|
|
}
|
|
|
|
// Precompute the message coordinates
|
|
myMsg.text = message;
|
|
myMsg.counter = uInt32(myOSystem->frameRate()) << 1; // Show message for 2 seconds
|
|
myMsg.color = kBtnTextColor;
|
|
|
|
myMsg.w = font().getStringWidth(myMsg.text) + 10;
|
|
myMsg.h = font().getFontHeight() + 8;
|
|
myMsg.surface->setWidth(myMsg.w);
|
|
myMsg.surface->setHeight(myMsg.h);
|
|
myMsg.position = position;
|
|
myMsg.enabled = true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::toggleFrameStats()
|
|
{
|
|
showFrameStats(!myOSystem->settings().getBool("stats"));
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::showFrameStats(bool enable)
|
|
{
|
|
myOSystem->settings().setValue("stats", enable);
|
|
myStatsMsg.enabled = enable;
|
|
refresh();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::enableMessages(bool enable)
|
|
{
|
|
if(enable)
|
|
{
|
|
// Only re-enable frame stats if they were already enabled before
|
|
myStatsMsg.enabled = myOSystem->settings().getBool("stats");
|
|
}
|
|
else
|
|
{
|
|
// Temporarily disable frame stats
|
|
myStatsMsg.enabled = false;
|
|
|
|
// Erase old messages on the screen
|
|
myMsg.enabled = false;
|
|
myMsg.counter = 0;
|
|
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
inline void FrameBuffer::drawMessage()
|
|
{
|
|
// Draw the bounded box and text
|
|
switch(myMsg.position)
|
|
{
|
|
case kTopLeft:
|
|
myMsg.x = 5;
|
|
myMsg.y = 5;
|
|
break;
|
|
|
|
case kTopCenter:
|
|
myMsg.x = (myImageRect.width() - myMsg.w) >> 1;
|
|
myMsg.y = 5;
|
|
break;
|
|
|
|
case kTopRight:
|
|
myMsg.x = myImageRect.width() - myMsg.w - 5;
|
|
myMsg.y = 5;
|
|
break;
|
|
|
|
case kMiddleLeft:
|
|
myMsg.x = 5;
|
|
myMsg.y = (myImageRect.height() - myMsg.h) >> 1;
|
|
break;
|
|
|
|
case kMiddleCenter:
|
|
myMsg.x = (myImageRect.width() - myMsg.w) >> 1;
|
|
myMsg.y = (myImageRect.height() - myMsg.h) >> 1;
|
|
break;
|
|
|
|
case kMiddleRight:
|
|
myMsg.x = myImageRect.width() - myMsg.w - 5;
|
|
myMsg.y = (myImageRect.height() - myMsg.h) >> 1;
|
|
break;
|
|
|
|
case kBottomLeft:
|
|
myMsg.x = 5;
|
|
myMsg.y = myImageRect.height() - myMsg.h - 5;
|
|
break;
|
|
|
|
case kBottomCenter:
|
|
myMsg.x = (myImageRect.width() - myMsg.w) >> 1;
|
|
myMsg.y = myImageRect.height() - myMsg.h - 5;
|
|
break;
|
|
|
|
case kBottomRight:
|
|
myMsg.x = myImageRect.width() - myMsg.w - 5;
|
|
myMsg.y = myImageRect.height() - myMsg.h - 5;
|
|
break;
|
|
}
|
|
|
|
myMsg.surface->setPos(myMsg.x + myImageRect.x(), myMsg.y + myImageRect.y());
|
|
myMsg.surface->fillRect(1, 1, myMsg.w-2, myMsg.h-2, kBtnColor);
|
|
myMsg.surface->box(0, 0, myMsg.w, myMsg.h, kColor, kShadowColor);
|
|
myMsg.surface->drawString(font(), myMsg.text, 4, 4,
|
|
myMsg.w, myMsg.color, kTextAlignLeft);
|
|
myMsg.counter--;
|
|
|
|
// Either erase the entire message (when time is reached),
|
|
// or show again this frame
|
|
if(myMsg.counter == 0) // Force an immediate update
|
|
{
|
|
myMsg.enabled = false;
|
|
refresh();
|
|
}
|
|
else
|
|
{
|
|
myMsg.surface->addDirtyRect(0, 0, 0, 0);
|
|
myMsg.surface->update();
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::refresh()
|
|
{
|
|
// This method partly duplicates the behaviour in ::update()
|
|
// Here, however, make sure to redraw *all* surfaces applicable to the
|
|
// current EventHandler state
|
|
// We also check for double-buffered modes, and when present
|
|
// update both buffers accordingly
|
|
//
|
|
// This method is in essence a FULL refresh, putting all rendering
|
|
// buffers in a known, fully redrawn state
|
|
|
|
switch(myOSystem->eventHandler().state())
|
|
{
|
|
case EventHandler::S_EMULATE:
|
|
case EventHandler::S_PAUSE:
|
|
invalidate();
|
|
drawTIA(true);
|
|
if(isDoubleBuffered())
|
|
{
|
|
postFrameUpdate();
|
|
invalidate();
|
|
drawTIA(true);
|
|
}
|
|
break;
|
|
|
|
case EventHandler::S_MENU:
|
|
invalidate();
|
|
drawTIA(true);
|
|
myOSystem->menu().draw(true);
|
|
if(isDoubleBuffered())
|
|
{
|
|
postFrameUpdate();
|
|
invalidate();
|
|
drawTIA(true);
|
|
myOSystem->menu().draw(true);
|
|
}
|
|
break;
|
|
|
|
case EventHandler::S_CMDMENU:
|
|
invalidate();
|
|
drawTIA(true);
|
|
myOSystem->commandMenu().draw(true);
|
|
if(isDoubleBuffered())
|
|
{
|
|
postFrameUpdate();
|
|
invalidate();
|
|
drawTIA(true);
|
|
myOSystem->commandMenu().draw(true);
|
|
}
|
|
break;
|
|
|
|
case EventHandler::S_LAUNCHER:
|
|
invalidate();
|
|
myOSystem->launcher().draw(true);
|
|
if(isDoubleBuffered())
|
|
{
|
|
postFrameUpdate();
|
|
invalidate();
|
|
myOSystem->launcher().draw(true);
|
|
}
|
|
break;
|
|
|
|
#ifdef DEBUGGER_SUPPORT
|
|
case EventHandler::S_DEBUGGER:
|
|
invalidate();
|
|
myOSystem->debugger().draw(true);
|
|
if(isDoubleBuffered())
|
|
{
|
|
postFrameUpdate();
|
|
invalidate();
|
|
myOSystem->debugger().draw(true);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setNTSC(NTSCFilter::Preset preset, bool show)
|
|
{
|
|
ostringstream buf;
|
|
if(preset == NTSCFilter::PRESET_OFF)
|
|
{
|
|
enableNTSC(false);
|
|
buf << "TV filtering disabled";
|
|
}
|
|
else
|
|
{
|
|
enableNTSC(true);
|
|
const string& mode = myNTSCFilter.setPreset(preset);
|
|
buf << "TV filtering (" << mode << " mode)";
|
|
}
|
|
myOSystem->settings().setValue("tv.filter", (int)preset);
|
|
|
|
if(show) showMessage(buf.str());
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setScanlineIntensity(int amount)
|
|
{
|
|
ostringstream buf;
|
|
if(ntscEnabled())
|
|
{
|
|
uInt32 intensity = enableScanlines(amount);
|
|
buf << "Scanline intensity at " << intensity << "%";
|
|
myOSystem->settings().setValue("tv.scanlines", intensity);
|
|
}
|
|
else
|
|
buf << "Scanlines only available in TV filtering mode";
|
|
|
|
showMessage(buf.str());
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::toggleScanlineInterpolation()
|
|
{
|
|
ostringstream buf;
|
|
if(ntscEnabled())
|
|
{
|
|
bool enable = !myOSystem->settings().getBool("tv.scaninter");
|
|
enableScanlineInterpolation(enable);
|
|
buf << "Scanline interpolation " << (enable ? "enabled" : "disabled");
|
|
myOSystem->settings().setValue("tv.scaninter", enable);
|
|
}
|
|
else
|
|
buf << "Scanlines only available in TV filtering mode";
|
|
|
|
showMessage(buf.str());
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt32 FrameBuffer::allocateSurface(int w, int h)
|
|
{
|
|
// Create a new surface
|
|
FBSurface* surface = createSurface(w, h);
|
|
|
|
// Add it to the list
|
|
mySurfaceList.insert(make_pair((uInt32)mySurfaceList.size(), surface));
|
|
|
|
// Return a reference to it
|
|
return mySurfaceList.size() - 1;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FBSurface* FrameBuffer::surface(uInt32 id) const
|
|
{
|
|
map<uInt32,FBSurface*>::const_iterator iter = mySurfaceList.find(id);
|
|
return iter != mySurfaceList.end() ? iter->second : NULL;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::resetSurfaces(FBSurface* tiasurface)
|
|
{
|
|
// Free all resources for each surface, then reload them
|
|
// Due to possible timing and/or synchronization issues, all free()'s
|
|
// are done first, then all reload()'s
|
|
// Any derived FrameBuffer classes that call this method should be
|
|
// aware of these restrictions, and act accordingly
|
|
|
|
map<uInt32,FBSurface*>::iterator iter;
|
|
for(iter = mySurfaceList.begin(); iter != mySurfaceList.end(); ++iter)
|
|
iter->second->free();
|
|
if(tiasurface)
|
|
tiasurface->free();
|
|
for(iter = mySurfaceList.begin(); iter != mySurfaceList.end(); ++iter)
|
|
iter->second->reload();
|
|
if(tiasurface)
|
|
tiasurface->reload();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt32 FrameBuffer::tiaPixel(uInt32 idx, uInt8 shift) const
|
|
{
|
|
uInt8 c = *(myOSystem->console().tia().currentFrameBuffer() + idx) | shift;
|
|
uInt8 p = *(myOSystem->console().tia().previousFrameBuffer() + idx) | shift;
|
|
|
|
return (!myUsePhosphor ? myDefPalette[c] : myAvgPalette[c][p]);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setTIAPalette(const uInt32* palette)
|
|
{
|
|
int i, j;
|
|
|
|
// Set palette for normal fill
|
|
for(i = 0; i < 256; ++i)
|
|
{
|
|
Uint8 r = (palette[i] >> 16) & 0xff;
|
|
Uint8 g = (palette[i] >> 8) & 0xff;
|
|
Uint8 b = palette[i] & 0xff;
|
|
|
|
myDefPalette[i] = mapRGB(r, g, b);
|
|
}
|
|
|
|
// Set palette for phosphor effect
|
|
for(i = 0; i < 256; ++i)
|
|
{
|
|
for(j = 0; j < 256; ++j)
|
|
{
|
|
uInt8 ri = (palette[i] >> 16) & 0xff;
|
|
uInt8 gi = (palette[i] >> 8) & 0xff;
|
|
uInt8 bi = palette[i] & 0xff;
|
|
uInt8 rj = (palette[j] >> 16) & 0xff;
|
|
uInt8 gj = (palette[j] >> 8) & 0xff;
|
|
uInt8 bj = palette[j] & 0xff;
|
|
|
|
Uint8 r = (Uint8) getPhosphor(ri, rj);
|
|
Uint8 g = (Uint8) getPhosphor(gi, gj);
|
|
Uint8 b = (Uint8) getPhosphor(bi, bj);
|
|
|
|
myAvgPalette[i][j] = mapRGB(r, g, b);
|
|
}
|
|
}
|
|
|
|
myRedrawEntireFrame = true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setUIPalette()
|
|
{
|
|
// Set palette for GUI
|
|
for(int i = 0, j = 256; i < kNumColors-256; ++i, ++j)
|
|
{
|
|
Uint8 r = (ourGUIColors[i] >> 16) & 0xff;
|
|
Uint8 g = (ourGUIColors[i] >> 8) & 0xff;
|
|
Uint8 b = ourGUIColors[i] & 0xff;
|
|
|
|
myDefPalette[j] = mapRGB(r, g, b);
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::stateChanged(EventHandler::State state)
|
|
{
|
|
// Make sure any onscreen messages are removed
|
|
myMsg.enabled = false;
|
|
myMsg.counter = 0;
|
|
|
|
myRedrawEntireFrame = true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setFullscreen(bool enable)
|
|
{
|
|
cerr << "setFullscreen: " << enable << endl;
|
|
enableFullscreen(enable);
|
|
|
|
|
|
|
|
#if 0 //FIXSDL
|
|
#ifdef WINDOWED_SUPPORT
|
|
// '-1' means fullscreen mode is completely disabled
|
|
bool full = enable && myOSystem->settings().getString("fullscreen") != "-1";
|
|
setHint(kFullScreen, full);
|
|
|
|
// Do a dummy call to getSavedVidMode to set up the modelists
|
|
// and have it point to the correct 'current' mode
|
|
getSavedVidMode();
|
|
|
|
// Do a mode change to the 'current' mode by not passing a '+1' or '-1'
|
|
// to changeVidMode()
|
|
changeVidMode(0);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::toggleFullscreen()
|
|
{
|
|
setFullscreen(!fullScreen());
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool FrameBuffer::changeWindowedVidMode(int direction)
|
|
{
|
|
#ifdef WINDOWED_SUPPORT
|
|
EventHandler::State state = myOSystem->eventHandler().state();
|
|
bool tiaMode = (state != EventHandler::S_DEBUGGER &&
|
|
state != EventHandler::S_LAUNCHER);
|
|
|
|
// Ignore any attempts to change video size while in invalid modes
|
|
if(!tiaMode || fullScreen())
|
|
return false;
|
|
|
|
if(direction == +1)
|
|
myCurrentModeList->next();
|
|
else if(direction == -1)
|
|
myCurrentModeList->previous();
|
|
else
|
|
return false;
|
|
|
|
const VideoMode& mode = myCurrentModeList->current();
|
|
myImageRect = mode.image;
|
|
myScreenSize = mode.screen;
|
|
if(setVideoMode(myScreenTitle, mode, false))
|
|
{
|
|
showMessage(mode.description);
|
|
myOSystem->settings().setValue("tia.zoom", mode.zoom);
|
|
refresh();
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setCursorState()
|
|
{
|
|
// Always grab mouse in fullscreen or during emulation (if enabled),
|
|
// and don't show the cursor during emulation
|
|
bool emulation =
|
|
myOSystem->eventHandler().state() == EventHandler::S_EMULATE;
|
|
grabMouse(fullScreen() ||
|
|
(emulation && myOSystem->settings().getBool("grabmouse")));
|
|
showCursor(!emulation);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::toggleGrabMouse()
|
|
{
|
|
bool state = myOSystem->settings().getBool("grabmouse");
|
|
myOSystem->settings().setValue("grabmouse", !state);
|
|
setCursorState();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt8 FrameBuffer::getPhosphor(uInt8 c1, uInt8 c2) const
|
|
{
|
|
if(c2 > c1)
|
|
BSPF_swap(c1, c2);
|
|
|
|
return ((c1 - c2) * myPhosphorBlend)/100 + c2;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt32 FrameBuffer::maxWindowSizeForScreen(uInt32 baseWidth, uInt32 baseHeight,
|
|
uInt32 screenWidth, uInt32 screenHeight)
|
|
{
|
|
uInt32 multiplier = 1;
|
|
for(;;)
|
|
{
|
|
// Figure out the zoomed size of the window
|
|
uInt32 width = baseWidth * multiplier;
|
|
uInt32 height = baseHeight * multiplier;
|
|
|
|
if((width > screenWidth) || (height > screenHeight))
|
|
break;
|
|
|
|
++multiplier;
|
|
}
|
|
return multiplier > 1 ? multiplier - 1 : 1;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::setAvailableVidModes(uInt32 baseWidth, uInt32 baseHeight)
|
|
{
|
|
myWindowedModeList.clear();
|
|
myFullscreenModeList.clear();
|
|
|
|
// Check if zooming is allowed for this state (currently only allowed
|
|
// for TIA screens)
|
|
EventHandler::State state = myOSystem->eventHandler().state();
|
|
bool tiaMode = (state != EventHandler::S_DEBUGGER &&
|
|
state != EventHandler::S_LAUNCHER);
|
|
|
|
// TIA mode allows zooming at integral factors in windowed modes,
|
|
// and also non-integral factors in fullscreen mode
|
|
if(tiaMode)
|
|
{
|
|
// TIA windowed modes
|
|
uInt32 maxZoom = maxWindowSizeForScreen(baseWidth, baseHeight,
|
|
myDesktopSize.w, myDesktopSize.h);
|
|
|
|
// Aspect ratio
|
|
bool ntsc = myOSystem->console().about().InitialFrameRate == "60";
|
|
uInt32 aspect = myOSystem->settings().getInt(ntsc ?
|
|
"tia.aspectn" : "tia.aspectp");
|
|
|
|
// Figure our the smallest zoom level we can use
|
|
uInt32 firstZoom = 2;
|
|
if(myDesktopSize.w < 640 || myDesktopSize.h < 480)
|
|
firstZoom = 1;
|
|
for(uInt32 zoom = firstZoom; zoom <= maxZoom; ++zoom)
|
|
{
|
|
ostringstream desc;
|
|
desc << "Zoom " << zoom << "x";
|
|
|
|
VideoMode mode(baseWidth*zoom, baseHeight*zoom,
|
|
baseWidth*zoom, baseHeight*zoom, false, zoom, desc.str());
|
|
mode.applyAspectCorrection(aspect);
|
|
myWindowedModeList.add(mode);
|
|
}
|
|
|
|
// TIA fullscreen mode
|
|
// GUI::Size screen(myDesktopWidth, myDesktopHeight);
|
|
myFullscreenModeList.add(
|
|
VideoMode(baseWidth, baseHeight, myDesktopSize.w, myDesktopSize.h, true)
|
|
);
|
|
|
|
}
|
|
else // UI mode
|
|
{
|
|
// Windowed and fullscreen mode differ only in screen size
|
|
myWindowedModeList.add(
|
|
VideoMode(baseWidth, baseHeight, baseWidth, baseHeight, false)
|
|
);
|
|
myFullscreenModeList.add(
|
|
VideoMode(baseWidth, baseHeight, myDesktopSize.w, myDesktopSize.h, true)
|
|
);
|
|
}
|
|
|
|
cerr << "Windowed modes:\n" << myWindowedModeList << endl
|
|
<< "Fullscreen modes:\n" << myFullscreenModeList << endl
|
|
<< endl;
|
|
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
const FrameBuffer::VideoMode& FrameBuffer::getSavedVidMode(bool fullscreen)
|
|
{
|
|
EventHandler::State state = myOSystem->eventHandler().state();
|
|
|
|
if(fullscreen)
|
|
myCurrentModeList = &myFullscreenModeList;
|
|
else
|
|
myCurrentModeList = &myWindowedModeList;
|
|
|
|
// Now select the best resolution depending on the state
|
|
// UI modes (launcher and debugger) have only one supported resolution
|
|
// so the 'current' one is the only valid one
|
|
if(state == EventHandler::S_DEBUGGER || state == EventHandler::S_LAUNCHER)
|
|
myCurrentModeList->setZoom(1);
|
|
else
|
|
myCurrentModeList->setZoom(myOSystem->settings().getInt("tia.zoom"));
|
|
|
|
return myCurrentModeList->current();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
//
|
|
// VideoMode implementation
|
|
//
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::VideoMode::VideoMode()
|
|
: fullscreen(false),
|
|
zoom(1),
|
|
description("")
|
|
{
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::VideoMode::VideoMode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh,
|
|
bool full, uInt32 z, const string& desc)
|
|
: fullscreen(full),
|
|
zoom(z),
|
|
description(desc)
|
|
{
|
|
sw = BSPF_max(sw, (uInt32)FrameBuffer::kTIAMinW);
|
|
sh = BSPF_max(sh, (uInt32)FrameBuffer::kTIAMinH);
|
|
iw = BSPF_min(iw, sw);
|
|
ih = BSPF_min(ih, sh);
|
|
int ix = (sw - iw) >> 1;
|
|
int iy = (sh - ih) >> 1;
|
|
image = GUI::Rect(ix, iy, ix+iw, iy+ih);
|
|
screen = GUI::Size(sw, sh);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoMode::applyAspectCorrection(uInt32 aspect, uInt32 stretch)
|
|
{
|
|
// Width is modified by aspect ratio; other factors may be applied below
|
|
uInt32 iw = (uInt32)(float(image.width() * aspect) / 100.0);
|
|
uInt32 ih = image.height();
|
|
|
|
if(stretch)
|
|
{
|
|
#if 0
|
|
// Fullscreen mode stretching
|
|
if(fullScreen() &&
|
|
(mode.image_w < mode.screen_w) && (mode.image_h < mode.screen_h))
|
|
{
|
|
float stretchFactor = 1.0;
|
|
float scaleX = float(mode.image_w) / mode.screen_w;
|
|
float scaleY = float(mode.image_h) / mode.screen_h;
|
|
|
|
// Scale to actual or integral factors
|
|
if(myOSystem->settings().getBool("gl_fsscale"))
|
|
{
|
|
// Scale to full (non-integral) available space
|
|
if(scaleX > scaleY)
|
|
stretchFactor = float(mode.screen_w) / mode.image_w;
|
|
else
|
|
stretchFactor = float(mode.screen_h) / mode.image_h;
|
|
}
|
|
else
|
|
{
|
|
// Only scale to an integral amount
|
|
if(scaleX > scaleY)
|
|
{
|
|
int bw = mode.image_w / mode.gfxmode.zoom;
|
|
stretchFactor = float(int(mode.screen_w / bw) * bw) / mode.image_w;
|
|
}
|
|
else
|
|
{
|
|
int bh = mode.image_h / mode.gfxmode.zoom;
|
|
stretchFactor = float(int(mode.screen_h / bh) * bh) / mode.image_h;
|
|
}
|
|
}
|
|
mode.image_w = (Uint16) (stretchFactor * mode.image_w);
|
|
mode.image_h = (Uint16) (stretchFactor * mode.image_h);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// In non-stretch mode, the screen size changes to match the image width
|
|
// Height is never modified in this mode
|
|
screen.w = iw;
|
|
}
|
|
|
|
// Now re-calculate the dimensions
|
|
iw = BSPF_min(iw, (uInt32)screen.w);
|
|
ih = BSPF_min(ih, (uInt32)screen.h);
|
|
|
|
image.moveTo((screen.w - iw) >> 1, (screen.h - ih) >> 1);
|
|
image.setWidth(iw);
|
|
image.setHeight(ih);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
//
|
|
// VideoModeList implementation
|
|
//
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::VideoModeList::VideoModeList()
|
|
: myIdx(-1)
|
|
{
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FrameBuffer::VideoModeList::~VideoModeList()
|
|
{
|
|
clear();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::add(const VideoMode& mode)
|
|
{
|
|
myModeList.push_back(mode);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::clear()
|
|
{
|
|
myModeList.clear();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool FrameBuffer::VideoModeList::isEmpty() const
|
|
{
|
|
return myModeList.isEmpty();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt32 FrameBuffer::VideoModeList::size() const
|
|
{
|
|
return myModeList.size();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::previous()
|
|
{
|
|
--myIdx;
|
|
if(myIdx < 0) myIdx = myModeList.size() - 1;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
const FrameBuffer::VideoMode& FrameBuffer::VideoModeList::current() const
|
|
{
|
|
return myModeList[myIdx];
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::next()
|
|
{
|
|
myIdx = (myIdx + 1) % myModeList.size();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FrameBuffer::VideoModeList::setZoom(uInt32 zoom)
|
|
{
|
|
for(uInt32 i = 0; i < myModeList.size(); ++i)
|
|
{
|
|
if(myModeList[i].zoom == zoom)
|
|
{
|
|
myIdx = i;
|
|
return;
|
|
}
|
|
}
|
|
myIdx = 0;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
//
|
|
// FBSurface implementation
|
|
//
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FBSurface::box(uInt32 x, uInt32 y, uInt32 w, uInt32 h,
|
|
uInt32 colorA, uInt32 colorB)
|
|
{
|
|
hLine(x + 1, y, x + w - 2, colorA);
|
|
hLine(x, y + 1, x + w - 1, colorA);
|
|
vLine(x, y + 1, y + h - 2, colorA);
|
|
vLine(x + 1, y, y + h - 1, colorA);
|
|
|
|
hLine(x + 1, y + h - 2, x + w - 1, colorB);
|
|
hLine(x + 1, y + h - 1, x + w - 2, colorB);
|
|
vLine(x + w - 1, y + 1, y + h - 2, colorB);
|
|
vLine(x + w - 2, y + 1, y + h - 1, colorB);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FBSurface::frameRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h,
|
|
uInt32 color, FrameStyle style)
|
|
{
|
|
switch(style)
|
|
{
|
|
case kSolidLine:
|
|
hLine(x, y, x + w - 1, color);
|
|
hLine(x, y + h - 1, x + w - 1, color);
|
|
vLine(x, y, y + h - 1, color);
|
|
vLine(x + w - 1, y, y + h - 1, color);
|
|
break;
|
|
|
|
case kDashLine:
|
|
unsigned int i, skip, lwidth = 1;
|
|
|
|
for(i = x, skip = 1; i < x+w-1; i=i+lwidth+1, ++skip)
|
|
{
|
|
if(skip % 2)
|
|
{
|
|
hLine(i, y, i + lwidth, color);
|
|
hLine(i, y + h - 1, i + lwidth, color);
|
|
}
|
|
}
|
|
for(i = y, skip = 1; i < y+h-1; i=i+lwidth+1, ++skip)
|
|
{
|
|
if(skip % 2)
|
|
{
|
|
vLine(x, i, i + lwidth, color);
|
|
vLine(x + w - 1, i, i + lwidth, color);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void FBSurface::drawString(const GUI::Font& font, const string& s,
|
|
int x, int y, int w,
|
|
uInt32 color, TextAlignment align,
|
|
int deltax, bool useEllipsis)
|
|
{
|
|
const int leftX = x, rightX = x + w;
|
|
unsigned int i;
|
|
int width = font.getStringWidth(s);
|
|
string str;
|
|
|
|
if(useEllipsis && width > w)
|
|
{
|
|
// String is too wide. So we shorten it "intelligently", by replacing
|
|
// parts of it by an ellipsis ("..."). There are three possibilities
|
|
// for this: replace the start, the end, or the middle of the string.
|
|
// What is best really depends on the context; but unless we want to
|
|
// make this configurable, replacing the middle probably is a good
|
|
// compromise.
|
|
const int ellipsisWidth = font.getStringWidth("...");
|
|
|
|
// SLOW algorithm to remove enough of the middle. But it is good enough for now.
|
|
const int halfWidth = (w - ellipsisWidth) / 2;
|
|
int w2 = 0;
|
|
|
|
for(i = 0; i < s.size(); ++i)
|
|
{
|
|
int charWidth = font.getCharWidth(s[i]);
|
|
if(w2 + charWidth > halfWidth)
|
|
break;
|
|
|
|
w2 += charWidth;
|
|
str += s[i];
|
|
}
|
|
|
|
// At this point we know that the first 'i' chars are together 'w2'
|
|
// pixels wide. We took the first i-1, and add "..." to them.
|
|
str += "...";
|
|
|
|
// The original string is width wide. Of those we already skipped past
|
|
// w2 pixels, which means (width - w2) remain.
|
|
// The new str is (w2+ellipsisWidth) wide, so we can accomodate about
|
|
// (w - (w2+ellipsisWidth)) more pixels.
|
|
// Thus we skip ((width - w2) - (w - (w2+ellipsisWidth))) =
|
|
// (width + ellipsisWidth - w)
|
|
int skip = width + ellipsisWidth - w;
|
|
for(; i < s.size() && skip > 0; ++i)
|
|
skip -= font.getCharWidth(s[i]);
|
|
|
|
// Append the remaining chars, if any
|
|
for(; i < s.size(); ++i)
|
|
str += s[i];
|
|
|
|
width = font.getStringWidth(str);
|
|
}
|
|
else
|
|
str = s;
|
|
|
|
if(align == kTextAlignCenter)
|
|
x = x + (w - width - 1)/2;
|
|
else if(align == kTextAlignRight)
|
|
x = x + w - width;
|
|
|
|
x += deltax;
|
|
for(i = 0; i < str.size(); ++i)
|
|
{
|
|
w = font.getCharWidth(str[i]);
|
|
if(x+w > rightX)
|
|
break;
|
|
if(x >= leftX)
|
|
drawChar(font, str[i], x, y, color);
|
|
|
|
x += w;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
/*
|
|
Palette is defined as follows:
|
|
// Base colors
|
|
kColor Normal foreground color (non-text)
|
|
kBGColor Normal background color (non-text)
|
|
kShadowColor Item is disabled
|
|
kTextColor Normal text color
|
|
kTextColorHi Highlighted text color
|
|
kTextColorEm Emphasized text color
|
|
|
|
// UI elements (dialog and widgets)
|
|
kDlgColor Dialog background
|
|
kWidColor Widget background
|
|
kWidFrameColor Border for currently selected widget
|
|
|
|
// Button colors
|
|
kBtnColor Normal button background
|
|
kBtnColorHi Highlighted button background
|
|
kBtnTextColor Normal button font color
|
|
kBtnTextColorHi Highlighted button font color
|
|
|
|
// Checkbox colors
|
|
kCheckColor Color of 'X' in checkbox
|
|
|
|
// Scrollbar colors
|
|
kScrollColor Normal scrollbar color
|
|
kScrollColorHi Highlighted scrollbar color
|
|
|
|
// Debugger colors
|
|
kDbgChangedColor Background color for changed cells
|
|
kDbgChangedTextColor Text color for changed cells
|
|
kDbgColorHi Highlighted color in debugger data cells
|
|
*/
|
|
uInt32 FrameBuffer::ourGUIColors[kNumColors-256] = {
|
|
0x686868, 0x000000, 0x404040, 0x000000, 0x62a108, 0x9f0000,
|
|
0xc9af7c, 0xf0f0cf, 0xc80000,
|
|
0xac3410, 0xd55941, 0xffffff, 0xffd652,
|
|
0xac3410,
|
|
0xac3410, 0xd55941,
|
|
0xac3410, 0xd55941,
|
|
0xc80000, 0x00ff00, 0xc8c8ff
|
|
};
|