1011 lines
29 KiB
C++
1011 lines
29 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/macresman.h"
|
|
|
|
#include "graphics/blit.h"
|
|
#include "graphics/cursorman.h"
|
|
#include "graphics/macgui/macwindowmanager.h"
|
|
#include "graphics/paletteman.h"
|
|
#include "graphics/primitives.h"
|
|
#include "graphics/surface.h"
|
|
|
|
#include "scumm/scumm.h"
|
|
#include "scumm/detection.h"
|
|
#include "scumm/macgui/macgui_impl.h"
|
|
|
|
namespace Scumm {
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Dialog window
|
|
//
|
|
// This can either be used as a modal dialog (options, etc.), or as a framed
|
|
// drawing area (about and pause). It can not be dragged.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
MacGuiImpl::MacDialogWindow::MacDialogWindow(MacGuiImpl *gui, OSystem *system, Graphics::Surface *from, Common::Rect bounds, MacDialogWindowStyle windowStyle, MacDialogMenuStyle menuStyle) : _gui(gui), _system(system), _from(from), _bounds(bounds) {
|
|
// Only apply menu style if the menu is open.
|
|
Graphics::MacMenu *menu = _gui->_windowManager->getMenu();
|
|
|
|
if (!menu->_active)
|
|
menuStyle = kMenuStyleNone;
|
|
|
|
_pauseToken = _gui->_vm->pauseEngine();
|
|
|
|
// Remember if the screen was shaking. We don't use the SCUMM engine's
|
|
// own _shakeTempSavedState to remember this, because there may be
|
|
// nested dialogs. They each need their own.
|
|
|
|
_shakeWasEnabled = _gui->_vm->_shakeEnabled;
|
|
_gui->_vm->setShake(0);
|
|
|
|
_black = _gui->getBlack();
|
|
_white = _gui->getWhite();
|
|
|
|
_backup = new Graphics::Surface();
|
|
_backup->create(bounds.width(), bounds.height(), Graphics::PixelFormat::createFormatCLUT8());
|
|
_backup->copyRectToSurface(*_from, 0, 0, bounds);
|
|
|
|
_margin = (windowStyle == kWindowStyleNormal) ? 6 : 4;
|
|
|
|
_surface = _from->getSubArea(bounds);
|
|
bounds.grow(-_margin);
|
|
_innerSurface = _from->getSubArea(bounds);
|
|
|
|
_dirtyRects.clear();
|
|
|
|
Graphics::Surface *s = surface();
|
|
Common::Rect r = Common::Rect(s->w, s->h);
|
|
|
|
r.grow(-1);
|
|
s->fillRect(r, _white);
|
|
|
|
if (windowStyle == kWindowStyleNormal) {
|
|
if (_gui->_vm->_game.version < 6) {
|
|
int growths[] = { 1, -3, -1 };
|
|
|
|
for (int i = 0; i < ARRAYSIZE(growths); i++) {
|
|
r.grow(growths[i]);
|
|
s->frameRect(r, _black);
|
|
}
|
|
} else {
|
|
uint32 light = _gui->_windowManager->findBestColor(0xCC, 0xCC, 0xFF);
|
|
uint32 medium = _gui->_windowManager->findBestColor(0xBB, 0xBB, 0xBB);
|
|
uint32 dark = _gui->_windowManager->findBestColor(0x66, 0x66, 0x99);
|
|
|
|
s->frameRect(r, _black);
|
|
s->frameRect(Common::Rect(r.left, r.top, r.right, r.bottom), dark);
|
|
s->frameRect(Common::Rect(r.left, r.top, r.right - 1, r.bottom - 1), light);
|
|
s->frameRect(Common::Rect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1), medium);
|
|
s->frameRect(Common::Rect(r.left + 2, r.top + 2, r.right - 2, r.bottom - 2), dark);
|
|
s->frameRect(Common::Rect(r.left + 3, r.top + 3, r.right - 2, r.bottom - 2), light);
|
|
s->frameRect(Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.bottom - 3), _black);
|
|
}
|
|
} else if (windowStyle == kWindowStyleRounded) {
|
|
r.grow(1);
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
s->hLine(r.left + 2, r.top, r.right - 3, _black);
|
|
s->hLine(r.left + 2, r.bottom - 1, r.right - 3, _black);
|
|
s->vLine(r.left, r.top + 2, r.bottom - 3, _black);
|
|
s->vLine(r.right - 1, r.top + 2, r.bottom - 3, _black);
|
|
s->setPixel(r.left + 1, r.top + 1, _black);
|
|
s->setPixel(r.left + 1, r.bottom - 2, _black);
|
|
s->setPixel(r.right - 2, r.top + 1, _black);
|
|
s->setPixel(r.right - 2, r.bottom - 2, _black);
|
|
r.grow(-2);
|
|
}
|
|
}
|
|
|
|
if (menuStyle != kMenuStyleNone) {
|
|
// The menu bar isn't part of the Mac screen. We copy it to the
|
|
// Mac screen so that the beam cursor is correctly drawn if it
|
|
// ever moves that far up. There's no reason for it to, but it
|
|
// can happen.
|
|
|
|
Graphics::Surface *screen = _gui->surface();
|
|
Graphics::Surface *realScreen = _system->lockScreen();
|
|
|
|
// On a real Mac, the menu stays interactable. But the only
|
|
// thing that is actually used for is the Edit menu, which we
|
|
// don't implement. In ScummVM, the menu bar is just a drawing
|
|
// at the point, so we can retouch it to look correct.
|
|
//
|
|
// However, the Mac Window Manager's ideas of what's black and
|
|
// what's white may no longer be valid.
|
|
|
|
uint32 macWhite = _gui->_macWhite;
|
|
uint32 macBlack = _gui->_macBlack;
|
|
|
|
if (macWhite != _white || macBlack != _black) {
|
|
for (int y = 0; y < 20; y++) {
|
|
for (int x = 0; x < realScreen->w; x++) {
|
|
uint32 color = realScreen->getPixel(x, y);
|
|
if (color == macWhite)
|
|
realScreen->setPixel(x, y, _white);
|
|
else if (color == macBlack)
|
|
realScreen->setPixel(x, y, _black);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (menuStyle == kMenuStyleDisabled) {
|
|
for (int y = 0; y < 19; y++) {
|
|
for (int x = 5; x < 635; x++) {
|
|
if (((x + y) & 1) == 0)
|
|
realScreen->setPixel(x, y, _white);
|
|
}
|
|
}
|
|
} else if (menuStyle == kMenuStyleApple) {
|
|
for (int y = 1; y < 19; y++) {
|
|
for (int x = 10; x < 38; x++) {
|
|
uint32 color = realScreen->getPixel(x, y);
|
|
if (color == _black)
|
|
realScreen->setPixel(x, y, _white);
|
|
else
|
|
realScreen->setPixel(x, y, _black);
|
|
}
|
|
}
|
|
}
|
|
|
|
screen->copyRectToSurface(*realScreen, 0, 0, Common::Rect(0, 0, 640, 19));
|
|
|
|
_system->unlockScreen();
|
|
}
|
|
}
|
|
|
|
MacGuiImpl::MacDialogWindow::~MacDialogWindow() {
|
|
if (!CursorMan.isVisible())
|
|
CursorMan.showMouse(true);
|
|
|
|
CursorMan.showMouse(_cursorWasVisible);
|
|
_gui->_windowManager->popCursor();
|
|
|
|
copyToScreen(_backup);
|
|
_backup->free();
|
|
delete _backup;
|
|
|
|
for (uint i = 0; i < _widgets.size(); i++)
|
|
delete _widgets[i];
|
|
|
|
_widgets.clear();
|
|
_pauseToken.clear();
|
|
_gui->_vm->setShake(_shakeWasEnabled);
|
|
|
|
_gui->restoreScreen();
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::copyToScreen(Graphics::Surface *s) const {
|
|
if (s) {
|
|
_from->copyRectToSurface(*s, _bounds.left, _bounds.top, Common::Rect(_bounds.width(), _bounds.height()));
|
|
}
|
|
_system->copyRectToScreen(_from->getBasePtr(_bounds.left, _bounds.top), _from->pitch, _bounds.left, _bounds.top, _bounds.width(), _bounds.height());
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::show() {
|
|
_visible = true;
|
|
|
|
copyToScreen();
|
|
_dirtyRects.clear();
|
|
_gui->_windowManager->pushCursor(Graphics::MacGUIConstants::kMacCursorArrow);
|
|
_cursorWasVisible = CursorMan.showMouse(true);
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::setFocusedWidget(int x, int y) {
|
|
int nr = findWidget(x, y);
|
|
if (nr >= 0) {
|
|
_focusedWidget = _widgets[nr];
|
|
_focusClick.x = x;
|
|
_focusClick.y = y;
|
|
_focusedWidget->getFocus();
|
|
} else
|
|
clearFocusedWidget();
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::clearFocusedWidget() {
|
|
if (_focusedWidget) {
|
|
_focusedWidget->loseFocus();
|
|
_focusedWidget = nullptr;
|
|
_focusClick.x = -1;
|
|
_focusClick.y = -1;
|
|
}
|
|
}
|
|
|
|
int MacGuiImpl::MacDialogWindow::findWidget(int x, int y) const {
|
|
for (uint i = 0; i < _widgets.size(); i++) {
|
|
if (_widgets[i]->isEnabled() && _widgets[i]->isVisible() && _widgets[i]->findWidget(x, y))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::addWidget(MacWidget *widget, MacWidgetType type) {
|
|
widget->setId(_widgets.size());
|
|
widget->setType(type);
|
|
_widgets.push_back(widget);
|
|
}
|
|
|
|
MacGuiImpl::MacButton *MacGuiImpl::MacDialogWindow::addButton(Common::Rect bounds, Common::String text, bool enabled) {
|
|
MacGuiImpl::MacButton *button = new MacButton(this, bounds, text, enabled);
|
|
addWidget(button, kWidgetButton);
|
|
return button;
|
|
}
|
|
|
|
MacGuiImpl::MacCheckbox *MacGuiImpl::MacDialogWindow::addCheckbox(Common::Rect bounds, Common::String text, bool enabled) {
|
|
MacGuiImpl::MacCheckbox *checkbox = new MacCheckbox(this, bounds, text, enabled);
|
|
addWidget(checkbox, kWidgetCheckbox);
|
|
return checkbox;
|
|
}
|
|
|
|
MacGuiImpl::MacStaticText *MacGuiImpl::MacDialogWindow::addStaticText(Common::Rect bounds, Common::String text, bool enabled, Graphics::TextAlign alignment) {
|
|
MacGuiImpl::MacStaticText *staticText = new MacStaticText(this, bounds, text, enabled, alignment);
|
|
addWidget(staticText, kWidgetStaticText);
|
|
return staticText;
|
|
}
|
|
|
|
MacGuiImpl::MacEditText *MacGuiImpl::MacDialogWindow::addEditText(Common::Rect bounds, Common::String text, bool enabled) {
|
|
MacGuiImpl::MacEditText *editText = new MacEditText(this, bounds, text, enabled);
|
|
addWidget(editText, kWidgetEditText);
|
|
return editText;
|
|
}
|
|
|
|
MacGuiImpl::MacImage *MacGuiImpl::MacDialogWindow::addIcon(int x, int y, int id, bool enabled) {
|
|
Graphics::Surface *icon = nullptr;
|
|
Graphics::Surface *mask = nullptr;
|
|
MacGuiImpl::MacImage *image = nullptr;
|
|
|
|
if (_gui->loadIcon(id, &icon, &mask)) {
|
|
image = new MacImage(this, Common::Rect(x, y, x + icon->w, y + icon->h), icon, mask, false);
|
|
addWidget(image, kWidgetIcon);
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
MacGuiImpl::MacImage *MacGuiImpl::MacDialogWindow::addPicture(Common::Rect bounds, int id, bool enabled) {
|
|
MacGuiImpl::MacImage *image = new MacImage(this, bounds, _gui->loadPict(id), nullptr, false);
|
|
addWidget(image, kWidgetImage);
|
|
return image;
|
|
}
|
|
|
|
MacGuiImpl::MacSlider *MacGuiImpl::MacDialogWindow::addSlider(int x, int y, int h, int minValue, int maxValue, int pageSize, bool enabled) {
|
|
MacGuiImpl::MacSlider *slider = new MacSlider(this, Common::Rect(x, y, x + 16, y + h), minValue, maxValue, pageSize, enabled);
|
|
addWidget(slider, kWidgetSlider);
|
|
return slider;
|
|
}
|
|
|
|
MacGuiImpl::MacImageSlider *MacGuiImpl::MacDialogWindow::addImageSlider(int backgroundId, int handleId, bool enabled, int minX, int maxX, int minValue, int maxValue, int leftMargin, int rightMargin) {
|
|
MacImage *background = (MacImage *)_widgets[backgroundId];
|
|
MacImage *handle = (MacImage *)_widgets[handleId];
|
|
|
|
background->setVisible(false);
|
|
handle->setVisible(false);
|
|
|
|
MacGuiImpl::MacImageSlider *slider = new MacImageSlider(this, background, handle, enabled, minX, maxX, minValue, maxValue, leftMargin, rightMargin);
|
|
addWidget(slider, kWidgetImageSlider);
|
|
return slider;
|
|
}
|
|
|
|
MacGuiImpl::MacImageSlider *MacGuiImpl::MacDialogWindow::addImageSlider(Common::Rect bounds, MacImage *handle, bool enabled, int minX, int maxX, int minValue, int maxValue) {
|
|
handle->setVisible(false);
|
|
|
|
MacGuiImpl::MacImageSlider *slider = new MacImageSlider(this, bounds, handle, enabled, minX, maxX, minValue, maxValue);
|
|
addWidget(slider, kWidgetImageSlider);
|
|
return slider;
|
|
}
|
|
|
|
MacGuiImpl::MacListBox *MacGuiImpl::MacDialogWindow::addListBox(Common::Rect bounds, Common::StringArray texts, bool enabled, bool contentUntouchable) {
|
|
MacGuiImpl::MacListBox *listBox = new MacListBox(this, bounds, texts, enabled, contentUntouchable);
|
|
addWidget(listBox, kWidgetListBox);
|
|
return listBox;
|
|
}
|
|
|
|
MacGuiImpl::MacPopUpMenu *MacGuiImpl::MacDialogWindow::addPopUpMenu(Common::Rect bounds, Common::String text, int textWidth, Common::StringArray texts, bool enabled) {
|
|
MacGuiImpl::MacPopUpMenu *popUpMenu = new MacPopUpMenu(this, bounds, text, textWidth, texts, enabled);
|
|
addWidget(popUpMenu, kWidgetPopUpMenu);
|
|
return popUpMenu;
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::addControl(Common::Rect bounds, uint16 controlId) {
|
|
Common::MacResManager resource;
|
|
|
|
resource.open(_gui->_resourceFile);
|
|
|
|
Common::SeekableReadStream *cntl = resource.getResource(MKTAG('C', 'N', 'T', 'L'), controlId);
|
|
if (cntl) {
|
|
byte cntlHeader[22];
|
|
|
|
cntl->read(cntlHeader, sizeof(cntlHeader));
|
|
Common::String cntlText = cntl->readPascalString();
|
|
|
|
uint16 procId = READ_BE_UINT16(cntlHeader + 16);
|
|
|
|
if ((procId & 0xFFF0) == 0x03F0) {
|
|
uint16 textWidth = READ_BE_UINT16(cntlHeader + 12);
|
|
uint16 menuId = READ_BE_UINT16(cntlHeader + 14);
|
|
|
|
Common::SeekableReadStream *menu = resource.getResource(MKTAG('M', 'E', 'N', 'U'), menuId);
|
|
if (menu) {
|
|
menu->skip(14);
|
|
menu->skip(menu->readByte());
|
|
|
|
Common::StringArray items;
|
|
|
|
while (true) {
|
|
Common::String str;
|
|
|
|
str = menu->readPascalString();
|
|
|
|
if (str.empty())
|
|
break;
|
|
|
|
items.push_back(str);
|
|
menu->skip(4);
|
|
}
|
|
|
|
delete menu;
|
|
|
|
addPopUpMenu(bounds, cntlText, textWidth, items, true);
|
|
} else
|
|
warning("MacGuiImpl::addPopUpMenu: Could not load MENU %d", menuId);
|
|
} else
|
|
warning("MacGuiImpl::addPopUpMenu: Unknown control ProcID: %d", procId);
|
|
delete cntl;
|
|
} else
|
|
warning("MacGuiImpl::addPopUpMenu: Could not load CNTL %d", controlId);
|
|
|
|
resource.close();
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::markRectAsDirty(Common::Rect r) {
|
|
_dirtyRects.push_back(r);
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::drawBeamCursor() {
|
|
int x0 = _beamCursorPos.x - _beamCursorHotspotX;
|
|
int y0 = _beamCursorPos.y - _beamCursorHotspotY;
|
|
int x1 = x0 + _beamCursor->w;
|
|
int y1 = y0 + _beamCursor->h;
|
|
|
|
Graphics::Surface *screen = _gui->surface();
|
|
|
|
_beamCursor->copyRectToSurface(*screen, 0, 0, Common::Rect(x0, y0, x1, y1));
|
|
|
|
const byte beam[] = {
|
|
0, 0, 1, 0, 5, 0, 6, 0, 2, 1, 4, 1, 3, 2, 3, 3,
|
|
3, 4, 3, 5, 3, 6, 3, 7, 3, 8, 3, 9, 3, 10, 3, 11,
|
|
3, 12, 3, 13, 2, 14, 4, 14, 0, 15, 1, 15, 5, 15, 6, 15
|
|
};
|
|
|
|
for (int i = 0; i < ARRAYSIZE(beam); i += 2) {
|
|
uint32 color = _beamCursor->getPixel(beam[i], beam[i + 1]);
|
|
|
|
color = _gui->_windowManager->inverter(color);
|
|
_beamCursor->setPixel(beam[i], beam[i + 1], color);
|
|
}
|
|
|
|
int srcX = 0;
|
|
int srcY = 0;
|
|
|
|
if (x0 < 0) {
|
|
srcX = -x0;
|
|
x0 = 0;
|
|
}
|
|
|
|
x1 = MIN<int>(x1, screen->w);
|
|
|
|
if (y0 < 0) {
|
|
srcY = -y0;
|
|
y0 = 0;
|
|
}
|
|
|
|
y1 = MIN<int>(y1, screen->h);
|
|
|
|
_system->copyRectToScreen(_beamCursor->getBasePtr(srcX, srcY), _beamCursor->pitch, x0, y0, x1 - x0, y1 - y0);
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::undrawBeamCursor() {
|
|
int x0 = _beamCursorPos.x - _beamCursorHotspotX;
|
|
int y0 = _beamCursorPos.y - _beamCursorHotspotY;
|
|
int x1 = x0 + _beamCursor->w;
|
|
int y1 = y0 + _beamCursor->h;
|
|
|
|
Graphics::Surface *screen = _gui->surface();
|
|
|
|
x0 = MAX(x0, 0);
|
|
x1 = MIN<int>(x1, screen->w);
|
|
y0 = MAX(y0, 0);
|
|
y1 = MIN<int>(y1, screen->h);
|
|
|
|
_system->copyRectToScreen(screen->getBasePtr(x0, y0), screen->pitch, x0, y0, x1 - x0, y1 - y0);
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::update(bool fullRedraw) {
|
|
if (_dirtyPalette) {
|
|
_gui->_vm->updatePalette();
|
|
_dirtyPalette = false;
|
|
}
|
|
|
|
for (uint i = 0; i < _widgets.size(); i++) {
|
|
if (_widgets[i]->isVisible())
|
|
_widgets[i]->draw();
|
|
}
|
|
|
|
if (fullRedraw) {
|
|
_dirtyRects.clear();
|
|
markRectAsDirty(Common::Rect(_innerSurface.w, _innerSurface.h));
|
|
}
|
|
|
|
for (uint i = 0; i < _dirtyRects.size(); i++) {
|
|
_system->copyRectToScreen(
|
|
_innerSurface.getBasePtr(_dirtyRects[i].left, _dirtyRects[i].top),
|
|
_innerSurface.pitch,
|
|
_bounds.left + _margin + _dirtyRects[i].left, _bounds.top + _margin + _dirtyRects[i].top,
|
|
_dirtyRects[i].width(), _dirtyRects[i].height());
|
|
}
|
|
|
|
_dirtyRects.clear();
|
|
|
|
if (_beamCursor) {
|
|
if (_beamCursorVisible)
|
|
undrawBeamCursor();
|
|
|
|
_beamCursorPos = _realMousePos;
|
|
|
|
if (_beamCursorVisible)
|
|
drawBeamCursor();
|
|
}
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::drawDottedHLine(int x0, int y, int x1) {
|
|
Graphics::Surface *s = innerSurface();
|
|
|
|
uint32 color[2];
|
|
|
|
// The dotted line is used by the default save/load dialogs as a
|
|
// separator between buttons. Surprisingly, this is only drawn using
|
|
// shades of gray in 16 color mode. Not, as you might think, in 256
|
|
// color mode.
|
|
//
|
|
// At least not in the version of MacOS that was used for reference.
|
|
|
|
if (_gui->_vm->_renderMode == Common::kRenderMacintoshBW || !(_gui->_vm->_game.features & GF_16COLOR)) {
|
|
color[0] = _black;
|
|
color[1] = _white;
|
|
} else {
|
|
color[0] = _gui->_windowManager->findBestColor(0x75, 0x75, 0x75);
|
|
color[1] = _gui->_windowManager->findBestColor(0xBE, 0xBE, 0xBE);
|
|
}
|
|
|
|
for (int x = x0; x <= x1; x++)
|
|
s->setPixel(x, y, color[x & 1]);
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::fillPattern(Common::Rect r, uint16 pattern, bool fillBlack, bool fillWhite) {
|
|
for (int y = r.top; y < r.bottom; y++) {
|
|
for (int x = r.left; x < r.right; x++) {
|
|
int bit = 0x8000 >> (4 * (y % 4) + (x % 4));
|
|
|
|
if (pattern & bit) {
|
|
if (fillBlack)
|
|
_innerSurface.setPixel(x, y, _black);
|
|
} else {
|
|
if (fillWhite)
|
|
_innerSurface.setPixel(x, y, _white);
|
|
}
|
|
}
|
|
}
|
|
|
|
markRectAsDirty(r);
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::queueEvent(MacWidget *widget, MacDialogEventType type) {
|
|
MacDialogEvent event;
|
|
|
|
event.widget = widget;
|
|
event.type = type;
|
|
|
|
_eventQueue.push(event);
|
|
}
|
|
|
|
bool MacGuiImpl::MacDialogWindow::runDialog(MacGuiImpl::MacDialogEvent &dialogEvent) {
|
|
for (uint i = 0; i < _widgets.size(); i++)
|
|
_widgets[i]->rememberValue();
|
|
|
|
// The first time the function is called, show the dialog and redraw
|
|
// all widgets completely.
|
|
|
|
if (!_visible) {
|
|
show();
|
|
|
|
for (uint i = 0; i < _widgets.size(); i++) {
|
|
if (_widgets[i]->isVisible()) {
|
|
_widgets[i]->setRedraw(true);
|
|
_widgets[i]->draw();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle all incoming events and turn them into dialog events.
|
|
|
|
bool buttonPressed = false;
|
|
uint32 nextMouseRepeat = 0;
|
|
|
|
Common::Event event;
|
|
|
|
while (_system->getEventManager()->pollEvent(event)) {
|
|
// Adjust mouse coordinates to the dialog window
|
|
|
|
if (Common::isMouseEvent(event)) {
|
|
_realMousePos.x = event.mouse.x;
|
|
_realMousePos.y = event.mouse.y;
|
|
|
|
event.mouse.x -= (_bounds.left + _margin);
|
|
event.mouse.y -= (_bounds.top + _margin);
|
|
|
|
_oldMousePos = _mousePos;
|
|
|
|
_mousePos.x = event.mouse.x;
|
|
_mousePos.y = event.mouse.y;
|
|
|
|
// Update engine mouse position
|
|
_gui->_vm->_mouse.x = _realMousePos.x / 2;
|
|
_gui->_vm->_mouse.y = _realMousePos.y / 2;
|
|
}
|
|
|
|
int w;
|
|
|
|
switch (event.type) {
|
|
case Common::EVENT_LBUTTONDOWN:
|
|
// When a widget is clicked, it becomes the focused
|
|
// widget. Focused widgets are often indicated by some
|
|
// sort of highlight, e.g. buttons become inverted.
|
|
//
|
|
// This highlight is usually only shown while the mouse
|
|
// is within the widget bounds, but as long as it
|
|
// remains focused it can regain the highlight by
|
|
// moving the cursor back into the widget bounds again.
|
|
//
|
|
// It's unclear to me if Macs should handle double
|
|
// clicks on mouse down, mouse up or both.
|
|
|
|
buttonPressed = true;
|
|
nextMouseRepeat = _system->getMillis() + 40;
|
|
setFocusedWidget(event.mouse.x, event.mouse.y);
|
|
if (_focusedWidget) {
|
|
_focusedWidget->handleMouseDown(event);
|
|
|
|
uint32 now = _system->getMillis();
|
|
bool doubleClick =
|
|
(now - _lastClickTime < 500 &&
|
|
ABS(event.mouse.x - _lastClickPos.x) < 5 &&
|
|
ABS(event.mouse.y - _lastClickPos.y) < 5);
|
|
|
|
_lastClickTime = _system->getMillis();
|
|
_lastClickPos.x = event.mouse.x;
|
|
_lastClickPos.y = event.mouse.y;
|
|
|
|
if (doubleClick && _focusedWidget->handleDoubleClick(event))
|
|
queueEvent(_focusedWidget, kDialogClick);
|
|
}
|
|
break;
|
|
|
|
case Common::EVENT_LBUTTONUP:
|
|
buttonPressed = false;
|
|
|
|
// Only the focused widget receives the button up
|
|
// event. If the widget handles the event, it produces
|
|
// a dialog click event.
|
|
|
|
if (_focusedWidget) {
|
|
MacWidget *widget = _focusedWidget;
|
|
|
|
if (widget->findWidget(event.mouse.x, event.mouse.y)) {
|
|
if (widget->handleMouseUp(event)) {
|
|
clearFocusedWidget();
|
|
queueEvent(widget, kDialogClick);
|
|
}
|
|
}
|
|
|
|
clearFocusedWidget();
|
|
}
|
|
|
|
updateCursor();
|
|
break;
|
|
|
|
case Common::EVENT_MOUSEMOVE:
|
|
// The "beam" cursor can be hidden, but will become
|
|
// visible again when the user moves the mouse.
|
|
|
|
if (_beamCursor)
|
|
_beamCursorVisible = true;
|
|
|
|
// Only the focused widget receives mouse move events,
|
|
// and then only if the mouse is within the widget's
|
|
// area of control. This are of control is usually the
|
|
// widget bounds, but widgets can redefine findWidget()
|
|
// to change this, e.g. for slider widgets.
|
|
|
|
if (_focusedWidget) {
|
|
bool wasActive = _focusedWidget->findWidget(_oldMousePos.x, _oldMousePos.y);
|
|
bool isActive = _focusedWidget->findWidget(_mousePos.x, _mousePos.y);
|
|
|
|
if (wasActive != isActive)
|
|
_focusedWidget->setRedraw();
|
|
|
|
// The widget gets mouse events while it's
|
|
// active, but also one last one when it
|
|
// becomes inactive.
|
|
|
|
if (isActive || wasActive)
|
|
_focusedWidget->handleMouseMove(event);
|
|
} else {
|
|
updateCursor();
|
|
}
|
|
|
|
break;
|
|
|
|
case Common::EVENT_WHEELUP:
|
|
if (!_gui->_vm->enhancementEnabled(kEnhUIUX) || _focusedWidget)
|
|
break;
|
|
|
|
w = findWidget(event.mouse.x, event.mouse.y);
|
|
if (w >= 0)
|
|
_widgets[w]->handleWheelUp();
|
|
|
|
break;
|
|
|
|
case Common::EVENT_WHEELDOWN:
|
|
if (!_gui->_vm->enhancementEnabled(kEnhUIUX) || _focusedWidget)
|
|
break;
|
|
|
|
w = findWidget(event.mouse.x, event.mouse.y);
|
|
if (w >= 0)
|
|
_widgets[w]->handleWheelDown();
|
|
|
|
break;
|
|
|
|
case Common::EVENT_KEYDOWN:
|
|
// Ignore keyboard while mouse is pressed
|
|
if (buttonPressed)
|
|
break;
|
|
|
|
// Handle default button
|
|
if (event.kbd.keycode == Common::KEYCODE_RETURN) {
|
|
MacWidget *widget = getDefaultWidget();
|
|
if (widget && widget->isEnabled() && widget->isVisible()) {
|
|
for (int i = 0; i < 2; i++) {
|
|
widget->setRedraw();
|
|
widget->draw(i == 0);
|
|
update();
|
|
|
|
for (int j = 0; j < 10; j++) {
|
|
_system->delayMillis(10);
|
|
_system->updateScreen();
|
|
}
|
|
}
|
|
|
|
queueEvent(widget, kDialogClick);
|
|
}
|
|
}
|
|
|
|
// Otherwise, give widgets a chance to react to key
|
|
// presses. All widgets get a chance, whether or not
|
|
// they are focused. This may be a bad idea if there
|
|
// is ever more than one edit text widget in the
|
|
// window.
|
|
//
|
|
// Typing hides the "beam" cursor.
|
|
|
|
for (uint i = 0; i < _widgets.size(); i++) {
|
|
if (_widgets[i]->isVisible() && _widgets[i]->isEnabled() && _widgets[i]->handleKeyDown(event)) {
|
|
if (_beamCursor) {
|
|
_beamCursorVisible = false;
|
|
undrawBeamCursor();
|
|
}
|
|
|
|
if (_widgets[i]->reactsToKeyDown())
|
|
queueEvent(_widgets[i], kDialogKeyDown);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// A focused widget implies that the mouse button is being held down.
|
|
// It must be active and visible, so it can receive mouse repeat
|
|
// events, e.g. for holding down scroll buttons on a slider widget.
|
|
|
|
if (_focusedWidget && _system->getMillis() > nextMouseRepeat) {
|
|
nextMouseRepeat = _system->getMillis() + 40;
|
|
_focusedWidget->handleMouseHeld();
|
|
}
|
|
|
|
for (uint i = 0; i < _widgets.size(); i++) {
|
|
if (_widgets[i]->valueHasChanged())
|
|
queueEvent(_widgets[i], kDialogValueChange);
|
|
}
|
|
|
|
if (!_eventQueue.empty()) {
|
|
dialogEvent = _eventQueue.pop();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::delayAndUpdate() {
|
|
_system->delayMillis(10);
|
|
update();
|
|
_system->updateScreen();
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::updateCursor() {
|
|
int mouseOverWidget = findWidget(_mousePos.x, _mousePos.y);
|
|
bool useBeamCursor = (mouseOverWidget >= 0 && _widgets[mouseOverWidget]->useBeamCursor());
|
|
|
|
if (useBeamCursor && !_beamCursor) {
|
|
CursorMan.showMouse(false);
|
|
_beamCursor = new Graphics::Surface();
|
|
_beamCursor->create(7, 16, Graphics::PixelFormat::createFormatCLUT8());
|
|
_beamCursorVisible = true;
|
|
_beamCursorPos = _realMousePos;
|
|
} else if (!useBeamCursor && _beamCursor) {
|
|
CursorMan.showMouse(true);
|
|
|
|
undrawBeamCursor();
|
|
_beamCursor->free();
|
|
delete _beamCursor;
|
|
_beamCursor = nullptr;
|
|
_beamCursorVisible = false;
|
|
}
|
|
}
|
|
|
|
MacGuiImpl::MacWidget *MacGuiImpl::MacDialogWindow::getWidget(uint nr) const {
|
|
if (nr < _widgets.size())
|
|
return _widgets[nr];
|
|
return nullptr;
|
|
}
|
|
|
|
MacGuiImpl::MacWidget *MacGuiImpl::MacDialogWindow::getWidget(MacWidgetType type, uint nr) const {
|
|
for (uint i = 0; i < _widgets.size(); i++) {
|
|
if (_widgets[i]->getType() == type) {
|
|
if (nr == 0)
|
|
return _widgets[i];
|
|
nr--;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::drawSprite(const MacImage *image, int x, int y) {
|
|
Graphics::Surface *dstSurface = innerSurface();
|
|
const Graphics::Surface *srcSurface = image->getImage();
|
|
const Graphics::Surface *maskSurface = image->getMask();
|
|
|
|
if (maskSurface) {
|
|
byte *dst = (byte*)dstSurface->getBasePtr(x, y);
|
|
const byte *src = (const byte *)srcSurface->getBasePtr(0, 0);
|
|
const byte *mask = (const byte *)maskSurface->getBasePtr(0, 0);
|
|
|
|
assert(srcSurface->format == dstSurface->format);
|
|
|
|
Graphics::maskBlit(dst, src, mask,
|
|
dstSurface->pitch, srcSurface->pitch, maskSurface->pitch,
|
|
srcSurface->w, srcSurface->h,
|
|
dstSurface->format.bytesPerPixel);
|
|
markRectAsDirty(Common::Rect(x, y, x + srcSurface->w, y + srcSurface->h));
|
|
} else
|
|
drawSprite(srcSurface, x, y);
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::drawSprite(const Graphics::Surface *sprite, int x, int y) {
|
|
_innerSurface.copyRectToSurface(*sprite, x, y, Common::Rect(sprite->w, sprite->h));
|
|
markRectAsDirty(Common::Rect(x, y, x + sprite->w, y + sprite->h));
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::drawSprite(const Graphics::Surface *sprite, int x, int y, Common::Rect(clipRect)) {
|
|
Common::Rect subRect(sprite->w, sprite->h);
|
|
|
|
if (x < clipRect.left) {
|
|
subRect.left += (clipRect.left - x);
|
|
x = clipRect.left;
|
|
}
|
|
|
|
if (y < clipRect.top) {
|
|
subRect.top += (clipRect.top - y);
|
|
y = clipRect.top;
|
|
}
|
|
|
|
if (x + sprite->w >= clipRect.right) {
|
|
subRect.right -= (x + sprite->w - clipRect.right);
|
|
}
|
|
|
|
if (y + sprite->h >= clipRect.bottom) {
|
|
subRect.bottom -= (y + sprite->h - clipRect.bottom);
|
|
}
|
|
|
|
if (subRect.width() > 0 && subRect.height() > 0) {
|
|
_innerSurface.copyRectToSurface(*sprite, x, y, subRect);
|
|
markRectAsDirty(Common::Rect(x, y, x + subRect.width(), y + subRect.height()));
|
|
}
|
|
}
|
|
|
|
// I don't know if the original actually used two different plot functions, one
|
|
// to fill and one to darken (used to draw over the text screens). It's such a
|
|
// subtle effect that I suspect it was just doing some different magic, maybe
|
|
// with XOR, but I couldn't get that to work by eye only.
|
|
|
|
class PatternPrimitives : public Graphics::Primitives {
|
|
void drawPoint(int x, int y, uint32 pattern, void *data) override {
|
|
const uint16 patterns[] = {
|
|
0x0000, 0x2828, 0xA5A5, 0xD7D7,
|
|
0xFFFF, 0xD7D7, 0xA5A5, 0x2828
|
|
};
|
|
|
|
MacGuiImpl::MacDialogWindow *window = (MacGuiImpl::MacDialogWindow *)data;
|
|
Graphics::Surface *s = window->innerSurface();
|
|
int bit = 0x8000 >> (4 * (y % 4) + (x % 4));
|
|
if (patterns[pattern] & bit)
|
|
s->setPixel(x, y, window->_gui->getBlack());
|
|
else
|
|
s->setPixel(x, y, window->_gui->getWhite());
|
|
}
|
|
};
|
|
|
|
class PatternDarkenOnlyPrimitives : public Graphics::Primitives {
|
|
void drawPoint(int x, int y, uint32 pattern, void *data) override {
|
|
const uint16 patterns[] = {
|
|
0x0000, 0x2828, 0xA5A5, 0xD7D7, 0xFFFF
|
|
};
|
|
|
|
MacGuiImpl::MacDialogWindow *window = (MacGuiImpl::MacDialogWindow *)data;
|
|
Graphics::Surface *s = window->innerSurface();
|
|
int bit = 0x8000 >> (4 * (y % 4) + (x % 4));
|
|
if (patterns[pattern] & bit)
|
|
s->setPixel(x, y, window->_gui->getBlack());
|
|
}
|
|
};
|
|
|
|
void MacGuiImpl::MacDialogWindow::drawPatternRoundRect(const Common::Rect &rect, int arc, uint32 color, bool filled, bool darkenOnly) {
|
|
// FIXME: This is a deprecated method, but we should replace it with
|
|
// something that matches QuickDraw's rounded rects instead.
|
|
|
|
if (darkenOnly) {
|
|
PatternDarkenOnlyPrimitives().drawRoundRect(rect, arc, color, filled, this);
|
|
} else {
|
|
PatternPrimitives().drawRoundRect(rect, arc, color, filled, this);
|
|
}
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::drawTexts(Common::Rect r, const TextLine *lines, bool inverse) {
|
|
if (!lines)
|
|
return;
|
|
|
|
uint32 fg, bg;
|
|
|
|
if (inverse) {
|
|
fg = _white;
|
|
bg = _black;
|
|
} else {
|
|
fg = _black;
|
|
bg = _white;
|
|
}
|
|
|
|
Graphics::Surface *s = innerSurface();
|
|
|
|
for (int i = 0; lines[i].str; i++) {
|
|
const Graphics::Font *f1 = nullptr;
|
|
const Graphics::Font *f2 = nullptr;
|
|
|
|
switch (lines[i].style) {
|
|
case kStyleHeader1:
|
|
f1 = _gui->getFont(kAboutFontHeaderOutside);
|
|
f2 = _gui->getFont(kAboutFontHeaderInside);
|
|
break;
|
|
|
|
case kStyleHeader2:
|
|
f1 = _gui->getFont(kAboutFontHeader);
|
|
break;
|
|
|
|
case kStyleHeaderSimple1:
|
|
f1 = _gui->getFont(kAboutFontHeaderSimple1);
|
|
break;
|
|
|
|
case kStyleHeaderSimple2:
|
|
f1 = _gui->getFont(kAboutFontHeaderSimple2);
|
|
break;
|
|
|
|
case kStyleBold:
|
|
f1 = _gui->getFont(kAboutFontBold);
|
|
break;
|
|
|
|
case kStyleBold2:
|
|
f1 = _gui->getFont(kAboutFontBold2);
|
|
break;
|
|
|
|
case kStyleExtraBold:
|
|
f1 = _gui->getFont(kAboutFontExtraBold);
|
|
break;
|
|
|
|
case kStyleRegular:
|
|
f1 = _gui->getFont(kAboutFontRegular);
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
const char *msg = lines[i].str;
|
|
int x = r.left + lines[i].x;
|
|
int y = r.top + lines[i].y;
|
|
Graphics::TextAlign align = lines[i].align;
|
|
int width = r.right - x;
|
|
|
|
switch (lines[i].style) {
|
|
case kStyleHeader1:
|
|
f1->drawString(s, msg, x - 1, y + 1, width, fg, align);
|
|
f2->drawString(s, msg, x + 1, y + 1, width, fg, align);
|
|
f1->drawString(s, msg, x - 2, y, width, fg, align);
|
|
f2->drawString(s, msg, x, y, width, bg, align);
|
|
break;
|
|
|
|
case kStyleHeader2:
|
|
f1->drawString(s, msg, x, y, width, fg, align);
|
|
f1->drawString(s, msg, x + 1, y, width, fg, align);
|
|
break;
|
|
|
|
default:
|
|
f1->drawString(s, msg, x, y, width, fg, align);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MacGuiImpl::MacDialogWindow::drawTextBox(Common::Rect r, const TextLine *lines, bool inverse, int arc) {
|
|
uint32 fg, bg;
|
|
|
|
if (inverse) {
|
|
fg = _white;
|
|
bg = _black;
|
|
} else {
|
|
fg = _black;
|
|
bg = _white;
|
|
}
|
|
|
|
innerSurface()->drawRoundRect(r, arc, bg, true);
|
|
innerSurface()->drawRoundRect(r, arc, fg, false);
|
|
markRectAsDirty(r);
|
|
|
|
drawTexts(r, lines, inverse);
|
|
}
|
|
|
|
} // End of namespace Scumm
|