scummvm/gui/ThemeEngine.cpp

1152 lines
30 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.
*
* $URL$
* $Id$
*
*/
#include "common/util.h"
#include "common/system.h"
#include "common/events.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/fs.h"
#include "common/unzip.h"
#include "graphics/surface.h"
#include "graphics/colormasks.h"
#include "graphics/imagedec.h"
#include "graphics/cursorman.h"
#include "graphics/VectorRenderer.h"
#include "gui/launcher.h"
#include "gui/ThemeEngine.h"
#include "gui/ThemeEval.h"
#include "gui/ThemeParser.h"
#include "gui/ThemeData.h"
#define GUI_ENABLE_BUILTIN_THEME
namespace GUI {
/**********************************************************
* ThemeEngine class
*********************************************************/
ThemeEngine::ThemeEngine(Common::String fileName, GraphicsMode mode) :
_system(0), _vectorRenderer(0),
_buffering(false), _bytesPerPixel(0), _graphicsMode(kGfxDisabled),
_font(0), _initOk(false), _themeOk(false), _enabled(false), _cursor(0) {
_system = g_system;
_parser = new ThemeParser(this);
_themeEval = new GUI::ThemeEval();
_useCursor = false;
for (int i = 0; i < kDrawDataMAX; ++i) {
_widgets[i] = 0;
}
for (int i = 0; i < kTextDataMAX; ++i) {
_texts[i] = 0;
}
_graphicsMode = mode;
_themeId = fileName;
_themeArchive = 0;
_initOk = false;
}
ThemeEngine::~ThemeEngine() {
delete _vectorRenderer;
_vectorRenderer = 0;
_screen.free();
_backBuffer.free();
unloadTheme();
delete _parser;
delete _themeEval;
delete[] _cursor;
}
/**********************************************************
* Rendering mode management
*********************************************************/
const ThemeEngine::Renderer ThemeEngine::_rendererModes[] = {
{ "Disabled GFX", "none", kGfxDisabled },
{ "Standard Renderer (16bpp)", "normal_16bpp", kGfxStandard16bit },
#ifndef DISABLE_FANCY_THEMES
{ "Antialiased Renderer (16bpp)", "aa_16bpp", kGfxAntialias16bit }
#endif
};
const uint ThemeEngine::_rendererModesSize = ARRAYSIZE(ThemeEngine::_rendererModes);
const ThemeEngine::GraphicsMode ThemeEngine::_defaultRendererMode =
#ifndef DISABLE_FANCY_THEMES
ThemeEngine::kGfxAntialias16bit;
#else
ThemeEngine::kGfxStandard16bit;
#endif
ThemeEngine::GraphicsMode ThemeEngine::findMode(const Common::String &cfg) {
for (uint i = 0; i < _rendererModesSize; ++i) {
if (cfg.equalsIgnoreCase(_rendererModes[i].cfg))
return _rendererModes[i].mode;
}
return kGfxDisabled;
}
const char *ThemeEngine::findModeConfigName(GraphicsMode mode) {
for (uint i = 0; i < _rendererModesSize; ++i) {
if (mode == _rendererModes[i].mode)
return _rendererModes[i].cfg;
}
return findModeConfigName(kGfxDisabled);
}
/**********************************************************
* Theme setup/initialization
*********************************************************/
bool ThemeEngine::init() {
// reset everything and reload the graphics
_initOk = false;
setGraphicsMode(_graphicsMode);
if (_screen.pixels && _backBuffer.pixels) {
_initOk = true;
}
// TODO: Instead of hard coding the font here, it should be possible
// to specify the fonts to be used for each resolution in the theme XML.
if (_screen.w >= 400 && _screen.h >= 300) {
_font = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont);
} else {
_font = FontMan.getFontByUsage(Graphics::FontManager::kGUIFont);
}
loadTheme(_themeId);
return ready();
}
void ThemeEngine::clearAll() {
if (_initOk) {
_system->clearOverlay();
_system->grabOverlay((OverlayColor *)_screen.pixels, _screen.w);
}
}
void ThemeEngine::refresh() {
init();
if (_enabled) {
_system->showOverlay();
if (_useCursor) {
CursorMan.replaceCursorPalette(_cursorPal, 0, MAX_CURS_COLORS);
CursorMan.replaceCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, _cursorTargetScale);
}
}
}
void ThemeEngine::enable() {
if (_enabled)
return;
if (_useCursor) {
CursorMan.pushCursorPalette(_cursorPal, 0, MAX_CURS_COLORS);
CursorMan.pushCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, _cursorTargetScale);
CursorMan.showMouse(true);
}
_system->showOverlay();
clearAll();
_enabled = true;
}
void ThemeEngine::disable() {
if (!_enabled)
return;
_system->hideOverlay();
if (_useCursor) {
CursorMan.popCursorPalette();
CursorMan.popCursor();
}
_enabled = false;
}
void ThemeEngine::setGraphicsMode(GraphicsMode mode) {
switch (mode) {
case kGfxStandard16bit:
#ifndef DISABLE_FANCY_THEMES
case kGfxAntialias16bit:
#endif
_bytesPerPixel = sizeof(uint16);
break;
default:
error("Invalid graphics mode");
}
uint32 width = _system->getOverlayWidth();
uint32 height = _system->getOverlayHeight();
_backBuffer.free();
_backBuffer.create(width, height, _bytesPerPixel);
_screen.free();
_screen.create(width, height, _bytesPerPixel);
delete _vectorRenderer;
_vectorRenderer = Graphics::createRenderer(mode);
_vectorRenderer->setSurface(&_screen);
}
bool ThemeEngine::isWidgetCached(DrawData type, const Common::Rect &r) {
return _widgets[type] && _widgets[type]->_cached &&
_widgets[type]->_surfaceCache->w == r.width() &&
_widgets[type]->_surfaceCache->h == r.height();
}
void ThemeEngine::drawCached(DrawData type, const Common::Rect &r) {
assert(_widgets[type]->_surfaceCache->bytesPerPixel == _screen.bytesPerPixel);
_vectorRenderer->blitSurface(_widgets[type]->_surfaceCache, r);
}
void ThemeEngine::calcBackgroundOffset(DrawData type) {
uint maxShadow = 0;
for (Common::List<Graphics::DrawStep>::const_iterator step = _widgets[type]->_steps.begin();
step != _widgets[type]->_steps.end(); ++step) {
if ((step->autoWidth || step->autoHeight) && step->shadow > maxShadow)
maxShadow = step->shadow;
if (step->drawingCall == &Graphics::VectorRenderer::drawCallback_BEVELSQ && step->bevel > maxShadow)
maxShadow = step->bevel;
}
_widgets[type]->_backgroundOffset = maxShadow;
}
void ThemeEngine::restoreBackground(Common::Rect r) {
r.clip(_screen.w, _screen.h); // AHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHA... Oh god. :(
_vectorRenderer->blitSurface(&_backBuffer, r);
}
/**********************************************************
* Theme elements management
*********************************************************/
void ThemeEngine::addDrawStep(const Common::String &drawDataId, const Graphics::DrawStep &step) {
DrawData id = getDrawDataId(drawDataId);
assert(_widgets[id] != 0);
_widgets[id]->_steps.push_back(step);
}
bool ThemeEngine::addTextData(const Common::String &drawDataId, const Common::String &textDataId, Graphics::TextAlign alignH, TextAlignVertical alignV) {
DrawData id = getDrawDataId(drawDataId);
TextData textId = getTextDataId(textDataId);
if (id == -1 || textId == -1 || !_widgets[id])
return false;
_widgets[id]->_textDataId = textId;
_widgets[id]->_textAlignH = alignH;
_widgets[id]->_textAlignV = alignV;
return true;
}
bool ThemeEngine::addFont(const Common::String &fontId, const Common::String &file, int r, int g, int b) {
TextData textId = getTextDataId(fontId);
if (textId == -1)
return false;
if (_texts[textId] != 0)
delete _texts[textId];
_texts[textId] = new TextDrawData;
if (file == "default") {
_texts[textId]->_fontPtr = _font;
} else {
_texts[textId]->_fontPtr = FontMan.getFontByName(file);
if (!_texts[textId]->_fontPtr) {
_texts[textId]->_fontPtr = loadFont(file);
if (!_texts[textId]->_fontPtr)
error("Couldn't load %s font '%s'", fontId.c_str(), file.c_str());
FontMan.assignFontToName(file, _texts[textId]->_fontPtr);
}
}
_texts[textId]->_color.r = r;
_texts[textId]->_color.g = g;
_texts[textId]->_color.b = b;
return true;
}
bool ThemeEngine::addBitmap(const Common::String &filename) {
// Release any existing surface with that name.
Graphics::Surface *surf = _bitmaps[filename];
if (surf) {
surf->free();
delete surf;
}
// Now try to load the bitmap via the ImageDecoder class.
surf = Graphics::ImageDecoder::loadFile(filename);
if (!surf && _themeArchive) {
Common::SeekableReadStream *stream = _themeArchive->openFile(filename);
if (stream) {
surf = Graphics::ImageDecoder::loadFile(*stream);
delete stream;
}
}
// Store the surface into our hashmap (attention, may store NULL entries!)
_bitmaps[filename] = surf;
return surf != 0;
}
bool ThemeEngine::addDrawData(const Common::String &data, bool cached) {
DrawData data_id = getDrawDataId(data);
if (data_id == -1)
return false;
if (_widgets[data_id] != 0)
delete _widgets[data_id];
_widgets[data_id] = new WidgetDrawData;
_widgets[data_id]->_cached = cached;
_widgets[data_id]->_buffer = kDrawDataDefaults[data_id].buffer;
_widgets[data_id]->_surfaceCache = 0;
_widgets[data_id]->_textDataId = -1;
return true;
}
/**********************************************************
* Theme XML loading
*********************************************************/
void ThemeEngine::loadTheme(const Common::String &themeId) {
unloadTheme();
if (themeId == "builtin") {
_themeOk = loadDefaultXML();
} else {
// Load the archive containing image and XML data
_themeOk = loadThemeXML(themeId);
}
if (!_themeOk) {
delete _themeArchive;
_themeArchive = 0;
warning("Failed to load theme '%s'", themeId.c_str());
return;
}
for (int i = 0; i < kDrawDataMAX; ++i) {
if (_widgets[i] == 0) {
warning("Missing data asset: '%s'", kDrawDataDefaults[i].name);
} else {
calcBackgroundOffset((DrawData)i);
// TODO: draw the cached widget to the cache surface
if (_widgets[i]->_cached) {}
}
}
}
void ThemeEngine::unloadTheme() {
if (!_themeOk)
return;
for (int i = 0; i < kDrawDataMAX; ++i) {
delete _widgets[i];
_widgets[i] = 0;
}
for (int i = 0; i < kTextDataMAX; ++i) {
delete _texts[i];
_texts[i] = 0;
}
// Release all graphics surfaces
for (ImagesMap::iterator i = _bitmaps.begin(); i != _bitmaps.end(); ++i) {
Graphics::Surface *surf = i->_value;
if (surf) {
surf->free();
delete surf;
}
}
_bitmaps.clear();
delete _themeArchive;
_themeArchive = 0;
_themeEval->reset();
_themeOk = false;
}
bool ThemeEngine::loadDefaultXML() {
// The default XML theme is included on runtime from a pregenerated
// file inside the themes directory.
// Use the Python script "makedeftheme.py" to convert a normal XML theme
// into the "default.inc" file, which is ready to be included in the code.
bool result;
#ifdef GUI_ENABLE_BUILTIN_THEME
const char *defaultXML =
#include "themes/default.inc"
;
if (!_parser->loadBuffer((const byte*)defaultXML, strlen(defaultXML), false))
return false;
_themeName = "ScummVM Classic Theme (Builtin Version)";
_themeId = "builtin";
result = _parser->parse();
_parser->close();
return result;
#else
warning("The built-in theme is not enabled in the current build. Please load an external theme");
return false;
#endif
}
bool ThemeEngine::loadThemeXML(const Common::String &themeId) {
assert(_parser);
_themeName.clear();
//
// Try to create a Common::Archive with the files of the theme
//
Common::FSNode node(themeId);
if (!node.exists() || !node.isReadable())
return false;
if (node.getName().hasSuffix(".zip") && !node.isDirectory()) {
#ifdef USE_ZLIB
Common::ZipArchive *zipArchive = new Common::ZipArchive(node);
if (!zipArchive || !zipArchive->isOpen()) {
delete zipArchive;
zipArchive = 0;
warning("Failed to open Zip archive '%s'.", themeId.c_str());
}
_themeArchive = zipArchive;
#endif
} else if (node.isDirectory()) {
_themeArchive = new Common::FSDirectory(node);
}
if (!_themeArchive)
return false;
//
// Now that we have a Common::Archive, verify that it contains a valid THEMERC File
//
Common::File themercFile;
themercFile.open("THEMERC", *_themeArchive);
if (!themercFile.isOpen()) {
warning("Theme '%s' contains no 'THEMERC' file.", themeId.c_str());
return false;
}
Common::String stxHeader = themercFile.readLine();
if (!themeConfigParseHeader(stxHeader, _themeName) || _themeName.empty()) {
warning("Corrupted 'THEMERC' file in theme '%s'", themeId.c_str());
return false;
}
Common::ArchiveMemberList members;
if (0 == _themeArchive->listMatchingMembers(members, "*.stx")) {
warning("Found no STX files for theme '%s'.", themeId.c_str());
return false;
}
//
// Loop over all STX files, load and parse them
//
for (Common::ArchiveMemberList::iterator i = members.begin(); i != members.end(); ++i) {
assert((*i)->getName().hasSuffix(".stx"));
if (_parser->loadStream((*i)->open()) == false) {
warning("Failed to load STX file '%s'", (*i)->getDisplayName().c_str());
_parser->close();
return false;
}
if (_parser->parse() == false) {
warning("Failed to parse STX file '%s'", (*i)->getDisplayName().c_str());
_parser->close();
return false;
}
_parser->close();
}
assert(!_themeName.empty());
return true;
}
/**********************************************************
* Drawing Queue management
*********************************************************/
void ThemeEngine::queueDD(DrawData type, const Common::Rect &r, uint32 dynamic) {
if (_widgets[type] == 0)
return;
Common::Rect area = r;
area.clip(_screen.w, _screen.h);
ThemeItemDrawData *q = new ThemeItemDrawData(this, _widgets[type], area, dynamic);
if (_buffering) {
if (_widgets[type]->_buffer) {
_bufferQueue.push_back(q);
} else {
if (kDrawDataDefaults[type].parent != kDDNone && kDrawDataDefaults[type].parent != type)
queueDD(kDrawDataDefaults[type].parent, r);
_screenQueue.push_back(q);
}
} else {
q->drawSelf(!_widgets[type]->_buffer, _widgets[type]->_buffer);
delete q;
}
}
void ThemeEngine::queueDDText(TextData type, const Common::Rect &r, const Common::String &text, bool restoreBg,
bool ellipsis, Graphics::TextAlign alignH, TextAlignVertical alignV, int deltax) {
if (_texts[type] == 0)
return;
Common::Rect area = r;
area.clip(_screen.w, _screen.h);
ThemeItemTextData *q = new ThemeItemTextData(this, _texts[type], area, text, alignH, alignV, ellipsis, restoreBg, deltax);
if (_buffering) {
_screenQueue.push_back(q);
} else {
q->drawSelf(true, false);
delete q;
}
}
void ThemeEngine::queueBitmap(const Graphics::Surface *bitmap, const Common::Rect &r, bool alpha) {
Common::Rect area = r;
area.clip(_screen.w, _screen.h);
ThemeItemBitmap *q = new ThemeItemBitmap(this, area, bitmap, alpha);
if (_buffering) {
_bufferQueue.push_back(q);
} else {
q->drawSelf(true, false);
delete q;
}
}
/**********************************************************
* Widget drawing functions
*********************************************************/
void ThemeEngine::drawButton(const Common::Rect &r, const Common::String &str, WidgetStateInfo state, uint16 hints) {
if (!ready())
return;
DrawData dd = kDDButtonIdle;
if (state == kStateEnabled)
dd = kDDButtonIdle;
else if (state == kStateHighlight)
dd = kDDButtonHover;
else if (state == kStateDisabled)
dd = kDDButtonDisabled;
queueDD(dd, r);
queueDDText(getTextData(dd), r, str, true, false, _widgets[dd]->_textAlignH, _widgets[dd]->_textAlignV);
}
void ThemeEngine::drawLineSeparator(const Common::Rect &r, WidgetStateInfo state) {
if (!ready())
return;
queueDD(kDDSeparator, r);
}
void ThemeEngine::drawCheckbox(const Common::Rect &r, const Common::String &str, bool checked, WidgetStateInfo state) {
if (!ready())
return;
Common::Rect r2 = r;
DrawData dd = kDDCheckboxDefault;
if (checked)
dd = kDDCheckboxSelected;
if (state == kStateDisabled)
dd = kDDCheckboxDisabled;
TextData td = (state == kStateHighlight) ? kTextDataHover : getTextData(dd);
const int checkBoxSize = MIN((int)r.height(), getFontHeight());
r2.bottom = r2.top + checkBoxSize;
r2.right = r2.left + checkBoxSize;
queueDD(dd, r2);
r2.left = r2.right + checkBoxSize;
r2.right = r.right;
queueDDText(td, r2, str, false, false, _widgets[kDDCheckboxDefault]->_textAlignH, _widgets[dd]->_textAlignV);
}
void ThemeEngine::drawSlider(const Common::Rect &r, int width, WidgetStateInfo state) {
if (!ready())
return;
DrawData dd = kDDSliderFull;
if (state == kStateHighlight)
dd = kDDSliderHover;
else if (state == kStateDisabled)
dd = kDDSliderDisabled;
Common::Rect r2 = r;
r2.setWidth(MIN((int16)width, r.width()));
// r2.top++; r2.bottom--; r2.left++; r2.right--;
drawWidgetBackground(r, 0, kWidgetBackgroundSlider, kStateEnabled);
queueDD(dd, r2);
}
void ThemeEngine::drawScrollbar(const Common::Rect &r, int sliderY, int sliderHeight, ScrollbarState scrollState, WidgetStateInfo state) {
if (!ready())
return;
queueDD(kDDScrollbarBase, r);
Common::Rect r2 = r;
const int buttonExtra = (r.width() * 120) / 100;
r2.bottom = r2.top + buttonExtra;
queueDD(scrollState == kScrollbarStateUp ? kDDScrollbarButtonHover : kDDScrollbarButtonIdle, r2, Graphics::VectorRenderer::kTriangleUp);
r2.translate(0, r.height() - r2.height());
queueDD(scrollState == kScrollbarStateDown ? kDDScrollbarButtonHover : kDDScrollbarButtonIdle, r2, Graphics::VectorRenderer::kTriangleDown);
r2 = r;
r2.left += 1;
r2.right -= 1;
r2.top += sliderY;
r2.bottom = r2.top + sliderHeight - 1;
r2.top += r.width() / 5;
r2.bottom -= r.width() / 5;
queueDD(scrollState == kScrollbarStateSlider ? kDDScrollbarHandleHover : kDDScrollbarHandleIdle, r2);
}
void ThemeEngine::drawDialogBackground(const Common::Rect &r, DialogBackground bgtype, WidgetStateInfo state) {
if (!ready())
return;
switch (bgtype) {
case kDialogBackgroundMain:
queueDD(kDDMainDialogBackground, r);
break;
case kDialogBackgroundSpecial:
queueDD(kDDSpecialColorBackground, r);
break;
case kDialogBackgroundPlain:
queueDD(kDDPlainColorBackground, r);
break;
case kDialogBackgroundDefault:
queueDD(kDDDefaultBackground, r);
break;
}
}
void ThemeEngine::drawCaret(const Common::Rect &r, bool erase, WidgetStateInfo state) {
if (!ready())
return;
if (erase) {
restoreBackground(r);
addDirtyRect(r);
} else
queueDD(kDDCaret, r);
}
void ThemeEngine::drawPopUpWidget(const Common::Rect &r, const Common::String &sel, int deltax, WidgetStateInfo state, Graphics::TextAlign align) {
if (!ready())
return;
DrawData dd = kDDPopUpIdle;
if (state == kStateEnabled)
dd = kDDPopUpIdle;
else if (state == kStateHighlight)
dd = kDDPopUpHover;
else if (state == kStateDisabled)
dd = kDDPopUpDisabled;
queueDD(dd, r);
if (!sel.empty()) {
Common::Rect text(r.left, r.top, r.right - 16, r.bottom);
queueDDText(getTextData(dd), text, sel, true, false, _widgets[dd]->_textAlignH, _widgets[dd]->_textAlignV, deltax);
}
}
void ThemeEngine::drawSurface(const Common::Rect &r, const Graphics::Surface &surface, WidgetStateInfo state, int alpha, bool themeTrans) {
if (!ready())
return;
queueBitmap(&surface, r, themeTrans);
}
void ThemeEngine::drawWidgetBackground(const Common::Rect &r, uint16 hints, WidgetBackground background, WidgetStateInfo state) {
if (!ready())
return;
switch (background) {
case kWidgetBackgroundBorderSmall:
queueDD(kDDWidgetBackgroundSmall, r);
break;
case kWidgetBackgroundEditText:
queueDD(kDDWidgetBackgroundEditText, r);
break;
case kWidgetBackgroundSlider:
queueDD(kDDWidgetBackgroundSlider, r);
break;
default:
queueDD(kDDWidgetBackgroundDefault, r);
break;
}
}
void ThemeEngine::drawTab(const Common::Rect &r, int tabHeight, int tabWidth, const Common::Array<Common::String> &tabs, int active, uint16 hints, int titleVPad, WidgetStateInfo state) {
if (!ready())
return;
const int tabOffset = 2;
tabWidth -= tabOffset;
queueDD(kDDTabBackground, Common::Rect(r.left, r.top, r.right, r.top + tabHeight));
for (int i = 0; i < (int)tabs.size(); ++i) {
if (i == active)
continue;
Common::Rect tabRect(r.left + i * (tabWidth + tabOffset), r.top, r.left + i * (tabWidth + tabOffset) + tabWidth, r.top + tabHeight);
queueDD(kDDTabInactive, tabRect);
queueDDText(getTextData(kDDTabInactive), tabRect, tabs[i], false, false, _widgets[kDDTabInactive]->_textAlignH, _widgets[kDDTabInactive]->_textAlignV);
}
if (active >= 0) {
Common::Rect tabRect(r.left + active * (tabWidth + tabOffset), r.top, r.left + active * (tabWidth + tabOffset) + tabWidth, r.top + tabHeight);
const uint16 tabLeft = active * (tabWidth + tabOffset);
const uint16 tabRight = MAX(r.right - tabRect.right, 0);
queueDD(kDDTabActive, tabRect, (tabLeft << 16) | (tabRight & 0xFFFF));
queueDDText(getTextData(kDDTabActive), tabRect, tabs[active], false, false, _widgets[kDDTabActive]->_textAlignH, _widgets[kDDTabActive]->_textAlignV);
}
}
void ThemeEngine::drawText(const Common::Rect &r, const Common::String &str, WidgetStateInfo state, Graphics::TextAlign align, bool inverted, int deltax, bool useEllipsis, FontStyle font) {
if (!ready())
return;
if (inverted) {
queueDD(kDDTextSelectionBackground, r);
queueDDText(kTextDataInverted, r, str, false, useEllipsis, align, kTextAlignVCenter, deltax);
return;
}
switch (font) {
case kFontStyleNormal:
queueDDText(kTextDataNormalFont, r, str, true, useEllipsis, align, kTextAlignVCenter, deltax);
return;
default:
break;
}
switch (state) {
case kStateDisabled:
queueDDText(kTextDataDisabled, r, str, true, useEllipsis, align, kTextAlignVCenter, deltax);
return;
case kStateHighlight:
queueDDText(kTextDataHover, r, str, true, useEllipsis, align, kTextAlignVCenter, deltax);
return;
case kStateEnabled:
queueDDText(kTextDataDefault, r, str, true, useEllipsis, align, kTextAlignVCenter, deltax);
return;
}
}
void ThemeEngine::drawChar(const Common::Rect &r, byte ch, const Graphics::Font *font, WidgetStateInfo state) {
if (!ready())
return;
Common::Rect charArea = r;
charArea.clip(_screen.w, _screen.h);
Graphics::PixelFormat format = _system->getOverlayFormat();
uint32 color = Graphics::RGBToColor(_texts[kTextDataDefault]->_color.r, _texts[kTextDataDefault]->_color.g, _texts[kTextDataDefault]->_color.b, format);
restoreBackground(charArea);
font->drawChar(&_screen, ch, charArea.left, charArea.top, color);
addDirtyRect(charArea);
}
void ThemeEngine::debugWidgetPosition(const char *name, const Common::Rect &r) {
_font->drawString(&_screen, name, r.left, r.top, r.width(), 0xFFFF, Graphics::kTextAlignRight, 0, true);
_screen.hLine(r.left, r.top, r.right, 0xFFFF);
_screen.hLine(r.left, r.bottom, r.right, 0xFFFF);
_screen.vLine(r.left, r.top, r.bottom, 0xFFFF);
_screen.vLine(r.right, r.top, r.bottom, 0xFFFF);
}
/**********************************************************
* Screen/overlay management
*********************************************************/
void ThemeEngine::updateScreen() {
if (!_bufferQueue.empty()) {
_vectorRenderer->setSurface(&_backBuffer);
for (Common::List<ThemeItem*>::iterator q = _bufferQueue.begin(); q != _bufferQueue.end(); ++q) {
(*q)->drawSelf(true, false);
delete *q;
}
_vectorRenderer->setSurface(&_screen);
memcpy(_screen.getBasePtr(0,0), _backBuffer.getBasePtr(0,0), _screen.pitch * _screen.h);
_bufferQueue.clear();
}
if (!_screenQueue.empty()) {
_vectorRenderer->disableShadows();
for (Common::List<ThemeItem*>::iterator q = _screenQueue.begin(); q != _screenQueue.end(); ++q) {
(*q)->drawSelf(true, false);
delete *q;
}
_vectorRenderer->enableShadows();
_screenQueue.clear();
}
renderDirtyScreen();
}
void ThemeEngine::renderDirtyScreen() {
if (_dirtyScreen.empty())
return;
Common::List<Common::Rect>::const_iterator i, j;
for (i = _dirtyScreen.begin(); i != _dirtyScreen.end(); ++i) {
for (j = i; j != _dirtyScreen.end(); ++j)
if (j != i && i->contains(*j))
j = _dirtyScreen.reverse_erase(j);
_vectorRenderer->copyFrame(_system, *i);
}
_dirtyScreen.clear();
}
void ThemeEngine::openDialog(bool doBuffer, ShadingStyle style) {
if (doBuffer)
_buffering = true;
if (style != kShadingNone) {
_vectorRenderer->applyScreenShading(style);
addDirtyRect(Common::Rect(0, 0, _screen.w, _screen.h));
}
memcpy(_backBuffer.getBasePtr(0,0), _screen.getBasePtr(0,0), _screen.pitch * _screen.h);
_vectorRenderer->setSurface(&_screen);
}
bool ThemeEngine::createCursor(const Common::String &filename, int hotspotX, int hotspotY, int scale) {
if (!_system->hasFeature(OSystem::kFeatureCursorHasPalette))
return true;
// Try to locate the specified file among all loaded bitmaps
const Graphics::Surface *cursor = _bitmaps[filename];
if (!cursor)
return false;
// Set up the cursor parameters
_cursorHotspotX = hotspotX;
_cursorHotspotY = hotspotY;
_cursorTargetScale = scale;
_cursorWidth = cursor->w;
_cursorHeight = cursor->h;
// Allocate a new buffer for the cursor
delete[] _cursor;
_cursor = new byte[_cursorWidth * _cursorHeight];
assert(_cursor);
memset(_cursor, 0xFF, sizeof(byte) * _cursorWidth * _cursorHeight);
// Now, scan the bitmap. We have to convert it from 16 bit color mode
// to 8 bit mode, and have to create a suitable palette on the fly.
uint colorsFound = 0;
Common::HashMap<int, int> colorToIndex;
const OverlayColor *src = (const OverlayColor*)cursor->pixels;
Graphics::PixelFormat format = _system->getOverlayFormat();
for (uint y = 0; y < _cursorHeight; ++y) {
for (uint x = 0; x < _cursorWidth; ++x) {
byte r, g, b;
Graphics::colorToRGB(src[x], r, g, b, format);
const int col = (r << 16) | (g << 8) | b;
// Skip transparency (the transparent color actually is 0xFF00FF,
// but the RGB conversion chops of the lower bits).
if ((r > 0xF1) && (g < 0x03) && (b > 0xF1))
continue;
// If there is no entry yet for this color in the palette: Add one
if (!colorToIndex.contains(col)) {
const int index = colorsFound++;
colorToIndex[col] = index;
_cursorPal[index * 4 + 0] = r;
_cursorPal[index * 4 + 1] = g;
_cursorPal[index * 4 + 2] = b;
_cursorPal[index * 4 + 3] = 0xFF;
if (colorsFound > MAX_CURS_COLORS) {
warning("Cursor contains too many colors (%d, but only %d are allowed)", colorsFound, MAX_CURS_COLORS);
return false;
}
}
// Copy pixel from the 16 bit source surface to the 8bit target surface
const int index = colorToIndex[col];
_cursor[y * _cursorWidth + x] = index;
}
src += _cursorWidth;
}
_useCursor = true;
return true;
}
/**********************************************************
* Legacy GUI::Theme support functions
*********************************************************/
const Graphics::Font *ThemeEngine::getFont(FontStyle font) const {
return _texts[fontStyleToData(font)]->_fontPtr;
}
int ThemeEngine::getFontHeight(FontStyle font) const {
return ready() ? _texts[fontStyleToData(font)]->_fontPtr->getFontHeight() : 0;
}
int ThemeEngine::getStringWidth(const Common::String &str, FontStyle font) const {
return ready() ? _texts[fontStyleToData(font)]->_fontPtr->getStringWidth(str) : 0;
}
int ThemeEngine::getCharWidth(byte c, FontStyle font) const {
return ready() ? _texts[fontStyleToData(font)]->_fontPtr->getCharWidth(c) : 0;
}
ThemeEngine::TextData ThemeEngine::getTextData(DrawData ddId) {
return _widgets[ddId] ? (TextData)_widgets[ddId]->_textDataId : kTextDataNone;
}
/**********************************************************
* External data loading
*********************************************************/
const Graphics::Font *ThemeEngine::loadFontFromArchive(const Common::String &filename) {
Common::SeekableReadStream *stream = 0;
const Graphics::Font *font = 0;
if (_themeArchive)
stream = _themeArchive->openFile(filename);
if (stream) {
font = Graphics::NewFont::loadFromCache(*stream);
delete stream;
}
return font;
}
const Graphics::Font *ThemeEngine::loadFont(const Common::String &filename) {
const Graphics::Font *font = 0;
Common::String cacheFilename = genCacheFilename(filename.c_str());
Common::File fontFile;
if (!cacheFilename.empty()) {
if (fontFile.open(cacheFilename))
font = Graphics::NewFont::loadFromCache(fontFile);
if (font)
return font;
if ((font = loadFontFromArchive(cacheFilename)))
return font;
}
// normal open
if (fontFile.open(filename)) {
font = Graphics::NewFont::loadFont(fontFile);
}
if (!font) {
font = loadFontFromArchive(filename);
}
if (font) {
if (!cacheFilename.empty()) {
if (!Graphics::NewFont::cacheFontData(*(const Graphics::NewFont*)font, cacheFilename)) {
warning("Couldn't create cache file for font '%s'", filename.c_str());
}
}
}
return font;
}
Common::String ThemeEngine::genCacheFilename(const char *filename) {
Common::String cacheName(filename);
for (int i = cacheName.size() - 1; i >= 0; --i) {
if (cacheName[i] == '.') {
while ((uint)i < cacheName.size() - 1) {
cacheName.deleteLastChar();
}
cacheName += "fcc";
return cacheName;
}
}
return "";
}
/**********************************************************
* Static Theme XML functions
*********************************************************/
bool ThemeEngine::themeConfigParseHeader(Common::String header, Common::String &themeName) {
header.trim();
if (header.empty())
return false;
if (header[0] != '[' || header.lastChar() != ']')
return false;
header.deleteChar(0);
header.deleteLastChar();
Common::StringTokenizer tok(header, ":");
if (tok.nextToken() != SCUMMVM_THEME_VERSION_STR)
return false;
themeName = tok.nextToken();
Common::String author = tok.nextToken();
return tok.empty();
}
bool ThemeEngine::themeConfigUseable(const Common::FSNode &node, Common::String &themeName) {
Common::File stream;
bool foundHeader = false;
if (node.getName().hasSuffix(".zip") && !node.isDirectory()) {
#ifdef USE_ZLIB
Common::ZipArchive zipArchive(node);
if (zipArchive.hasFile("THEMERC")) {
stream.open("THEMERC", zipArchive);
}
#endif
} else if (node.isDirectory()) {
Common::FSNode headerfile = node.getChild("THEMERC");
if (!headerfile.exists() || !headerfile.isReadable() || headerfile.isDirectory())
return false;
stream.open(headerfile);
}
if (stream.isOpen()) {
Common::String stxHeader = stream.readLine();
foundHeader = themeConfigParseHeader(stxHeader, themeName);
}
return foundHeader;
}
} // end of namespace GUI.