aboutsummaryrefslogtreecommitdiff
path: root/engines/tsage/graphics.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/tsage/graphics.cpp')
-rw-r--r--engines/tsage/graphics.cpp1430
1 files changed, 1430 insertions, 0 deletions
diff --git a/engines/tsage/graphics.cpp b/engines/tsage/graphics.cpp
new file mode 100644
index 0000000000..641e10b3e9
--- /dev/null
+++ b/engines/tsage/graphics.cpp
@@ -0,0 +1,1430 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#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/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._transColour = *(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._transColour);
+
+ 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 = _vm->_dataManager->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;
+}
+
+/**
+ * Centres the rectangle at a given position
+ *
+ * @xp x position for new centre
+ * @yp y position for new centre
+ */
+void Rect::centre(int xp, int yp) {
+ moveTo(xp - (width() / 2), yp - (height() / 2));
+}
+
+/**
+ * Centres the rectangle at the centre of a second passed rectangle
+ *
+ * @r Second rectangle whose centre to use
+ */
+void Rect::centre(const Rect &r) {
+ centre(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::synchronise(Serialiser &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;
+}
+
+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;
+ _customSurface = new Graphics::Surface();
+ _customSurface->create(width, height, 1);
+ _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.bytesPerPixel = src->bytesPerPixel;
+ 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 colour
+ *
+ * @bounds Area to fill
+ * @colour Colour to use
+ */
+void GfxSurface::fillRect(const Rect &bounds, int colour) {
+ Graphics::Surface surface = lockSurface();
+ surface.fillRect(bounds, colour);
+ 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;
+ _transColour = s._transColour;
+
+ 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, 1);
+ 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._colours.background = 0;
+ gfxManager._font._colours.foreground = 7;
+ gfxManager._font.setFontNumber(2);
+
+ // Get the area for text display
+ Rect textRect;
+ gfxManager.getStringBounds(msg.c_str(), textRect, 200);
+ textRect.centre(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 = _vm->_dataManager->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) {
+ int scale = 100 * size / srcSize;
+ assert(scale >= 0);
+ int *v = new int[size];
+ Common::set_to(v, &v[size], 0);
+
+ int distCtr = 0;
+ int *destP = v;
+ for (int distIndex = 0; distIndex < srcSize; ++distIndex) {
+ distCtr += scale;
+ while (distCtr >= 100) {
+ assert(destP < &v[size]);
+ *destP++ = distIndex;
+ distCtr -= 100;
+ }
+ }
+
+ 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) {
+ 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) {
+ const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]);
+ byte *destP = (byte *)destImage.getBasePtr(0, yp);
+
+ for (int xp = 0; xp < xSize; ++xp) {
+ const byte *tempSrcP = srcP + horizUsage[xp];
+ *destP++ = *tempSrcP++;
+ }
+ }
+
+ // 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 == 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());
+
+ 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._transColour == -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._transColour)
+ *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;
+ _colours = _globals->_gfxColours;
+ _fontColours = _globals->_fontColours;
+}
+
+/**
+ * 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
+ // colour with the background colour 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 == _colours.background) *lineP = _colours.foreground;
+ else if (*lineP == _colours.foreground) *lineP = _colours.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 bgColour, fgColour;
+ if (_flags & GFXFLAG_THICK_FRAME) {
+ bgColour = 0;
+ fgColour = 0;
+ } else {
+ bgColour = _fontColours.background;
+ fgColour = _fontColours.foreground;
+ }
+
+ Rect tempRect = _bounds;
+ tempRect.collapse(3, 3);
+ tempRect.collapse(-1, -1);
+ gfxManager.fillRect(tempRect, _colours.background);
+
+ --tempRect.bottom; --tempRect.right;
+ gfxManager.fillArea(tempRect.left, tempRect.top, bgColour);
+ gfxManager.fillArea(tempRect.left, tempRect.bottom, fgColour);
+ gfxManager.fillArea(tempRect.right, tempRect.top, fgColour);
+ gfxManager.fillArea(tempRect.right, tempRect.bottom, fgColour);
+
+ tempRect.collapse(-1, -1);
+ gfxManager.fillRect2(tempRect.left + 1, tempRect.top, tempRect.width() - 1, 1, bgColour);
+ gfxManager.fillRect2(tempRect.left, tempRect.top + 1, 1, tempRect.height() - 1, bgColour);
+ gfxManager.fillRect2(tempRect.left + 1, tempRect.bottom, tempRect.width() - 1, 1, fgColour);
+ gfxManager.fillRect2(tempRect.right, tempRect.top + 1, 1, tempRect.height() - 1, fgColour);
+
+ 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) {
+ bool highlightFlag = false;
+
+ while (!_vm->getEventManager()->shouldQuit()) {
+ g_system->delayMillis(10);
+
+ if (_bounds.contains(event.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_BUTTON_UP))
+ break;
+ }
+
+ 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 = _vm->_dataManager->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 colour
+ gfxManager.setFillFlag(false);
+ gfxManager._font.setFontNumber(_fontNumber);
+ gfxManager._font._colours.foreground = this->_colours.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 colour
+ gfxManager._font.setFontNumber(_fontNumber);
+ gfxManager._font._colours.foreground = this->_colours.foreground;
+
+ // Display the button's text
+ Rect tempRect(_bounds);
+ tempRect.collapse(3, 3);
+ gfxManager._font.writeLines(_message.c_str(), tempRect, ALIGN_CENTRE);
+
+ 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::setCentre(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;
+
+ while (!_vm->getEventManager()->shouldQuit()) {
+ Event event;
+ while (_globals->_events.getEvent(event)) {
+ // 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)
+ break;
+ else if (!event.handled) {
+ if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_ESCAPE)) {
+ selectedButton = NULL;
+ break;
+ } else if ((event.eventType == EVENT_KEYPRESS) && (event.kbd.keycode == Common::KEYCODE_RETURN)) {
+ selectedButton = defaultButton;
+ break;
+ }
+ }
+ }
+
+ _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._colours.foreground, 1);
+ _globals->_scenePalette.setPalette(_globals->_fontColours.background, 1);
+ _globals->_scenePalette.setPalette(_globals->_fontColours.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._colours = _globals->_fontColours;
+ _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 colour) {
+ _surface.setBounds(_bounds);
+ Rect tempRect(xp, yp, xp + _font._edgeSize.x, yp + _font._edgeSize.y);
+ _surface.fillRect(tempRect, colour);
+}
+
+void GfxManager::fillRect(const Rect &bounds, int colour) {
+ _surface.setBounds(_bounds);
+ _surface.fillRect(bounds, colour);
+}
+
+void GfxManager::fillRect2(int xs, int ys, int width, int height, int colour) {
+ _surface.setBounds(_bounds);
+ _surface.fillRect(Rect(xs, ys, xs + width, ys + height), colour);
+}
+
+/**
+ * Sets up the standard palette for dialog displays
+ */
+void GfxManager::setDialogPalette() {
+ // Get the main palette information
+ RGB8 palData[256];
+ uint count, start;
+ _vm->_dataManager->getPalette(0, &palData[0], &start, &count);
+ g_system->getPaletteManager()->setPalette((byte *)&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 = 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 = _vm->_tSageManager->getResource(RES_FONT, _fontNumber, 0, true);
+ if (!_fontData)
+ _fontData = _vm->_dataManager->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, _colours.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 colour 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 = _colours.background; break;
+ case 1: *destP = _colours.foreground; break;
+ case 2: *destP = _colours2.background; break;
+ case 3: *destP = _colours2.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_CENTRE:
+ // 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;
+ _colours = _globals->gfxManager()._font._colours;
+ _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._colours = _colours;
+}
+
+
+} // End of namespace tSage