/* 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 "bladerunner/ui/ui_scroll_box.h" #include "bladerunner/audio_player.h" #include "bladerunner/bladerunner.h" #include "bladerunner/font.h" #include "bladerunner/game_info.h" #include "bladerunner/shape.h" #include "bladerunner/time.h" #include "bladerunner/game_constants.h" #include "bladerunner/ui/kia.h" #include "bladerunner/ui/kia_shapes.h" namespace BladeRunner { const Color256 UIScrollBox::k3DFrameColors[] = { { 32, 32, 24 }, { 40, 40, 40 }, { 40, 40, 48 }, { 72, 64, 64 }, { 160, 136, 128 }, { 160, 136, 128 }, { 0, 0, 0 }, { 0, 0, 0 } }; const Color256 UIScrollBox::kTextBackgroundColors[] = { { 40, 56, 80 }, { 48, 64, 96 }, { 56, 72, 112 }, { 72, 88, 128 }, { 152, 192, 248 }, { 0, 0, 0 } }; const Color256 UIScrollBox::kTextColors1[] = { { 72, 104, 152 }, { 96, 120, 184 }, { 112, 144, 216 }, { 136, 168, 248 }, { 152, 192, 248 } }; const Color256 UIScrollBox::kTextColors2[] = { { 200, 216, 248 }, { 216, 224, 248 }, { 224, 232, 248 }, { 232, 240, 248 }, { 248, 248, 248 } }; const Color256 UIScrollBox::kTextColors3[] = { { 240, 232, 192 }, { 240, 232, 208 }, { 240, 240, 216 }, { 248, 240, 232 }, { 248, 248, 248 } }; const Color256 UIScrollBox::kTextColors4[] = { { 152, 112, 56 }, { 184, 144, 88 }, { 216, 184, 112 }, { 232, 208, 136 }, { 248, 224, 144 } }; UIScrollBox::UIScrollBox(BladeRunnerEngine *vm, UIScrollBoxCallback *lineSelectedCallback, void *callbackData, int maxLineCount, int style, bool center, Common::Rect rect, Common::Rect scrollBarRect) : UIComponent(vm) { _selectedLineState = 0; _scrollUpButtonState = 0; _scrollDownButtonState = 0; _scrollAreaUpState = 0; _scrollAreaDownState = 0; _scrollBarState = 0; _scrollUpButtonHover = false; _scrollDownButtonHover = false; _scrollAreaUpHover = false; _scrollAreaDownHover = false; _scrollBarHover = false; _hoveredLine = -1; _selectedLineIndex = -1; _lineSelectedCallback = lineSelectedCallback; _callbackData = callbackData; _isVisible = false; _style = style; _center = center; _timeLastScroll = _vm->_time->currentSystem(); _timeLastCheckbox = _vm->_time->currentSystem(); _timeLastHighlight = _vm->_time->currentSystem(); _highlightFrame = 0; _rect = rect; _scrollBarRect = scrollBarRect; _scrollBarRect.right += 15; // right side was not used, but it's useful for determining if the control is selected _lineCount = 0; _maxLineCount = maxLineCount; _firstLineVisible = 0; _maxLinesVisible = _rect.height() / kLineHeight; _mouseButton = false; _rect.bottom = _rect.top + kLineHeight * _maxLinesVisible - 1; _lines.resize(_maxLineCount); for (int i = 0; i < _maxLineCount; ++i) { _lines[i] = new Line(); _lines[i]->lineData = -1; _lines[i]->flags = 0x00; _lines[i]->checkboxFrame = 5u; } _mouseOver = false; } UIScrollBox::~UIScrollBox() { for (int i = 0; i < _maxLineCount; ++i) { delete _lines[i]; } } void UIScrollBox::show() { _selectedLineState = 0; _scrollUpButtonState = 0; _scrollDownButtonState = 0; _scrollAreaUpState = 0; _scrollAreaDownState = 0; _scrollBarState = 0; _hoveredLine = -1; _selectedLineIndex = -1; _scrollUpButtonHover = false; _scrollDownButtonHover = false; _scrollAreaUpHover = false; _scrollAreaDownHover = false; _scrollBarHover = false; _timeLastScroll = _vm->_time->currentSystem(); _timeLastCheckbox = _vm->_time->currentSystem(); _timeLastHighlight = _vm->_time->currentSystem(); _highlightFrame = 0; _isVisible = true; _mouseOver = false; } void UIScrollBox::hide() { _isVisible = false; } void UIScrollBox::clearLines() { _lineCount = 0; _firstLineVisible = 0; } void UIScrollBox::addLine(const Common::String &text, int lineData, int flags) { _lines[_lineCount]->text = text; _lines[_lineCount]->lineData = lineData; _lines[_lineCount]->flags = flags; ++_lineCount; } void UIScrollBox::addLine(const char *text, int lineData, int flags) { _lines[_lineCount]->text = text; _lines[_lineCount]->lineData = lineData; _lines[_lineCount]->flags = flags; ++_lineCount; } void UIScrollBox::sortLines() { qsort(_lines.data(), _lineCount, sizeof(Line *), &sortFunction); } void UIScrollBox::handleMouseMove(int mouseX, int mouseY) { if (!_isVisible) { return; } _mouseOver = _rect.contains(mouseX, mouseY) || _scrollBarRect.contains(mouseX, mouseY); if (_rect.contains(mouseX, mouseY)) { int newHoveredLine = (mouseY - _rect.top) / 10 + _firstLineVisible; if (newHoveredLine >= _lineCount) { newHoveredLine = -1; } if (newHoveredLine != _hoveredLine && newHoveredLine >= 0 && newHoveredLine < _lineCount) { if (_lines[newHoveredLine]->lineData >= 0 && _selectedLineState == 0) { int soundId = kSfxTEXT1; if (_lines[newHoveredLine]->flags & 0x01 ) { soundId = kSfxTEXT3; } _vm->_audioPlayer->playAud(_vm->_gameInfo->getSfxTrack(soundId), 100, 0, 0, 50, 0); } } _hoveredLine = newHoveredLine; } else { _hoveredLine = -1; } _scrollUpButtonHover = (mouseX >= _scrollBarRect.left) && (mouseX < _scrollBarRect.left + 15) && (mouseY >= _scrollBarRect.top) && (mouseY < _scrollBarRect.top + 8); _scrollDownButtonHover = (mouseX >= _scrollBarRect.left) && (mouseX < _scrollBarRect.left + 15) && (mouseY > _scrollBarRect.bottom - 8) && (mouseY <= _scrollBarRect.bottom); int scrollAreaHeight = _scrollBarRect.bottom - _scrollBarRect.top - 15; int scrollBarHeight = scrollAreaHeight; if (_lineCount > _maxLinesVisible) { scrollBarHeight = _maxLinesVisible * scrollAreaHeight / _lineCount; } if (scrollBarHeight < 16) { scrollBarHeight = 16; } int scrollAreaEmptySize = scrollAreaHeight - scrollBarHeight; int scrollBarY = 0; if (_lineCount > _maxLinesVisible) { scrollBarY = scrollAreaEmptySize * _firstLineVisible / (_lineCount - _maxLinesVisible); } if (_scrollBarState == 2) { int v12 = scrollBarHeight / 2 + 8; if (mouseY - _scrollBarRect.top > v12 && _lineCount > _maxLinesVisible && scrollAreaEmptySize > 0) { _firstLineVisible = (_lineCount - _maxLinesVisible) * (mouseY - _scrollBarRect.top - v12) / scrollAreaEmptySize; if (_firstLineVisible > _lineCount - _maxLinesVisible) { _firstLineVisible = _lineCount - _maxLinesVisible; } } else { _firstLineVisible = 0; } if (_lineCount <= _maxLinesVisible) { scrollBarY = 0; } else { scrollBarY = scrollAreaEmptySize * _firstLineVisible/ (_lineCount - _maxLinesVisible); } } scrollBarY = scrollBarY + _scrollBarRect.top + 8; _scrollBarHover = (mouseX >= _scrollBarRect.left) && (mouseX < _scrollBarRect.left + 15) && (mouseY >= scrollBarY) && (mouseY < scrollBarY + scrollBarHeight); _scrollAreaUpHover = (mouseX >= _scrollBarRect.left) && (mouseX < _scrollBarRect.left + 15) && (mouseY >= _scrollBarRect.top + 8) && (mouseY < scrollBarY); _scrollAreaDownHover = (mouseX >= _scrollBarRect.left) && (mouseX < _scrollBarRect.left + 15) && (mouseY >= scrollBarY + scrollBarHeight) && (mouseY < _scrollBarRect.bottom - 8); } void UIScrollBox::handleMouseDown(bool alternateButton) { if (!_isVisible) { return; } _mouseButton = alternateButton; if (_hoveredLine == -1) { _selectedLineState = 1; } else if (_selectedLineIndex == -1) { _selectedLineIndex = _hoveredLine; _selectedLineState = 2; if (_hoveredLine < _lineCount) { if (_lineSelectedCallback) { _lineSelectedCallback(_callbackData, this, _lines[_selectedLineIndex]->lineData, _mouseButton); } if (_lines[_selectedLineIndex]->flags & 0x01) { _vm->_audioPlayer->playAud(_vm->_gameInfo->getSfxTrack(kSfxBEEP10), 100, 0, 0, 50, 0); } } } if (!alternateButton) { if (_scrollUpButtonHover) { _scrollUpButtonState = 2; _timeLastScroll = _vm->_time->currentSystem() - 160u; } else { _scrollUpButtonState = 1; } if (_scrollDownButtonHover) { _scrollDownButtonState = 2; } else { _scrollDownButtonState = 1; } if (_scrollBarHover) { _scrollBarState = 2; } else { _scrollBarState = 1; } if (_scrollAreaUpHover) { _scrollAreaUpState = 2; _timeLastScroll = _vm->_time->currentSystem() - 160u; } else { _scrollAreaUpState = 1; } if (_scrollAreaDownHover) { _scrollAreaDownState = 2; _timeLastScroll = _vm->_time->currentSystem() - 160u; } else { _scrollAreaDownState = 1; } } } void UIScrollBox::handleMouseUp(bool alternateButton) { if (_isVisible) { if ( alternateButton == _mouseButton) { _selectedLineState = 0; _selectedLineIndex = -1; } if (!alternateButton) { _scrollUpButtonState = 0; _scrollDownButtonState = 0; _scrollAreaUpState = 0; _scrollAreaDownState = 0; _scrollBarState = 0; } } } void UIScrollBox::handleMouseScroll(int direction) { if (_mouseOver) { if (direction > 0) { scrollDown(); } else if (direction < 0) { scrollUp(); } } } int UIScrollBox::getSelectedLineData() { if (_hoveredLine >= 0 && _selectedLineState != 1 && _hoveredLine < _lineCount) { return _lines[_hoveredLine]->lineData; } return -1; } void UIScrollBox::draw(Graphics::Surface &surface) { uint32 timeNow = _vm->_time->currentSystem(); // update scrolling if (_scrollUpButtonState == 2 && _scrollUpButtonHover) { // unsigned difference is intentional if ((timeNow - _timeLastScroll) > 160u) { scrollUp(); _timeLastScroll = timeNow; } } else if (_scrollDownButtonState == 2 && _scrollDownButtonHover) { // unsigned difference is intentional if ((timeNow - _timeLastScroll) > 160u) { scrollDown(); _timeLastScroll = timeNow; } } else if (_scrollAreaUpState == 2 && _scrollAreaUpHover) { // unsigned difference is intentional if ((timeNow - _timeLastScroll) > 160u) { _firstLineVisible -= _maxLinesVisible - 1; _firstLineVisible = CLIP(_firstLineVisible, 0, _lineCount - _maxLinesVisible); _timeLastScroll = timeNow; } } else if (_scrollAreaDownState == 2 && _scrollAreaDownHover) { // unsigned difference is intentional if ((timeNow - _timeLastScroll) > 160u) { _firstLineVisible += _maxLinesVisible - 1; _firstLineVisible = CLIP(_firstLineVisible, 0, _lineCount - _maxLinesVisible); _timeLastScroll = timeNow; } } // update checkboxes // unsigned difference is intentional uint32 timeDiffCheckBox = timeNow - _timeLastCheckbox; if (timeDiffCheckBox > 67u) { _timeLastCheckbox = timeNow; for (int i = 0; i < _lineCount; ++i) { if (_lines[i]->flags & 0x01) { // has checkbox if (_lines[i]->flags & 0x02) { // checkbox checked if (_lines[i]->checkboxFrame < 5u) { _lines[i]->checkboxFrame += timeDiffCheckBox / 67u; } if (_lines[i]->checkboxFrame > 5u) { _lines[i]->checkboxFrame = 5u; } } else { // checkbox not checked if (_lines[i]->checkboxFrame > 0u) { _lines[i]->checkboxFrame = (_lines[i]->checkboxFrame < (timeDiffCheckBox / 67u)) ? 0u : _lines[i]->checkboxFrame - (timeDiffCheckBox / 67u); } if (_lines[i]->checkboxFrame == 0u) { // original was < 0, int _lines[i]->checkboxFrame = 0u; } } } } } // update highlight // unsigned difference is intentional if ((timeNow - _timeLastHighlight) > 67u) { _timeLastHighlight = timeNow; _highlightFrame = (_highlightFrame + 1) % 8; } // draw text lines int linesVisible = 0; int lastLineVisible = 0; if (_maxLinesVisible < _lineCount - _firstLineVisible) { linesVisible = _maxLinesVisible; lastLineVisible = _firstLineVisible + _maxLinesVisible; } else { linesVisible = _lineCount - _firstLineVisible; lastLineVisible = _lineCount; } if (_firstLineVisible < lastLineVisible) { int y = _rect.top; int y1 = _rect.top + 8; int y2 = _rect.top + 2; int i = _firstLineVisible; do { int startingColorIndex = 3; if (i - _firstLineVisible < 3) { startingColorIndex = i - _firstLineVisible; } int endingColorIndex = 3; if (i - _firstLineVisible >= linesVisible - 3) { endingColorIndex = linesVisible - (i - _firstLineVisible + 1); } int colorIndex = endingColorIndex; if (startingColorIndex < endingColorIndex) { colorIndex = startingColorIndex; } bool v35 = false; int color = 0; if ((((_selectedLineState == 0 && i == _hoveredLine) || (_selectedLineState == 2 && i == _selectedLineIndex && _selectedLineIndex == _hoveredLine)) && _lines[i]->lineData != -1) || _lines[i]->flags & 0x04) { v35 = true; if (_style) { color = surface.format.RGBToColor(kTextColors2[colorIndex].r, kTextColors2[colorIndex].g, kTextColors2[colorIndex].b); } else { color = surface.format.RGBToColor(kTextColors3[colorIndex].r, kTextColors3[colorIndex].g, kTextColors3[colorIndex].b); } } else { if (_style) { color = surface.format.RGBToColor(kTextColors1[colorIndex].r, kTextColors1[colorIndex].g, kTextColors1[colorIndex].b); } else { color = surface.format.RGBToColor(kTextColors4[colorIndex].r, kTextColors4[colorIndex].g, kTextColors4[colorIndex].b); } } int x = _rect.left; if (_lines[i]->flags & 0x01) { // has checkbox int checkboxShapeId = 0; if (_style == 0) { if (_lines[i]->checkboxFrame || v35) { if (_lines[i]->checkboxFrame != 5u || v35) { checkboxShapeId = _lines[i]->checkboxFrame + 62u; } else { checkboxShapeId = 61; } } else { checkboxShapeId = 60; } } else if (_lines[i]->checkboxFrame || v35) { if (_lines[i]->checkboxFrame != 5u || v35) { checkboxShapeId = _lines[i]->checkboxFrame + 54u; } else { checkboxShapeId = 53; } } else { checkboxShapeId = 52; } _vm->_kia->_shapes->get(checkboxShapeId)->draw(surface, x - 1, y); x += 11; } if (_lines[i]->flags & 0x10) { // highlighted line if (_lines[i]->flags & 0x20) { int highlightShapeId = _highlightFrame; if (highlightShapeId > 4) { highlightShapeId = 8 - highlightShapeId; } _vm->_kia->_shapes->get(highlightShapeId + 85)->draw(surface, x, y2); } x += 6; } if (_lines[i]->flags & 0x08) { // has background rectangle int colorBackground = 0; if (_style) { colorBackground = surface.format.RGBToColor(kTextBackgroundColors[colorIndex].r, kTextBackgroundColors[colorIndex].g, kTextBackgroundColors[colorIndex].b); } else { colorBackground = surface.format.RGBToColor(80, 56, 32); } surface.fillRect(Common::Rect(x, y, _rect.right + 1, y1 + 1), colorBackground); } if (_center) { x = _rect.left + (_rect.width() - _vm->_mainFont->getStringWidth(_lines[i]->text)) / 2; } _vm->_mainFont->drawString(&surface, _lines[i]->text, x, y, surface.w, color); y1 += kLineHeight; y2 += kLineHeight; y += kLineHeight; ++i; } while (i < lastLineVisible); } // draw scroll up button int scrollUpButtonShapeId = 0; if (_scrollUpButtonState) { if (_scrollUpButtonState == 2) { if (_scrollUpButtonHover) { scrollUpButtonShapeId = 72; } else { scrollUpButtonShapeId = 71; } } else { scrollUpButtonShapeId = 70; } } else if (_scrollUpButtonHover) { scrollUpButtonShapeId = 71; } else { scrollUpButtonShapeId = 70; } _vm->_kia->_shapes->get(scrollUpButtonShapeId)->draw(surface, _scrollBarRect.left, _scrollBarRect.top); // draw scroll down button int scrollDownButtonShapeId = 0; if (_scrollDownButtonState) { if (_scrollDownButtonState == 2) { if (_scrollDownButtonHover) { scrollDownButtonShapeId = 75; } else { scrollDownButtonShapeId = 74; } } else { scrollDownButtonShapeId = 73; } } else if (_scrollDownButtonHover) { scrollDownButtonShapeId = 74; } else { scrollDownButtonShapeId = 73; } _vm->_kia->_shapes->get(scrollDownButtonShapeId)->draw(surface, _scrollBarRect.left, _scrollBarRect.bottom - 7); int scrollAreaSize = _scrollBarRect.bottom - (_scrollBarRect.top + 15); int scrollBarHeight = 0; if (_lineCount <= _maxLinesVisible) { scrollBarHeight = _scrollBarRect.bottom - (_scrollBarRect.top + 15); } else { scrollBarHeight = _maxLinesVisible * scrollAreaSize / _lineCount; } scrollBarHeight = MAX(scrollBarHeight, 16); int v56 = 0; if (_lineCount <= _maxLinesVisible) { v56 = 0; } else { v56 = _firstLineVisible * (scrollAreaSize - scrollBarHeight) / (_lineCount - _maxLinesVisible); } int v58 = v56 + _scrollBarRect.top + 8; if (_scrollBarState == 2) { draw3DFrame(surface, Common::Rect(_scrollBarRect.left, v58, _scrollBarRect.left + 15, v58 + scrollBarHeight), 1, 1); } else if (!_scrollBarState && _scrollBarHover) { draw3DFrame(surface, Common::Rect(_scrollBarRect.left, v56 + _scrollBarRect.top + 8, _scrollBarRect.left + 15, v58 + scrollBarHeight), 0, 1); } else { draw3DFrame(surface, Common::Rect(_scrollBarRect.left, v58, _scrollBarRect.left + 15, v58 + scrollBarHeight), 0, 0); } } void UIScrollBox::checkAll() { for (int i = 0; i < _lineCount; ++i) { if (_lines[i]->flags & 0x01) { _lines[i]->flags |= 0x02; } } } void UIScrollBox::uncheckAll() { for (int i = 0; i < _lineCount; ++i) { if (_lines[i]->flags & 0x01) { _lines[i]->flags &= ~0x02; } } } void UIScrollBox::toggleCheckBox(int lineData) { int i = findLine(lineData); if (i != -1) { if (_lines[i]->flags & 0x02) { _lines[i]->flags &= ~0x02; } else { _lines[i]->flags |= 0x02; } } } bool UIScrollBox::hasLine(int lineData) { return findLine(lineData) != -1; } void UIScrollBox::resetHighlight(int lineData) { int i = findLine(lineData); if (i != -1) { _lines[i]->flags &= ~0x20; } } void UIScrollBox::setFlags(int lineData, int flags) { int i = findLine(lineData); if (i != -1) { _lines[i]->flags |= flags; } } void UIScrollBox::resetFlags(int lineData, int flags) { int i = findLine(lineData); if (i != -1) { _lines[i]->flags &= ~flags; } } int UIScrollBox::sortFunction(const void *item1, const void *item2) { Line *line1 = *(Line * const *)item1; Line *line2 = *(Line * const *)item2; return line1->text.compareToIgnoreCase(line2->text); } void UIScrollBox::draw3DFrame(Graphics::Surface &surface, Common::Rect rect, bool pressed, int style) { int color1, color2; if (pressed) { color1 = surface.format.RGBToColor(k3DFrameColors[style + 6].r, k3DFrameColors[style + 6].g, k3DFrameColors[style + 6].b); color2 = surface.format.RGBToColor(k3DFrameColors[style + 4].r, k3DFrameColors[style + 4].g, k3DFrameColors[style + 4].b); } else { color1 = surface.format.RGBToColor(k3DFrameColors[style + 4].r, k3DFrameColors[style + 4].g, k3DFrameColors[style + 4].b); color2 = surface.format.RGBToColor(k3DFrameColors[style + 6].r, k3DFrameColors[style + 6].g, k3DFrameColors[style + 6].b); } int color3 = surface.format.RGBToColor(k3DFrameColors[style].r, k3DFrameColors[style].g, k3DFrameColors[style].b); int fillColor = surface.format.RGBToColor(k3DFrameColors[style + 2].r, k3DFrameColors[style + 2].g, k3DFrameColors[style + 2].b); surface.fillRect(Common::Rect(rect.left + 1, rect.top + 1, rect.right - 1, rect.bottom - 1), fillColor); surface.hLine(rect.left + 1, rect.top, rect.right - 2, color1); surface.hLine(rect.left + 1, rect.bottom - 1, rect.right - 2, color2); surface.vLine(rect.left, rect.top, rect.bottom - 2, color1); surface.vLine(rect.right - 1, rect.top + 1, rect.bottom - 1, color2); surface.hLine(rect.right - 1, rect.top, rect.right - 1, color3); surface.hLine(rect.left, rect.bottom - 1, rect.left, color3); } void UIScrollBox::scrollUp() { if (_firstLineVisible > 0) { --_firstLineVisible; } } void UIScrollBox::scrollDown() { if (_lineCount - _firstLineVisible > _maxLinesVisible) { ++_firstLineVisible; } } int UIScrollBox::findLine(int lineData) { for (int i = 0; i < _lineCount; ++i) { if (_lines[i]->lineData == lineData) { return i; } } return -1; } } // End of namespace BladeRunner