/* 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 3 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, see . * */ #include "common/system.h" #include "common/events.h" #include "engines/engine.h" #include "graphics/surface.h" #include "scumm/macgui/macgui_impl.h" namespace Scumm { // --------------------------------------------------------------------------- // Widgets imitating the look and feel of an 80s Macintosh // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Base widget // --------------------------------------------------------------------------- MacGuiImpl::MacWidget::MacWidget(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::String text, bool enabled) : MacGuiObject(bounds, enabled), _window(window), _text(text) { _black = _window->_gui->getBlack(); _white = _window->_gui->getWhite(); // Widgets are clipped to the inner surface of the dialog. If a widget // is clipped out of existence, make it invisible to avoid crashes. Graphics::Surface *s = _window->innerSurface(); _bounds.clip(Common::Rect(s->w, s->h)); if (_bounds.width() <= 0 || _bounds.height() <= 0) _visible = false; } Common::String MacGuiImpl::MacWidget::getText() const { Common::String temp = Common::U32String(_text, Common::kMacRoman).encode(Common::kUtf8); return temp; } bool MacGuiImpl::MacWidget::findWidget(int x, int y) const { return _enabled && _bounds.contains(x, y); } void MacGuiImpl::MacWidget::setRedraw(bool fullRedraw) { if (fullRedraw) _fullRedraw = true; else _redraw = true; } void MacGuiImpl::MacWidget::setEnabled(bool enabled) { _enabled = enabled; setRedraw(true); } void MacGuiImpl::MacWidget::setValue(int value) { _value = value; setRedraw(); } void MacGuiImpl::MacWidget::drawBitmap(Common::Rect r, const uint16 *bitmap, uint32 color) const { _window->_gui->drawBitmap(_window->innerSurface(), r, bitmap, color); } int MacGuiImpl::MacWidget::drawText(Common::String text, int x, int y, int w, uint32 fg, uint32 bg, Graphics::TextAlign align, bool wordWrap, int deltax) const { if (text.empty()) return 0; if (fg == 0 && bg == 0) { fg = _black; bg = _white; } const Graphics::Font *font = _window->_gui->getFont(kSystemFont); // Apply text substitutions for (uint i = 0; i < text.size() - 1; i++) { if (text[i] == '^') { uint nr = text[i + 1] - '0'; if (_window->hasSubstitution(nr)) { Common::String &subst = _window->getSubstitution(nr); text.replace(i, 2, subst); } } } // Word-wrap text Common::StringArray lines; int maxLineWidth = 0; if (wordWrap) { maxLineWidth = font->wordWrapText(text, w, lines); } else { lines.push_back(text); maxLineWidth = font->getStringWidth(text); } // Draw the text. Disabled text is implemented as a filter on top of // the already drawn text. int y0 = y; for (uint i = 0; i < lines.size(); i++) { font->drawString(_window->innerSurface(), lines[i], x, y0, w, fg, align, deltax); if (!_enabled) { Common::Rect textBox = font->getBoundingBox(lines[i], x, y0, w, align); for (int y1 = textBox.top; y1 < textBox.bottom; y1++) { for (int x1 = textBox.left; x1 < textBox.right; x1++) { if (((x1 + y1) % 2) == 0) _window->innerSurface()->setPixel(x1, y1, bg); } } } y0 += font->getFontHeight(); } return maxLineWidth; } // --------------------------------------------------------------------------- // Button widget // --------------------------------------------------------------------------- void MacGuiImpl::MacButton::draw(bool drawFocused) { if (!_redraw && !_fullRedraw) return; debug(1, "MacGuiImpl::MacButton: Drawing button %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value); // ScummVM's rounded rectangles aren't a complete match for QuickDraw's // rounded rectangles, and for arcs this small they don't look very // good. So we draw the corners manually. CornerLine buttonCorner[] = { { 0, 0 }, { 1, 2 }, { 1, 1 }, { 0, -1 } }; CornerLine smallButtonCorner[] = { { 0, 0 }, { 1, 2 }, { 1, 1 }, { 0, -1 } }; CornerLine frameCorner[] = { { 5, 1 }, { 3, 3 }, { 2, 4 }, { 1, 5 }, { 1, 3 }, { 0, 4 }, { 0, -1 } }; Graphics::Surface *s = _window->innerSurface(); uint32 fg, bg; if (drawFocused || (_window->getFocusedWidget() == this && _bounds.contains(_window->getMousePos()))) { fg = _white; bg = _black; } else { fg = _black; bg = _white; } int x0 = _bounds.left; int x1 = _bounds.right - 1; int y0 = _bounds.top; int y1 = _bounds.bottom - 1; CornerLine *corner; int cornerSize; if (_bounds.height() >= 20) { corner = buttonCorner; cornerSize = 3; } else { corner = smallButtonCorner; cornerSize = 2; } s->hLine(x0 + cornerSize, y0, x1 - cornerSize, _black); s->hLine(x0 + cornerSize, y1, x1 - cornerSize, _black); s->vLine(x0, y0 + cornerSize, y1 - cornerSize, _black); s->vLine(x1, y0 + cornerSize, y1 - cornerSize, _black); // The way the corners are rounded, we can fill this entire rectangle // in one go. Common::Rect inside = _bounds; inside.grow(-1); s->fillRect(inside, bg); drawCorners(_bounds, corner, true); const Graphics::Font *font = _window->_gui->getFont(kSystemFont); drawText(_text, _bounds.left, _bounds.top + (_bounds.height() - font->getFontHeight()) / 2, _bounds.width(), fg, bg, Graphics::kTextAlignCenter); Common::Rect bounds = _bounds; if (_window->getDefaultWidget() == this && _fullRedraw) { bounds.grow(4); x0 = bounds.left; x1 = bounds.right - 1; y0 = bounds.top; y1 = bounds.bottom - 1; for (int i = 0; i < 3; i++) { hLine(x0 + 6, y0 + i, x1 - 6, _enabled); hLine(x0 + 6, y1 - i, x1 - 6, _enabled); vLine(x0 + i, y0 + 6, y1 - 6, _enabled); vLine(x1 - i, y0 + 6, y1 - 6, _enabled); } drawCorners(bounds, frameCorner, _enabled); } _redraw = false; _fullRedraw = false; _window->markRectAsDirty(bounds); } void MacGuiImpl::MacButton::hLine(int x0, int y0, int x1, bool enabled) { Graphics::Surface *s = _window->innerSurface(); if (!enabled) { if (x0 > x1) SWAP(x0, x1); for (int x = x0; x <= x1; x++) { if (((x + y0) % 2) == 0) s->setPixel(x, y0, _black); else s->setPixel(x, y0, _white); } } else s->hLine(x0, y0, x1, _black); } void MacGuiImpl::MacButton::vLine(int x0, int y0, int y1, bool enabled) { Graphics::Surface *s = _window->innerSurface(); if (!enabled) { if (y0 > y1) SWAP(y0, y1); for (int y = y0; y <= y1; y++) { if (((x0 + y) % 2) == 0) s->setPixel(x0, y, _black); else s->setPixel(x0, y, _white); } } else s->vLine(x0, y0, y1, _black); } void MacGuiImpl::MacButton::drawCorners(Common::Rect r, CornerLine *corner, bool enabled) { for (int i = 0; corner[i].length >= 0; i++) { if (corner[i].length == 0) continue; int x0 = r.left + corner[i].start; int x1 = r.left + corner[i].start + corner[i].length - 1; int x2 = r.right - 1 - corner[i].start; int x3 = r.right - 1 - corner[i].start - corner[i].length + 1; int y0 = r.top + i; int y1 = r.bottom - i - 1; hLine(x0, y0, x1, enabled); hLine(x2, y0, x3, enabled); hLine(x0, y1, x1, enabled); hLine(x2, y1, x3, enabled); } } // --------------------------------------------------------------------------- // Checkbox widget // --------------------------------------------------------------------------- MacGuiImpl::MacCheckbox::MacCheckbox(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::String text, bool enabled) : MacWidget(window, bounds, text, enabled) { // The DITL may define a larger than necessary area for the checkbox, // so we need to calculate the hit bounds. const Graphics::Font *font = _window->_gui->getFont(kSystemFont); _hitBounds.left = _bounds.left; _hitBounds.top = _bounds.bottom - _bounds.height() / 2 - 8; _hitBounds.bottom = _hitBounds.top + 16; _hitBounds.right = _bounds.left + 18 + font->getStringWidth(_text) + 2; } bool MacGuiImpl::MacCheckbox::findWidget(int x, int y) const { return _hitBounds.contains(x, y); } void MacGuiImpl::MacCheckbox::draw(bool drawFocused) { if (!_redraw && !_fullRedraw) return; debug(1, "MacGuiImpl::MacCheckbox: Drawing checkbox %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value); Graphics::Surface *s = _window->innerSurface(); Common::Rect box(_hitBounds.left + 2, _hitBounds.top + 2, _hitBounds.left + 14, _hitBounds.top + 14); if (_fullRedraw) { _window->innerSurface()->fillRect(_bounds, _white); int x = _hitBounds.left + 18; int y = _hitBounds.top; drawText(_text, x, y, _hitBounds.right - x); _window->markRectAsDirty(_bounds); } else _window->markRectAsDirty(box); s->fillRect(box, _black); if (drawFocused || (_window->getFocusedWidget() == this && _hitBounds.contains(_window->getMousePos()))) { box.grow(-2); } else { box.grow(-1); } s->fillRect(box, _white); if (_value && _enabled) { s->drawLine(box.left, box.top, box.right - 1, box.bottom - 1, _black); s->drawLine(box.left, box.bottom - 1, box.right - 1, box.top, _black); } _redraw = false; _fullRedraw = false; } bool MacGuiImpl::MacCheckbox::handleMouseUp(Common::Event &event) { _value = _value ? 0 : 1; setRedraw(); return true; } // --------------------------------------------------------------------------- // Static text widget. Text is encoded as MacRoman, so any outside strings // (e.g.save file names or hard-coded texts) have to be re-encoded. // --------------------------------------------------------------------------- void MacGuiImpl::MacStaticText::draw(bool drawFocused) { if (!_redraw && !_fullRedraw) return; debug(1, "MacGuiImpl::MacStaticText: Drawing text %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value); _window->innerSurface()->fillRect(_bounds, _bg); drawText(_text, _bounds.left, _bounds.top, _bounds.width(), _fg, _bg, _alignment, _wordWrap, 1); _window->markRectAsDirty(_bounds); _redraw = false; _fullRedraw = false; } // --------------------------------------------------------------------------- // Editable text widget // // Text is encoded as MacRoman, and has to be re-encoded before handed over // to the outside world. // // The current edit position is stored in _caretPos. This holds the character // the caret is placed right after, so 0 is the first character. // // The length of the current selection is stored in _selectionLen. Selections // can extend left or right of the _caretPos. This makes it slightly more // tricky to handle selections after they've been made, but simplifies the // actual selection by mouse enormously. // --------------------------------------------------------------------------- MacGuiImpl::MacEditText::MacEditText(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::String text, bool enabled) : MacWidget(window, bounds, text, enabled) { _font = _window->_gui->getFont(kSystemFont); // This widget gets its own text surface, to ensure that the text is // properly clipped to the widget's bounds. Technically, the other // widgets that draw text could use this too, but there we assume that // the texts are chosen to fit without clipping. Common::Rect textBounds = _bounds; textBounds.left++; _textSurface = _window->innerSurface()->getSubArea(textBounds); } bool MacGuiImpl::MacEditText::findWidget(int x, int y) const { // Once we start drag-selecting, any mouse position is considered // within the widget. if (_window->getFocusedWidget() == this) return true; return _bounds.contains(x, y); } int MacGuiImpl::MacEditText::getTextPosFromMouse(int x, int y) { if (_text.empty()) return 0; if (y < _bounds.top) return 0; if (y >= _bounds.bottom) return _text.size(); x -= _bounds.left; int textX = _textPos; uint i; for (i = 0; i < _text.size() && textX <= _bounds.width(); i++) { int charWidth = _font->getCharWidth(_text[i]); if (x >= textX && x < textX + charWidth) { if (x > textX + charWidth / 2) return i + 1; return i; } textX += charWidth; } if (x <= _bounds.left) return 0; return i; } void MacGuiImpl::MacEditText::updateSelection(int x, int y) { int oldSelectLen = _selectLen; int pos = getTextPosFromMouse(x, y); _selectLen = pos - _caretPos; if (_selectLen != oldSelectLen) setRedraw(); } void MacGuiImpl::MacEditText::deleteSelection() { int startPos; int len; if (_selectLen < 0) { startPos = _caretPos + _selectLen; len = -_selectLen; } else { startPos = _caretPos; len = _selectLen; } _text.erase(startPos, len); _caretPos = startPos; _selectLen = 0; setRedraw(); } void MacGuiImpl::MacEditText::selectAll() { _caretPos = 0; _selectLen = _text.size(); setRedraw(); } void MacGuiImpl::MacEditText::draw(bool drawFocused) { int caretX = 0; // Calculate the caret position, and make sure that it will be placed // inside the visible area of the widget. This may require scrolling // the text, which will trigger a redraw. if (_selectLen == 0) { caretX = _textPos - 1; for (int i = 0; i < _caretPos; i++) caretX += _font->getCharWidth((byte)_text[i]); int delta = 0; if (caretX < 0) delta = caretX; else if (caretX >= _textSurface.w) delta = caretX - _textSurface.w + 1; if (delta) { _textPos -= delta; _caretX -= delta; caretX -= delta; setRedraw(); } } // Redraw the contents of the widget. This redraws the entire text, // which is a bit wasteful. if (_redraw || _fullRedraw) { Graphics::Surface *s = _window->innerSurface(); s->fillRect(_bounds, _white); int selectStart = -1; int selectEnd = -1; if (_selectLen != 0) { if (_selectLen < 0) { selectStart = _caretPos + _selectLen; selectEnd = _caretPos - 1; } else { selectStart = _caretPos; selectEnd = _caretPos + _selectLen - 1; } } int x = _textPos; int y = 0; bool firstChar = true; bool firstCharSelected = false; bool lastCharSelected = false; for (int i = 0; i < (int)_text.size() && x < _textSurface.w; i++) { uint32 color = _black; int charWidth = _font->getCharWidth((byte)_text[i]); if (x + charWidth >= 0) { if (_selectLen != 0 && i >= selectStart && i <= selectEnd) { if (firstChar) firstCharSelected = true; lastCharSelected = true; _textSurface.fillRect(Common::Rect(x, 0, x + charWidth, _textSurface.h), _black); color = _white; } else lastCharSelected = false; _font->drawChar(&_textSurface, (byte)_text[i], x, y, color); firstChar = false; } x += charWidth; } if (firstCharSelected) _window->innerSurface()->fillRect(Common::Rect(_bounds.left + 1, _bounds.top, _bounds.left + 2, _bounds.bottom), _black); if (lastCharSelected && _bounds.left + x + 1 < _bounds.right) _window->innerSurface()->fillRect(Common::Rect(_bounds.left + x + 1, _bounds.top, _bounds.right, _bounds.bottom), _black); _window->markRectAsDirty(_bounds); } // Redraw the caret, if it has changed since the last time. if (_selectLen == 0) { uint32 now = _window->_system->getMillis(); bool caretVisible = _caretVisible; if (now >= _nextCaretBlink) { _caretVisible = !_caretVisible; _nextCaretBlink = now + 500; } if (caretX != _caretX || caretVisible != _caretVisible) { if (caretX != _caretX && !_redraw && !_fullRedraw) { // Erase the old caret. Not needed if the whole // widget was just redrawn. _textSurface.vLine(_caretX, 0, _bounds.bottom - 1, _white); if (!_redraw && !_fullRedraw) _window->markRectAsDirty(Common::Rect(_bounds.left + _caretX + 1, _bounds.top, _bounds.left + _caretX + 2, _bounds.bottom)); } // Draw the new caret _textSurface.vLine(caretX, 0, _bounds.bottom - 1, _caretVisible ? _black : _white); if (!_redraw && !_fullRedraw) _window->markRectAsDirty(Common::Rect(_bounds.left + caretX + 1, _bounds.top, _bounds.left + caretX + 2, _bounds.bottom)); _caretX = caretX; } } _redraw = false; _fullRedraw = false; } void MacGuiImpl::MacEditText::handleMouseDown(Common::Event &event) { int oldSelectLen = _selectLen; int oldCaretPos = _caretPos; _caretPos = getTextPosFromMouse(event.mouse.x, event.mouse.y); _selectLen = 0; if (_selectLen != oldSelectLen || _caretPos != oldCaretPos) setRedraw(); } bool MacGuiImpl::MacEditText::handleDoubleClick(Common::Event &event) { if (_text.empty()) return false; _selectLen = 0; int startPos = _caretPos; int endPos = _caretPos; // Check if used clicked past the end of the text if (_caretPos >= (int)_text.size()) startPos = endPos = _text.size() - 1; if (_text[startPos] == ' ') { while (startPos >= 0) { if (_text[startPos] != ' ') { startPos++; break; } startPos--; } while (endPos < (int)_text.size()) { if (_text[endPos] != ' ') { endPos--; break; } endPos++; } } else { while (startPos >= 0) { if (_text[startPos] == ' ') { startPos++; break; } startPos--; } while (endPos < (int)_text.size()) { if (_text[endPos] == ' ') { endPos--; break; } endPos++; } } if (startPos < 0) startPos = 0; if (endPos >= (int)_text.size()) endPos = _text.size() - 1; _caretPos = startPos; _selectLen = endPos - startPos + 1; setRedraw(); return false; } bool MacGuiImpl::MacEditText::handleKeyDown(Common::Event &event) { if (event.kbd.flags & (Common::KBD_CTRL | Common::KBD_ALT | Common::KBD_META)) return false; // Stop caret blinking while typing. We do this by making the caret // visible, but force the next blink to happen immediately. Otherwise, // it may not register that the blink state has changed. _caretVisible = false; _nextCaretBlink = _window->_system->getMillis(); switch (event.kbd.keycode) { case Common::KEYCODE_LEFT: if (_selectLen < 0) _caretPos = _caretPos + _selectLen; _caretPos--; if (_caretPos < 0) _caretPos = 0; if (_selectLen != 0) { _selectLen = 0; setRedraw(); } return true; case Common::KEYCODE_RIGHT: if (_selectLen > 0) _caretPos += _selectLen; _caretPos++; if (_caretPos > (int)_text.size()) _caretPos = _text.size(); if (_selectLen != 0) { _selectLen = 0; setRedraw(); } return true; case Common::KEYCODE_HOME: case Common::KEYCODE_UP: _caretPos = 0; if (_selectLen != 0) { _selectLen = 0; setRedraw(); } return true; case Common::KEYCODE_END: case Common::KEYCODE_DOWN: _caretPos = _text.size(); if (_selectLen != 0) { _selectLen = 0; setRedraw(); } return true; case Common::KEYCODE_BACKSPACE: if (_selectLen != 0) { deleteSelection(); } else if (_caretPos > 0) { _caretPos--; _text.deleteChar(_caretPos); setRedraw(); } return true; case Common::KEYCODE_DELETE: if (_selectLen != 0) { deleteSelection(); } else if (_caretPos < (int)_text.size()) { _text.deleteChar(_caretPos); setRedraw(); } return true; default: break; } int c = _window->_gui->toMacRoman(event.kbd.ascii); if (c > 0) { if (_selectLen != 0) deleteSelection(); if (_text.size() < _maxLength) { _text.insertChar(c, _caretPos); _caretPos++; setRedraw(); } return true; } return false; } void MacGuiImpl::MacEditText::handleMouseHeld() { if (_text.empty()) return; Common::Point mousePos = _window->getMousePos(); int oldTextPos = _textPos; int minTextPos = MIN(_bounds.width() - _font->getStringWidth(_text) - 1, 1); if (mousePos.x < _bounds.left + 1 && mousePos.y < _bounds.bottom && _textPos < 1) { _textPos += 8; if (_textPos > 1) _textPos = 1; } else if (mousePos.x >= _bounds.right) { _textPos -= 8; if (_textPos < minTextPos) _textPos = minTextPos; } if (_textPos != oldTextPos) { updateSelection(mousePos.x, mousePos.y); setRedraw(); } } void MacGuiImpl::MacEditText::handleMouseMove(Common::Event &event) { updateSelection(event.mouse.x, event.mouse.y); } // --------------------------------------------------------------------------- // Image widget // --------------------------------------------------------------------------- MacGuiImpl::MacImage::MacImage(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Graphics::Surface *surface, Graphics::Surface *mask, bool enabled) : MacWidget(window, bounds, "Picture", enabled) { _image = surface; _mask = mask; } MacGuiImpl::MacImage::~MacImage() { if (_image) { _image->free(); delete _image; } if (_mask) { _mask->free(); delete _mask; } } void MacGuiImpl::MacImage::draw(bool drawFocused) { if (!_redraw && !_fullRedraw) return; debug(1, "MacGuiImpl::MacImage: Drawing picture %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value); if (_image) _window->drawSprite(this, _bounds.left, _bounds.top); _redraw = false; _fullRedraw = false; } // --------------------------------------------------------------------------- // Slider base class // // The idea here is that _maxPos and _minPos correspond to _maxValue and // _minValue respectively. The position can be calculated from the value and // vice versa. // // For more exact positioning, where you have to match it exactly to a visible // ruler, you can use addStop() to override the calculated values. // --------------------------------------------------------------------------- void MacGuiImpl::MacSliderBase::setValue(int value) { int newValue = CLIP(value, _minValue, _maxValue); MacWidget::setValue(newValue); _handlePos = calculatePosFromValue(); } int MacGuiImpl::MacSliderBase::calculateValueFromPos() const { return calculateValueFromPos(_handlePos); } int MacGuiImpl::MacSliderBase::calculateValueFromPos(int pos) const { int value; if (_posToValue.tryGetVal(pos, value)) return value; int posOffset = pos - _minPos; int posRange = _maxPos - _minPos; int valueRange = _maxValue - _minValue; int valueOffset = (posRange / 2 + valueRange * posOffset) / posRange; return _minValue + valueOffset; } int MacGuiImpl::MacSliderBase::calculatePosFromValue() const { return calculatePosFromValue(_value); } int MacGuiImpl::MacSliderBase::calculatePosFromValue(int value) const { int pos; if (_valueToPos.tryGetVal(value, pos)) return pos; int valueRange = _maxValue - _minValue; int valueOffset = value - _minValue; int posRange = _maxPos - _minPos; int posOffset = (valueRange / 2 + posRange * valueOffset) / valueRange; return _minPos + posOffset; } // --------------------------------------------------------------------------- // Standard slider widget // --------------------------------------------------------------------------- MacGuiImpl::MacSlider::MacSlider(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, int minValue, int maxValue, int pageSize, bool enabled) : MacSliderBase(window, bounds, minValue, maxValue, 0, 0, enabled), _pageSize(pageSize) { _boundsButtonUp = Common::Rect(_bounds.left, _bounds.top, _bounds.right, _bounds.top + 16); _boundsButtonDown = Common::Rect(_bounds.left, _bounds.bottom - 16, _bounds.right, _bounds.bottom); _boundsBody = Common::Rect(_bounds.left, _bounds.top + 16, _bounds.right, _bounds.bottom - 16); _minPos = _boundsBody.top; _maxPos = _boundsBody.bottom - 16; _clickPos.x = -1; _clickPos.y = -1; } bool MacGuiImpl::MacSlider::findWidget(int x, int y) const { if (!isScrollable()) return false; Common::Rect bounds = _bounds; // While dragging the handle, you're allowed to go outside the slider. // I don't know by how much, though. if (_grabOffset >= 0) { bounds.left -= 25; bounds.right += 25; bounds.top -= 50; bounds.bottom += 50; } return bounds.contains(x, y); } Common::Rect MacGuiImpl::MacSlider::getHandleRect(int value) { int handlePos = value * (_boundsBody.bottom - _boundsBody.top - 16) / (_maxValue - _minValue); Common::Rect handleRect; handleRect.left = _boundsBody.left + 1; handleRect.top = _boundsBody.top + handlePos; handleRect.right = _boundsBody.right - 1; handleRect.bottom = handleRect.top + 16; return handleRect; } void MacGuiImpl::MacSlider::fill(Common::Rect r, bool inverted) { uint32 pattern[2][4] = { { _white, _white, _black, _white }, { _black, _white, _white, _white } }; Graphics::Surface *s = _window->innerSurface(); for (int y = r.top; y < r.bottom; y++) { for (int x = r.left; x < r.right; x++) { if (inverted) { // The inverted style is used for drawing the "ghost" of the // slider handle while dragging. I think this matches the // original behavior, though I'm not quite sure. bool srcPixel = s->getPixel(x, y) == _black; bool dstPixel = pattern[y % 2][x % 4] == _white; uint32 color = (srcPixel ^ dstPixel) ? _black : _white; s->setPixel(x, y, color); } else s->setPixel(x, y, pattern[y % 2][x % 4]); } } } void MacGuiImpl::MacSlider::draw(bool drawFocused) { if (!_redraw && !_fullRedraw) return; // There are several things that will trigger a redraw, but unlike // other widgets this one only handles full redraws. Everything else // is handled outside of draw(). if (_fullRedraw) { debug(1, "MacGuiImpl::MacSlider: Drawing slider (_fullRedraw = %d, drawFocused = %d, _value = %d)", _fullRedraw, drawFocused, _value); Graphics::Surface *s = _window->innerSurface(); s->frameRect(_bounds, _black); s->hLine(_bounds.left + 1, _bounds.top + 15, _bounds.right - 2, _black); s->hLine(_bounds.left + 1, _bounds.bottom - 16, _bounds.right - 2, _black); drawUpArrow(false); drawDownArrow(false); Common::Rect fillRect(_boundsBody.left + 1, _boundsBody.top, _boundsBody.right - 1, _boundsBody.bottom); if (isScrollable()) { fill(fillRect); Common::Rect handleRect = getHandleRect(_value); drawHandle(handleRect); } else s->fillRect(fillRect, _white); _window->markRectAsDirty(_bounds); } _redraw = false; _fullRedraw = false; } // There is a narrower version of these arrows, but I'm speculating that // they're intended for a 9" Mac screen. We go with the slightly wider version // here to make them easier to hit. void MacGuiImpl::MacSlider::drawUpArrow(bool markAsDirty) { debug(1, "MacGuiImpl::MacSlider: Drawing up arrow (_upArrowPressed = %d, markAsDirty = %d)", _upArrowPressed, markAsDirty); const uint16 upArrow[] = { 0x0600, 0x0900, 0x1080, 0x2040, 0x4020, 0xF0F0, 0x1080, 0x1080, 0x1080, 0x1F80 }; const uint16 upArrowFilled[] = { 0x0600, 0x0F00, 0x1F80, 0x3FC0, 0x7FE0, 0xFFF0, 0x1F80, 0x1F80, 0x1F80, 0x1F80 }; drawArrow(_boundsButtonUp, (_upArrowPressed ? upArrowFilled : upArrow), markAsDirty); } void MacGuiImpl::MacSlider::drawDownArrow(bool markAsDirty) { debug(1, "MacGuiImpl::MacSlider: Drawing down arrow (_downArrowPressed = %d, markAsDirty = %d)", _downArrowPressed, markAsDirty); const uint16 downArrow[] = { 0x1F80, 0x1080, 0x1080, 0x1080, 0xF0F0, 0x4020, 0x2040, 0x1080, 0x0900, 0x0600 }; const uint16 downArrowFilled[] = { 0x1F80, 0x1F80, 0x1F80, 0x1F80, 0xFFF0, 0x7FE0, 0x3FC0, 0x1F80, 0x0F00, 0x0600 }; drawArrow(_boundsButtonDown, (_downArrowPressed ? downArrowFilled : downArrow), markAsDirty); } void MacGuiImpl::MacSlider::drawArrow(Common::Rect r, const uint16 *bitmap, bool markAsDirty) { Graphics::Surface *s = _window->innerSurface(); r.grow(-1); s->fillRect(r, _white); drawBitmap(Common::Rect(r.left + 1, r.top + 2, r.right - 1, r.top + 12), bitmap, _black); if (markAsDirty) _window->markRectAsDirty(r); } void MacGuiImpl::MacSlider::eraseDragHandle() { Common::Rect r(_boundsBody.left + 1, _handlePos, _boundsBody.right - 1, _handlePos + 16); fill(r); _window->markRectAsDirty(r); } void MacGuiImpl::MacSlider::drawHandle(Common::Rect r) { debug(2, "MacGuiImpl::MacSlider::drawHandle(%d)", r.top); Graphics::Surface *s = _window->innerSurface(); s->frameRect(r, _black); r.grow(-1); s->fillRect(r, _white); } void MacGuiImpl::MacSlider::redrawHandle(int oldValue, int newValue) { Common::Rect r = getHandleRect(oldValue); fill(r); _window->markRectAsDirty(r); r = getHandleRect(newValue); drawHandle(r); _window->markRectAsDirty(r); } void MacGuiImpl::MacSlider::handleMouseDown(Common::Event &event) { int x = event.mouse.x; int y = event.mouse.y; _clickPos.x = x; _clickPos.y = y; _paging = 0; _grabOffset = -1; _handlePos = -1; int oldValue = _value; if (_boundsButtonUp.contains(x, y)) { _nextRepeat = _window->_system->getMillis() + 200; _upArrowPressed = true; _value = MAX(_minValue, _value - 1); drawUpArrow(true); } else if (_boundsButtonDown.contains(x, y)) { _nextRepeat = _window->_system->getMillis() + 200; _downArrowPressed = true; _value = MIN(_maxValue, _value + 1); drawDownArrow(true); } else { Common::Rect handleRect = getHandleRect(_value); if (y < handleRect.top) { _nextRepeat = _window->_system->getMillis() + 200; _paging = -1; _value = MAX(_minValue, _value - (_pageSize - 1)); } else if (y >= handleRect.bottom) { _nextRepeat = _window->_system->getMillis() + 200; _paging = 1; _value = MIN(_maxValue, _value + (_pageSize - 1)); } else { _grabOffset = y - handleRect.top; _handlePos = handleRect.top; } } if (_value != oldValue) redrawHandle(oldValue, _value); } bool MacGuiImpl::MacSlider::handleMouseUp(Common::Event &event) { if (_upArrowPressed) { _upArrowPressed = false; drawUpArrow(true); } else if (_downArrowPressed) { _downArrowPressed = false; drawDownArrow(true); } else if (_grabOffset >= 0) { // Erase the drag handle, since the handle might not end up in // the exact same spot. eraseDragHandle(); // Calculate new value and move the handle there int newValue = calculateValueFromPos(); redrawHandle(_value, newValue); _value = newValue; } _paging = 0; _grabOffset = -1; _handlePos = -1; _clickPos.x = -1; _clickPos.y = -1; return false; } void MacGuiImpl::MacSlider::handleMouseMove(Common::Event &event) { int x = event.mouse.x; int y = event.mouse.y; if (_grabOffset >= 0) { if (!findWidget(x, y)) { eraseDragHandle(); Common::Rect handleRect = getHandleRect(_value); if (ABS(_handlePos - handleRect.top) <= handleRect.height()) { drawHandle(handleRect); _window->markRectAsDirty(handleRect); } return; } int newHandlePos = CLIP(y - _grabOffset, _boundsBody.top, _boundsBody.bottom - 16); // Theoretically, we could end here if the handle position has // not changed. However, we currently don't keep track of if // the handle is hidden because the mouse has moved out of the // widget's control. eraseDragHandle(); Common::Rect handleRect = getHandleRect(_value); if (ABS(_handlePos - handleRect.top) <= handleRect.height()) { drawHandle(handleRect); _window->markRectAsDirty(handleRect); } _handlePos = newHandlePos; int x0 = _boundsBody.left + 1; int x1 = _boundsBody.right - 1; int y0 = _handlePos; int y1 = _handlePos + 16; // Drawing a solid rectangle would be easier, and probably look // better. But it seems the original Mac widget would draw the // frame as an inverted slider background, even when drawing it // on top of the slider handle. fill(Common::Rect(x0, y0, x1, y0 + 1), true); fill(Common::Rect(x0, y1 - 1, x1, y1), true); fill(Common::Rect(x0, y0 + 1, x0 + 1, y1 - 1), true); fill(Common::Rect(x1 - 1, y0 + 1, x1, y1 - 1), true); _window->markRectAsDirty(Common::Rect(x0, y0, x1, y1)); } else { if (!_boundsButtonUp.contains(x, y)) { if (_upArrowPressed) { _upArrowPressed = false; drawUpArrow(true); } } else { if (_boundsButtonUp.contains(_clickPos) && !_upArrowPressed) { _nextRepeat = _window->_system->getMillis() + 200; _upArrowPressed = true; drawUpArrow(true); } } if (!_boundsButtonDown.contains(x, y)) { if (_downArrowPressed) { _downArrowPressed = false; drawDownArrow(true); } } else { if (_boundsButtonDown.contains(_clickPos) && !_downArrowPressed) { _nextRepeat = _window->_system->getMillis() + 200; _downArrowPressed = true; drawDownArrow(true); } } } } void MacGuiImpl::MacSlider::handleMouseHeld() { uint32 now = _window->_system->getMillis(); Common::Point p = _window->getMousePos(); if (now < _nextRepeat || !findWidget(p.x, p.y)) return; int oldValue = _value; if (_upArrowPressed) { _value = MAX(_minValue, _value - 1); _nextRepeat = now + 80; } if (_downArrowPressed) { _value = MIN(_maxValue, _value + 1); _nextRepeat = now + 80; } if (_paging) { Common::Rect r = getHandleRect(_value); // Keep paging until at least half the scroll handle has gone // past the mouse cursor. This may have to be tuned. if (_paging == -1) { if (p.y < r.top + r.height() / 2 && _value > _minValue) { _nextRepeat = now + 100; _value = MAX(_minValue, _value - (_pageSize - 1)); } } else if (_paging == 1) { if (p.y >= r.bottom - r.height() / 2 && _value < _maxValue) { _nextRepeat = now + 100; _value = MIN(_maxValue, _value + (_pageSize - 1)); } } } if (_value != oldValue) redrawHandle(oldValue, _value); } void MacGuiImpl::MacSlider::handleWheelUp() { int oldValue = _value; _value = MAX(_minValue, _value - (_pageSize - 1)); if (_value != oldValue) redrawHandle(oldValue, _value); } void MacGuiImpl::MacSlider::handleWheelDown() { int oldValue = _value; _value = MIN(_maxValue, _value + (_pageSize - 1)); if (_value != oldValue) redrawHandle(oldValue, _value); } // --------------------------------------------------------------------------- // Image slider widget. This is the custom slider widget used for the Loom // and Indy 3 options dialogs. It consists of a background image and a slider // drag handle. // // In addition to the min and max value positions, this one also maintains a // _minX and _maxX position to which the handle can be dragged. This is only // used for the older games. // --------------------------------------------------------------------------- MacGuiImpl::MacImageSlider::MacImageSlider(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, MacImage *handle, bool enabled, int minX, int maxX, int minValue, int maxValue) : MacSliderBase(window, bounds, minValue, maxValue, minX, maxX, enabled), _handle(handle), _minX(minX), _maxX(maxX) { _background = new Graphics::Surface(); _background->copyFrom(window->innerSurface()->getSubArea(bounds)); _freeBackground = true; } MacGuiImpl::MacImageSlider::~MacImageSlider() { if (_freeBackground && _background) { _background->free(); delete _background; } } void MacGuiImpl::MacImageSlider::setValue(int value) { int newValue = CLIP(value, _minValue, _maxValue); MacWidget::setValue(newValue); eraseHandle(); _handlePos = calculatePosFromValue(); drawHandle(); } bool MacGuiImpl::MacImageSlider::findWidget(int x, int y) const { // Once we start dragging the handle, any mouse position is considered // within the widget. if (_window->getFocusedWidget() == this) return true; return _bounds.contains(x, y); } void MacGuiImpl::MacImageSlider::draw(bool drawFocused) { if (!_redraw && !_fullRedraw) return; debug(1, "MacGuiImpl::MacImageSlider: Drawing slider %d (_fullRedraw = %d, drawFocused = %d, _value = %d)", _id, _fullRedraw, drawFocused, _value); if (_fullRedraw) { _window->drawSprite(_background, _bounds.left, _bounds.top); drawHandle(); } _redraw = false; _fullRedraw = false; } void MacGuiImpl::MacImageSlider::eraseHandle() { if (_handlePos == -1) return; Common::Rect r = _handle->getBounds(); int y = r.top - _bounds.top; int w = r.width(); int h = r.height(); Graphics::Surface sprite = _background->getSubArea(Common::Rect(_handlePos, y, _handlePos + w, y + h)); _window->drawSprite(&sprite, _bounds.left + _handlePos, r.top); } void MacGuiImpl::MacImageSlider::drawHandle() { Common::Rect r = _handle->getBounds(); _window->drawSprite(_handle, _bounds.left + _handlePos, r.top); } void MacGuiImpl::MacImageSlider::handleMouseDown(Common::Event &event) { int mouseX = event.mouse.x; int handleWidth = _handle->getBounds().width(); if (mouseX >= _handlePos && mouseX < _handlePos + handleWidth) _grabOffset = event.mouse.x - _bounds.left - _handlePos; else _grabOffset = handleWidth / 2; handleMouseMove(event); } bool MacGuiImpl::MacImageSlider::handleMouseUp(Common::Event &event) { // Erase the drag rect, since the handle might not end up in // the exact same spot. int newValue = calculateValueFromPos(); // Even if the value doesn't change, we need to reposition the slider // handle, or it may be left between two values. This is particularly // noticeable for the music quality slider. eraseHandle(); setValue(newValue); drawHandle(); return false; } void MacGuiImpl::MacImageSlider::handleMouseMove(Common::Event &event) { int newPos; if (_snapWhileDragging) { // Even when overriding the stops, the calculated one should // be close enough here. int newValue = calculateValueFromPos(CLIP(event.mouse.x - _bounds.left - _handle->getImage()->w / 2, _minX, _maxX)); newPos = calculatePosFromValue(newValue); } else newPos = CLIP(event.mouse.x - _bounds.left - _grabOffset, _minX, _maxX); if (newPos != _handlePos) { eraseHandle(); _handlePos = newPos; drawHandle(); } } void MacGuiImpl::MacImageSlider::handleWheelUp() { int newValue = MAX(_minValue, _value + 1); if (_value != newValue) { eraseHandle(); setValue(newValue); drawHandle(); } } void MacGuiImpl::MacImageSlider::handleWheelDown() { int newValue = MIN(_maxValue, _value - 1); if (_value != newValue) { eraseHandle(); setValue(newValue); drawHandle(); } } // --------------------------------------------------------------------------- // List box widget // --------------------------------------------------------------------------- MacGuiImpl::MacListBox::MacListBox(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::StringArray texts, bool enabled, bool contentUntouchable) : MacWidget(window, bounds, "ListBox", enabled), _texts(texts) { int pageSize = _bounds.height() / 16; int numSlots = MIN(pageSize, texts.size()); for (int i = 0; i < numSlots; i++) { Common::Rect r(_bounds.left + 1, _bounds.top + 1 + 16 * i, _bounds.right - 16, _bounds.top + 1 + 16 * (i + 1)); MacStaticText *tmp = new MacStaticText(window, r, texts[i], enabled); if (contentUntouchable) tmp->setEnabled(false); _textWidgets.push_back(tmp); } _slider = new MacSlider(window, Common::Rect(_bounds.right - 16, _bounds.top, _bounds.right, _bounds.bottom), 0, texts.size() - pageSize, pageSize, enabled); // The widget value indicates the selected element _value = 0; updateTexts(); } MacGuiImpl::MacListBox::~MacListBox() { _texts.clear(); delete _slider; for (uint i = 0; i < _textWidgets.size(); i++) delete _textWidgets[i]; } bool MacGuiImpl::MacListBox::findWidget(int x, int y) const { return MacWidget::findWidget(x, y) || _slider->findWidget(x, y); } void MacGuiImpl::MacListBox::setRedraw(bool fullRedraw) { MacWidget::setRedraw(fullRedraw); _slider->setRedraw(fullRedraw); for (uint i = 0; i < _textWidgets.size(); i++) _textWidgets[i]->setRedraw(fullRedraw); } void MacGuiImpl::MacListBox::updateTexts() { int offset = _slider->getValue(); for (uint i = 0; i < _textWidgets.size(); i++) { _textWidgets[i]->setText(_texts[i + offset]); if (_textWidgets[i]->isEnabled() && (int)i + offset == _value) _textWidgets[i]->setColor(_white, _black); else _textWidgets[i]->setColor(_black, _white); } } void MacGuiImpl::MacListBox::draw(bool drawFocused) { for (uint i = 0; i < _textWidgets.size(); i++) _textWidgets[i]->draw(drawFocused); _slider->draw(drawFocused); if (!_redraw && !_fullRedraw) return; debug(1, "MacGuiImpl::MacListBox: Drawing list box (_fullRedraw = %d, drawFocused = %d)", _fullRedraw, drawFocused); Graphics::Surface *s = _window->innerSurface(); s->hLine(_bounds.left, _bounds.top, _bounds.right - 17, _black); s->hLine(_bounds.left, _bounds.bottom - 1, _bounds.right - 17, _black); s->vLine(_bounds.left, _bounds.top + 1, _bounds.bottom - 2, _black); _redraw = false; _fullRedraw = false; _window->markRectAsDirty(_bounds); } void MacGuiImpl::MacListBox::handleMouseDown(Common::Event &event) { if (_slider->findWidget(event.mouse.x, event.mouse.y)) { int oldValue = _slider->getValue(); _sliderFocused = true; _slider->handleMouseDown(event); if (_slider->getValue() != oldValue) updateTexts(); return; } int offset = _slider->getValue(); for (uint i = 0; i < _textWidgets.size(); i++) { if (_textWidgets[i]->findWidget(event.mouse.x, event.mouse.y)) { setValue(i + offset); break; } } } bool MacGuiImpl::MacListBox::handleDoubleClick(Common::Event &event) { for (uint i = 0; i < _textWidgets.size(); i++) { if (_textWidgets[i]->findWidget(event.mouse.x, event.mouse.y)) return true; } return false; } bool MacGuiImpl::MacListBox::handleMouseUp(Common::Event &event) { if (_sliderFocused) { int oldValue = _slider->getValue(); _sliderFocused = false; _slider->handleMouseUp(event); if (_slider->getValue() != oldValue) updateTexts(); } return false; } void MacGuiImpl::MacListBox::handleMouseMove(Common::Event &event) { if (_sliderFocused) { int oldValue = _slider->getValue(); _slider->handleMouseMove(event); if (_slider->getValue() != oldValue) updateTexts(); } } void MacGuiImpl::MacListBox::handleMouseHeld() { if (_sliderFocused) { int oldValue = _slider->getValue(); _slider->handleMouseHeld(); if (_slider->getValue() != oldValue) updateTexts(); } } void MacGuiImpl::MacListBox::handleWheelUp() { handleWheel(-1); } void MacGuiImpl::MacListBox::handleWheelDown() { handleWheel(1); } void MacGuiImpl::MacListBox::handleWheel(int distance) { if (!_slider->isScrollable()) return; Common::Point mousePos = _window->getMousePos(); int oldValue = _slider->getValue(); if (_slider->findWidget(mousePos.x, mousePos.y)) distance *= _slider->getPageSize(); _slider->setValue(oldValue + distance); int newValue = _slider->getValue(); if (newValue != oldValue) { updateTexts(); _slider->redrawHandle(oldValue, newValue); } } bool MacGuiImpl::MacListBox::handleKeyDown(Common::Event &event) { if (_texts.size() <= 1 || !_textWidgets[0]->isEnabled()) return false; if (event.kbd.flags & (Common::KBD_CTRL | Common::KBD_ALT | Common::KBD_META)) return false; int oldValue = _value; switch (event.kbd.keycode) { case Common::KEYCODE_UP: _value = MAX(_value - 1, 0); break; case Common::KEYCODE_DOWN: _value = MIN(_value + 1, _texts.size() - 1); break; default: break; } if (_value != oldValue) { int sliderValue = _slider->getValue(); int pageSize = _slider->getPageSize(); int newSliderValue = sliderValue; if (_value < sliderValue) newSliderValue = _value; else if (_value >= sliderValue + pageSize) newSliderValue = _value - pageSize + 1; if (sliderValue != newSliderValue) { _slider->setValue(newSliderValue); _slider->redrawHandle(sliderValue, newSliderValue); } updateTexts(); } return false; } // --------------------------------------------------------------------------- // Pop-up menu widget // --------------------------------------------------------------------------- MacGuiImpl::MacPopUpMenu::MacPopUpMenu(MacGuiImpl::MacDialogWindow *window, Common::Rect bounds, Common::String text, int textWidth, Common::StringArray texts, bool enabled) : MacWidget(window, bounds, text, enabled), _textWidth(textWidth), _texts(texts) { _black = _window->_gui->getBlack(); _white = _window->_gui->getWhite(); _popUpBounds.left = _bounds.left + _textWidth; _popUpBounds.right = _bounds.right; } MacGuiImpl::MacPopUpMenu::~MacPopUpMenu() { _texts.clear(); _popUpBackground.free(); } bool MacGuiImpl::MacPopUpMenu::findWidget(int x, int y) const { // Once we have opened the drop down list, any mouse position is // considered within the widget. if (_window->getFocusedWidget() == this) return true; return _bounds.contains(x, y); } void MacGuiImpl::MacPopUpMenu::draw(bool drawFocused) { if (!_redraw && !_fullRedraw) return; debug(1, "MacGuiImpl::MacPopUpMenu: Drawing list box (_fullRedraw = %d, drawFocused = %d)", _fullRedraw, drawFocused); // I don't know how Mac originally drew disabled drop downs lists, or // if that was even a thing. For our purposes, the text is still // relevant. We just need to make it obvious that you can't change it // in any way. uint32 fg, bg; bool focused = drawFocused || _window->getFocusedWidget() == this; if (focused) { fg = _white; bg = _black; } else { fg = _black; bg = _white; } Graphics::Surface *s = _window->innerSurface(); const Graphics::Font *font = _window->_gui->getFont(kSystemFont); s->fillRect(Common::Rect(_bounds.left, _bounds.top + 1, _bounds.left + _textWidth, _bounds.bottom - 3), bg); font->drawString(s, _text, _bounds.left, _bounds.top + 1, _textWidth, fg, Graphics::kTextAlignLeft, 4); if (focused) { Common::Rect r = _popUpBounds; r.bottom--; r.right--; s->fillRect(r, _white); s->frameRect(r, _black); s->hLine(r.left + 3, r.bottom, r.right, _black); s->vLine(r.right, r.top + 3, r.bottom - 1, _black); Common::Rect textRect(r.left + 1, r.top + 1, r.right - 1, r.top + 17); for (int i = 0; i < (int)_texts.size(); i++) { if (i == _selected) { fg = _white; bg = _black; } else { fg = _black; bg = _white; } s->fillRect(textRect, bg); font->drawString(s, _texts[i], textRect.left, textRect.top, textRect.width(), fg, Graphics::kTextAlignLeft, 14); if (i == _value) font->drawString(s, "\x12", textRect.left + 2, textRect.top, 10, fg); textRect.translate(0, 16); } } else { Common::Rect r(_bounds.left + _textWidth, _bounds.top, _bounds.right - 1, _bounds.bottom - 2); s->fillRect(r, _white); s->frameRect(r, _black); s->hLine(r.left + 3, r.bottom, r.right, _black); s->vLine(r.right, r.top + 3, r.bottom - 1, _black); font->drawString(s, _texts[_value], r.left, r.top + 1, r.width() - 20, _black, Graphics::kTextAlignLeft, 15); const uint16 arrowDownIcon[16] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x3FF8, 0x1FF0, 0x0FE0, 0x07C0, 0x0380, 0x0100, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }; const uint16 disabledArrowDownIcon[16] = { 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2AA8, 0x1550, 0x0AA0, 0x0540, 0x0280, 0x0100, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }; Common::Rect iconRect(16, 16); iconRect.moveTo(r.right - 19, r.top + 1); drawBitmap(iconRect, _enabled ? arrowDownIcon : disabledArrowDownIcon, _black); } _redraw = false; _fullRedraw = false; _window->markRectAsDirty(_bounds); if (focused) _window->markRectAsDirty(_popUpBounds); } void MacGuiImpl::MacPopUpMenu::handleMouseDown(Common::Event &event) { _popUpBounds.top = _bounds.top - 16 * _value; _popUpBounds.bottom = _bounds.bottom - 1 + 16 * (_texts.size() - _value - 1); Graphics::Surface background = _window->innerSurface()->getSubArea(_popUpBounds); _popUpBackground.free(); _popUpBackground.copyFrom(background); _menuVisible = true; _selected = _value; } bool MacGuiImpl::MacPopUpMenu::handleMouseUp(Common::Event &event) { if (_selected != -1) { int selected = _selected; for (int i = 0; i < 6; i++) { if (_selected == selected) _selected = -1; else _selected = selected; // It is a bit wasteful to redraw the entire widget // just for this. It's also a lot easier. setRedraw(); _window->update(); for (int j = 0; j < 3; j++) { Common::Event e; while (_window->_system->getEventManager()->pollEvent(e)) ; _window->_system->delayMillis(10); _window->_system->updateScreen(); } } setValue(selected); } _window->drawSprite(&_popUpBackground, _popUpBounds.left, _popUpBounds.top); _menuVisible = false; return false; } void MacGuiImpl::MacPopUpMenu::handleMouseMove(Common::Event &event) { if (!_menuVisible) return; Common::Rect menuBounds(_popUpBounds.left + 1, _popUpBounds.top + 1, _popUpBounds.right - 2, _popUpBounds.bottom - 2); int selected = -1; if (menuBounds.contains(event.mouse.x, event.mouse.y)) { selected = (event.mouse.y - menuBounds.top) / 16; int maxValue = _texts.size() - 1; if (selected > maxValue) { warning("MacGuiImpl::MacPopUpMenu::handleMouseMove: Max selection value exceeded"); selected = -1; } } if (selected != _selected) { _selected = selected; setRedraw(); } } } // End of namespace Scumm