diff options
Diffstat (limited to 'engines/sword2/controls.cpp')
-rw-r--r-- | engines/sword2/controls.cpp | 1416 |
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 |