scummvm/engines/tsage/graphics.cpp
eriktorbjorn 9dbf058909 TSAGE: Fix graphics button behaviour (slightly hackish)
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.
2011-05-16 23:37:20 +02:00

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