SCI32: Implement kScrollWindow

These should be all the actually used subfunctions.

Co-authored-by: Colin Snover <github.com@zetafleet.com>
This commit is contained in:
Willem Jan Palenstijn 2016-03-20 16:53:58 +01:00 committed by Colin Snover
parent a613a27b44
commit ab864ba366
10 changed files with 1089 additions and 157 deletions

View file

@ -41,7 +41,31 @@ GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *tex
_gfxCache(cache),
_gfxText32(text),
_overwriteMode(false),
_nextCursorFlashTick(0) {}
_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 Garbage collection
Common::Array<reg_t> GfxControls32::listObjectReferences() {
Common::Array<reg_t> ret;
ScrollWindowMap::const_iterator it;
for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it)
ret.push_back(it->_value->getBitmap());
return ret;
}
#pragma mark -
#pragma mark Text input control
reg_t GfxControls32::kernelEditText(const reg_t controlObject) {
SegManager *segMan = _segMan;
@ -350,4 +374,437 @@ void GfxControls32::flashCursor(TextEditor &editor) {
_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) :
_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->getCurrentBuffer().scriptWidth;
const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
Common::Rect bitmapRect(gameRect);
mulinc(bitmapRect, Ratio(_gfxText32._scaledWidth, scriptWidth), Ratio(_gfxText32._scaledHeight, 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);
debugC(1, kDebugLevelGraphics, "New ScrollWindow: textRect size: %d x %d, bitmap: %04x:%04x", _textRect.width(), _textRect.height(), PRINT_REG(_bitmap));
}
ScrollWindow::~ScrollWindow() {
// _gfxText32._bitmap will get GCed once ScrollWindow is gone.
// _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);
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();
// NOTE: 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;
}
// NOTE: 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.
// NOTE: SSCI added "|s<lineIndex>|" here, but |s| is
// not a valid control code, so it just always ended up
// getting skipped
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);
// NOTE: 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();
// NOTE: The original engine had a 1000-line limit; we
// do not enforce any limit
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;
}
} // End of namespace Sci