mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-16 14:50:17 +00:00
d11c61db14
These are flagged by GCC if -Wswitch-default is enabled.
836 lines
25 KiB
C++
836 lines
25 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 "common/system.h"
|
|
#include "common/translation.h"
|
|
#include "gui/message.h"
|
|
#include "sci/sci.h"
|
|
#include "sci/console.h"
|
|
#include "sci/event.h"
|
|
#include "sci/engine/kernel.h"
|
|
#include "sci/engine/seg_manager.h"
|
|
#include "sci/engine/state.h"
|
|
#include "sci/graphics/cache.h"
|
|
#include "sci/graphics/compare.h"
|
|
#include "sci/graphics/controls32.h"
|
|
#include "sci/graphics/font.h"
|
|
#include "sci/graphics/screen.h"
|
|
#include "sci/graphics/text32.h"
|
|
|
|
namespace Sci {
|
|
GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text) :
|
|
_segMan(segMan),
|
|
_gfxCache(cache),
|
|
_gfxText32(text),
|
|
_overwriteMode(false),
|
|
_nextCursorFlashTick(0),
|
|
// SSCI used a memory handle for a ScrollWindow object as ID. We use a
|
|
// simple numeric handle instead.
|
|
_nextScrollWindowId(10000) {}
|
|
|
|
GfxControls32::~GfxControls32() {
|
|
ScrollWindowMap::iterator it;
|
|
for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it)
|
|
delete it->_value;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Text input control
|
|
|
|
reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
|
|
SegManager *segMan = _segMan;
|
|
|
|
TextEditor editor;
|
|
reg_t textObject = readSelector(_segMan, controlObject, SELECTOR(text));
|
|
editor.text = _segMan->getString(textObject);
|
|
editor.foreColor = readSelectorValue(_segMan, controlObject, SELECTOR(fore));
|
|
editor.backColor = readSelectorValue(_segMan, controlObject, SELECTOR(back));
|
|
editor.skipColor = readSelectorValue(_segMan, controlObject, SELECTOR(skip));
|
|
editor.fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font));
|
|
editor.maxLength = readSelectorValue(_segMan, controlObject, SELECTOR(width));
|
|
editor.bitmap = readSelector(_segMan, controlObject, SELECTOR(bitmap));
|
|
editor.cursorCharPosition = 0;
|
|
editor.cursorIsDrawn = false;
|
|
editor.borderColor = readSelectorValue(_segMan, controlObject, SELECTOR(borderColor));
|
|
|
|
reg_t titleObject = readSelector(_segMan, controlObject, SELECTOR(title));
|
|
|
|
int16 titleHeight = 0;
|
|
GuiResourceId titleFontId = readSelectorValue(_segMan, controlObject, SELECTOR(titleFont));
|
|
if (!titleObject.isNull()) {
|
|
GfxFont *titleFont = _gfxCache->getFont(titleFontId);
|
|
titleHeight += _gfxText32->scaleUpHeight(titleFont->getHeight()) + 1;
|
|
if (editor.borderColor != -1) {
|
|
titleHeight += 2;
|
|
}
|
|
}
|
|
|
|
int16 width = 0;
|
|
int16 height = titleHeight;
|
|
|
|
GfxFont *editorFont = _gfxCache->getFont(editor.fontId);
|
|
height += _gfxText32->scaleUpHeight(editorFont->getHeight()) + 1;
|
|
_gfxText32->setFont(editor.fontId);
|
|
int16 emSize = _gfxText32->getCharWidth('M', true);
|
|
width += editor.maxLength * emSize + 1;
|
|
if (editor.borderColor != -1) {
|
|
width += 4;
|
|
height += 2;
|
|
}
|
|
|
|
Common::Rect editorPlaneRect(width, height);
|
|
editorPlaneRect.translate(readSelectorValue(_segMan, controlObject, SELECTOR(x)), readSelectorValue(_segMan, controlObject, SELECTOR(y)));
|
|
|
|
reg_t planeObj = readSelector(_segMan, controlObject, SELECTOR(plane));
|
|
Plane *sourcePlane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
|
|
if (sourcePlane == nullptr) {
|
|
sourcePlane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
|
|
if (sourcePlane == nullptr) {
|
|
error("Could not find plane %04x:%04x", PRINT_REG(planeObj));
|
|
}
|
|
}
|
|
editorPlaneRect.translate(sourcePlane->_gameRect.left, sourcePlane->_gameRect.top);
|
|
|
|
editor.textRect = Common::Rect(2, titleHeight + 2, width - 1, height - 1);
|
|
editor.width = width;
|
|
|
|
if (editor.bitmap.isNull()) {
|
|
TextAlign alignment = (TextAlign)readSelectorValue(_segMan, controlObject, SELECTOR(mode));
|
|
|
|
if (titleObject.isNull()) {
|
|
bool dimmed = readSelectorValue(_segMan, controlObject, SELECTOR(dimmed));
|
|
editor.bitmap = _gfxText32->createFontBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, dimmed, true, false);
|
|
} else {
|
|
error("Titled bitmaps are not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
|
|
}
|
|
}
|
|
|
|
drawCursor(editor);
|
|
|
|
Plane *plane = new Plane(editorPlaneRect, kPlanePicTransparent);
|
|
plane->changePic();
|
|
g_sci->_gfxFrameout->addPlane(plane);
|
|
|
|
CelInfo32 celInfo;
|
|
celInfo.type = kCelTypeMem;
|
|
celInfo.bitmap = editor.bitmap;
|
|
|
|
ScreenItem *screenItem = new ScreenItem(plane->_object, celInfo, Common::Point(), ScaleInfo());
|
|
plane->_screenItemList.add(screenItem);
|
|
|
|
// frameOut must be called after the screen item is created, and before it
|
|
// is updated at the end of the event loop, otherwise it has both created
|
|
// and updated flags set which crashes the engine (updates are handled
|
|
// before creations, but the screen item is not in the correct state for an
|
|
// update)
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
|
|
EventManager *eventManager = g_sci->getEventManager();
|
|
bool clearTextOnInput = true;
|
|
bool textChanged = false;
|
|
for (;;) {
|
|
// We peek here because the last event needs to be allowed to dispatch a
|
|
// second time to the normal event handling system. In SSCI, the event
|
|
// is always consumed and then the last event just gets posted back to
|
|
// the event manager for reprocessing, but instead, we only remove the
|
|
// event from the queue *after* we have determined it is not a
|
|
// defocusing event
|
|
const SciEvent event = eventManager->getSciEvent(kSciEventAny | kSciEventPeek);
|
|
|
|
bool focused = true;
|
|
// SSCI did not have a QUIT event, but we do, so we have to handle it
|
|
if (event.type == kSciEventQuit) {
|
|
focused = false;
|
|
} else if (event.type == kSciEventMousePress && !editorPlaneRect.contains(event.mousePosSci)) {
|
|
focused = false;
|
|
} else if (event.type == kSciEventKeyDown) {
|
|
switch (event.character) {
|
|
case kSciKeyEsc:
|
|
case kSciKeyUp:
|
|
case kSciKeyDown:
|
|
case kSciKeyTab:
|
|
case kSciKeyShiftTab:
|
|
case kSciKeyEnter:
|
|
focused = false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!focused) {
|
|
break;
|
|
}
|
|
|
|
// Consume the event now that we know it is not one of the defocusing
|
|
// events above
|
|
if (event.type != kSciEventNone)
|
|
eventManager->getSciEvent(kSciEventAny);
|
|
|
|
// In SSCI, the font and bitmap were reset here on each iteration
|
|
// through the loop, but this is not necessary since control is not
|
|
// yielded back to the VM until input is received, which means there is
|
|
// nothing that could modify the GfxText32's state with a different font
|
|
// in the meantime
|
|
|
|
bool shouldDeleteChar = false;
|
|
bool shouldRedrawText = false;
|
|
uint16 lastCursorPosition = editor.cursorCharPosition;
|
|
if (event.type == kSciEventKeyDown) {
|
|
switch (event.character) {
|
|
case kSciKeyLeft:
|
|
clearTextOnInput = false;
|
|
if (editor.cursorCharPosition > 0) {
|
|
--editor.cursorCharPosition;
|
|
}
|
|
break;
|
|
|
|
case kSciKeyRight:
|
|
clearTextOnInput = false;
|
|
if (editor.cursorCharPosition < editor.text.size()) {
|
|
++editor.cursorCharPosition;
|
|
}
|
|
break;
|
|
|
|
case kSciKeyHome:
|
|
clearTextOnInput = false;
|
|
editor.cursorCharPosition = 0;
|
|
break;
|
|
|
|
case kSciKeyEnd:
|
|
clearTextOnInput = false;
|
|
editor.cursorCharPosition = editor.text.size();
|
|
break;
|
|
|
|
case kSciKeyInsert:
|
|
clearTextOnInput = false;
|
|
// Redrawing also changes the cursor rect to reflect the new
|
|
// insertion mode
|
|
shouldRedrawText = true;
|
|
_overwriteMode = !_overwriteMode;
|
|
break;
|
|
|
|
case kSciKeyDelete:
|
|
clearTextOnInput = false;
|
|
if (editor.cursorCharPosition < editor.text.size()) {
|
|
shouldDeleteChar = true;
|
|
}
|
|
break;
|
|
|
|
case kSciKeyBackspace:
|
|
clearTextOnInput = false;
|
|
shouldDeleteChar = true;
|
|
if (editor.cursorCharPosition > 0) {
|
|
--editor.cursorCharPosition;
|
|
}
|
|
break;
|
|
|
|
case kSciKeyEtx:
|
|
editor.text.clear();
|
|
editor.cursorCharPosition = 0;
|
|
shouldRedrawText = true;
|
|
break;
|
|
|
|
default: {
|
|
if (event.character >= 20 && event.character < 257) {
|
|
if (clearTextOnInput) {
|
|
clearTextOnInput = false;
|
|
editor.text.clear();
|
|
}
|
|
|
|
if (
|
|
(_overwriteMode && editor.cursorCharPosition < editor.maxLength) ||
|
|
(editor.text.size() < editor.maxLength && _gfxText32->getCharWidth(event.character, true) + _gfxText32->getStringWidth(editor.text) < editor.textRect.width())
|
|
) {
|
|
if (_overwriteMode && editor.cursorCharPosition < editor.text.size()) {
|
|
editor.text.setChar(event.character, editor.cursorCharPosition);
|
|
} else {
|
|
editor.text.insertChar(event.character, editor.cursorCharPosition);
|
|
}
|
|
|
|
++editor.cursorCharPosition;
|
|
shouldRedrawText = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shouldDeleteChar) {
|
|
shouldRedrawText = true;
|
|
if (editor.cursorCharPosition < editor.text.size()) {
|
|
editor.text.deleteChar(editor.cursorCharPosition);
|
|
}
|
|
}
|
|
|
|
if (shouldRedrawText) {
|
|
eraseCursor(editor);
|
|
_gfxText32->erase(editor.textRect, true);
|
|
_gfxText32->drawTextBox(editor.text);
|
|
drawCursor(editor);
|
|
textChanged = true;
|
|
screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
|
|
} else if (editor.cursorCharPosition != lastCursorPosition) {
|
|
eraseCursor(editor);
|
|
drawCursor(editor);
|
|
screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
|
|
} else {
|
|
flashCursor(editor);
|
|
screenItem->_updated = g_sci->_gfxFrameout->getScreenCount();
|
|
}
|
|
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
g_sci->_gfxFrameout->throttle();
|
|
}
|
|
|
|
g_sci->_gfxFrameout->deletePlane(*plane);
|
|
if (readSelectorValue(segMan, controlObject, SELECTOR(frameOut))) {
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
|
|
_segMan->freeBitmap(editor.bitmap);
|
|
|
|
if (textChanged) {
|
|
editor.text.trim();
|
|
SciArray &string = *_segMan->lookupArray(textObject);
|
|
string.fromString(editor.text);
|
|
}
|
|
|
|
return make_reg(0, textChanged);
|
|
}
|
|
|
|
void GfxControls32::drawCursor(TextEditor &editor) {
|
|
if (!editor.cursorIsDrawn) {
|
|
editor.cursorRect.left = editor.textRect.left + _gfxText32->getTextWidth(editor.text, 0, editor.cursorCharPosition);
|
|
|
|
const int16 scaledFontHeight = _gfxText32->scaleUpHeight(_gfxText32->_font->getHeight());
|
|
|
|
// SSCI branched on borderColor here but the two branches appeared to be
|
|
// identical, differing only because the compiler decided to be
|
|
// differently clever when optimising multiplication in each branch
|
|
if (_overwriteMode) {
|
|
editor.cursorRect.top = editor.textRect.top;
|
|
editor.cursorRect.setHeight(scaledFontHeight);
|
|
} else {
|
|
editor.cursorRect.top = editor.textRect.top + scaledFontHeight - 1;
|
|
editor.cursorRect.setHeight(1);
|
|
}
|
|
|
|
const char currentChar = editor.cursorCharPosition < editor.text.size() ? editor.text[editor.cursorCharPosition] : ' ';
|
|
editor.cursorRect.setWidth(_gfxText32->getCharWidth(currentChar, true));
|
|
|
|
_gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
|
|
|
|
editor.cursorIsDrawn = true;
|
|
}
|
|
|
|
_nextCursorFlashTick = g_sci->getTickCount() + 30;
|
|
}
|
|
|
|
void GfxControls32::eraseCursor(TextEditor &editor) {
|
|
if (editor.cursorIsDrawn) {
|
|
_gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
|
|
editor.cursorIsDrawn = false;
|
|
}
|
|
|
|
_nextCursorFlashTick = g_sci->getTickCount() + 30;
|
|
}
|
|
|
|
void GfxControls32::flashCursor(TextEditor &editor) {
|
|
if (g_sci->getTickCount() > _nextCursorFlashTick) {
|
|
_gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true);
|
|
|
|
editor.cursorIsDrawn = !editor.cursorIsDrawn;
|
|
_nextCursorFlashTick = g_sci->getTickCount() + 30;
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Scrollable window control
|
|
|
|
ScrollWindow::ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) :
|
|
_segMan(segMan),
|
|
_gfxText32(segMan, g_sci->_gfxCache),
|
|
_maxNumEntries(maxNumEntries),
|
|
_firstVisibleChar(0),
|
|
_topVisibleLine(0),
|
|
_lastVisibleChar(0),
|
|
_bottomVisibleLine(0),
|
|
_numLines(0),
|
|
_numVisibleLines(0),
|
|
_plane(plane),
|
|
_foreColor(defaultForeColor),
|
|
_backColor(defaultBackColor),
|
|
_borderColor(defaultBorderColor),
|
|
_fontId(defaultFontId),
|
|
_alignment(defaultAlignment),
|
|
_visible(false),
|
|
_position(position),
|
|
_screenItem(nullptr),
|
|
_nextEntryId(1) {
|
|
|
|
_entries.reserve(maxNumEntries);
|
|
|
|
_gfxText32.setFont(_fontId);
|
|
_pointSize = _gfxText32._font->getHeight();
|
|
|
|
const uint16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth();
|
|
const uint16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
|
|
|
|
Common::Rect bitmapRect(gameRect);
|
|
mulinc(bitmapRect, Ratio(_gfxText32._xResolution, scriptWidth), Ratio(_gfxText32._yResolution, scriptHeight));
|
|
|
|
_textRect.left = 2;
|
|
_textRect.top = 2;
|
|
_textRect.right = bitmapRect.width() - 2;
|
|
_textRect.bottom = bitmapRect.height() - 2;
|
|
|
|
uint8 skipColor = 0;
|
|
while (skipColor == _foreColor || skipColor == _backColor) {
|
|
skipColor++;
|
|
}
|
|
|
|
assert(bitmapRect.width() > 0 && bitmapRect.height() > 0);
|
|
_bitmap = _gfxText32.createFontBitmap(bitmapRect.width(), bitmapRect.height(), _textRect, "", _foreColor, _backColor, skipColor, _fontId, _alignment, _borderColor, false, false, false);
|
|
|
|
debugC(1, kDebugLevelGraphics, "New ScrollWindow: textRect size: %d x %d, bitmap: %04x:%04x", _textRect.width(), _textRect.height(), PRINT_REG(_bitmap));
|
|
}
|
|
|
|
ScrollWindow::~ScrollWindow() {
|
|
_segMan->freeBitmap(_bitmap);
|
|
// _screenItem will be deleted by GfxFrameout
|
|
}
|
|
|
|
Ratio ScrollWindow::where() const {
|
|
return Ratio(_topVisibleLine, MAX(_numLines, 1));
|
|
}
|
|
|
|
void ScrollWindow::show() {
|
|
if (_visible) {
|
|
return;
|
|
}
|
|
|
|
if (_screenItem == nullptr) {
|
|
CelInfo32 celInfo;
|
|
celInfo.type = kCelTypeMem;
|
|
celInfo.bitmap = _bitmap;
|
|
|
|
_screenItem = new ScreenItem(_plane, celInfo, _position, ScaleInfo());
|
|
}
|
|
|
|
Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane);
|
|
|
|
if (plane == nullptr) {
|
|
error("[ScrollWindow::show]: Plane %04x:%04x not found", PRINT_REG(_plane));
|
|
}
|
|
|
|
plane->_screenItemList.add(_screenItem);
|
|
|
|
_visible = true;
|
|
}
|
|
|
|
void ScrollWindow::hide() {
|
|
if (!_visible) {
|
|
return;
|
|
}
|
|
|
|
g_sci->_gfxFrameout->deleteScreenItem(*_screenItem, _plane);
|
|
_screenItem = nullptr;
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
|
|
_visible = false;
|
|
}
|
|
|
|
reg_t ScrollWindow::add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
|
|
if (_entries.size() == _maxNumEntries) {
|
|
ScrollWindowEntry removedEntry = _entries.remove_at(0);
|
|
_text.erase(0, removedEntry.text.size());
|
|
// `_firstVisibleChar` will be reset shortly if `scrollTo` is true, so
|
|
// there is no reason to update it
|
|
if (!scrollTo) {
|
|
_firstVisibleChar -= removedEntry.text.size();
|
|
}
|
|
}
|
|
|
|
_entries.push_back(ScrollWindowEntry());
|
|
ScrollWindowEntry &entry = _entries.back();
|
|
|
|
// In SSCI, the line ID was a memory handle for the string of this line. We
|
|
// use a numeric ID instead.
|
|
entry.id = make_reg(0, _nextEntryId++);
|
|
|
|
if (_nextEntryId > _maxNumEntries) {
|
|
_nextEntryId = 1;
|
|
}
|
|
|
|
// In SSCI, this was updated after _text was updated, which meant there was
|
|
// an extra unnecessary subtraction operation (subtracting `entry.text`
|
|
// size)
|
|
if (scrollTo) {
|
|
_firstVisibleChar = _text.size();
|
|
}
|
|
|
|
fillEntry(entry, text, fontId, foreColor, alignment);
|
|
_text += entry.text;
|
|
|
|
computeLineIndices();
|
|
update(true);
|
|
|
|
return entry.id;
|
|
}
|
|
|
|
void ScrollWindow::fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment) {
|
|
entry.alignment = alignment;
|
|
entry.foreColor = foreColor;
|
|
entry.fontId = fontId;
|
|
|
|
Common::String formattedText;
|
|
|
|
// NB: There are inconsistencies here.
|
|
// If there is a multi-line entry with non-default properties, and it
|
|
// is only partially displayed, it may not be displayed right, since the
|
|
// property directives are only added to the first line.
|
|
// (Verified by trying this in SSCI SQ6 with a custom ScrollWindowAdd call.)
|
|
//
|
|
// The converse is also a potential issue (but unverified), where lines
|
|
// with properties -1 can inherit properties from the previously rendered
|
|
// line instead of the defaults.
|
|
|
|
// SSCI added "|s<lineIndex>|" here, but |s| is not a valid control code, so
|
|
// it just always ended up getting skipped by the text rendering code
|
|
if (entry.fontId != -1) {
|
|
formattedText += Common::String::format("|f%d|", entry.fontId);
|
|
}
|
|
if (entry.foreColor != -1) {
|
|
formattedText += Common::String::format("|c%d|", entry.foreColor);
|
|
}
|
|
if (entry.alignment != -1) {
|
|
formattedText += Common::String::format("|a%d|", entry.alignment);
|
|
}
|
|
formattedText += text;
|
|
entry.text = formattedText;
|
|
}
|
|
|
|
reg_t ScrollWindow::modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) {
|
|
|
|
EntriesList::iterator it = _entries.begin();
|
|
uint firstCharLocation = 0;
|
|
for ( ; it != _entries.end(); ++it) {
|
|
if (it->id == id) {
|
|
break;
|
|
}
|
|
firstCharLocation += it->text.size();
|
|
}
|
|
|
|
if (it == _entries.end()) {
|
|
return make_reg(0, 0);
|
|
}
|
|
|
|
ScrollWindowEntry &entry = *it;
|
|
|
|
uint oldTextLength = entry.text.size();
|
|
|
|
fillEntry(entry, text, fontId, foreColor, alignment);
|
|
_text.replace(firstCharLocation, oldTextLength, entry.text);
|
|
|
|
if (scrollTo) {
|
|
_firstVisibleChar = firstCharLocation;
|
|
}
|
|
|
|
computeLineIndices();
|
|
update(true);
|
|
|
|
return entry.id;
|
|
}
|
|
|
|
void ScrollWindow::upArrow() {
|
|
if (_topVisibleLine == 0) {
|
|
return;
|
|
}
|
|
|
|
_topVisibleLine--;
|
|
_bottomVisibleLine--;
|
|
|
|
if (_bottomVisibleLine - _topVisibleLine + 1 < _numVisibleLines) {
|
|
_bottomVisibleLine = _numLines - 1;
|
|
}
|
|
|
|
_firstVisibleChar = _startsOfLines[_topVisibleLine];
|
|
_lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
|
|
|
|
_visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
|
|
|
|
Common::String lineText(_text.c_str() + _startsOfLines[_topVisibleLine], _text.c_str() + _startsOfLines[_topVisibleLine + 1] - 1);
|
|
|
|
debugC(3, kDebugLevelGraphics, "ScrollWindow::upArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
|
|
|
|
_gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollUp);
|
|
|
|
if (_visible) {
|
|
assert(_screenItem);
|
|
|
|
_screenItem->update();
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
}
|
|
|
|
void ScrollWindow::downArrow() {
|
|
if (_topVisibleLine + 1 >= _numLines) {
|
|
return;
|
|
}
|
|
|
|
_topVisibleLine++;
|
|
_bottomVisibleLine++;
|
|
|
|
if (_bottomVisibleLine + 1 >= _numLines) {
|
|
_bottomVisibleLine = _numLines - 1;
|
|
}
|
|
|
|
_firstVisibleChar = _startsOfLines[_topVisibleLine];
|
|
_lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
|
|
|
|
_visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
|
|
|
|
Common::String lineText;
|
|
if (_bottomVisibleLine - _topVisibleLine + 1 == _numVisibleLines) {
|
|
lineText = Common::String(_text.c_str() + _startsOfLines[_bottomVisibleLine], _text.c_str() + _startsOfLines[_bottomVisibleLine + 1] - 1);
|
|
} else {
|
|
// scroll in empty string
|
|
}
|
|
|
|
debugC(3, kDebugLevelGraphics, "ScrollWindow::downArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str());
|
|
|
|
|
|
_gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollDown);
|
|
|
|
if (_visible) {
|
|
assert(_screenItem);
|
|
|
|
_screenItem->update();
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
}
|
|
|
|
void ScrollWindow::go(const Ratio location) {
|
|
const int line = (location * _numLines).toInt();
|
|
if (line < 0 || line > _numLines) {
|
|
error("Index is Out of Range in ScrollWindow");
|
|
}
|
|
|
|
_firstVisibleChar = _startsOfLines[line];
|
|
update(true);
|
|
|
|
// HACK:
|
|
// It usually isn't possible to set _topVisibleLine >= _numLines, and so
|
|
// update() doesn't. However, in this case we should set _topVisibleLine
|
|
// past the end. This is clearly visible in Phantasmagoria when dragging
|
|
// the slider in the About dialog to the very end. The slider ends up lower
|
|
// than where it can be moved by scrolling down with the arrows.
|
|
if (location.isOne()) {
|
|
_topVisibleLine = _numLines;
|
|
}
|
|
}
|
|
|
|
void ScrollWindow::home() {
|
|
if (_firstVisibleChar == 0) {
|
|
return;
|
|
}
|
|
|
|
_firstVisibleChar = 0;
|
|
update(true);
|
|
}
|
|
|
|
void ScrollWindow::end() {
|
|
if (_bottomVisibleLine + 1 >= _numLines) {
|
|
return;
|
|
}
|
|
|
|
int line = _numLines - _numVisibleLines;
|
|
if (line < 0) {
|
|
line = 0;
|
|
}
|
|
_firstVisibleChar = _startsOfLines[line];
|
|
update(true);
|
|
}
|
|
|
|
void ScrollWindow::pageUp() {
|
|
if (_topVisibleLine == 0) {
|
|
return;
|
|
}
|
|
|
|
_topVisibleLine -= _numVisibleLines;
|
|
if (_topVisibleLine < 0) {
|
|
_topVisibleLine = 0;
|
|
}
|
|
|
|
_firstVisibleChar = _startsOfLines[_topVisibleLine];
|
|
update(true);
|
|
}
|
|
|
|
void ScrollWindow::pageDown() {
|
|
if (_topVisibleLine + 1 >= _numLines) {
|
|
return;
|
|
}
|
|
|
|
_topVisibleLine += _numVisibleLines;
|
|
if (_topVisibleLine + 1 >= _numLines) {
|
|
_topVisibleLine = _numLines - 1;
|
|
}
|
|
|
|
_firstVisibleChar = _startsOfLines[_topVisibleLine];
|
|
update(true);
|
|
}
|
|
|
|
void ScrollWindow::computeLineIndices() {
|
|
_gfxText32.setFont(_fontId);
|
|
// Unlike SSCI, foreColor and alignment are not set since these properties
|
|
// do not affect the width of lines
|
|
|
|
if (_gfxText32._font->getHeight() != _pointSize) {
|
|
error("Illegal font size font = %d pointSize = %d, should be %d.", _fontId, _gfxText32._font->getHeight(), _pointSize);
|
|
}
|
|
|
|
Common::Rect lineRect(0, 0, _textRect.width(), _pointSize + 3);
|
|
|
|
_startsOfLines.clear();
|
|
|
|
// SSCI had a 1000-line limit; we do not enforce any limit since we use
|
|
// dynamic containers
|
|
for (uint charIndex = 0; charIndex < _text.size(); ) {
|
|
_startsOfLines.push_back(charIndex);
|
|
charIndex += _gfxText32.getTextCount(_text, charIndex, lineRect, false);
|
|
}
|
|
|
|
_numLines = _startsOfLines.size();
|
|
|
|
_startsOfLines.push_back(_text.size());
|
|
|
|
_lastVisibleChar = _gfxText32.getTextCount(_text, 0, _fontId, _textRect, false) - 1;
|
|
|
|
_bottomVisibleLine = 0;
|
|
while (
|
|
_bottomVisibleLine < _numLines - 1 &&
|
|
_startsOfLines[_bottomVisibleLine + 1] < _lastVisibleChar
|
|
) {
|
|
++_bottomVisibleLine;
|
|
}
|
|
|
|
_numVisibleLines = _bottomVisibleLine + 1;
|
|
}
|
|
|
|
void ScrollWindow::update(const bool doFrameOut) {
|
|
_topVisibleLine = 0;
|
|
while (
|
|
_topVisibleLine < _numLines - 1 &&
|
|
_firstVisibleChar >= _startsOfLines[_topVisibleLine + 1]
|
|
) {
|
|
++_topVisibleLine;
|
|
}
|
|
|
|
_bottomVisibleLine = _topVisibleLine + _numVisibleLines - 1;
|
|
if (_bottomVisibleLine >= _numLines) {
|
|
_bottomVisibleLine = _numLines - 1;
|
|
}
|
|
|
|
_firstVisibleChar = _startsOfLines[_topVisibleLine];
|
|
|
|
if (_bottomVisibleLine >= 0) {
|
|
_lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1;
|
|
} else {
|
|
_lastVisibleChar = -1;
|
|
}
|
|
|
|
_visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1);
|
|
|
|
_gfxText32.erase(_textRect, false);
|
|
_gfxText32.drawTextBox(_visibleText);
|
|
|
|
if (_visible) {
|
|
assert(_screenItem);
|
|
|
|
_screenItem->update();
|
|
if (doFrameOut) {
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
reg_t GfxControls32::makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) {
|
|
|
|
ScrollWindow *scrollWindow = new ScrollWindow(_segMan, gameRect, position, planeObj, defaultForeColor, defaultBackColor, defaultFontId, defaultAlignment, defaultBorderColor, maxNumEntries);
|
|
|
|
const uint16 id = _nextScrollWindowId++;
|
|
_scrollWindows[id] = scrollWindow;
|
|
return make_reg(0, id);
|
|
}
|
|
|
|
ScrollWindow *GfxControls32::getScrollWindow(const reg_t id) {
|
|
ScrollWindowMap::iterator it;
|
|
it = _scrollWindows.find(id.toUint16());
|
|
if (it == _scrollWindows.end())
|
|
error("Invalid ScrollWindow ID");
|
|
|
|
return it->_value;
|
|
}
|
|
|
|
void GfxControls32::destroyScrollWindow(const reg_t id) {
|
|
ScrollWindow *scrollWindow = getScrollWindow(id);
|
|
scrollWindow->hide();
|
|
_scrollWindows.erase(id.getOffset());
|
|
delete scrollWindow;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Message box
|
|
|
|
int16 GfxControls32::showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue) {
|
|
GUI::MessageDialog dialog(message, okLabel, altLabel);
|
|
return (dialog.runModal() == GUI::kMessageOK) ? okValue : altValue;
|
|
}
|
|
|
|
reg_t GfxControls32::kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style) {
|
|
if (g_engine) {
|
|
g_engine->pauseEngine(true);
|
|
}
|
|
|
|
int16 result;
|
|
|
|
switch (style & 0xF) {
|
|
case kMessageBoxOK:
|
|
result = showMessageBox(message, _("OK"), NULL, 1, 1);
|
|
break;
|
|
case kMessageBoxYesNo:
|
|
result = showMessageBox(message, _("Yes"), _("No"), 6, 7);
|
|
break;
|
|
default:
|
|
error("Unsupported MessageBox style 0x%x", style & 0xF);
|
|
}
|
|
|
|
if (g_engine) {
|
|
g_engine->pauseEngine(false);
|
|
}
|
|
|
|
return make_reg(0, result);
|
|
}
|
|
|
|
} // End of namespace Sci
|