diff options
Diffstat (limited to 'engines/sci/graphics/controls32.cpp')
-rw-r--r-- | engines/sci/graphics/controls32.cpp | 495 |
1 files changed, 493 insertions, 2 deletions
diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp index faf1d7d1a2..61dfbedfc5 100644 --- a/engines/sci/graphics/controls32.cpp +++ b/engines/sci/graphics/controls32.cpp @@ -21,7 +21,8 @@ */ #include "common/system.h" - +#include "common/translation.h" +#include "gui/message.h" #include "sci/sci.h" #include "sci/console.h" #include "sci/event.h" @@ -41,7 +42,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 +375,470 @@ 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; +} + +#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 |