aboutsummaryrefslogtreecommitdiff
path: root/engines/sword2/controls.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sword2/controls.cpp')
-rw-r--r--engines/sword2/controls.cpp1416
1 files changed, 1416 insertions, 0 deletions
diff --git a/engines/sword2/controls.cpp b/engines/sword2/controls.cpp
new file mode 100644
index 0000000000..df1b38c83e
--- /dev/null
+++ b/engines/sword2/controls.cpp
@@ -0,0 +1,1416 @@
+/* Copyright (C) 1994-1998 Revolution Software Ltd.
+ * Copyright (C) 2003-2006 The ScummVM project
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "common/stdafx.h"
+#include "common/rect.h"
+#include "common/config-manager.h"
+#include "common/system.h"
+
+#include "sword2/sword2.h"
+#include "sword2/defs.h"
+#include "sword2/controls.h"
+#include "sword2/mouse.h"
+#include "sword2/resman.h"
+#include "sword2/sound.h"
+
+#define MAX_STRING_LEN 64 // 20 was too low; better to be safe ;)
+#define CHARACTER_OVERLAP 2 // overlap characters by 3 pixels
+
+// our fonts start on SPACE character (32)
+#define SIZE_OF_CHAR_SET (256 - 32)
+
+namespace Sword2 {
+
+static int baseSlot = 0;
+
+class Widget;
+
+/**
+ * Base class for all widgets.
+ */
+
+class Widget {
+protected:
+ Sword2Engine *_vm;
+ Dialog *_parent;
+
+ SpriteInfo *_sprites;
+
+ struct WidgetSurface {
+ byte *_surface;
+ bool _original;
+ };
+
+ WidgetSurface *_surfaces;
+ int _numStates;
+ int _state;
+
+ Common::Rect _hitRect;
+
+public:
+ Widget(Dialog *parent, int states);
+
+ virtual ~Widget();
+
+ void createSurfaceImage(int state, uint32 res, int x, int y, uint32 pc);
+ void linkSurfaceImage(Widget *from, int state, int x, int y);
+
+ void createSurfaceImages(uint32 res, int x, int y);
+ void linkSurfaceImages(Widget *from, int x, int y);
+
+ void setHitRect(int x, int y, int width, int height);
+ bool isHit(int16 x, int16 y);
+
+ void setState(int state);
+ int getState();
+
+ virtual void paint(Common::Rect *clipRect = NULL);
+
+ virtual void onMouseEnter() {}
+ virtual void onMouseExit() {}
+ virtual void onMouseMove(int x, int y) {}
+ virtual void onMouseDown(int x, int y) {}
+ virtual void onMouseUp(int x, int y) {}
+ virtual void onWheelUp(int x, int y) {}
+ virtual void onWheelDown(int x, int y) {}
+ virtual void onKey(KeyboardEvent *ke) {}
+ virtual void onTick() {}
+
+ virtual void releaseMouse(int x, int y) {}
+};
+
+/**
+ * This class is used to draw text in dialogs, buttons, etc.
+ */
+
+class FontRendererGui {
+private:
+ Sword2Engine *_vm;
+
+ struct Glyph {
+ byte *_data;
+ int _width;
+ int _height;
+ };
+
+ Glyph _glyph[SIZE_OF_CHAR_SET];
+
+ int _fontId;
+
+public:
+ enum {
+ kAlignLeft,
+ kAlignRight,
+ kAlignCenter
+ };
+
+ FontRendererGui(Sword2Engine *vm, int fontId);
+ ~FontRendererGui();
+
+ void fetchText(uint32 textId, byte *buf);
+
+ int getCharWidth(byte c);
+ int getCharHeight(byte c);
+
+ int getTextWidth(byte *text);
+ int getTextWidth(uint32 textId);
+
+ void drawText(byte *text, int x, int y, int alignment = kAlignLeft);
+ void drawText(uint32 textId, int x, int y, int alignment = kAlignLeft);
+};
+
+FontRendererGui::FontRendererGui(Sword2Engine *vm, int fontId)
+ : _vm(vm), _fontId(fontId) {
+ byte *font = _vm->_resman->openResource(fontId);
+ SpriteInfo sprite;
+
+ sprite.type = RDSPR_NOCOMPRESSION | RDSPR_TRANS;
+
+ for (int i = 0; i < SIZE_OF_CHAR_SET; i++) {
+ byte *frame = _vm->fetchFrameHeader(font, i);
+
+ FrameHeader frame_head;
+
+ frame_head.read(frame);
+
+ sprite.data = frame + FrameHeader::size();
+ sprite.w = frame_head.width;
+ sprite.h = frame_head.height;
+ _vm->_screen->createSurface(&sprite, &_glyph[i]._data);
+ _glyph[i]._width = frame_head.width;
+ _glyph[i]._height = frame_head.height;
+ }
+
+ _vm->_resman->closeResource(fontId);
+}
+
+FontRendererGui::~FontRendererGui() {
+ for (int i = 0; i < SIZE_OF_CHAR_SET; i++)
+ _vm->_screen->deleteSurface(_glyph[i]._data);
+}
+
+void FontRendererGui::fetchText(uint32 textId, byte *buf) {
+ byte *data = _vm->fetchTextLine(_vm->_resman->openResource(textId / SIZE), textId & 0xffff);
+ int i;
+
+ for (i = 0; data[i + 2]; i++) {
+ if (buf)
+ buf[i] = data[i + 2];
+ }
+
+ buf[i] = 0;
+ _vm->_resman->closeResource(textId / SIZE);
+}
+
+int FontRendererGui::getCharWidth(byte c) {
+ if (c < 32)
+ return 0;
+ return _glyph[c - 32]._width;
+}
+
+int FontRendererGui::getCharHeight(byte c) {
+ if (c < 32)
+ return 0;
+ return _glyph[c - 32]._height;
+}
+
+int FontRendererGui::getTextWidth(byte *text) {
+ int textWidth = 0;
+
+ for (int i = 0; text[i]; i++)
+ if (text[i] >= ' ')
+ textWidth += (getCharWidth(text[i]) - CHARACTER_OVERLAP);
+ return textWidth;
+}
+
+int FontRendererGui::getTextWidth(uint32 textId) {
+ byte text[MAX_STRING_LEN];
+
+ fetchText(textId, text);
+ return getTextWidth(text);
+}
+
+void FontRendererGui::drawText(byte *text, int x, int y, int alignment) {
+ SpriteInfo sprite;
+ int i;
+
+ if (alignment != kAlignLeft) {
+ int textWidth = getTextWidth(text);
+
+ switch (alignment) {
+ case kAlignRight:
+ x -= textWidth;
+ break;
+ case kAlignCenter:
+ x -= (textWidth / 2);
+ break;
+ }
+ }
+
+ sprite.x = x;
+ sprite.y = y;
+
+ for (i = 0; text[i]; i++) {
+ if (text[i] >= ' ') {
+ sprite.w = getCharWidth(text[i]);
+ sprite.h = getCharHeight(text[i]);
+
+ _vm->_screen->drawSurface(&sprite, _glyph[text[i] - 32]._data);
+
+ sprite.x += (getCharWidth(text[i]) - CHARACTER_OVERLAP);
+ }
+ }
+}
+
+void FontRendererGui::drawText(uint32 textId, int x, int y, int alignment) {
+ byte text[MAX_STRING_LEN];
+
+ fetchText(textId, text);
+ drawText(text, x, y, alignment);
+}
+
+//
+// Dialog class functions
+//
+
+Dialog::Dialog(Sword2Engine *vm)
+ : _numWidgets(0), _finish(false), _result(0), _vm(vm) {
+ _vm->_screen->setFullPalette(CONTROL_PANEL_PALETTE);
+ _vm->_screen->clearScene();
+ _vm->_screen->updateDisplay();
+
+ // Usually the mouse pointer will already be "normal", but not always.
+ _vm->_mouse->setMouse(NORMAL_MOUSE_ID);
+}
+
+Dialog::~Dialog() {
+ for (int i = 0; i < _numWidgets; i++)
+ delete _widgets[i];
+ _vm->_screen->clearScene();
+ _vm->_screen->updateDisplay();
+}
+
+void Dialog::registerWidget(Widget *widget) {
+ if (_numWidgets < MAX_WIDGETS)
+ _widgets[_numWidgets++] = widget;
+}
+
+void Dialog::paint() {
+ _vm->_screen->clearScene();
+ for (int i = 0; i < _numWidgets; i++)
+ _widgets[i]->paint();
+}
+
+void Dialog::setResult(int result) {
+ _result = result;
+ _finish = true;
+}
+
+int Dialog::runModal() {
+ uint32 oldFilter = _vm->setInputEventFilter(0);
+
+ int i;
+
+ paint();
+
+ int oldMouseX = -1;
+ int oldMouseY = -1;
+
+ while (!_finish) {
+ // So that the menu icons will reach their full size
+ _vm->_mouse->processMenu();
+ _vm->_screen->updateDisplay(false);
+
+ int newMouseX, newMouseY;
+
+ _vm->_mouse->getPos(newMouseX, newMouseY);
+
+ newMouseY += 40;
+
+ MouseEvent *me = _vm->mouseEvent();
+ KeyboardEvent *ke = _vm->keyboardEvent();
+
+ if (ke) {
+ if (ke->keycode == 27)
+ setResult(0);
+ else if (ke->keycode == '\n' || ke->keycode == '\r')
+ setResult(1);
+ }
+
+ int oldHit = -1;
+ int newHit = -1;
+
+ // Find out which widget the mouse was over the last time, and
+ // which it is currently over. This assumes the widgets do not
+ // overlap.
+
+ for (i = 0; i < _numWidgets; i++) {
+ if (_widgets[i]->isHit(oldMouseX, oldMouseY))
+ oldHit = i;
+ if (_widgets[i]->isHit(newMouseX, newMouseY))
+ newHit = i;
+ }
+
+ // Was the mouse inside a widget the last time?
+
+ if (oldHit >= 0) {
+ if (newHit != oldHit)
+ _widgets[oldHit]->onMouseExit();
+ }
+
+ // Is the mouse currently in a widget?
+
+ if (newHit >= 0) {
+ if (newHit != oldHit)
+ _widgets[newHit]->onMouseEnter();
+
+ if (me) {
+ switch (me->buttons) {
+ case RD_LEFTBUTTONDOWN:
+ _widgets[newHit]->onMouseDown(newMouseX, newMouseY);
+ break;
+ case RD_LEFTBUTTONUP:
+ _widgets[newHit]->onMouseUp(newMouseX, newMouseY);
+ break;
+ case RD_WHEELUP:
+ _widgets[newHit]->onWheelUp(newMouseX, newMouseY);
+ break;
+ case RD_WHEELDOWN:
+ _widgets[newHit]->onWheelDown(newMouseX, newMouseY);
+ break;
+ }
+ }
+ }
+
+ // Some events are passed to the widgets regardless of where
+ // the mouse cursor is.
+
+ for (i = 0; i < _numWidgets; i++) {
+ if (me && me->buttons == RD_LEFTBUTTONUP) {
+ // So that slider widgets will know when the
+ // user releases the mouse button, even if the
+ // cursor is outside of the slider's hit area.
+ _widgets[i]->releaseMouse(newMouseX, newMouseY);
+ }
+
+ // This is to make it easier to drag the slider widget
+
+ if (newMouseX != oldMouseX || newMouseY != oldMouseY)
+ _widgets[i]->onMouseMove(newMouseX, newMouseY);
+
+ if (ke)
+ _widgets[i]->onKey(ke);
+
+ _widgets[i]->onTick();
+ }
+
+ oldMouseX = newMouseX;
+ oldMouseY = newMouseY;
+
+ _vm->_system->delayMillis(20);
+
+ if (_vm->_quit)
+ setResult(0);
+ }
+
+ _vm->setInputEventFilter(oldFilter);
+ return _result;
+}
+
+//
+// Widget functions
+//
+
+Widget::Widget(Dialog *parent, int states)
+ : _vm(parent->_vm), _parent(parent), _numStates(states), _state(0) {
+ _sprites = (SpriteInfo *)calloc(states, sizeof(SpriteInfo));
+ _surfaces = (WidgetSurface *)calloc(states, sizeof(WidgetSurface));
+
+ _hitRect.left = _hitRect.right = _hitRect.top = _hitRect.bottom = -1;
+}
+
+Widget::~Widget() {
+ for (int i = 0; i < _numStates; i++) {
+ if (_surfaces[i]._original)
+ _vm->_screen->deleteSurface(_surfaces[i]._surface);
+ }
+ free(_sprites);
+ free(_surfaces);
+}
+
+void Widget::createSurfaceImage(int state, uint32 res, int x, int y, uint32 pc) {
+ byte *file, *colTablePtr = NULL;
+ AnimHeader anim_head;
+ FrameHeader frame_head;
+ CdtEntry cdt_entry;
+ uint32 spriteType = RDSPR_TRANS;
+
+ // open anim resource file, point to base
+ file = _vm->_resman->openResource(res);
+
+ byte *frame = _vm->fetchFrameHeader(file, pc);
+
+ anim_head.read(_vm->fetchAnimHeader(file));
+ cdt_entry.read(_vm->fetchCdtEntry(file, pc));
+ frame_head.read(frame);
+
+ // If the frame is flipped. (Only really applicable to frames using
+ // offsets.)
+
+ if (cdt_entry.frameType & FRAME_FLIPPED)
+ spriteType |= RDSPR_FLIP;
+
+ // Which compression was used?
+
+ switch (anim_head.runTimeComp) {
+ case NONE:
+ spriteType |= RDSPR_NOCOMPRESSION;
+ break;
+ case RLE256:
+ spriteType |= RDSPR_RLE256;
+ break;
+ case RLE16:
+ spriteType |= RDSPR_RLE256;
+ // Points to just after last cdt_entry, i.e. start of colour
+ // table
+ colTablePtr = _vm->fetchAnimHeader(file) + AnimHeader::size()
+ + anim_head.noAnimFrames * CdtEntry::size();
+ break;
+ }
+
+ _sprites[state].x = x;
+ _sprites[state].y = y;
+ _sprites[state].w = frame_head.width;
+ _sprites[state].h = frame_head.height;
+ _sprites[state].scale = 0;
+ _sprites[state].type = spriteType;
+ _sprites[state].blend = anim_head.blend;
+
+ // Points to just after frame header, ie. start of sprite data
+ _sprites[state].data = frame + FrameHeader::size();
+
+ _vm->_screen->createSurface(&_sprites[state], &_surfaces[state]._surface);
+ _surfaces[state]._original = true;
+
+ // Release the anim resource
+ _vm->_resman->closeResource(res);
+}
+
+void Widget::linkSurfaceImage(Widget *from, int state, int x, int y) {
+ _sprites[state].x = x;
+ _sprites[state].y = y;
+ _sprites[state].w = from->_sprites[state].w;
+ _sprites[state].h = from->_sprites[state].h;
+ _sprites[state].scale = from->_sprites[state].scale;
+ _sprites[state].type = from->_sprites[state].type;
+ _sprites[state].blend = from->_sprites[state].blend;
+
+ _surfaces[state]._surface = from->_surfaces[state]._surface;
+ _surfaces[state]._original = false;
+}
+
+void Widget::createSurfaceImages(uint32 res, int x, int y) {
+ for (int i = 0; i < _numStates; i++)
+ createSurfaceImage(i, res, x, y, i);
+}
+
+void Widget::linkSurfaceImages(Widget *from, int x, int y) {
+ for (int i = 0; i < from->_numStates; i++)
+ linkSurfaceImage(from, i, x, y);
+}
+
+void Widget::setHitRect(int x, int y, int width, int height) {
+ _hitRect.left = x;
+ _hitRect.right = x + width;
+ _hitRect.top = y;
+ _hitRect.bottom = y + height;
+}
+
+bool Widget::isHit(int16 x, int16 y) {
+ return _hitRect.left >= 0 && _hitRect.contains(x, y);
+}
+
+void Widget::setState(int state) {
+ if (state != _state) {
+ _state = state;
+ paint();
+ }
+}
+
+int Widget::getState() {
+ return _state;
+}
+
+void Widget::paint(Common::Rect *clipRect) {
+ _vm->_screen->drawSurface(&_sprites[_state], _surfaces[_state]._surface, clipRect);
+}
+
+/**
+ * Standard button class.
+ */
+
+class Button : public Widget {
+public:
+ Button(Dialog *parent, int x, int y, int w, int h)
+ : Widget(parent, 2) {
+ setHitRect(x, y, w, h);
+ }
+
+ virtual void onMouseExit() {
+ setState(0);
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ setState(1);
+ }
+
+ virtual void onMouseUp(int x, int y) {
+ if (getState() != 0) {
+ setState(0);
+ _parent->onAction(this);
+ }
+ }
+};
+
+/**
+ * Scroll buttons are used to scroll the savegame list. The difference between
+ * this and a normal button is that we want this to repeat.
+ */
+
+class ScrollButton : public Widget {
+private:
+ uint32 _holdCounter;
+
+public:
+ ScrollButton(Dialog *parent, int x, int y, int w, int h)
+ : Widget(parent, 2), _holdCounter(0) {
+ setHitRect(x, y, w, h);
+ }
+
+ virtual void onMouseExit() {
+ setState(0);
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ setState(1);
+ _parent->onAction(this);
+ _holdCounter = 0;
+ }
+
+ virtual void onMouseUp(int x, int y) {
+ setState(0);
+ }
+
+ virtual void onTick() {
+ if (getState() != 0) {
+ _holdCounter++;
+ if (_holdCounter > 16 && (_holdCounter % 4) == 0)
+ _parent->onAction(this);
+ }
+ }
+};
+
+/**
+ * A switch is a button that changes state when clicked, and keeps that state
+ * until clicked again.
+ */
+
+class Switch : public Widget {
+private:
+ bool _holding, _value;
+ int _upState, _downState;
+
+public:
+ Switch(Dialog *parent, int x, int y, int w, int h)
+ : Widget(parent, 2), _holding(false), _value(false),
+ _upState(0), _downState(1) {
+ setHitRect(x, y, w, h);
+ }
+
+ // The sound mute switches have 0 as their "down" state and 1 as
+ // their "up" state, so this function is needed to get consistent
+ // behaviour.
+
+ void reverseStates() {
+ _upState = 1;
+ _downState = 0;
+ }
+
+ void setValue(bool value) {
+ _value = value;
+ if (_value)
+ setState(_downState);
+ else
+ setState(_upState);
+ }
+
+ bool getValue() {
+ return _value;
+ }
+
+ virtual void onMouseExit() {
+ if (_holding && !_value)
+ setState(_upState);
+ _holding = false;
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ _holding = true;
+ setState(_downState);
+ }
+
+ virtual void onMouseUp(int x, int y) {
+ if (_holding) {
+ _holding = false;
+ _value = !_value;
+ if (_value)
+ setState(_downState);
+ else
+ setState(_upState);
+ _parent->onAction(this, getState());
+ }
+ }
+};
+
+/**
+ * A slider is used to specify a value within a pre-defined range.
+ */
+
+class Slider : public Widget {
+private:
+ Widget *_background;
+ bool _dragging;
+ int _value, _targetValue;
+ int _maxValue;
+ int _valueStep;
+ int _dragOffset;
+
+ int posFromValue(int value) {
+ return _hitRect.left + (value * (_hitRect.width() - 38)) / _maxValue;
+ }
+
+ int valueFromPos(int x) {
+ return (int)((double)(_maxValue * (x - _hitRect.left)) / (double)(_hitRect.width() - 38) + 0.5);
+ }
+
+public:
+ Slider(Dialog *parent, Widget *background, int max,
+ int x, int y, int w, int h, int step, Widget *base = NULL)
+ : Widget(parent, 1), _background(background),
+ _dragging(false), _value(0), _targetValue(0),
+ _maxValue(max), _valueStep(step) {
+ setHitRect(x, y, w, h);
+
+ if (_valueStep <= 0)
+ _valueStep = 1;
+
+ if (base)
+ linkSurfaceImages(base, x, y);
+ else
+ createSurfaceImages(3406, x, y);
+ }
+
+ virtual void paint(Common::Rect *clipRect = NULL) {
+ // This will redraw a bit more than is strictly necessary,
+ // but I doubt that will make any noticeable difference.
+
+ _background->paint(&_hitRect);
+ Widget::paint(clipRect);
+ }
+
+ void setValue(int value) {
+ _value = value;
+ _targetValue = value;
+ _sprites[0].x = posFromValue(_value);
+ paint();
+ }
+
+ int getValue() {
+ return _value;
+ }
+
+ virtual void onMouseMove(int x, int y) {
+ if (_dragging) {
+ int newX = x - _dragOffset;
+ int newValue;
+
+ if (newX < _hitRect.left)
+ newX = _hitRect.left;
+ else if (newX + 38 > _hitRect.right)
+ newX = _hitRect.right - 38;
+
+ _sprites[0].x = newX;
+
+ newValue = valueFromPos(newX);
+ if (newValue != _value) {
+ _value = newValue;
+ _targetValue = newValue;
+ _parent->onAction(this, newValue);
+ }
+
+ paint();
+ }
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ if (x >= _sprites[0].x && x < _sprites[0].x + 38) {
+ _dragging = true;
+ _dragOffset = x - _sprites[0].x;
+ } else if (x < _sprites[0].x) {
+ if (_targetValue >= _valueStep)
+ _targetValue -= _valueStep;
+ else
+ _targetValue = 0;
+ } else {
+ if (_targetValue < _maxValue - _valueStep)
+ _targetValue += _valueStep;
+ else
+ _targetValue = _maxValue;
+ }
+ }
+
+ virtual void releaseMouse(int x, int y) {
+ if (_dragging)
+ _dragging = false;
+ }
+
+ virtual void onTick() {
+ if (!_dragging) {
+ int target = posFromValue(_targetValue);
+
+ if (target != _sprites[0].x) {
+ if (target < _sprites[0].x) {
+ _sprites[0].x -= 4;
+ if (_sprites[0].x < target)
+ _sprites[0].x = target;
+ } else if (target > _sprites[0].x) {
+ _sprites[0].x += 4;
+ if (_sprites[0].x > target)
+ _sprites[0].x = target;
+ }
+
+ int newValue = valueFromPos(_sprites[0].x);
+ if (newValue != _value) {
+ _value = newValue;
+ _parent->onAction(this, newValue);
+ }
+
+ paint();
+ }
+ }
+ }
+};
+
+/**
+ * The "mini" dialog.
+ */
+
+MiniDialog::MiniDialog(Sword2Engine *vm, uint32 headerTextId, uint32 okTextId, uint32 cancelTextId) : Dialog(vm) {
+ _headerTextId = headerTextId;
+ _okTextId = okTextId;
+ _cancelTextId = cancelTextId;
+
+ _fr = new FontRendererGui(_vm, _vm->_controlsFontId);
+
+ _panel = new Widget(this, 1);
+ _panel->createSurfaceImages(1996, 203, 104);
+
+ _okButton = new Button(this, 243, 214, 24, 24);
+ _okButton->createSurfaceImages(2002, 243, 214);
+
+ _cancelButton = new Button(this, 243, 276, 24, 24);
+ _cancelButton->linkSurfaceImages(_okButton, 243, 276);
+
+ registerWidget(_panel);
+ registerWidget(_okButton);
+ registerWidget(_cancelButton);
+}
+
+MiniDialog::~MiniDialog() {
+ delete _fr;
+}
+
+void MiniDialog::paint() {
+ Dialog::paint();
+
+ if (_headerTextId)
+ _fr->drawText(_headerTextId, 310, 134, FontRendererGui::kAlignCenter);
+ _fr->drawText(_okTextId, 270, 214);
+ _fr->drawText(_cancelTextId, 270, 276);
+}
+
+void MiniDialog::onAction(Widget *widget, int result) {
+ if (widget == _okButton)
+ setResult(1);
+ else if (widget == _cancelButton)
+ setResult(0);
+}
+
+StartDialog::StartDialog(Sword2Engine *vm) : MiniDialog(vm, 0) {}
+
+int StartDialog::runModal() {
+ while (1) {
+ MiniDialog startDialog(_vm, 0, TEXT_RESTART, TEXT_RESTORE);
+
+ if (startDialog.runModal())
+ return 1;
+
+ if (_vm->_quit)
+ return 0;
+
+ RestoreDialog restoreDialog(_vm);
+
+ if (restoreDialog.runModal())
+ return 0;
+
+ if (_vm->_quit)
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * The restart dialog.
+ */
+
+RestartDialog::RestartDialog(Sword2Engine *vm) : MiniDialog(vm, TEXT_RESTART) {}
+
+int RestartDialog::runModal() {
+ int result = MiniDialog::runModal();
+
+ if (result)
+ _vm->restartGame();
+
+ return result;
+}
+
+/**
+ * The quit dialog.
+ */
+
+QuitDialog::QuitDialog(Sword2Engine *vm) : MiniDialog(vm, TEXT_QUIT) {}
+
+int QuitDialog::runModal() {
+ int result = MiniDialog::runModal();
+
+ if (result)
+ _vm->closeGame();
+
+ return result;
+}
+
+/**
+ * The game settings dialog.
+ */
+
+OptionsDialog::OptionsDialog(Sword2Engine *vm) : Dialog(vm) {
+ _fr = new FontRendererGui(_vm, _vm->_controlsFontId);
+
+ _mixer = _vm->_mixer;
+
+ _panel = new Widget(this, 1);
+ _panel->createSurfaceImages(3405, 0, 40);
+
+ _objectLabelsSwitch = new Switch(this, 304, 100, 53, 32);
+ _objectLabelsSwitch->createSurfaceImages(3687, 304, 100);
+
+ _subtitlesSwitch = new Switch(this, 510, 100, 53, 32);
+ _subtitlesSwitch->linkSurfaceImages(_objectLabelsSwitch, 510, 100);
+
+ _reverseStereoSwitch = new Switch(this, 304, 293, 53, 32);
+ _reverseStereoSwitch->linkSurfaceImages(_objectLabelsSwitch, 304, 293);
+
+ _musicSwitch = new Switch(this, 516, 157, 40, 32);
+ _musicSwitch->createSurfaceImages(3315, 516, 157);
+ _musicSwitch->reverseStates();
+
+ _speechSwitch = new Switch(this, 516, 205, 40, 32);
+ _speechSwitch->linkSurfaceImages(_musicSwitch, 516, 205);
+ _speechSwitch->reverseStates();
+
+ _fxSwitch = new Switch(this, 516, 250, 40, 32);
+ _fxSwitch->linkSurfaceImages(_musicSwitch, 516, 250);
+ _fxSwitch->reverseStates();
+
+ int volStep = Audio::Mixer::kMaxMixerVolume / 10;
+
+ _musicSlider = new Slider(this, _panel, Audio::Mixer::kMaxMixerVolume, 309, 161, 170, 27, volStep);
+ _speechSlider = new Slider(this, _panel, Audio::Mixer::kMaxMixerVolume, 309, 208, 170, 27, volStep, _musicSlider);
+ _fxSlider = new Slider(this, _panel, Audio::Mixer::kMaxMixerVolume, 309, 254, 170, 27, volStep, _musicSlider);
+ _gfxSlider = new Slider(this, _panel, 3, 309, 341, 170, 27, 1, _musicSlider);
+
+ _gfxPreview = new Widget(this, 4);
+ _gfxPreview->createSurfaceImages(256, 495, 310);
+
+ _okButton = new Button(this, 203, 382, 53, 32);
+ _okButton->createSurfaceImages(901, 203, 382);
+
+ _cancelButton = new Button(this, 395, 382, 53, 32);
+ _cancelButton->linkSurfaceImages(_okButton, 395, 382);
+
+ registerWidget(_panel);
+ registerWidget(_objectLabelsSwitch);
+ registerWidget(_subtitlesSwitch);
+ registerWidget(_reverseStereoSwitch);
+ registerWidget(_musicSwitch);
+ registerWidget(_speechSwitch);
+ registerWidget(_fxSwitch);
+ registerWidget(_musicSlider);
+ registerWidget(_speechSlider);
+ registerWidget(_fxSlider);
+ registerWidget(_gfxSlider);
+ registerWidget(_gfxPreview);
+ registerWidget(_okButton);
+ registerWidget(_cancelButton);
+
+ _objectLabelsSwitch->setValue(_vm->_mouse->getObjectLabels());
+ _subtitlesSwitch->setValue(_vm->getSubtitles());
+ _reverseStereoSwitch->setValue(_vm->_sound->isReverseStereo());
+ _musicSwitch->setValue(!_vm->_sound->isMusicMute());
+ _speechSwitch->setValue(!_vm->_sound->isSpeechMute());
+ _fxSwitch->setValue(!_vm->_sound->isFxMute());
+
+ _musicSlider->setValue(_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType));
+ _speechSlider->setValue(_mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType));
+ _fxSlider->setValue(_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType));
+
+ _gfxSlider->setValue(_vm->_screen->getRenderLevel());
+ _gfxPreview->setState(_vm->_screen->getRenderLevel());
+}
+
+OptionsDialog::~OptionsDialog() {
+ delete _fr;
+}
+
+void OptionsDialog::paint() {
+ Dialog::paint();
+
+ int maxWidth = 0;
+ int width;
+
+ uint32 alignTextIds[] = {
+ TEXT_OBJECT_LABELS,
+ TEXT_MUSIC_VOLUME,
+ TEXT_SPEECH_VOLUME,
+ TEXT_FX_VOLUME,
+ TEXT_GFX_QUALITY,
+ TEXT_REVERSE_STEREO
+ };
+
+ for (int i = 0; i < ARRAYSIZE(alignTextIds); i++) {
+ width = _fr->getTextWidth(alignTextIds[i]);
+ if (width > maxWidth)
+ maxWidth = width;
+ }
+
+ _fr->drawText(TEXT_OPTIONS, 321, 55, FontRendererGui::kAlignCenter);
+ _fr->drawText(TEXT_SUBTITLES, 500, 103, FontRendererGui::kAlignRight);
+ _fr->drawText(TEXT_OBJECT_LABELS, 299 - maxWidth, 103);
+ _fr->drawText(TEXT_MUSIC_VOLUME, 299 - maxWidth, 161);
+ _fr->drawText(TEXT_SPEECH_VOLUME, 299 - maxWidth, 208);
+ _fr->drawText(TEXT_FX_VOLUME, 299 - maxWidth, 254);
+ _fr->drawText(TEXT_REVERSE_STEREO, 299 - maxWidth, 296);
+ _fr->drawText(TEXT_GFX_QUALITY, 299 - maxWidth, 341);
+ _fr->drawText(TEXT_OK, 193, 382, FontRendererGui::kAlignRight);
+ _fr->drawText(TEXT_CANCEL, 385, 382, FontRendererGui::kAlignRight);
+}
+
+void OptionsDialog::onAction(Widget *widget, int result) {
+ // Since there is music playing while the dialog is displayed we need
+ // to update music volume immediately.
+
+ if (widget == _musicSwitch) {
+ _vm->_sound->muteMusic(result != 0);
+ } else if (widget == _musicSlider) {
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, result);
+ _vm->_sound->muteMusic(result == 0);
+ _musicSwitch->setValue(result != 0);
+ } else if (widget == _speechSlider) {
+ _speechSwitch->setValue(result != 0);
+ } else if (widget == _fxSlider) {
+ _fxSwitch->setValue(result != 0);
+ } else if (widget == _gfxSlider) {
+ _gfxPreview->setState(result);
+ _vm->_screen->setRenderLevel(result);
+ } else if (widget == _okButton) {
+ // Apply the changes
+ _vm->setSubtitles(_subtitlesSwitch->getValue());
+ _vm->_mouse->setObjectLabels(_objectLabelsSwitch->getValue());
+ _vm->_sound->muteMusic(!_musicSwitch->getValue());
+ _vm->_sound->muteSpeech(!_speechSwitch->getValue());
+ _vm->_sound->muteFx(!_fxSwitch->getValue());
+ _vm->_sound->setReverseStereo(_reverseStereoSwitch->getValue());
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, _musicSlider->getValue());
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, _speechSlider->getValue());
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, _fxSlider->getValue());
+ _vm->_screen->setRenderLevel(_gfxSlider->getValue());
+
+ _vm->writeSettings();
+ setResult(1);
+ } else if (widget == _cancelButton) {
+ // Revert the changes
+ _vm->readSettings();
+ setResult(0);
+ }
+}
+
+// Slot button actions. Note that keyboard input generates positive actions
+
+enum {
+ kSelectSlot = -1,
+ kDeselectSlot = -2,
+ kWheelDown = -3,
+ kWheelUp = -4,
+ kStartEditing = -5,
+ kCursorTick = -6
+};
+
+class Slot : public Widget {
+private:
+ int _mode;
+ FontRendererGui *_fr;
+ byte _text[SAVE_DESCRIPTION_LEN];
+ bool _clickable;
+ bool _editable;
+
+public:
+ Slot(Dialog *parent, int x, int y, int w, int h)
+ : Widget(parent, 2), _clickable(false), _editable(false) {
+ setHitRect(x, y, w, h);
+ _text[0] = 0;
+ }
+
+ void setMode(int mode) {
+ _mode = mode;
+ }
+
+ void setClickable(bool clickable) {
+ _clickable = clickable;
+ }
+
+ void setEditable(bool editable) {
+ _editable = editable;
+ _vm->_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, editable);
+ }
+
+ bool isEditable() {
+ return _editable;
+ }
+
+ void setText(FontRendererGui *fr, int slot, byte *text) {
+ _fr = fr;
+ if (text)
+ sprintf((char *)_text, "%d. %s", slot, text);
+ else
+ sprintf((char *)_text, "%d. ", slot);
+ }
+
+ byte *getText() {
+ return &_text[0];
+ }
+
+ virtual void paint(Common::Rect *clipRect = NULL) {
+ Widget::paint();
+
+ // HACK: The main dialog is responsible for drawing the text
+ // when in editing mode.
+
+ if (!_editable)
+ _fr->drawText(_text, _sprites[0].x + 16, _sprites[0].y + 4 + 2 * getState());
+ }
+
+ virtual void onMouseDown(int x, int y) {
+ if (_clickable) {
+ if (getState() == 0) {
+ setState(1);
+ _parent->onAction(this, kSelectSlot);
+ if (_mode == kSaveDialog)
+ _parent->onAction(this, kStartEditing);
+ } else if (_mode == kRestoreDialog) {
+ setState(0);
+ _parent->onAction(this, kDeselectSlot);
+ }
+ }
+ }
+
+ virtual void onWheelUp(int x, int y) {
+ _parent->onAction(this, kWheelUp);
+ }
+
+ virtual void onWheelDown(int x, int y) {
+ _parent->onAction(this, kWheelDown);
+ }
+
+ virtual void onKey(KeyboardEvent *ke) {
+ if (_editable) {
+ if (ke->keycode == 8)
+ _parent->onAction(this, 8);
+ else if (ke->ascii >= ' ' && ke->ascii <= 255) {
+ // Accept the character if the font renderer
+ // has what looks like a valid glyph for it.
+ if (_fr->getCharWidth(ke->ascii))
+ _parent->onAction(this, ke->ascii);
+ }
+ }
+ }
+
+ virtual void onTick() {
+ if (_editable)
+ _parent->onAction(this, kCursorTick);
+ }
+
+ void setY(int y) {
+ for (int i = 0; i < _numStates; i++)
+ _sprites[i].y = y;
+ setHitRect(_hitRect.left, y, _hitRect.width(), _hitRect.height());
+ }
+
+ int getY() {
+ return _sprites[0].y;
+ }
+};
+
+SaveRestoreDialog::SaveRestoreDialog(Sword2Engine *vm, int mode) : Dialog(vm) {
+ int i;
+
+ _mode = mode;
+ _selectedSlot = -1;
+
+ // FIXME: The "control font" and the "red font" are currently always
+ // the same font, so one should be eliminated.
+
+ _fr1 = new FontRendererGui(_vm, _vm->_controlsFontId);
+ _fr2 = new FontRendererGui(_vm, _vm->_redFontId);
+
+ _panel = new Widget(this, 1);
+ _panel->createSurfaceImages(2016, 0, 40);
+
+ for (i = 0; i < 4; i++) {
+ _slotButton[i] = new Slot(this, 114, 0, 384, 36);
+ _slotButton[i]->createSurfaceImages(2006 + i, 114, 0);
+ _slotButton[i]->setMode(mode);
+ _slotButton[i + 4] = new Slot(this, 114, 0, 384, 36);
+ _slotButton[i + 4]->linkSurfaceImages(_slotButton[i], 114, 0);
+ _slotButton[i + 4]->setMode(mode);
+ }
+
+ updateSlots();
+
+ _zupButton = new ScrollButton(this, 516, 65, 17, 17);
+ _zupButton->createSurfaceImages(1982, 516, 65);
+
+ _upButton = new ScrollButton(this, 516, 85, 17, 17);
+ _upButton->createSurfaceImages(2067, 516, 85);
+
+ _downButton = new ScrollButton(this, 516, 329, 17, 17);
+ _downButton->createSurfaceImages(1986, 516, 329);
+
+ _zdownButton = new ScrollButton(this, 516, 350, 17, 17);
+ _zdownButton->createSurfaceImages(1988, 516, 350);
+
+ _okButton = new Button(this, 130, 377, 24, 24);
+ _okButton->createSurfaceImages(2002, 130, 377);
+
+ _cancelButton = new Button(this, 350, 377, 24, 24);
+ _cancelButton->linkSurfaceImages(_okButton, 350, 377);
+
+ registerWidget(_panel);
+
+ for (i = 0; i < 8; i++)
+ registerWidget(_slotButton[i]);
+
+ registerWidget(_zupButton);
+ registerWidget(_upButton);
+ registerWidget(_downButton);
+ registerWidget(_zdownButton);
+ registerWidget(_okButton);
+ registerWidget(_cancelButton);
+}
+
+SaveRestoreDialog::~SaveRestoreDialog() {
+ delete _fr1;
+ delete _fr2;
+}
+
+// There aren't really a hundred different button objects of course, there are
+// only eight. Re-arrange them to simulate scrolling.
+
+void SaveRestoreDialog::updateSlots() {
+ for (int i = 0; i < 8; i++) {
+ Slot *slot = _slotButton[(baseSlot + i) % 8];
+ FontRendererGui *fr;
+ byte description[SAVE_DESCRIPTION_LEN];
+
+ slot->setY(72 + i * 36);
+
+ if (baseSlot + i == _selectedSlot) {
+ slot->setEditable(_mode == kSaveDialog);
+ slot->setState(1);
+ fr = _fr2;
+ } else {
+ slot->setEditable(false);
+ slot->setState(0);
+ fr = _fr1;
+ }
+
+ if (_vm->getSaveDescription(baseSlot + i, description) == SR_OK) {
+ slot->setText(fr, baseSlot + i, description);
+ slot->setClickable(true);
+ } else {
+ slot->setText(fr, baseSlot + i, NULL);
+ slot->setClickable(_mode == kSaveDialog);
+ }
+
+ if (slot->isEditable())
+ drawEditBuffer(slot);
+ else
+ slot->paint();
+ }
+}
+
+void SaveRestoreDialog::drawEditBuffer(Slot *slot) {
+ if (_selectedSlot == -1)
+ return;
+
+ // This will redraw a bit more than is strictly necessary, but I doubt
+ // that will make any noticeable difference.
+
+ slot->paint();
+ _fr2->drawText(_editBuffer, 130, 78 + (_selectedSlot - baseSlot) * 36);
+}
+
+void SaveRestoreDialog::onAction(Widget *widget, int result) {
+ if (widget == _zupButton) {
+ if (baseSlot > 0) {
+ if (baseSlot >= 8)
+ baseSlot -= 8;
+ else
+ baseSlot = 0;
+ updateSlots();
+ }
+ } else if (widget == _upButton) {
+ if (baseSlot > 0) {
+ baseSlot--;
+ updateSlots();
+ }
+ } else if (widget == _downButton) {
+ if (baseSlot < 92) {
+ baseSlot++;
+ updateSlots();
+ }
+ } else if (widget == _zdownButton) {
+ if (baseSlot < 92) {
+ if (baseSlot <= 84)
+ baseSlot += 8;
+ else
+ baseSlot = 92;
+ updateSlots();
+ }
+ } else if (widget == _okButton) {
+ setResult(1);
+ } else if (widget == _cancelButton) {
+ setResult(0);
+ } else {
+ Slot *slot = (Slot *)widget;
+ int textWidth;
+ byte tmp;
+ int i;
+ int j;
+
+ switch (result) {
+ case kWheelUp:
+ onAction(_upButton);
+ break;
+ case kWheelDown:
+ onAction(_downButton);
+ break;
+ case kSelectSlot:
+ case kDeselectSlot:
+ if (result == kSelectSlot)
+ _selectedSlot = baseSlot + (slot->getY() - 72) / 35;
+ else if (result == kDeselectSlot)
+ _selectedSlot = -1;
+
+ for (i = 0; i < 8; i++)
+ if (widget == _slotButton[i])
+ break;
+
+ for (j = 0; j < 8; j++) {
+ if (j != i) {
+ _slotButton[j]->setEditable(false);
+ _slotButton[j]->setState(0);
+ }
+ }
+ break;
+ case kStartEditing:
+ if (_selectedSlot >= 10)
+ _firstPos = 5;
+ else
+ _firstPos = 4;
+
+ strcpy((char *)_editBuffer, (char *)slot->getText());
+ _editPos = strlen((char *)_editBuffer);
+ _cursorTick = 0;
+ _editBuffer[_editPos] = '_';
+ _editBuffer[_editPos + 1] = 0;
+ slot->setEditable(true);
+ drawEditBuffer(slot);
+ break;
+ case kCursorTick:
+ _cursorTick++;
+ if (_cursorTick == 7) {
+ _editBuffer[_editPos] = ' ';
+ drawEditBuffer(slot);
+ } else if (_cursorTick == 14) {
+ _cursorTick = 0;
+ _editBuffer[_editPos] = '_';
+ drawEditBuffer(slot);
+ }
+ break;
+ case 8:
+ if (_editPos > _firstPos) {
+ _editBuffer[_editPos - 1] = _editBuffer[_editPos];
+ _editBuffer[_editPos--] = 0;
+ drawEditBuffer(slot);
+ }
+ break;
+ default:
+ tmp = _editBuffer[_editPos];
+ _editBuffer[_editPos] = 0;
+ textWidth = _fr2->getTextWidth(_editBuffer);
+ _editBuffer[_editPos] = tmp;
+
+ if (textWidth < 340 && _editPos < SAVE_DESCRIPTION_LEN - 2) {
+ _editBuffer[_editPos + 1] = _editBuffer[_editPos];
+ _editBuffer[_editPos + 2] = 0;
+ _editBuffer[_editPos++] = result;
+ drawEditBuffer(slot);
+ }
+ break;
+ }
+ }
+}
+
+void SaveRestoreDialog::paint() {
+ Dialog::paint();
+
+ _fr1->drawText((_mode == kRestoreDialog) ? TEXT_RESTORE : TEXT_SAVE, 165, 377);
+ _fr1->drawText(TEXT_CANCEL, 382, 377);
+}
+
+void SaveRestoreDialog::setResult(int result) {
+ if (result) {
+ if (_selectedSlot == -1)
+ return;
+
+ if (_mode == kSaveDialog) {
+ if (_editPos <= _firstPos)
+ return;
+ }
+ }
+
+ Dialog::setResult(result);
+}
+
+int SaveRestoreDialog::runModal() {
+ int result = Dialog::runModal();
+
+ if (result) {
+ switch (_mode) {
+ case kSaveDialog:
+ // Remove the cursor character from the savegame name
+ _editBuffer[_editPos] = 0;
+
+ if (_vm->saveGame(_selectedSlot, (byte *)&_editBuffer[_firstPos]) != SR_OK)
+ result = 0;
+ break;
+ case kRestoreDialog:
+ if (_vm->restoreGame(_selectedSlot) != SR_OK)
+ result = 0;
+ break;
+ }
+ }
+
+ return result;
+}
+
+} // End of namespace Sword2