1846 lines
48 KiB
C++
1846 lines
48 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#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<int>(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<int>(event.mouse.x - _bounds.left - _handle->getImage()->w / 2, _minX, _maxX));
|
|
newPos = calculatePosFromValue(newValue);
|
|
} else
|
|
newPos = CLIP<int>(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<int>(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<int>(_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
|