mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-05 01:00:48 +00:00
9dbf058909
Don't rely on event.mousePos staying the same throughout the loop. This makes sure the button stays highlighted for as long as the mouse button is depressed, unless the mouse is moved off the button. The calculation of mousePos is slightly hackish. It should probably use a GfxManager object for that, but this will do for now.
1465 lines
39 KiB
C++
1465 lines
39 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "tsage/events.h"
|
|
#include "tsage/graphics.h"
|
|
#include "tsage/resources.h"
|
|
#include "tsage/tsage.h"
|
|
#include "tsage/core.h"
|
|
#include "common/algorithm.h"
|
|
#include "graphics/palette.h"
|
|
#include "graphics/surface.h"
|
|
#include "tsage/globals.h"
|
|
|
|
namespace tSage {
|
|
|
|
/**
|
|
* Creates a new graphics surface with the specified area of another surface
|
|
*
|
|
* @src Source surface
|
|
* @bounds Area to backup
|
|
*/
|
|
GfxSurface *Surface_getArea(GfxSurface &src, const Rect &bounds) {
|
|
assert(bounds.isValidRect());
|
|
GfxSurface *dest = new GfxSurface();
|
|
dest->create(bounds.width(), bounds.height());
|
|
|
|
Graphics::Surface srcSurface = src.lockSurface();
|
|
Graphics::Surface destSurface = dest->lockSurface();
|
|
|
|
byte *srcP = (byte *)srcSurface.getBasePtr(bounds.left, bounds.top);
|
|
byte *destP = (byte *)destSurface.getBasePtr(0, 0);
|
|
|
|
for (int y = bounds.top; y < bounds.bottom; ++y, srcP += srcSurface.pitch, destP += destSurface.pitch)
|
|
Common::copy(srcP, srcP + destSurface.pitch, destP);
|
|
|
|
src.unlockSurface();
|
|
dest->unlockSurface();
|
|
return dest;
|
|
}
|
|
|
|
/**
|
|
* Translates a raw image resource into a graphics surface. The caller is then responsible
|
|
* for managing and destroying the surface when done with it
|
|
*
|
|
* @imgData Raw image resource
|
|
* @size Size of the resource
|
|
*/
|
|
GfxSurface surfaceFromRes(const byte *imgData) {
|
|
Rect r(0, 0, READ_LE_UINT16(imgData), READ_LE_UINT16(imgData + 2));
|
|
GfxSurface s;
|
|
s.create(r.width(), r.height());
|
|
s._centroid.x = READ_LE_UINT16(imgData + 4);
|
|
s._centroid.y = READ_LE_UINT16(imgData + 6);
|
|
s._transColor = *(imgData + 8);
|
|
|
|
bool rleEncoded = (imgData[9] & 2) != 0;
|
|
|
|
const byte *srcP = imgData + 10;
|
|
Graphics::Surface destSurface = s.lockSurface();
|
|
byte *destP = (byte *)destSurface.getBasePtr(0, 0);
|
|
|
|
if (!rleEncoded) {
|
|
Common::copy(srcP, srcP + (r.width() * r.height()), destP);
|
|
} else {
|
|
Common::set_to(destP, destP + (r.width() * r.height()), s._transColor);
|
|
|
|
for (int yp = 0; yp < r.height(); ++yp) {
|
|
int width = r.width();
|
|
destP = (byte *)destSurface.getBasePtr(0, yp);
|
|
|
|
while (width > 0) {
|
|
uint8 controlVal = *srcP++;
|
|
if ((controlVal & 0x80) == 0) {
|
|
// Copy specified number of bytes
|
|
|
|
Common::copy(srcP, srcP + controlVal, destP);
|
|
width -= controlVal;
|
|
srcP += controlVal;
|
|
destP += controlVal;
|
|
} else if ((controlVal & 0x40) == 0) {
|
|
// Skip a specified number of output pixels
|
|
destP += controlVal & 0x3f;
|
|
width -= controlVal & 0x3f;
|
|
} else {
|
|
// Copy a specified pixel a given number of times
|
|
controlVal &= 0x3f;
|
|
int pixel = *srcP++;
|
|
|
|
Common::set_to(destP, destP + controlVal, pixel);
|
|
destP += controlVal;
|
|
width -= controlVal;
|
|
}
|
|
}
|
|
assert(width == 0);
|
|
}
|
|
}
|
|
|
|
s.unlockSurface();
|
|
return s;
|
|
}
|
|
|
|
GfxSurface surfaceFromRes(int resNum, int rlbNum, int subNum) {
|
|
uint size;
|
|
byte *imgData = _resourceManager->getSubResource(resNum, rlbNum, subNum, &size);
|
|
GfxSurface surface = surfaceFromRes(imgData);
|
|
DEALLOCATE(imgData);
|
|
|
|
return surface;
|
|
}
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
void Rect::set(int16 x1, int16 y1, int16 x2, int16 y2) {
|
|
left = x1; top = y1;
|
|
right = x2; bottom = y2;
|
|
}
|
|
|
|
/**
|
|
* Collapses the rectangle in all four directions by the given x and y amounts
|
|
*
|
|
* @dx x amount to collapse x edges by
|
|
* @dy y amount to collapse y edges by
|
|
*/
|
|
void Rect::collapse(int dx, int dy) {
|
|
left += dx; right -= dx;
|
|
top += dy; bottom -= dy;
|
|
}
|
|
|
|
/**
|
|
* Centers the rectangle at a given position
|
|
*
|
|
* @xp x position for new center
|
|
* @yp y position for new center
|
|
*/
|
|
void Rect::center(int xp, int yp) {
|
|
moveTo(xp - (width() / 2), yp - (height() / 2));
|
|
}
|
|
|
|
/**
|
|
* Centers the rectangle at the center of a second passed rectangle
|
|
*
|
|
* @r Second rectangle whose center to use
|
|
*/
|
|
void Rect::center(const Rect &r) {
|
|
center(r.left + (r.width() / 2), r.top + (r.height() / 2));
|
|
}
|
|
|
|
/*
|
|
* Repositions the bounds if necessary so it falls entirely within the passed bounds
|
|
*
|
|
* @r The bounds the current rect should be within
|
|
*/
|
|
void Rect::contain(const Rect &r) {
|
|
if (left < r.left) translate(r.left - left, 0);
|
|
if (right > r.right) translate(r.right - right, 0);
|
|
if (top < r.top) translate(0, r.top - top);
|
|
if (bottom > r.bottom) translate(0, r.bottom - bottom);
|
|
}
|
|
|
|
/**
|
|
* Resizes and positions a given rect based on raw image data and a passed scaling percentage
|
|
*
|
|
* @frame Raw image frame
|
|
* @xp New x position
|
|
* @yp New y position
|
|
* @percent Scaling percentage
|
|
*/
|
|
void Rect::resize(const GfxSurface &surface, int xp, int yp, int percent) {
|
|
int xe = surface.getBounds().width() * percent / 100;
|
|
int ye = surface.getBounds().height() * percent / 100;
|
|
this->set(0, 0, xe, ye);
|
|
|
|
if (!right) ++right;
|
|
if (!bottom) ++bottom;
|
|
|
|
this->moveTo(xp, yp);
|
|
|
|
int xd = surface._centroid.x * percent / 100;
|
|
int yd = surface._centroid.y * percent / 100;
|
|
this->translate(-xd, -yd);
|
|
}
|
|
|
|
/**
|
|
* Expands the pane region to contain the specified Rect
|
|
*/
|
|
void Rect::expandPanes() {
|
|
_globals->_paneRegions[0].uniteRect(*this);
|
|
_globals->_paneRegions[1].uniteRect(*this);
|
|
}
|
|
|
|
/**
|
|
* Serialises the given rect
|
|
*/
|
|
void Rect::synchronize(Serializer &s) {
|
|
s.syncAsSint16LE(left);
|
|
s.syncAsSint16LE(top);
|
|
s.syncAsSint16LE(right);
|
|
s.syncAsSint16LE(bottom);
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
GfxSurface::GfxSurface() : _bounds(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) {
|
|
_disableUpdates = false;
|
|
_screenSurface = false;
|
|
_lockSurfaceCtr = 0;
|
|
_customSurface = NULL;
|
|
_screenSurfaceP = NULL;
|
|
_transColor = -1;
|
|
}
|
|
|
|
GfxSurface::GfxSurface(const GfxSurface &s) {
|
|
_lockSurfaceCtr = 0;
|
|
_customSurface = NULL;
|
|
this->operator =(s);
|
|
}
|
|
|
|
GfxSurface::~GfxSurface() {
|
|
if (_customSurface) {
|
|
_customSurface->free();
|
|
delete _customSurface;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Specifies that the surface will encapsulate the ScummVM screen surface
|
|
*/
|
|
void GfxSurface::setScreenSurface() {
|
|
_screenSurface = true;
|
|
_customSurface = NULL;
|
|
_lockSurfaceCtr = 0;
|
|
}
|
|
|
|
/**
|
|
* Specifies that the surface should maintain it's own internal surface
|
|
*/
|
|
void GfxSurface::create(int width, int height) {
|
|
assert((width >= 0) && (height >= 0));
|
|
_screenSurface = false;
|
|
if (_customSurface) {
|
|
_customSurface->free();
|
|
delete _customSurface;
|
|
}
|
|
_customSurface = new Graphics::Surface();
|
|
_customSurface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
|
|
Common::set_to((byte *)_customSurface->pixels, (byte *)_customSurface->pixels + (width * height), 0);
|
|
_bounds = Rect(0, 0, width, height);
|
|
}
|
|
|
|
/**
|
|
* Locks the surface for access, and returns a raw ScummVM surface to manipulate it
|
|
*/
|
|
Graphics::Surface GfxSurface::lockSurface() {
|
|
++_lockSurfaceCtr;
|
|
|
|
Graphics::Surface *src;
|
|
if (_screenSurface) {
|
|
if (_lockSurfaceCtr == 1)
|
|
_screenSurfaceP = g_system->lockScreen();
|
|
src = _screenSurfaceP;
|
|
} else
|
|
src = _customSurface;
|
|
assert(src);
|
|
|
|
// Setup the returned surface either as one pointing to the same pixels as the source, or
|
|
// as a subset of the source one based on the currently set bounds
|
|
Graphics::Surface result;
|
|
result.w = _bounds.width();
|
|
result.h = _bounds.height();
|
|
result.pitch = src->pitch;
|
|
result.format = src->format;
|
|
result.pixels = src->getBasePtr(_bounds.left, _bounds.top);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Unlocks the surface after having accessed it with the lockSurface method
|
|
*/
|
|
void GfxSurface::unlockSurface() {
|
|
assert(_lockSurfaceCtr > 0);
|
|
--_lockSurfaceCtr;
|
|
|
|
if ((_lockSurfaceCtr == 0) && _screenSurface) {
|
|
g_system->unlockScreen();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fills a specified rectangle on the surface with the specified color
|
|
*
|
|
* @bounds Area to fill
|
|
* @color Color to use
|
|
*/
|
|
void GfxSurface::fillRect(const Rect &bounds, int color) {
|
|
Graphics::Surface surface = lockSurface();
|
|
surface.fillRect(bounds, color);
|
|
unlockSurface();
|
|
}
|
|
|
|
GfxSurface &GfxSurface::operator=(const GfxSurface &s) {
|
|
assert(_lockSurfaceCtr == 0);
|
|
assert(s._lockSurfaceCtr == 0);
|
|
|
|
if (_customSurface) {
|
|
_customSurface->free();
|
|
delete _customSurface;
|
|
}
|
|
|
|
_customSurface = s._customSurface;
|
|
_screenSurface = s._screenSurface;
|
|
_disableUpdates = s._disableUpdates;
|
|
_bounds = s._bounds;
|
|
_centroid = s._centroid;
|
|
_transColor = s._transColor;
|
|
|
|
if (_customSurface) {
|
|
// Surface owns the internal data, so replicate it so new surface owns it's own
|
|
_customSurface = new Graphics::Surface();
|
|
_customSurface->create(s._customSurface->w, s._customSurface->h, Graphics::PixelFormat::createFormatCLUT8());
|
|
const byte *srcP = (const byte *)s._customSurface->getBasePtr(0, 0);
|
|
byte *destP = (byte *)_customSurface->getBasePtr(0, 0);
|
|
|
|
Common::copy(srcP, srcP + (_bounds.width() * _bounds.height()), destP);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Displays a message on-screen until either a mouse or keypress
|
|
*/
|
|
bool GfxSurface::displayText(const Common::String &msg, const Common::Point &pt) {
|
|
// Set up a new graphics manager
|
|
GfxManager gfxManager;
|
|
gfxManager.activate();
|
|
gfxManager._font._colors.background = 0;
|
|
gfxManager._font._colors.foreground = 7;
|
|
gfxManager._font.setFontNumber(2);
|
|
|
|
// Get the area for text display
|
|
Rect textRect;
|
|
gfxManager.getStringBounds(msg.c_str(), textRect, 200);
|
|
textRect.center(pt.x, pt.y);
|
|
|
|
// Make a backup copy of the area the text will occupy
|
|
Rect saveRect = textRect;
|
|
saveRect.collapse(-20, -8);
|
|
GfxSurface *savedArea = Surface_getArea(gfxManager.getSurface(), saveRect);
|
|
|
|
// Display the text
|
|
gfxManager._font.writeLines(msg.c_str(), textRect, ALIGN_LEFT);
|
|
|
|
// Write for a mouse or keypress
|
|
Event event;
|
|
while (!_globals->_events.getEvent(event, EVENT_BUTTON_DOWN | EVENT_KEYPRESS) && !_vm->getEventManager()->shouldQuit())
|
|
;
|
|
|
|
// Restore the display area
|
|
gfxManager.copyFrom(*savedArea, saveRect.left, saveRect.top);
|
|
delete savedArea;
|
|
|
|
gfxManager.deactivate();
|
|
return (event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_RETURN);
|
|
}
|
|
|
|
/**
|
|
* Loads a quarter of a screen from a resource
|
|
*/
|
|
void GfxSurface::loadScreenSection(Graphics::Surface &dest, int xHalf, int yHalf, int xSection, int ySection) {
|
|
int screenNum = _globals->_sceneManager._scene->_activeScreenNumber;
|
|
Rect updateRect(0, 0, 160, 100);
|
|
updateRect.translate(xHalf * 160, yHalf * 100);
|
|
int xHalfCount = (_globals->_sceneManager._scene->_backgroundBounds.right + 159) / 160;
|
|
int yHalfCount = (_globals->_sceneManager._scene->_backgroundBounds.bottom + 99) / 100;
|
|
|
|
if (xSection < xHalfCount && ySection < yHalfCount) {
|
|
int rlbNum = xSection * yHalfCount + ySection;
|
|
byte *data = _resourceManager->getResource(RES_BITMAP, screenNum, rlbNum);
|
|
|
|
for (int y = 0; y < updateRect.height(); ++y) {
|
|
byte *pSrc = data + y * 160;
|
|
byte *pDest = (byte *)dest.getBasePtr(updateRect.left, updateRect.top + y);
|
|
|
|
for (int x = 0; x < updateRect.width(); ++x, ++pSrc, ++pDest) {
|
|
*pDest = *pSrc;
|
|
}
|
|
}
|
|
|
|
DEALLOCATE(data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an array indicating which pixels of a source image horizontally or vertically get
|
|
* included in a scaled image
|
|
*/
|
|
static int *scaleLine(int size, int srcSize) {
|
|
const int PRECISION_FACTOR = 1000;
|
|
int scale = PRECISION_FACTOR * size / srcSize;
|
|
assert(scale >= 0);
|
|
int *v = new int[size];
|
|
Common::set_to(v, &v[size], -1);
|
|
|
|
int distCtr = PRECISION_FACTOR / 2;
|
|
int *destP = v;
|
|
for (int distIndex = 0; distIndex < srcSize; ++distIndex) {
|
|
distCtr += scale;
|
|
while (distCtr > PRECISION_FACTOR) {
|
|
assert(destP < &v[size]);
|
|
*destP++ = distIndex;
|
|
distCtr -= PRECISION_FACTOR;
|
|
}
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
/**
|
|
* Scales a passed surface, creating a new surface with the result
|
|
* @param srcImage Source image to scale
|
|
* @param NewWidth New width for scaled image
|
|
* @param NewHeight New height for scaled image
|
|
* @remarks Caller is responsible for freeing the returned surface
|
|
*/
|
|
static GfxSurface ResizeSurface(GfxSurface &src, int xSize, int ySize, int transIndex) {
|
|
GfxSurface s;
|
|
s.create(xSize, ySize);
|
|
|
|
Graphics::Surface srcImage = src.lockSurface();
|
|
Graphics::Surface destImage = s.lockSurface();
|
|
|
|
int *horizUsage = scaleLine(xSize, srcImage.w);
|
|
int *vertUsage = scaleLine(ySize, srcImage.h);
|
|
|
|
// Loop to create scaled version
|
|
for (int yp = 0; yp < ySize; ++yp) {
|
|
byte *destP = (byte *)destImage.getBasePtr(0, yp);
|
|
|
|
if (vertUsage[yp] == -1) {
|
|
Common::set_to(destP, destP + xSize, transIndex);
|
|
} else {
|
|
const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]);
|
|
|
|
for (int xp = 0; xp < xSize; ++xp) {
|
|
if (horizUsage[xp] != -1) {
|
|
const byte *tempSrcP = srcP + horizUsage[xp];
|
|
*destP++ = *tempSrcP++;
|
|
} else {
|
|
// Pixel overrun at the end of the line
|
|
*destP++ = transIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unlock surfaces
|
|
src.unlockSurface();
|
|
s.unlockSurface();
|
|
|
|
// Delete arrays and return surface
|
|
delete[] horizUsage;
|
|
delete[] vertUsage;
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Copys an area from one GfxSurface to another
|
|
*/
|
|
void GfxSurface::copyFrom(GfxSurface &src, Rect srcBounds, Rect destBounds, Region *priorityRegion) {
|
|
GfxSurface srcImage;
|
|
if (srcBounds.isEmpty())
|
|
return;
|
|
|
|
if (srcBounds == src.getBounds())
|
|
srcImage = src;
|
|
else {
|
|
// Set the source image to be the subset specified by the source bounds
|
|
Graphics::Surface srcSurface = src.lockSurface();
|
|
|
|
srcImage.create(srcBounds.width(), srcBounds.height());
|
|
Graphics::Surface destSurface = srcImage.lockSurface();
|
|
|
|
const byte *srcP = (const byte *)srcSurface.getBasePtr(srcBounds.left, srcBounds.top);
|
|
byte *destP = (byte *)destSurface.pixels;
|
|
for (int yp = srcBounds.top; yp < srcBounds.bottom; ++yp, srcP += srcSurface.pitch, destP += destSurface.pitch) {
|
|
Common::copy(srcP, srcP + srcBounds.width(), destP);
|
|
}
|
|
|
|
srcImage.unlockSurface();
|
|
src.unlockSurface();
|
|
}
|
|
|
|
if ((destBounds.width() != srcBounds.width()) || (destBounds.height() != srcBounds.height()))
|
|
srcImage = ResizeSurface(srcImage, destBounds.width(), destBounds.height(), src._transColor);
|
|
|
|
Graphics::Surface srcSurface = srcImage.lockSurface();
|
|
Graphics::Surface destSurface = lockSurface();
|
|
|
|
// Adjust bounds to ensure destination will be on-screen
|
|
int srcX = 0, srcY = 0;
|
|
if (destBounds.left < 0) {
|
|
srcX = -destBounds.left;
|
|
destBounds.left = 0;
|
|
}
|
|
if (destBounds.top < 0) {
|
|
srcY = -destBounds.top;
|
|
destBounds.top = 0;
|
|
}
|
|
if (destBounds.right > destSurface.w)
|
|
destBounds.right = destSurface.w;
|
|
if (destBounds.bottom > destSurface.h)
|
|
destBounds.bottom = destSurface.h;
|
|
|
|
if (destBounds.isValidRect()) {
|
|
const byte *pSrc = (const byte *)srcSurface.getBasePtr(srcX, srcY);
|
|
byte *pDest = (byte *)destSurface.getBasePtr(destBounds.left, destBounds.top);
|
|
|
|
for (int y = 0; y < destBounds.height(); ++y, pSrc += srcSurface.pitch, pDest += destSurface.pitch) {
|
|
|
|
if (!priorityRegion && (src._transColor == -1))
|
|
Common::copy(pSrc, pSrc + destBounds.width(), pDest);
|
|
else {
|
|
const byte *tempSrc = pSrc;
|
|
byte *tempDest = pDest;
|
|
int xp = destBounds.left;
|
|
|
|
while (tempSrc < (pSrc + destBounds.width())) {
|
|
if (!priorityRegion || !priorityRegion->contains(Common::Point(
|
|
xp + _globals->_sceneManager._scene->_sceneBounds.left,
|
|
destBounds.top + y + _globals->_sceneManager._scene->_sceneBounds.top))) {
|
|
if (*tempSrc != src._transColor)
|
|
*tempDest = *tempSrc;
|
|
}
|
|
++tempSrc;
|
|
++tempDest;
|
|
++xp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unlockSurface();
|
|
srcImage.unlockSurface();
|
|
}
|
|
|
|
void GfxSurface::draw(const Common::Point &pt, Rect *rect) {
|
|
Rect tempRect = getBounds();
|
|
tempRect.translate(-_centroid.x, -_centroid.y);
|
|
tempRect.translate(pt.x, pt.y);
|
|
|
|
if (rect) {
|
|
// Only copy needed rect out without drawing
|
|
*rect = tempRect;
|
|
} else {
|
|
// Draw image
|
|
_globals->gfxManager().copyFrom(*this, tempRect, NULL);
|
|
}
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
GfxElement::GfxElement() {
|
|
_owner = NULL;
|
|
_keycode = 0;
|
|
_flags = 0;
|
|
}
|
|
|
|
void GfxElement::setDefaults() {
|
|
_flags = 0;
|
|
_fontNumber = _globals->_gfxFontNumber;
|
|
_colors = _globals->_gfxColors;
|
|
_fontColors = _globals->_fontColors;
|
|
}
|
|
|
|
/**
|
|
* Highlights the specified graphics element
|
|
*/
|
|
void GfxElement::highlight() {
|
|
// Get a lock on the surface
|
|
GfxManager &gfxManager = _globals->gfxManager();
|
|
Graphics::Surface surface = gfxManager.lockSurface();
|
|
|
|
// Scan through the contents of the element, switching any occurances of the foreground
|
|
// color with the background color and vice versa
|
|
Rect tempRect(_bounds);
|
|
tempRect.collapse(2, 2);
|
|
|
|
for (int yp = tempRect.top; yp < tempRect.bottom; ++yp) {
|
|
byte *lineP = (byte *)surface.getBasePtr(tempRect.left, yp);
|
|
for (int xp = tempRect.left; xp < tempRect.right; ++xp, ++lineP) {
|
|
if (*lineP == _colors.background) *lineP = _colors.foreground;
|
|
else if (*lineP == _colors.foreground) *lineP = _colors.background;
|
|
}
|
|
}
|
|
|
|
// Release the surface
|
|
gfxManager.unlockSurface();
|
|
}
|
|
|
|
/**
|
|
* Fills the background of the specified element with a border frame
|
|
*/
|
|
void GfxElement::drawFrame() {
|
|
// Get a lock on the surface and save the active font
|
|
GfxManager &gfxManager = _globals->gfxManager();
|
|
gfxManager.lockSurface();
|
|
|
|
uint8 bgColor, fgColor;
|
|
if (_flags & GFXFLAG_THICK_FRAME) {
|
|
bgColor = 0;
|
|
fgColor = 0;
|
|
} else {
|
|
bgColor = _fontColors.background;
|
|
fgColor = _fontColors.foreground;
|
|
}
|
|
|
|
Rect tempRect = _bounds;
|
|
tempRect.collapse(3, 3);
|
|
tempRect.collapse(-1, -1);
|
|
gfxManager.fillRect(tempRect, _colors.background);
|
|
|
|
--tempRect.bottom; --tempRect.right;
|
|
gfxManager.fillArea(tempRect.left, tempRect.top, bgColor);
|
|
gfxManager.fillArea(tempRect.left, tempRect.bottom, fgColor);
|
|
gfxManager.fillArea(tempRect.right, tempRect.top, fgColor);
|
|
gfxManager.fillArea(tempRect.right, tempRect.bottom, fgColor);
|
|
|
|
tempRect.collapse(-1, -1);
|
|
gfxManager.fillRect2(tempRect.left + 1, tempRect.top, tempRect.width() - 1, 1, bgColor);
|
|
gfxManager.fillRect2(tempRect.left, tempRect.top + 1, 1, tempRect.height() - 1, bgColor);
|
|
gfxManager.fillRect2(tempRect.left + 1, tempRect.bottom, tempRect.width() - 1, 1, fgColor);
|
|
gfxManager.fillRect2(tempRect.right, tempRect.top + 1, 1, tempRect.height() - 1, fgColor);
|
|
|
|
gfxManager.fillArea(tempRect.left, tempRect.top, 0);
|
|
gfxManager.fillArea(tempRect.left, tempRect.bottom, 0);
|
|
gfxManager.fillArea(tempRect.right, tempRect.top, 0);
|
|
gfxManager.fillArea(tempRect.right, tempRect.bottom, 0);
|
|
|
|
tempRect.collapse(-1, -1);
|
|
gfxManager.fillRect2(tempRect.left + 2, tempRect.top, tempRect.width() - 3, 1, 0);
|
|
gfxManager.fillRect2(tempRect.left, tempRect.top + 2, 1, tempRect.height() - 3, 0);
|
|
gfxManager.fillRect2(tempRect.left + 2, tempRect.bottom, tempRect.width() - 3, 1, 0);
|
|
gfxManager.fillRect2(tempRect.right, tempRect.top + 2, 1, tempRect.height() - 3, 0);
|
|
|
|
gfxManager.unlockSurface();
|
|
}
|
|
|
|
/**
|
|
* Handles events when the control has focus
|
|
*
|
|
* @event Event to process
|
|
*/
|
|
bool GfxElement::focusedEvent(Event &event) {
|
|
Common::Point mousePos = event.mousePos;
|
|
bool highlightFlag = false;
|
|
|
|
// HACK: It should use the GfxManager object to figure out the relative
|
|
// position, but for now this seems like the easiest way.
|
|
int xOffset = mousePos.x - _globals->_events._mousePos.x;
|
|
int yOffset = mousePos.y - _globals->_events._mousePos.y;
|
|
|
|
while (event.eventType != EVENT_BUTTON_UP && !_vm->getEventManager()->shouldQuit()) {
|
|
g_system->delayMillis(10);
|
|
|
|
if (_bounds.contains(mousePos)) {
|
|
if (!highlightFlag) {
|
|
// First highlight call to show the highlight
|
|
highlightFlag = true;
|
|
highlight();
|
|
}
|
|
} else if (highlightFlag) {
|
|
// Mouse is outside the element, so remove the highlight
|
|
highlightFlag = false;
|
|
highlight();
|
|
}
|
|
|
|
if (_globals->_events.getEvent(event, EVENT_MOUSE_MOVE | EVENT_BUTTON_UP)) {
|
|
if (event.eventType == EVENT_MOUSE_MOVE) {
|
|
mousePos.x = event.mousePos.x + xOffset;
|
|
mousePos.y = event.mousePos.y + yOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (highlightFlag) {
|
|
// Mouse is outside the element, so remove the highlight
|
|
highlight();
|
|
}
|
|
|
|
return highlightFlag;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
GfxImage::GfxImage() : GfxElement() {
|
|
_resNum = 0;
|
|
_rlbNum = 0;
|
|
_cursorNum = 0;
|
|
}
|
|
|
|
void GfxImage::setDetails(int resNum, int rlbNum, int cursorNum) {
|
|
_resNum = resNum;
|
|
_rlbNum = rlbNum;
|
|
_cursorNum = cursorNum;
|
|
setDefaults();
|
|
}
|
|
|
|
void GfxImage::setDefaults() {
|
|
GfxElement::setDefaults();
|
|
|
|
// Decode the image
|
|
uint size;
|
|
byte *imgData = _resourceManager->getSubResource(_resNum, _rlbNum, _cursorNum, &size);
|
|
_surface = surfaceFromRes(imgData);
|
|
DEALLOCATE(imgData);
|
|
|
|
// Set up the display bounds
|
|
Rect imgBounds = _surface.getBounds();
|
|
imgBounds.moveTo(_bounds.left, _bounds.top);
|
|
_bounds = imgBounds;
|
|
}
|
|
|
|
void GfxImage::draw() {
|
|
Rect tempRect = _bounds;
|
|
tempRect.translate(_globals->gfxManager()._topLeft.x, _globals->gfxManager()._topLeft.y);
|
|
|
|
_globals->gfxManager().copyFrom(_surface, tempRect);
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
GfxMessage::GfxMessage() : GfxElement() {
|
|
_textAlign = ALIGN_LEFT;
|
|
_width = 0;
|
|
}
|
|
|
|
void GfxMessage::set(const Common::String &s, int width, TextAlign textAlign) {
|
|
_message = s;
|
|
_width = width;
|
|
_textAlign = textAlign;
|
|
|
|
setDefaults();
|
|
}
|
|
|
|
void GfxMessage::setDefaults() {
|
|
GfxElement::setDefaults();
|
|
|
|
GfxFontBackup font;
|
|
GfxManager &gfxManager = _globals->gfxManager();
|
|
Rect tempRect;
|
|
|
|
gfxManager._font.setFontNumber(this->_fontNumber);
|
|
gfxManager.getStringBounds(_message.c_str(), tempRect, _width);
|
|
|
|
tempRect.collapse(-1, -1);
|
|
tempRect.moveTo(_bounds.left, _bounds.top);
|
|
_bounds = tempRect;
|
|
}
|
|
|
|
void GfxMessage::draw() {
|
|
GfxFontBackup font;
|
|
GfxManager &gfxManager = _globals->gfxManager();
|
|
|
|
// Set the font and color
|
|
gfxManager.setFillFlag(false);
|
|
gfxManager._font.setFontNumber(_fontNumber);
|
|
gfxManager._font._colors.foreground = this->_colors.foreground;
|
|
|
|
// Display the text
|
|
gfxManager._font.writeLines(_message.c_str(), _bounds, _textAlign);
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
void GfxButton::setDefaults() {
|
|
GfxElement::setDefaults();
|
|
|
|
GfxFontBackup font;
|
|
GfxManager &gfxManager = _globals->gfxManager();
|
|
Rect tempRect;
|
|
|
|
// Get the string bounds and round up the x end to a multiple of 16
|
|
gfxManager._font.setFontNumber(this->_fontNumber);
|
|
gfxManager._font.getStringBounds(_message.c_str(), tempRect, 240);
|
|
tempRect.right = ((tempRect.right + 15) / 16) * 16;
|
|
|
|
// Set the button bounds to a reduced area
|
|
tempRect.collapse(-3, -3);
|
|
tempRect.moveTo(_bounds.left, _bounds.top);
|
|
_bounds = tempRect;
|
|
}
|
|
|
|
void GfxButton::draw() {
|
|
// Get a lock on the surface and save the active font
|
|
GfxFontBackup font;
|
|
GfxManager &gfxManager = _globals->gfxManager();
|
|
gfxManager.lockSurface();
|
|
|
|
// Draw a basic frame for the button
|
|
drawFrame();
|
|
|
|
// Set the font and color
|
|
gfxManager._font.setFontNumber(_fontNumber);
|
|
gfxManager._font._colors.foreground = this->_colors.foreground;
|
|
|
|
// Display the button's text
|
|
Rect tempRect(_bounds);
|
|
tempRect.collapse(3, 3);
|
|
gfxManager._font.writeLines(_message.c_str(), tempRect, ALIGN_CENTER);
|
|
|
|
gfxManager.unlockSurface();
|
|
}
|
|
|
|
bool GfxButton::process(Event &event) {
|
|
switch (event.eventType) {
|
|
case EVENT_BUTTON_DOWN:
|
|
if (!event.handled) {
|
|
if (_bounds.contains(event.mousePos)) {
|
|
bool result = focusedEvent(event);
|
|
event.handled = true;
|
|
return result;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EVENT_KEYPRESS:
|
|
if (!event.handled && (event.kbd.keycode == _keycode)) {
|
|
// TODO: Ensure momentary click operation displays
|
|
highlight();
|
|
g_system->delayMillis(20);
|
|
highlight();
|
|
|
|
event.handled = true;
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
GfxDialog::GfxDialog() {
|
|
_savedArea = NULL;
|
|
_defaultButton = NULL;
|
|
}
|
|
|
|
GfxDialog::~GfxDialog() {
|
|
remove();
|
|
}
|
|
|
|
void GfxDialog::setDefaults() {
|
|
GfxElement::setDefaults();
|
|
|
|
// Initialise the embedded graphics manager
|
|
_gfxManager.setDefaults();
|
|
|
|
// Figure out a rect needed for all the added elements
|
|
GfxElementList::iterator i;
|
|
Rect tempRect;
|
|
for (i = _elements.begin(); i != _elements.end(); ++i)
|
|
tempRect.extend((*i)->_bounds);
|
|
|
|
// Set the dialog boundaries
|
|
_gfxManager._bounds = tempRect;
|
|
tempRect.collapse(-6, -6);
|
|
_bounds = tempRect;
|
|
}
|
|
|
|
void GfxDialog::remove() {
|
|
if (_savedArea) {
|
|
// Restore the area the dialog covered
|
|
_globals->_gfxManagerInstance.copyFrom(*_savedArea, _bounds.left, _bounds.top);
|
|
|
|
delete _savedArea;
|
|
_savedArea = NULL;
|
|
}
|
|
}
|
|
|
|
void GfxDialog::draw() {
|
|
Rect tempRect(_bounds);
|
|
|
|
// Make a backup copy of the area the dialog will occupy
|
|
_savedArea = Surface_getArea(_globals->_gfxManagerInstance.getSurface(), _bounds);
|
|
|
|
// Set the palette for use in the dialog
|
|
setPalette();
|
|
|
|
_gfxManager.activate();
|
|
|
|
// Fill in the contents of the entire dialog
|
|
_gfxManager._bounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
drawFrame();
|
|
|
|
// Reset the dialog's graphics manager to only draw within the dialog boundaries
|
|
tempRect.translate(6, 6);
|
|
_gfxManager._bounds = tempRect;
|
|
|
|
// Draw each element in the dialog in order
|
|
GfxElementList::iterator i;
|
|
for (i = _elements.begin(); i != _elements.end(); ++i) {
|
|
(*i)->draw();
|
|
}
|
|
|
|
// If there's a default button, then draw it
|
|
if (_defaultButton) {
|
|
_defaultButton->_flags |= GFXFLAG_THICK_FRAME;
|
|
_defaultButton->draw();
|
|
}
|
|
|
|
_gfxManager.deactivate();
|
|
}
|
|
|
|
void GfxDialog::add(GfxElement *element) {
|
|
_elements.push_back(element);
|
|
element->_owner = this;
|
|
}
|
|
|
|
void GfxDialog::addElements(GfxElement *ge, ...) {
|
|
va_list va;
|
|
va_start(va, ge);
|
|
GfxElement *gfxElement = ge;
|
|
while (gfxElement) {
|
|
add(gfxElement);
|
|
|
|
gfxElement = va_arg(va, GfxElement *);
|
|
}
|
|
|
|
va_end(va);
|
|
}
|
|
|
|
void GfxDialog::setTopLeft(int xp, int yp) {
|
|
_bounds.moveTo(xp - 6, yp - 6);
|
|
}
|
|
|
|
void GfxDialog::setCenter(int xp, int yp) {
|
|
setTopLeft(xp - (_bounds.width() / 2), yp - (_bounds.height() / 2));
|
|
}
|
|
|
|
GfxButton *GfxDialog::execute(GfxButton *defaultButton) {
|
|
_gfxManager.activate();
|
|
|
|
if (defaultButton != _defaultButton) {
|
|
if (_defaultButton) {
|
|
_defaultButton->_flags &= ~GFXFLAG_THICK_FRAME;
|
|
_defaultButton->draw();
|
|
}
|
|
_defaultButton = defaultButton;
|
|
}
|
|
if (_defaultButton) {
|
|
_defaultButton->_flags |= GFXFLAG_THICK_FRAME;
|
|
_defaultButton->draw();
|
|
}
|
|
|
|
// Event loop
|
|
GfxButton *selectedButton = NULL;
|
|
|
|
bool breakFlag = false;
|
|
while (!_vm->getEventManager()->shouldQuit() && !breakFlag) {
|
|
Event event;
|
|
while (_globals->_events.getEvent(event) && !breakFlag) {
|
|
// Adjust mouse positions to be relative within the dialog
|
|
event.mousePos.x -= _gfxManager._bounds.left;
|
|
event.mousePos.y -= _gfxManager._bounds.top;
|
|
|
|
for (GfxElementList::iterator i = _elements.begin(); i != _elements.end(); ++i) {
|
|
if ((*i)->process(event))
|
|
selectedButton = static_cast<GfxButton *>(*i);
|
|
}
|
|
|
|
if (selectedButton) {
|
|
breakFlag = true;
|
|
break;
|
|
} else if (!event.handled) {
|
|
if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
|
|
selectedButton = NULL;
|
|
breakFlag = true;
|
|
break;
|
|
} else if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_RETURN)) {
|
|
selectedButton = defaultButton;
|
|
breakFlag = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_system->delayMillis(10);
|
|
g_system->updateScreen();
|
|
}
|
|
|
|
_gfxManager.deactivate();
|
|
if (_defaultButton)
|
|
_defaultButton->_flags &= ~GFXFLAG_THICK_FRAME;
|
|
|
|
return selectedButton;
|
|
}
|
|
|
|
void GfxDialog::setPalette() {
|
|
_globals->_scenePalette.loadPalette(0);
|
|
_globals->_scenePalette.setPalette(0, 1);
|
|
_globals->_scenePalette.setPalette(_globals->_scenePalette._colors.foreground, 1);
|
|
_globals->_scenePalette.setPalette(_globals->_fontColors.background, 1);
|
|
_globals->_scenePalette.setPalette(_globals->_fontColors.foreground, 1);
|
|
_globals->_scenePalette.setPalette(255, 1);
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
GfxManager::GfxManager() : _surface(_globals->_screenSurface), _oldManager(NULL) {
|
|
_font.setOwner(this);
|
|
_font._fillFlag = false;
|
|
_bounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
}
|
|
|
|
GfxManager::GfxManager(GfxSurface &s) : _surface(s), _oldManager(NULL) {
|
|
_font.setOwner(this);
|
|
_font._fillFlag = false;
|
|
}
|
|
|
|
void GfxManager::setDefaults() {
|
|
Rect screenBounds(0, 0, g_system->getWidth(), g_system->getHeight());
|
|
|
|
_surface.setBounds(screenBounds);
|
|
_bounds = screenBounds;
|
|
_pane0Rect4 = screenBounds;
|
|
|
|
_font._edgeSize = Common::Point(1, 1);
|
|
_font._colors = _globals->_fontColors;
|
|
_font.setFontNumber(_globals->_gfxFontNumber);
|
|
}
|
|
|
|
void GfxManager::activate() {
|
|
assert(!contains(_globals->_gfxManagers, this));
|
|
_globals->_gfxManagers.push_front(this);
|
|
}
|
|
|
|
void GfxManager::deactivate() {
|
|
// Assert that there will still be another manager, and we're correctly removing our own
|
|
assert((_globals->_gfxManagers.size() > 1) && (&_globals->gfxManager() == this));
|
|
_globals->_gfxManagers.pop_front();
|
|
}
|
|
|
|
int GfxManager::getStringWidth(const char *s, int numChars) {
|
|
return _font.getStringWidth(s, numChars);
|
|
}
|
|
|
|
int GfxManager::getStringWidth(const char *s) {
|
|
return _font.getStringWidth(s);
|
|
}
|
|
|
|
void GfxManager::getStringBounds(const char *s, Rect &bounds, int maxWidth) {
|
|
_font.getStringBounds(s, bounds, maxWidth);
|
|
}
|
|
|
|
void GfxManager::fillArea(int xp, int yp, int color) {
|
|
_surface.setBounds(_bounds);
|
|
Rect tempRect(xp, yp, xp + _font._edgeSize.x, yp + _font._edgeSize.y);
|
|
_surface.fillRect(tempRect, color);
|
|
}
|
|
|
|
void GfxManager::fillRect(const Rect &bounds, int color) {
|
|
_surface.setBounds(_bounds);
|
|
_surface.fillRect(bounds, color);
|
|
}
|
|
|
|
void GfxManager::fillRect2(int xs, int ys, int width, int height, int color) {
|
|
_surface.setBounds(_bounds);
|
|
_surface.fillRect(Rect(xs, ys, xs + width, ys + height), color);
|
|
}
|
|
|
|
/**
|
|
* Sets up the standard palette for dialog displays
|
|
*/
|
|
void GfxManager::setDialogPalette() {
|
|
// Get the main palette information
|
|
byte palData[256 * 3];
|
|
uint count, start;
|
|
_resourceManager->getPalette(0, &palData[0], &start, &count);
|
|
g_system->getPaletteManager()->setPalette(&palData[0], start, count);
|
|
|
|
// Miscellaneous
|
|
uint32 white = 0xffffffff;
|
|
g_system->getPaletteManager()->setPalette((const byte *)&white, 255, 1);
|
|
}
|
|
|
|
/**
|
|
* Returns the angle of line connecting two points
|
|
*/
|
|
int GfxManager::getAngle(const Common::Point &p1, const Common::Point &p2) {
|
|
int xDiff = p2.x - p1.x, yDiff = p1.y - p2.y;
|
|
|
|
if (!xDiff && !yDiff)
|
|
return -1;
|
|
else if (!xDiff)
|
|
return (p2.y >= p1.y) ? 180 : 0;
|
|
else if (!yDiff)
|
|
return (p2.x >= p1.x) ? 90 : 270;
|
|
else {
|
|
int result = (((xDiff * 100) / ((abs(xDiff) + abs(yDiff))) * 90) / 100);
|
|
|
|
if (yDiff < 0)
|
|
result = 180 - result;
|
|
else if (xDiff < 0)
|
|
result += 360;
|
|
|
|
return result;
|
|
}
|
|
}
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
|
|
GfxFont::GfxFont() {
|
|
_fontNumber = (_vm->getFeatures() & GF_DEMO) ? 0 : 50;
|
|
_numChars = 0;
|
|
_bpp = 0;
|
|
_fontData = NULL;
|
|
_fillFlag = false;
|
|
}
|
|
|
|
GfxFont::~GfxFont() {
|
|
DEALLOCATE(_fontData);
|
|
}
|
|
|
|
/**
|
|
* Sets the current active font number
|
|
*
|
|
* @fontNumber New font number
|
|
*/
|
|
void GfxFont::setFontNumber(uint32 fontNumber) {
|
|
if ((_fontNumber == fontNumber) && (_fontData))
|
|
return;
|
|
|
|
DEALLOCATE(_fontData);
|
|
|
|
_fontNumber = fontNumber;
|
|
|
|
_fontData = _resourceManager->getResource(RES_FONT, _fontNumber, 0, true);
|
|
if (!_fontData)
|
|
_fontData = _resourceManager->getResource(RES_FONT, _fontNumber, 0);
|
|
|
|
_numChars = READ_LE_UINT16(_fontData + 4);
|
|
_fontSize.y = READ_LE_UINT16(_fontData + 6);
|
|
_fontSize.x = READ_LE_UINT16(_fontData + 8);
|
|
_bpp = READ_LE_UINT16(_fontData + 10);
|
|
}
|
|
|
|
/**
|
|
* Returns the width of the given specified character
|
|
*
|
|
* @ch Character to return width of
|
|
*/
|
|
int GfxFont::getCharWidth(char ch) {
|
|
assert(_numChars > 0);
|
|
uint32 charOffset = READ_LE_UINT32(_fontData + 12 + (uint8)ch * 4);
|
|
return _fontData[charOffset] & 0x1f;
|
|
}
|
|
|
|
/**
|
|
* Returns the width of the given string in the current font
|
|
*
|
|
* @s String to return the width of
|
|
* @numChars Number of characters within the string to use
|
|
*/
|
|
int GfxFont::getStringWidth(const char *s, int numChars) {
|
|
assert(_numChars > 0);
|
|
int width = 0;
|
|
|
|
for (; numChars > 0; --numChars, ++s) {
|
|
uint32 charOffset = READ_LE_UINT32(_fontData + 12 + (uint8)*s * 4);
|
|
int charWidth = _fontData[charOffset] & 0x1f;
|
|
|
|
width += charWidth;
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
/**
|
|
* Returns the width of the given string in the current font
|
|
*
|
|
* @s String to return the width of
|
|
*/
|
|
int GfxFont::getStringWidth(const char *s) {
|
|
return getStringWidth(s, strlen(s));
|
|
}
|
|
|
|
/**
|
|
* Returns the maximum number of characters for words that will fit into a given width
|
|
*
|
|
* @s Message to be analysed
|
|
* @maxWidth Maximum allowed width
|
|
*/
|
|
int GfxFont::getStringFit(const char *&s, int maxWidth) {
|
|
const char *nextWord = NULL;
|
|
const char *sStart = s;
|
|
int numChars = 1;
|
|
int strWidth = 1;
|
|
char nextChar;
|
|
|
|
for (;;) {
|
|
nextChar = *s++;
|
|
|
|
if ((nextChar == '\r') || (nextChar == '\0'))
|
|
break;
|
|
|
|
// Check if it's a word end
|
|
if (nextChar == ' ') {
|
|
nextWord = s;
|
|
}
|
|
|
|
strWidth = getStringWidth(sStart, numChars);
|
|
if (strWidth > maxWidth) {
|
|
if (nextWord) {
|
|
s = nextWord;
|
|
nextChar = ' ';
|
|
}
|
|
break;
|
|
}
|
|
|
|
++numChars;
|
|
}
|
|
|
|
int totalChars = s - sStart;
|
|
if (nextChar == '\0')
|
|
--s;
|
|
if ((nextChar == ' ') || (nextChar == '\r') || (nextChar == '\0'))
|
|
--totalChars;
|
|
|
|
return totalChars;
|
|
}
|
|
|
|
/**
|
|
* Fills out the passed rect with the dimensions of a given string word-wrapped to a
|
|
* maximum specified width
|
|
*
|
|
* @s Message to be analysed
|
|
* @bounds Rectangle to put output size into
|
|
* @maxWidth Maximum allowed line width in pixels
|
|
*/
|
|
void GfxFont::getStringBounds(const char *s, Rect &bounds, int maxWidth) {
|
|
if (maxWidth == 0) {
|
|
// No maximum width, so set bounds for a single line
|
|
bounds.set(0, 0, getStringWidth(s), getHeight());
|
|
} else {
|
|
int numLines = 0;
|
|
int lineWidth = 0;
|
|
|
|
// Loop to figure out the number of lines required, and the maximum line width
|
|
while (*s) {
|
|
const char *msg = s;
|
|
int numChars = getStringFit(msg, maxWidth);
|
|
lineWidth = MAX(lineWidth, getStringWidth(s, numChars));
|
|
|
|
s = msg;
|
|
++numLines;
|
|
}
|
|
|
|
bounds.set(0, 0, lineWidth, numLines * getHeight());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes out a character at the currently set position using the active font
|
|
*
|
|
* @ch Character to display
|
|
*/
|
|
int GfxFont::writeChar(const char ch) {
|
|
assert((_fontData != NULL) && ((uint8)ch < _numChars));
|
|
uint32 charOffset = READ_LE_UINT32(_fontData + 12 + (uint8)ch * 4);
|
|
int charWidth = _fontData[charOffset] & 0x1f;
|
|
int charHeight = (READ_LE_UINT16(_fontData + charOffset) >> 5) & 0x3f;
|
|
int yOffset = (_fontData[charOffset + 1] >> 3) & 0x1f;
|
|
const uint8 *dataP = &_fontData[charOffset + 2];
|
|
|
|
// Lock the surface for access
|
|
Graphics::Surface surfacePtr = _gfxManager->lockSurface();
|
|
|
|
Rect charRect;
|
|
charRect.set(0, 0, charWidth, _fontSize.y);
|
|
charRect.translate(_topLeft.x + _position.x, _topLeft.y + _position.y + yOffset);
|
|
|
|
if (_fillFlag)
|
|
surfacePtr.fillRect(charRect, _colors.background);
|
|
|
|
charRect.bottom = charRect.top + charHeight;
|
|
|
|
// Display the character
|
|
int bitCtr = 0;
|
|
uint8 v = 0;
|
|
for (int yp = charRect.top; yp < charRect.bottom; ++yp) {
|
|
byte *destP = (byte *)surfacePtr.getBasePtr(charRect.left, yp);
|
|
|
|
for (int xs = 0; xs < charRect.width(); ++xs, ++destP) {
|
|
// Get the next color index to use
|
|
if ((bitCtr % 8) == 0) v = *dataP++;
|
|
int colIndex = 0;
|
|
for (int subCtr = 0; subCtr < _bpp; ++subCtr, ++bitCtr) {
|
|
colIndex = (colIndex << 1) | (v & 0x80 ? 1 : 0);
|
|
v <<= 1;
|
|
}
|
|
|
|
switch (colIndex) {
|
|
//case 0: *destP = _colors.background; break;
|
|
case 1: *destP = _colors.foreground; break;
|
|
case 2: *destP = _colors2.background; break;
|
|
case 3: *destP = _colors2.foreground; break;
|
|
}
|
|
}
|
|
}
|
|
|
|
_position.x += charWidth;
|
|
_gfxManager->unlockSurface();
|
|
return charWidth;
|
|
}
|
|
|
|
/**
|
|
* Writes the specified number of characters from the specified string at the current text position
|
|
*
|
|
* @s String to display
|
|
* @numChars Number of characters to print
|
|
*/
|
|
void GfxFont::writeString(const char *s, int numChars) {
|
|
// Lock the surface for access
|
|
_gfxManager->lockSurface();
|
|
|
|
while ((numChars-- > 0) && (*s != '\0')) {
|
|
writeChar(*s);
|
|
++s;
|
|
}
|
|
|
|
// Release the surface lock
|
|
_gfxManager->unlockSurface();
|
|
}
|
|
|
|
/**
|
|
* Writes the the specified string at the current text position
|
|
*
|
|
* @s String to display
|
|
*/
|
|
void GfxFont::writeString(const char *s) {
|
|
writeString(s, strlen(s));
|
|
}
|
|
|
|
/**
|
|
* Writes a specified string within a given area with support for word wrapping and text alignment types
|
|
*
|
|
* @s String to display
|
|
* @bounds Bounds to display the text within
|
|
* @align Text alignment mode
|
|
*/
|
|
void GfxFont::writeLines(const char *s, const Rect &bounds, TextAlign align) {
|
|
int lineNum = 0;
|
|
|
|
// Lock the surface for access
|
|
_gfxManager->lockSurface();
|
|
|
|
while (*s) {
|
|
const char *msgP = s;
|
|
int numChars = getStringFit(msgP, bounds.width());
|
|
|
|
_position.y = bounds.top + lineNum * getHeight();
|
|
|
|
switch (align) {
|
|
case ALIGN_RIGHT:
|
|
// Right aligned text
|
|
_position.x = bounds.right - getStringWidth(s, numChars);
|
|
writeString(s, numChars);
|
|
break;
|
|
|
|
case ALIGN_CENTER:
|
|
// Center aligned text
|
|
_position.x = bounds.left + (bounds.width() / 2) - (getStringWidth(s, numChars) / 2);
|
|
writeString(s, numChars);
|
|
break;
|
|
|
|
case ALIGN_JUSTIFIED: {
|
|
// Justified text
|
|
// Get the number of words in the string portion
|
|
int charCtr = 0, numWords = 0;
|
|
while (charCtr < numChars) {
|
|
if (s[charCtr] == ' ')
|
|
++numWords;
|
|
++charCtr;
|
|
}
|
|
// If end of string, count final word
|
|
if (*msgP == '\0')
|
|
++numWords;
|
|
|
|
// Display the words of the string
|
|
int spareWidth = bounds.width() - getStringWidth(s, numChars);
|
|
charCtr = 0;
|
|
_position.x = bounds.left;
|
|
|
|
while (charCtr < numChars) {
|
|
writeChar(s[charCtr]);
|
|
if ((numWords > 0) && (s[charCtr] == ' ')) {
|
|
int separationWidth = spareWidth / numWords;
|
|
spareWidth -= separationWidth;
|
|
--numWords;
|
|
_position.x += separationWidth;
|
|
}
|
|
|
|
++charCtr;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case ALIGN_LEFT:
|
|
default:
|
|
// Standard text
|
|
_position.x = bounds.left;
|
|
writeString(s, numChars);
|
|
break;
|
|
}
|
|
|
|
// Next line
|
|
s = msgP;
|
|
++lineNum;
|
|
}
|
|
|
|
// Release the surface lock
|
|
_gfxManager->unlockSurface();
|
|
}
|
|
|
|
/*--------------------------------------------------------------------------*/
|
|
|
|
GfxFontBackup::GfxFontBackup() {
|
|
_edgeSize = _globals->gfxManager()._font._edgeSize;
|
|
_position = _globals->gfxManager()._font._position;
|
|
_colors = _globals->gfxManager()._font._colors;
|
|
_fontNumber = _globals->gfxManager()._font._fontNumber;
|
|
}
|
|
|
|
GfxFontBackup::~GfxFontBackup() {
|
|
_globals->gfxManager()._font.setFontNumber(_fontNumber);
|
|
_globals->gfxManager()._font._edgeSize = _edgeSize;
|
|
_globals->gfxManager()._font._position = _position;
|
|
_globals->gfxManager()._font._colors = _colors;
|
|
}
|
|
|
|
|
|
} // End of namespace tSage
|