mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-22 09:49:11 +00:00
569 lines
14 KiB
C++
569 lines
14 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.
|
|
*
|
|
* Additional copyright for this file:
|
|
* Copyright (C) 1995-1997 Presto Studios, Inc.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include "common/macresman.h"
|
|
#include "common/stream.h"
|
|
|
|
#include "pegasus/elements.h"
|
|
#include "pegasus/graphics.h"
|
|
#include "pegasus/surface.h"
|
|
|
|
namespace Pegasus {
|
|
|
|
DisplayElement::DisplayElement(const DisplayElementID id) : IDObject(id) {
|
|
_elementIsDisplaying = false;
|
|
_elementIsVisible = false;
|
|
_elementOrder = 0;
|
|
_triggeredElement = this;
|
|
_nextElement = 0;
|
|
}
|
|
|
|
DisplayElement::~DisplayElement() {
|
|
if (isDisplaying())
|
|
((PegasusEngine *)g_engine)->_gfx->removeDisplayElement(this);
|
|
}
|
|
|
|
void DisplayElement::setDisplayOrder(const DisplayOrder order) {
|
|
if (_elementOrder != order) {
|
|
_elementOrder = order;
|
|
if (isDisplaying()) {
|
|
((PegasusEngine *)g_engine)->_gfx->removeDisplayElement(this);
|
|
((PegasusEngine *)g_engine)->_gfx->addDisplayElement(this);
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DisplayElement::startDisplaying() {
|
|
if (!isDisplaying()) {
|
|
((PegasusEngine *)g_engine)->_gfx->addDisplayElement(this);
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
|
|
void DisplayElement::stopDisplaying() {
|
|
if (isDisplaying()) {
|
|
triggerRedraw();
|
|
((PegasusEngine *)g_engine)->_gfx->removeDisplayElement(this);
|
|
}
|
|
}
|
|
|
|
void DisplayElement::setBounds(const CoordType left, const CoordType top, const CoordType right, const CoordType bottom) {
|
|
setBounds(Common::Rect(left, top, right, bottom));
|
|
}
|
|
|
|
void DisplayElement::getBounds(Common::Rect &r) const {
|
|
r = _bounds;
|
|
}
|
|
|
|
void DisplayElement::sizeElement(const CoordType h, const CoordType v) {
|
|
Common::Rect newBounds = _bounds;
|
|
newBounds.right = _bounds.left + h;
|
|
newBounds.bottom = _bounds.top + v;
|
|
setBounds(newBounds);
|
|
}
|
|
|
|
void DisplayElement::moveElementTo(const CoordType h, const CoordType v) {
|
|
Common::Rect newBounds = _bounds;
|
|
newBounds.moveTo(h, v);
|
|
setBounds(newBounds);
|
|
}
|
|
|
|
void DisplayElement::moveElement(const CoordType dh, const CoordType dv) {
|
|
Common::Rect newBounds = _bounds;
|
|
newBounds.translate(dh, dv);
|
|
setBounds(newBounds);
|
|
}
|
|
|
|
void DisplayElement::getLocation(CoordType &h, CoordType &v) const {
|
|
h = _bounds.left;
|
|
v = _bounds.top;
|
|
}
|
|
|
|
void DisplayElement::centerElementAt(const CoordType h, const CoordType v) {
|
|
Common::Rect newBounds = _bounds;
|
|
newBounds.moveTo(h - (_bounds.width() / 2), v - (_bounds.height() / 2));
|
|
setBounds(newBounds);
|
|
}
|
|
|
|
void DisplayElement::getCenter(CoordType &h, CoordType &v) const {
|
|
h = (_bounds.left + _bounds.right) / 2;
|
|
v = (_bounds.top + _bounds.bottom) / 2;
|
|
}
|
|
|
|
void DisplayElement::setBounds(const Common::Rect &r) {
|
|
if (r != _bounds) {
|
|
triggerRedraw();
|
|
_bounds = r;
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
|
|
void DisplayElement::hide() {
|
|
if (_elementIsVisible) {
|
|
triggerRedraw();
|
|
_elementIsVisible = false;
|
|
}
|
|
}
|
|
|
|
void DisplayElement::show() {
|
|
if (!_elementIsVisible) {
|
|
_elementIsVisible = true;
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
|
|
// Only invalidates this element's bounding rectangle if all these conditions are true:
|
|
// -- The triggered element is this element.
|
|
// -- The element is displaying on the display list.
|
|
// -- The element is visible.
|
|
// -- The element is part of the active layer OR is one of the reserved items.
|
|
void DisplayElement::triggerRedraw() {
|
|
GraphicsManager *gfx = ((PegasusEngine *)g_engine)->_gfx;
|
|
|
|
if (_triggeredElement == this) {
|
|
if (validToDraw(gfx->getBackOfActiveLayer(), gfx->getFrontOfActiveLayer()))
|
|
gfx->invalRect(_bounds);
|
|
} else {
|
|
_triggeredElement->triggerRedraw();
|
|
}
|
|
}
|
|
|
|
void DisplayElement::setTriggeredElement(DisplayElement *element) {
|
|
if (element)
|
|
_triggeredElement = element;
|
|
else
|
|
_triggeredElement = this;
|
|
}
|
|
|
|
bool DisplayElement::validToDraw(DisplayOrder backLayer, DisplayOrder frontLayer) {
|
|
return isDisplaying() && _elementIsVisible &&
|
|
(getObjectID() <= kHighestReservedElementID ||
|
|
(getDisplayOrder() >= backLayer &&
|
|
getDisplayOrder() <= frontLayer));
|
|
}
|
|
|
|
DropHighlight::DropHighlight(const DisplayElementID id) : DisplayElement(id) {
|
|
_highlightColor = 0;
|
|
_thickness = 2;
|
|
_cornerDiameter = 0;
|
|
}
|
|
|
|
void DropHighlight::draw(const Common::Rect &) {
|
|
Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea();
|
|
|
|
// Since this is only used in two different ways, I'm only
|
|
// going to implement it in those two ways. Deal with it.
|
|
|
|
Common::Rect rect = _bounds;
|
|
rect.grow(-_thickness);
|
|
screen->frameRect(rect, _highlightColor);
|
|
rect.grow(1);
|
|
screen->frameRect(rect, _highlightColor);
|
|
|
|
if (_cornerDiameter == 8 && _thickness == 4) {
|
|
rect.grow(1);
|
|
screen->frameRect(rect, _highlightColor);
|
|
screen->hLine(rect.left + 1, rect.top - 1, rect.right - 2, _highlightColor);
|
|
screen->hLine(rect.left + 1, rect.bottom, rect.right - 2, _highlightColor);
|
|
screen->vLine(rect.left - 1, rect.top + 1, rect.bottom - 2, _highlightColor);
|
|
screen->vLine(rect.right, rect.top + 1, rect.bottom - 2, _highlightColor);
|
|
}
|
|
}
|
|
|
|
IdlerAnimation::IdlerAnimation(const DisplayElementID id) : Animation(id) {
|
|
_lastTime = 0xffffffff;
|
|
}
|
|
|
|
void IdlerAnimation::startDisplaying() {
|
|
if (!isDisplaying()) {
|
|
Animation::startDisplaying();
|
|
startIdling();
|
|
}
|
|
}
|
|
|
|
void IdlerAnimation::stopDisplaying() {
|
|
if (isDisplaying()) {
|
|
Animation::stopDisplaying();
|
|
stopIdling();
|
|
}
|
|
}
|
|
|
|
void IdlerAnimation::useIdleTime() {
|
|
uint32 currentTime = getTime();
|
|
|
|
if (currentTime != _lastTime) {
|
|
_lastTime = currentTime;
|
|
timeChanged(_lastTime);
|
|
}
|
|
}
|
|
|
|
void IdlerAnimation::timeChanged(const TimeValue) {
|
|
triggerRedraw();
|
|
}
|
|
|
|
FrameSequence::FrameSequence(const DisplayElementID id) : IdlerAnimation(id) {
|
|
_duration = 0;
|
|
_currentFrameNum = 0;
|
|
_resFork = new Common::MacResManager();
|
|
_numFrames = 0;
|
|
}
|
|
|
|
FrameSequence::~FrameSequence() {
|
|
delete _resFork;
|
|
}
|
|
|
|
void FrameSequence::useFileName(const Common::String &fileName) {
|
|
_resFork->open(fileName);
|
|
}
|
|
|
|
void FrameSequence::openFrameSequence() {
|
|
if (!_resFork->hasResFork())
|
|
return;
|
|
|
|
Common::SeekableReadStream *res = _resFork->getResource(MKTAG('P', 'F', 'r', 'm'), 0x80);
|
|
|
|
if (!res)
|
|
return;
|
|
|
|
uint32 scale = res->readUint32BE();
|
|
_bounds.top = res->readUint16BE();
|
|
_bounds.left = res->readUint16BE();
|
|
_bounds.bottom = res->readUint16BE();
|
|
_bounds.right = res->readUint16BE();
|
|
_numFrames = res->readUint16BE();
|
|
_duration = 0;
|
|
|
|
_frameTimes.clear();
|
|
for (uint32 i = 0; i < _numFrames; i++) {
|
|
TimeValue time = res->readUint32BE();
|
|
_frameTimes.push_back(_duration);
|
|
_duration += time;
|
|
}
|
|
|
|
setScale(scale);
|
|
setSegment(0, _duration);
|
|
setTime(0);
|
|
_currentFrameNum = 0;
|
|
newFrame(_currentFrameNum);
|
|
triggerRedraw();
|
|
|
|
delete res;
|
|
}
|
|
|
|
void FrameSequence::closeFrameSequence() {
|
|
stop();
|
|
_resFork->close();
|
|
_duration = 0;
|
|
_numFrames = 0;
|
|
_frameTimes.clear();
|
|
}
|
|
|
|
void FrameSequence::timeChanged(const TimeValue time) {
|
|
int16 frameNum = 0;
|
|
for (int16 i = _numFrames - 1; i >= 0; i--) {
|
|
if (_frameTimes[i] < time) {
|
|
frameNum = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (frameNum != _currentFrameNum) {
|
|
_currentFrameNum = frameNum;
|
|
newFrame(_currentFrameNum);
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
|
|
void FrameSequence::setFrameNum(const int16 frameNum) {
|
|
int16 f = CLIP<int>(frameNum, 0, _numFrames);
|
|
|
|
if (_currentFrameNum != f) {
|
|
_currentFrameNum = f;
|
|
setTime(_frameTimes[f]);
|
|
newFrame(f);
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
|
|
bool FrameSequence::isSequenceOpen() const {
|
|
return _numFrames != 0;
|
|
}
|
|
|
|
Sprite::Sprite(const DisplayElementID id) : DisplayElement(id) {
|
|
_numFrames = 0;
|
|
_currentFrameNum = 0xffffffff;
|
|
_currentFrame = 0;
|
|
}
|
|
|
|
Sprite::~Sprite() {
|
|
discardFrames();
|
|
}
|
|
|
|
void Sprite::discardFrames() {
|
|
if (!_frameArray.empty()) {
|
|
for (uint32 i = 0; i < _numFrames; i++) {
|
|
SpriteFrame *frame = _frameArray[i].frame;
|
|
frame->_referenceCount--;
|
|
if (frame->_referenceCount == 0)
|
|
delete frame;
|
|
}
|
|
|
|
_frameArray.clear();
|
|
_numFrames = 0;
|
|
_currentFrame = 0;
|
|
_currentFrameNum = 0xffffffff;
|
|
setBounds(0, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
void Sprite::addPICTResourceFrame(const ResIDType pictID, bool transparent, const CoordType left, const CoordType top) {
|
|
SpriteFrame *frame = new SpriteFrame();
|
|
frame->initFromPICTResource(((PegasusEngine *)g_engine)->_resFork, pictID, transparent);
|
|
addFrame(frame, left, top);
|
|
}
|
|
|
|
uint32 Sprite::addFrame(SpriteFrame *frame, const CoordType left, const CoordType top) {
|
|
SpriteFrameRec frameRecord;
|
|
frameRecord.frame = frame;
|
|
frameRecord.frameLeft = left;
|
|
frameRecord.frameTop = top;
|
|
_frameArray.push_back(frameRecord);
|
|
_numFrames++;
|
|
frame->_referenceCount++;
|
|
|
|
Common::Rect frameBounds;
|
|
frame->getSurfaceBounds(frameBounds);
|
|
|
|
// 9/3/96
|
|
// BB Should this be + left or - left?
|
|
frameBounds.moveTo(_bounds.left + left, _bounds.top + top);
|
|
|
|
frameBounds.extend(_bounds);
|
|
|
|
if (_bounds != frameBounds)
|
|
setBounds(frameBounds);
|
|
|
|
return _numFrames - 1;
|
|
}
|
|
|
|
void Sprite::removeFrame(const uint32 frameNum) {
|
|
_frameArray[frameNum].frame->_referenceCount--;
|
|
if (_frameArray[frameNum].frame->_referenceCount == 0)
|
|
delete _frameArray[frameNum].frame;
|
|
|
|
// Calculate the new bounds
|
|
Common::Rect frameBounds;
|
|
for (uint32 i = 0; i < _numFrames; i++) {
|
|
if (i == frameNum)
|
|
continue;
|
|
|
|
Common::Rect r;
|
|
_frameArray[i].frame->getSurfaceBounds(r);
|
|
r.translate(_frameArray[i].frameLeft, _frameArray[i].frameTop);
|
|
frameBounds.extend(r);
|
|
}
|
|
|
|
_frameArray.remove_at(frameNum);
|
|
|
|
frameBounds.moveTo(_bounds.left, _bounds.top);
|
|
setBounds(frameBounds);
|
|
|
|
if (_currentFrameNum == frameNum)
|
|
triggerRedraw();
|
|
else if (_currentFrameNum != 0xffffffff && _currentFrameNum > frameNum)
|
|
--_currentFrameNum;
|
|
}
|
|
|
|
void Sprite::setCurrentFrameIndex(const int32 frameNum) {
|
|
if (frameNum < 0) {
|
|
if (_currentFrameNum != 0xffffffff) {
|
|
_currentFrameNum = 0xffffffff;
|
|
_currentFrame = 0;
|
|
triggerRedraw();
|
|
}
|
|
} else if (_numFrames > 0) {
|
|
uint32 f = frameNum % _numFrames;
|
|
if (f != _currentFrameNum) {
|
|
_currentFrameNum = f;
|
|
_currentFrame = &_frameArray[f];
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
SpriteFrame *Sprite::getFrame(const int32 index) {
|
|
if (index < 0 || (uint32)index >= _numFrames)
|
|
return 0;
|
|
|
|
return _frameArray[index].frame;
|
|
}
|
|
|
|
void Sprite::draw(const Common::Rect &r) {
|
|
if (_currentFrame) {
|
|
Common::Rect frameBounds;
|
|
_currentFrame->frame->getSurfaceBounds(frameBounds);
|
|
|
|
frameBounds.translate(_bounds.left + _currentFrame->frameLeft, _bounds.top + _currentFrame->frameTop);
|
|
Common::Rect r1 = frameBounds.findIntersectingRect(r);
|
|
|
|
Common::Rect r2 = frameBounds;
|
|
r2.translate(-_bounds.left - _currentFrame->frameLeft, -_bounds.top - _currentFrame->frameTop);
|
|
|
|
_currentFrame->frame->drawImage(r2, r1);
|
|
}
|
|
}
|
|
|
|
SpriteSequence::SpriteSequence(const DisplayElementID id, const DisplayElementID spriteID) :
|
|
FrameSequence(id), _sprite(spriteID), _transparent(false) {
|
|
}
|
|
|
|
void SpriteSequence::openFrameSequence() {
|
|
if (!isSequenceOpen()) {
|
|
FrameSequence::openFrameSequence();
|
|
|
|
if (isSequenceOpen()) {
|
|
uint32 numFrames = getNumFrames();
|
|
|
|
for (uint32 i = 0; i < numFrames; ++i) {
|
|
SpriteFrame *frame = new SpriteFrame();
|
|
frame->initFromPICTResource(_resFork, i + 0x80, _transparent);
|
|
_sprite.addFrame(frame, 0, 0);
|
|
}
|
|
|
|
_sprite.setBounds(_bounds);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SpriteSequence::closeFrameSequence() {
|
|
if (isSequenceOpen()) {
|
|
FrameSequence::closeFrameSequence();
|
|
_sprite.discardFrames();
|
|
}
|
|
}
|
|
|
|
void SpriteSequence::setBounds(const Common::Rect &bounds) {
|
|
FrameSequence::setBounds(bounds);
|
|
_sprite.setBounds(_bounds);
|
|
}
|
|
|
|
void SpriteSequence::draw(const Common::Rect &r) {
|
|
_sprite.draw(r);
|
|
}
|
|
|
|
void SpriteSequence::newFrame(const uint16 frame) {
|
|
_sprite.setCurrentFrameIndex(frame);
|
|
}
|
|
|
|
#define DRAW_PIXEL() \
|
|
if (bytesPerPixel == 2) \
|
|
*((uint16 *)dst) = black; \
|
|
else \
|
|
*((uint32 *)dst) = black; \
|
|
dst += bytesPerPixel
|
|
|
|
#define SKIP_PIXEL() \
|
|
dst += bytesPerPixel
|
|
|
|
void ScreenDimmer::draw(const Common::Rect &r) {
|
|
// We're going to emulate QuickDraw's srcOr+gray mode here
|
|
// In this mode, every other y column is all black (odd-columns).
|
|
// Basically, every row does three black and then one transparent
|
|
// repeatedly.
|
|
|
|
// The output is identical to the original
|
|
|
|
uint32 black = g_system->getScreenFormat().RGBToColor(0, 0, 0);
|
|
Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea();
|
|
byte bytesPerPixel = g_system->getScreenFormat().bytesPerPixel;
|
|
|
|
// We're currently doing it to the whole screen to simplify the code
|
|
|
|
for (int y = 0; y < 480; y++) {
|
|
byte *dst = (byte *)screen->getBasePtr(0, y);
|
|
|
|
for (int x = 0; x < 640; x += 4) {
|
|
if (y & 1) {
|
|
DRAW_PIXEL();
|
|
DRAW_PIXEL();
|
|
SKIP_PIXEL();
|
|
DRAW_PIXEL();
|
|
} else {
|
|
SKIP_PIXEL();
|
|
DRAW_PIXEL();
|
|
DRAW_PIXEL();
|
|
DRAW_PIXEL();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef DRAW_PIXEL
|
|
#undef SKIP_PIXEL
|
|
|
|
SoundLevel::SoundLevel(const DisplayElementID id) : DisplayElement(id) {
|
|
_soundLevel = 0;
|
|
}
|
|
|
|
void SoundLevel::incrementLevel() {
|
|
if (_soundLevel < 12) {
|
|
_soundLevel++;
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
|
|
void SoundLevel::decrementLevel() {
|
|
if (_soundLevel > 0) {
|
|
_soundLevel--;
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
|
|
uint16 SoundLevel::getSoundLevel() {
|
|
return CLIP<int>(_soundLevel * 22, 0, 256);
|
|
}
|
|
|
|
void SoundLevel::setSoundLevel(uint16 level) {
|
|
uint16 newLevel = (level + 21) / 22;
|
|
|
|
if (newLevel != _soundLevel) {
|
|
_soundLevel = newLevel;
|
|
triggerRedraw();
|
|
}
|
|
}
|
|
|
|
void SoundLevel::draw(const Common::Rect &r) {
|
|
Common::Rect levelRect(_bounds.right + (8 * (_soundLevel - 12)), _bounds.top, _bounds.right, _bounds.bottom);
|
|
levelRect = r.findIntersectingRect(levelRect);
|
|
|
|
if (!levelRect.isEmpty()) {
|
|
Graphics::Surface *screen = ((PegasusEngine *)g_engine)->_gfx->getWorkArea();
|
|
screen->fillRect(levelRect, g_system->getScreenFormat().RGBToColor(0, 0, 0));
|
|
}
|
|
}
|
|
|
|
} // End of namespace Pegasus
|