810 lines
27 KiB
C++
810 lines
27 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 "director/director.h"
|
|
#include "director/movie.h"
|
|
#include "director/score.h"
|
|
#include "director/cast.h"
|
|
#include "director/channel.h"
|
|
#include "director/picture.h"
|
|
#include "director/sprite.h"
|
|
#include "director/types.h"
|
|
#include "director/window.h"
|
|
#include "director/castmember/castmember.h"
|
|
#include "director/castmember/bitmap.h"
|
|
#include "director/castmember/digitalvideo.h"
|
|
#include "director/castmember/filmloop.h"
|
|
#include "director/castmember/movie.h"
|
|
|
|
#include "graphics/macgui/mactext.h"
|
|
#include "graphics/macgui/macbutton.h"
|
|
|
|
namespace Director {
|
|
|
|
Channel::Channel(Score *sc, Sprite *sp, int priority) {
|
|
_score = sc;
|
|
if (!sp)
|
|
_sprite = nullptr;
|
|
else
|
|
_sprite = new Sprite(*sp);
|
|
|
|
_widget = nullptr;
|
|
_constraint = 0;
|
|
_mask = nullptr;
|
|
|
|
_priority = priority;
|
|
|
|
_movieRate = 0.0;
|
|
_movieTime = 0;
|
|
_startTime = 0;
|
|
_stopTime = 0;
|
|
|
|
_filmLoopFrame = 0;
|
|
|
|
_visible = true;
|
|
_dirty = true;
|
|
|
|
if (sp) {
|
|
_startFrame = sp->_spriteInfo.startFrame;
|
|
_endFrame = sp->_spriteInfo.endFrame;
|
|
} else {
|
|
_startFrame = -1;
|
|
_endFrame = -1;
|
|
}
|
|
}
|
|
|
|
Channel::Channel(const Channel &channel) {
|
|
*this = channel;
|
|
}
|
|
|
|
Channel& Channel::operator=(const Channel &channel) {
|
|
_score = channel._score;
|
|
_sprite = channel._sprite ? new Sprite(*channel._sprite) : nullptr;
|
|
|
|
_widget = nullptr;
|
|
_constraint = channel._constraint;
|
|
_mask = nullptr;
|
|
|
|
_priority = channel._priority;
|
|
|
|
_movieRate = channel._movieRate;
|
|
_movieTime = channel._movieTime;
|
|
_startTime = channel._startTime;
|
|
_stopTime = channel._stopTime;
|
|
|
|
_filmLoopFrame = channel._filmLoopFrame;
|
|
|
|
_visible = channel._visible;
|
|
_dirty = channel._dirty;
|
|
|
|
_startFrame = channel._startFrame;
|
|
_endFrame = channel._endFrame;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
Channel::~Channel() {
|
|
if (_widget) {
|
|
delete _widget;
|
|
}
|
|
|
|
if (_mask)
|
|
delete _mask;
|
|
if (_sprite)
|
|
delete _sprite;
|
|
}
|
|
|
|
DirectorPlotData Channel::getPlotData() {
|
|
int blend = (_sprite->_thickness & kTHasBlend) || _sprite->_ink == kInkTypeBlend ? _sprite->_blendAmount : 0;
|
|
DirectorPlotData pd(g_director, _sprite->_spriteType, _sprite->_ink, blend, _sprite->getBackColor(), _sprite->getForeColor());
|
|
pd.colorWhite = g_director->getColorWhite();
|
|
pd.colorBlack = g_director->getColorBlack();
|
|
pd.dst = nullptr;
|
|
|
|
pd.srf = getSurface();
|
|
if (_sprite->_spriteType == kBitmapSprite &&
|
|
_sprite->_cast && _sprite->_cast->_type == kCastBitmap &&
|
|
((BitmapCastMember *)_sprite->_cast)->_bitsPerPixel == 1) {
|
|
// Add override flag for 1-bit images
|
|
pd.oneBitImage = true;
|
|
}
|
|
if (!pd.srf && _sprite->_spriteType != kBitmapSprite) {
|
|
// Shapes come colourized from macDrawPixel
|
|
pd.ms = _sprite->getShape();
|
|
pd.applyColor = false;
|
|
} else {
|
|
pd.setApplyColor();
|
|
}
|
|
|
|
return pd;
|
|
}
|
|
|
|
Graphics::ManagedSurface *Channel::getSurface() {
|
|
if (_widget) {
|
|
return _widget->getSurface();
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
const Graphics::Surface *Channel::getMask(bool forceMatte) {
|
|
if (!_sprite->_cast || _sprite->_spriteType == kTextSprite)
|
|
return nullptr;
|
|
|
|
bool needsMatte = _sprite->_ink == kInkTypeMatte ||
|
|
_sprite->_ink == kInkTypeNotCopy ||
|
|
_sprite->_ink == kInkTypeNotTrans ||
|
|
_sprite->_ink == kInkTypeNotReverse ||
|
|
_sprite->_ink == kInkTypeNotGhost ||
|
|
_sprite->_ink == kInkTypeBlend ||
|
|
_sprite->_ink == kInkTypeAddPin ||
|
|
_sprite->_ink == kInkTypeAdd ||
|
|
_sprite->_ink == kInkTypeSubPin ||
|
|
_sprite->_ink == kInkTypeLight ||
|
|
_sprite->_ink == kInkTypeSub ||
|
|
_sprite->_ink == kInkTypeDark ||
|
|
(((_sprite->_thickness & kTHasBlend) || _sprite->_ink == kInkTypeBlend) && _sprite->_blendAmount > 0);
|
|
|
|
if (!_sprite->isQDShape() && _sprite->_ink == kInkTypeCopy && _sprite->_thickness & kTHasBlend)
|
|
needsMatte = true;
|
|
|
|
Common::Rect bbox(getBbox());
|
|
|
|
if (needsMatte || forceMatte) {
|
|
// Mattes are only supported in bitmaps for now. Shapes don't need mattes,
|
|
// as they already have all non-enclosed white pixels transparent.
|
|
// Matte on text has a trivial enough effect to not worry about implementing.
|
|
if (_sprite->_cast->_type == kCastBitmap) {
|
|
BitmapCastMember *bitmap = ((BitmapCastMember *)_sprite->_cast);
|
|
// 1-bit images only require a matte for the matte ink type
|
|
if (bitmap->_bitsPerPixel == 1 && _sprite->_ink != kInkTypeMatte) {
|
|
// 1-bit images will not blend with kInkTypeCopy, whereas 8-bit images will.
|
|
if (_sprite->_ink == kInkTypeCopy) {
|
|
_sprite->_blendAmount = 0;
|
|
}
|
|
return nullptr;
|
|
}
|
|
return bitmap->getMatte(bbox);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
} else if (_sprite->_ink == kInkTypeMask) {
|
|
CastMemberID maskID(_sprite->_castId.member + 1, _sprite->_castId.castLib);
|
|
CastMember *member = g_director->getCurrentMovie()->getCastMember(maskID);
|
|
|
|
if (member) {
|
|
if (member->_type != kCastBitmap) {
|
|
warning("Channel::getMask(): Requested cast mask %s, but type is %s, not bitmap", maskID.asString().c_str(), castType2str(member->_type));
|
|
return nullptr;
|
|
}
|
|
BitmapCastMember *bitmap = (BitmapCastMember *)member;
|
|
if (bitmap->_bitsPerPixel != 1) {
|
|
warning("Channel::getMask(): Requested cast mask %s, but bitmap isn't 1bpp", maskID.asString().c_str());
|
|
return nullptr;
|
|
}
|
|
|
|
if (_mask) {
|
|
delete _mask;
|
|
_mask = nullptr;
|
|
}
|
|
if (bitmap->_picture) {
|
|
// reposition channel bounding box, so origin is at registration offset
|
|
Common::Point originPos = getPosition();
|
|
bbox.translate(-originPos.x, -originPos.y);
|
|
// create new mask surface, with the exact dimensions of the channel.
|
|
_mask = new Graphics::ManagedSurface(bbox.width(), bbox.height());
|
|
// get the bounding box of the mask image (origin at registration offset)
|
|
Common::Rect destRect = bitmap->getBbox();
|
|
// get position of channel's registration offset (origin at top left)
|
|
Common::Point channelRegOffset(-bbox.left, -bbox.top);
|
|
// move destination rect to sit at the channel's registration offset
|
|
destRect.translate(channelRegOffset.x, channelRegOffset.y);
|
|
Common::Point destOrigin(destRect.left, destRect.top);
|
|
// clip the destination rect so it is contained within the mask bounds
|
|
destRect.clip(_mask->getBounds());
|
|
// make a copy of the destination rect with the origin at the top left of the mask bitmap
|
|
Common::Rect srcRect = destRect;
|
|
srcRect.translate(-destOrigin.x, -destOrigin.y);
|
|
debugC(8, kDebugImages, "Channel::getMask(): cast mask %s, orig %dx%d, dest %dx%d, crop %d,%d %dx%d", maskID.asString().c_str(), bitmap->_picture->_surface.w, bitmap->_picture->_surface.h, bbox.width(), bbox.height(), destRect.left, destRect.top, destRect.width(), destRect.height());
|
|
_mask->copyRectToSurface(bitmap->_picture->_surface, destRect.left, destRect.top, srcRect);
|
|
return &_mask->rawSurface();
|
|
} else {
|
|
warning("Channel::getMask(): Requested cast mask %s, but no picture found", maskID.asString().c_str());
|
|
return nullptr;
|
|
}
|
|
} else {
|
|
warning("Channel::getMask(): Requested cast mask %s, but was not found", maskID.asString().c_str());
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// TODO: eliminate this function when we got the correct method to deal with sprite size
|
|
// since we didn't handle sprites very well for text cast members. thus we don't replace our text castmembers when only size changes
|
|
// for explicitly changing, we have isModified to check
|
|
bool hasTextCastMember(Sprite *sprite) {
|
|
if (sprite && sprite->_cast)
|
|
return sprite->_cast->_type == kCastText || sprite->_cast->_type == kCastButton;
|
|
return false;
|
|
}
|
|
|
|
bool Channel::isDirty(Sprite *nextSprite) {
|
|
// When a sprite is puppeted setTheSprite ensures that the dirty flag here is
|
|
// set. Otherwise, we need to rerender when the position, bounding box, or
|
|
// cast of the sprite changes.
|
|
if (!nextSprite)
|
|
return false;
|
|
|
|
bool isDirtyFlag = _dirty ||
|
|
(_sprite->_cast && _sprite->_cast->isModified());
|
|
|
|
if (_sprite && !_sprite->_puppet && !_sprite->_autoPuppet) {
|
|
// When puppet is set, the overall dirty flag should be set when sprite is
|
|
// modified.
|
|
isDirtyFlag |= _sprite->_castId != nextSprite->_castId ||
|
|
_sprite->_ink != nextSprite->_ink || _sprite->_backColor != nextSprite->_backColor ||
|
|
_sprite->_foreColor != nextSprite->_foreColor ||
|
|
_sprite->_blendAmount != nextSprite->_blendAmount || _sprite->_thickness != nextSprite->_thickness;
|
|
if (!_sprite->_moveable)
|
|
isDirtyFlag |= _sprite->getPosition() != nextSprite->getPosition();
|
|
if (isStretched() && !hasTextCastMember(_sprite))
|
|
isDirtyFlag |= _sprite->_width != nextSprite->_width || _sprite->_height != nextSprite->_height;
|
|
}
|
|
|
|
return isDirtyFlag;
|
|
}
|
|
|
|
Common::Rect Channel::getRollOverBbox() {
|
|
// In D4 and below, the rollOver command will check against whatever the last
|
|
// contents of the sprite were, regardless of whether the score has zeroed it out.
|
|
if (g_director->getVersion() < 500 && _sprite->_castId.member == 0) {
|
|
return _rollOverBbox;
|
|
}
|
|
return getBbox();
|
|
}
|
|
|
|
bool Channel::isStretched() {
|
|
return _sprite->_stretch;
|
|
}
|
|
|
|
bool Channel::isEmpty() {
|
|
return (_sprite->_spriteType == kInactiveSprite);
|
|
}
|
|
|
|
bool Channel::isActiveText() {
|
|
if (_sprite->_spriteType != kTextSprite)
|
|
return false;
|
|
|
|
if (_widget && _widget->hasAllFocus())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
CollisionTest Channel::isMouseIn(const Common::Point &pos) {
|
|
if (!_visible)
|
|
return kCollisionNo;
|
|
|
|
const Common::Rect bbox = getBbox();
|
|
|
|
if (_sprite->_cast) {
|
|
return _sprite->_cast->isWithin(bbox, pos, _sprite->_ink);
|
|
} else if (!bbox.contains(pos)) {
|
|
return kCollisionNo;
|
|
}
|
|
|
|
return kCollisionYes;
|
|
}
|
|
|
|
bool Channel::isMatteIntersect(Channel *channel) {
|
|
Common::Rect myBbox = getBbox();
|
|
Common::Rect yourBbox = channel->getBbox();
|
|
Common::Rect intersectRect = myBbox.findIntersectingRect(yourBbox);
|
|
|
|
if (intersectRect.isEmpty())
|
|
return false;
|
|
Graphics::Surface *myMatte = nullptr;
|
|
Graphics::Surface *yourMatte = nullptr;
|
|
|
|
if (_sprite->_cast && _sprite->_cast->_type == kCastBitmap)
|
|
myMatte = ((BitmapCastMember *)_sprite->_cast)->getMatte(myBbox);
|
|
if (channel->_sprite->_cast && channel->_sprite->_cast->_type == kCastBitmap)
|
|
yourMatte = ((BitmapCastMember *)channel->_sprite->_cast)->getMatte(yourBbox);
|
|
|
|
if (myMatte && yourMatte) {
|
|
for (int i = intersectRect.top; i < intersectRect.bottom; i++) {
|
|
const byte *my = (const byte *)myMatte->getBasePtr(intersectRect.left - myBbox.left, i - myBbox.top);
|
|
const byte *your = (const byte *)yourMatte->getBasePtr(intersectRect.left - yourBbox.left, i - yourBbox.top);
|
|
|
|
for (int j = intersectRect.left; j < intersectRect.right; j++, my++, your++)
|
|
if (*my && *your)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// this contains channel. i.e. myBox contain yourBox
|
|
bool Channel::isMatteWithin(Channel *channel) {
|
|
Common::Rect myBbox = getBbox();
|
|
Common::Rect yourBbox = channel->getBbox();
|
|
Common::Rect intersectRect = myBbox.findIntersectingRect(yourBbox);
|
|
|
|
if (!myBbox.contains(yourBbox))
|
|
return false;
|
|
Graphics::Surface *myMatte = nullptr;
|
|
Graphics::Surface *yourMatte = nullptr;
|
|
|
|
if (_sprite->_cast && _sprite->_cast->_type == kCastBitmap)
|
|
myMatte = ((BitmapCastMember *)_sprite->_cast)->getMatte(myBbox);
|
|
if (channel->_sprite->_cast && channel->_sprite->_cast->_type == kCastBitmap)
|
|
yourMatte = ((BitmapCastMember *)channel->_sprite->_cast)->getMatte(yourBbox);
|
|
|
|
if (myMatte && yourMatte) {
|
|
for (int i = intersectRect.top; i < intersectRect.bottom; i++) {
|
|
const byte *my = (const byte *)myMatte->getBasePtr(intersectRect.left - myBbox.left, i - myBbox.top);
|
|
const byte *your = (const byte *)yourMatte->getBasePtr(intersectRect.left - yourBbox.left, i - yourBbox.top);
|
|
|
|
for (int j = intersectRect.left; j < intersectRect.right; j++, my++, your++)
|
|
if (!*my && *your)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Channel::isActiveVideo() {
|
|
if (_sprite && (!_sprite->_cast || _sprite->_cast->_type != kCastDigitalVideo))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Channel::updateVideoTime() {
|
|
if (isActiveVideo())
|
|
_movieTime = ((DigitalVideoCastMember *)_sprite->_cast)->getMovieCurrentTime();
|
|
}
|
|
|
|
bool Channel::isVideoDirectToStage() {
|
|
if (!_sprite->_cast || _sprite->_cast->_type != kCastDigitalVideo)
|
|
return false;
|
|
|
|
return ((DigitalVideoCastMember *)_sprite->_cast)->_directToStage;
|
|
}
|
|
|
|
void Channel::setCast(CastMemberID memberID) {
|
|
// release previous widget
|
|
if (_sprite->_cast)
|
|
_sprite->_cast->releaseWidget();
|
|
|
|
bool hasChanged = _sprite->_castId != memberID;
|
|
// Replace the cast member in the sprite.
|
|
// Only change the dimensions if the "stretch" flag is set,
|
|
// indicating that the sprite has already been warped away from cast
|
|
// dimensions. In puppet mode Lingo can first change the
|
|
// dimensions of the sprite, -then- change the cast ID, and expect
|
|
// those custom dimensions to stick around.
|
|
_sprite->setCast(memberID, !_sprite->_stretch);
|
|
|
|
// Duplicate of the special cases in setClean.
|
|
// Maybe it makes sense to force setClean to use setCast instead?
|
|
if (hasChanged && _sprite->_cast) {
|
|
if (_sprite->_cast->_type == kCastDigitalVideo) {
|
|
DigitalVideoCastMember *dv = (DigitalVideoCastMember *)_sprite->_cast;
|
|
if (dv->loadVideoFromCast()) {
|
|
_movieTime = 0;
|
|
dv->setChannel(this);
|
|
dv->startVideo();
|
|
}
|
|
} else if (_sprite->_cast->_type == kCastFilmLoop ||
|
|
_sprite->_cast->_type == kCastMovie) {
|
|
// brand new film loop, reset the frame counter.
|
|
_filmLoopFrame = 1;
|
|
}
|
|
}
|
|
|
|
replaceWidget();
|
|
|
|
// Based on Director in a Nutshell, page 15
|
|
_sprite->setAutoPuppet(kAPCast, true);
|
|
}
|
|
|
|
void Channel::setClean(Sprite *nextSprite, bool partial) {
|
|
if (!nextSprite)
|
|
return;
|
|
|
|
CastMemberID previousCastId(0, 0);
|
|
bool replace = isDirty(nextSprite);
|
|
|
|
// for dirty situation that we need to replace widget.
|
|
// if cast are modified, then we need to replace it
|
|
// if cast size are changed, and we may need to replace it, because we may having the scaled bitmap castmember
|
|
// other situation, e.g. position changing, we will let channel to handle it. So we don't have to replace widget
|
|
bool dimsChanged = !hasTextCastMember(_sprite) && (_sprite->_width != nextSprite->_width || _sprite->_height != nextSprite->_height);
|
|
|
|
// if spriteType is changing, then we may need to re-create the widget since spriteType will guide when we creating widget
|
|
bool spriteTypeChanged = _sprite->_spriteType != nextSprite->_spriteType;
|
|
|
|
if (nextSprite) {
|
|
if (nextSprite->_cast && (_dirty || _sprite->_castId != nextSprite->_castId)) {
|
|
if (_sprite->_castId != nextSprite->_castId && nextSprite->_cast->_type == kCastDigitalVideo) {
|
|
if (((DigitalVideoCastMember *)nextSprite->_cast)->loadVideoFromCast()) {
|
|
_movieTime = 0;
|
|
((DigitalVideoCastMember *)nextSprite->_cast)->setChannel(this);
|
|
((DigitalVideoCastMember *)nextSprite->_cast)->startVideo();
|
|
}
|
|
} else if (nextSprite->_cast->_type == kCastFilmLoop || nextSprite->_cast->_type == kCastMovie) {
|
|
// brand new film loop, reset the frame counter.
|
|
_filmLoopFrame = 1;
|
|
}
|
|
}
|
|
|
|
// for the non-puppet QDShape, since we won't use isDirty to check whether the QDShape is changed.
|
|
// so we may always keep the sprite info because we need it to draw QDShape.
|
|
if (_sprite->_puppet || _sprite->_autoPuppet || (!nextSprite->isQDShape() && partial)) {
|
|
// Updating scripts, etc. does not require a full re-render
|
|
_sprite->_scriptId = nextSprite->_scriptId;
|
|
} else {
|
|
previousCastId = _sprite->_castId;
|
|
replaceSprite(nextSprite);
|
|
}
|
|
}
|
|
|
|
// FIXME: organize the logic here.
|
|
// for the dirty puppet sprites, we will always replaceWidget since previousCastId is 0, but we shouldn't replace the widget of there are only position changing
|
|
// e.g. we won't want a puppet editable text sprite changing because that will cause the loss of text.
|
|
if (replace) {
|
|
replaceWidget(previousCastId, dimsChanged || spriteTypeChanged);
|
|
}
|
|
|
|
updateTextCast();
|
|
updateGlobalAttr();
|
|
|
|
// reset the stop time when we are not playing video
|
|
if (_stopTime && (!_sprite->_cast || (_sprite->_cast && _sprite->_cast->_type != kCastDigitalVideo)))
|
|
_stopTime = 0;
|
|
|
|
_dirty = false;
|
|
}
|
|
|
|
void Channel::setStretch(bool enabled) {
|
|
if (!enabled) {
|
|
// when the stretch flag is manually disabled,
|
|
// revert whatever dimensions the sprite has to
|
|
// the default in the cast
|
|
g_director->getCurrentWindow()->addDirtyRect(getBbox());
|
|
_dirty = true;
|
|
|
|
if (_sprite->_cast) {
|
|
Common::Rect bbox = _sprite->_cast->getBbox();
|
|
_sprite->setWidth(bbox.width());
|
|
_sprite->setHeight(bbox.height());
|
|
}
|
|
}
|
|
_sprite->_stretch = enabled;
|
|
}
|
|
|
|
// this is used to for setting and updating text castmember
|
|
// e.g. set editable, update dims for auto expanding
|
|
void Channel::updateTextCast() {
|
|
if (!_sprite->_cast || _sprite->_cast->_type != kCastText)
|
|
return;
|
|
|
|
setEditable(_sprite->getEditable());
|
|
|
|
if (_widget) {
|
|
Graphics::MacText *textWidget = (Graphics::MacText *)_widget;
|
|
// if we got auto expand text, then we update dims to sprite
|
|
if (!textWidget->getFixDims() && (_sprite->_width != _widget->_dims.width() || _sprite->_height != _widget->_dims.height())) {
|
|
_sprite->_width = _widget->_dims.width();
|
|
_sprite->_height = _widget->_dims.height();
|
|
g_director->getCurrentWindow()->addDirtyRect(_widget->_dims);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Channel::getEditable() {
|
|
if (_sprite->_cast && _sprite->_cast->_type == kCastText) {
|
|
if (_widget && (Graphics::MacText *)_widget->isEditable()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Channel::setEditable(bool editable) {
|
|
if (_sprite->_cast && _sprite->_cast->_type == kCastText) {
|
|
// if the sprite is editable, then we refresh the selEnd and setStart
|
|
if (_widget) {
|
|
((Graphics::MacText *)_widget)->setEditable(editable);
|
|
// we only set the first editable text member in score active
|
|
if (editable) {
|
|
Graphics::MacWidget *activewidget = g_director->_wm->getActiveWidget();
|
|
if (activewidget == nullptr || !activewidget->isEditable())
|
|
g_director->_wm->setActiveWidget(_widget);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// we may optimize this by only update those attributes when we are changing them
|
|
// but not to pass them to widgets every time
|
|
void Channel::updateGlobalAttr() {
|
|
if (!_sprite->_cast)
|
|
return;
|
|
|
|
// update text info, including selEnd and selStart
|
|
if (_sprite->_cast->_type == kCastText && _sprite->_editable && _widget)
|
|
((Graphics::MacText *)_widget)->setSelRange(g_director->getCurrentMovie()->_selStart, g_director->getCurrentMovie()->_selEnd);
|
|
|
|
// update button info, including checkBoxType
|
|
if ((_sprite->_cast->_type == kCastButton || isButtonSprite(_sprite->_spriteType)) && _widget) {
|
|
((Graphics::MacButton *)_widget)->setCheckBoxType(g_director->getCurrentMovie()->_checkBoxType);
|
|
((Graphics::MacButton *)_widget)->setCheckBoxAccess(g_director->getCurrentMovie()->_checkBoxAccess);
|
|
}
|
|
}
|
|
|
|
void Channel::replaceSprite(Sprite *nextSprite) {
|
|
if (!nextSprite)
|
|
return;
|
|
|
|
bool widgetKeeped = _sprite->_cast && _widget;
|
|
|
|
// if there's a video in the old sprite that's different, stop it before we continue
|
|
if (_sprite->_castId != nextSprite->_castId && _sprite->_cast && _sprite->_cast->_type == kCastDigitalVideo) {
|
|
((DigitalVideoCastMember *)_sprite->_cast)->setChannel(nullptr);
|
|
((DigitalVideoCastMember *)_sprite->_cast)->stopVideo();
|
|
((DigitalVideoCastMember *)_sprite->_cast)->rewindVideo();
|
|
}
|
|
|
|
// update the _sprite we stored in channel, and point the originalSprite to the new one
|
|
// release the widget, because we may having the new one
|
|
if (_sprite->_cast && !canKeepWidget(_sprite, nextSprite)) {
|
|
widgetKeeped = false;
|
|
_sprite->_cast->releaseWidget();
|
|
}
|
|
|
|
// If the cast member is the same, persist the editable flag
|
|
bool editable = nextSprite->_editable;
|
|
if (_sprite->_castId == nextSprite->_castId) {
|
|
editable = _sprite->_editable;
|
|
}
|
|
|
|
int width = _sprite->_width;
|
|
int height = _sprite->_height;
|
|
bool immediate = _sprite->_immediate;
|
|
|
|
*_sprite = *nextSprite;
|
|
|
|
// Persist the immediate flag
|
|
_sprite->_immediate = immediate;
|
|
|
|
_sprite->_editable = editable;
|
|
|
|
// TODO: auto expand text size is meaning less for us, not all text
|
|
// since we are using initialRect for the text cast member now, then the sprite size is meaning less for us.
|
|
// thus, we keep the _sprite size here
|
|
if (hasTextCastMember(_sprite) && widgetKeeped) {
|
|
_sprite->_width = width;
|
|
_sprite->_height = height;
|
|
}
|
|
|
|
if (g_director->getVersion() >= 600) {
|
|
_startFrame = _sprite->_spriteInfo.startFrame;
|
|
_endFrame = _sprite->_spriteInfo.endFrame;
|
|
}
|
|
}
|
|
|
|
void Channel::setPosition(int x, int y, bool force) {
|
|
Common::Point newPos(x, y);
|
|
if (_constraint > 0 && _score && _constraint <= _score->_channels.size()) {
|
|
Common::Rect constraintBbox = _score->_channels[_constraint]->getRollOverBbox();
|
|
newPos.x = MIN(constraintBbox.right, MAX(constraintBbox.left, newPos.x));
|
|
newPos.y = MIN(constraintBbox.bottom, MAX(constraintBbox.top, newPos.y));
|
|
}
|
|
_sprite->setPosition(newPos.x, newPos.y);
|
|
// Update the dimensons on the widget
|
|
if (_widget) {
|
|
Common::Rect dims = _widget->getDimensions();
|
|
dims.translate(newPos.x - dims.left, newPos.y - dims.top);
|
|
_widget->setDimensions(dims);
|
|
}
|
|
}
|
|
|
|
// here is the place for deciding whether the widget can be keep or not
|
|
// here's the definition, we first need to have widgets to keep, and the cast is not modified(modified means we need to re-create the widget)
|
|
// and the castId should be same while castId should not be zero
|
|
bool Channel::canKeepWidget(CastMemberID castId) {
|
|
if (_widget && _sprite && _sprite->_cast && !_sprite->_cast->isModified() && castId.member && castId == _sprite->_castId) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Channel::canKeepWidget(Sprite *currentSprite, Sprite *nextSprite) {
|
|
if (_widget && currentSprite && currentSprite->_cast && nextSprite && nextSprite->_cast && !currentSprite->_cast->isModified() && currentSprite->_castId == nextSprite->_castId && currentSprite->_castId.member) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// currently, when we are setting hilite, we delete the widget and the re-create it
|
|
// so we may optimize this if this operation takes much time
|
|
void Channel::replaceWidget(CastMemberID previousCastId, bool force) {
|
|
// if the castmember is the same, and we are not modifying anything which cannot be handle by channel. Then we don't replace the widget
|
|
if (!force && canKeepWidget(previousCastId)) {
|
|
debug(5, "Channel::replaceWidget(): skip deleting %s", _sprite->_castId.asString().c_str());
|
|
return;
|
|
}
|
|
|
|
if (_widget) {
|
|
delete _widget;
|
|
_widget = nullptr;
|
|
}
|
|
|
|
if (_sprite && _sprite->_cast) {
|
|
// use sprite type to guide us how to draw the cast
|
|
// if the type don't match, then we will set it as transparent. i.e. don't create widget
|
|
if (!_sprite->checkSpriteType())
|
|
return;
|
|
|
|
if (_sprite->_cast->needsReload()) {
|
|
_sprite->_cast->load();
|
|
}
|
|
// always use the unstretched dims.
|
|
// because only the stretched sprite will have different channel size and sprite size
|
|
// we need the original image to scale the sprite.
|
|
// for the scaled bitmap castmember, it has scaled dims on sprite size, so we don't have to worry about it.
|
|
Common::Rect bbox(getBbox(true));
|
|
_sprite->_cast->setModified(false);
|
|
|
|
_widget = _sprite->_cast->createWidget(bbox, this, _sprite->_spriteType);
|
|
if (_widget) {
|
|
_widget->_priority = _priority;
|
|
_widget->draw();
|
|
|
|
if (_sprite->_cast->_type == kCastText || _sprite->_cast->_type == kCastButton) {
|
|
|
|
_sprite->_width = _widget->_dims.width();
|
|
_sprite->_height = _widget->_dims.height();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Channel::updateWidget() {
|
|
if (_sprite->_cast && (_sprite->_cast->_type == kCastDigitalVideo) && _sprite->_cast->isModified()) {
|
|
replaceWidget();
|
|
return true;
|
|
}
|
|
if (_widget && _widget->needsRedraw()) {
|
|
if (_sprite->_cast) {
|
|
_sprite->_cast->updateFromWidget(_widget, _sprite->_editable);
|
|
}
|
|
_widget->draw();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Channel::isTrail() {
|
|
return _sprite->_trails;
|
|
}
|
|
|
|
int Channel::getMouseChar(int x, int y) {
|
|
if (_sprite->_spriteType != kTextSprite)
|
|
return -1;
|
|
|
|
if (!_widget) {
|
|
warning("Channel::getMouseChar getting mouse char on a non-existing widget");
|
|
return -1;
|
|
}
|
|
|
|
return ((Graphics::MacText *)_widget)->getMouseChar(x, y);
|
|
}
|
|
|
|
int Channel::getMouseWord(int x, int y) {
|
|
if (_sprite->_spriteType != kTextSprite)
|
|
return -1;
|
|
|
|
if (!_widget) {
|
|
warning("Channel::getMouseWord getting mouse word on a non-existing widget");
|
|
return -1;
|
|
}
|
|
|
|
return ((Graphics::MacText *)_widget)->getMouseWord(x, y);
|
|
}
|
|
|
|
int Channel::getMouseItem(int x, int y) {
|
|
if (_sprite->_spriteType != kTextSprite)
|
|
return -1;
|
|
|
|
if (!_widget) {
|
|
warning("Channel::getMouseItem getting mouse item on a non-existing widget");
|
|
return -1;
|
|
}
|
|
|
|
return ((Graphics::MacText *)_widget)->getMouseItem(x, y);
|
|
}
|
|
|
|
int Channel::getMouseLine(int x, int y) {
|
|
if (_sprite->_spriteType != kTextSprite)
|
|
return -1;
|
|
|
|
if (!_widget) {
|
|
warning("Channel::getMouseLine getting mouse line on a non-existing widget");
|
|
return -1;
|
|
}
|
|
|
|
return ((Graphics::MacText *)_widget)->getMouseLine(x, y);
|
|
}
|
|
|
|
bool Channel::hasSubChannels() {
|
|
if ((_sprite->_cast) && (_sprite->_cast->_type == kCastFilmLoop || _sprite->_cast->_type == kCastMovie)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Common::Array<Channel> *Channel::getSubChannels() {
|
|
if (_sprite->_cast) {
|
|
Common::Rect bbox = getBbox();
|
|
if (_sprite->_cast->_type == kCastFilmLoop)
|
|
return ((FilmLoopCastMember *)_sprite->_cast)->getSubChannels(bbox, _filmLoopFrame);
|
|
else if (_sprite->_cast->_type == kCastMovie)
|
|
return ((MovieCastMember *)_sprite->_cast)->getSubChannels(bbox, _filmLoopFrame);
|
|
}
|
|
warning("Channel doesn't have any sub-channels");
|
|
return nullptr;
|
|
}
|
|
|
|
CastMemberID Channel::getSubChannelSound1() {
|
|
if (_sprite->_cast) {
|
|
if (_sprite->_cast->_type == kCastFilmLoop)
|
|
return ((FilmLoopCastMember *)_sprite->_cast)->getSubChannelSound1(_filmLoopFrame);
|
|
else if (_sprite->_cast->_type == kCastMovie)
|
|
return ((MovieCastMember *)_sprite->_cast)->getSubChannelSound2(_filmLoopFrame);
|
|
}
|
|
warning("Channel doesn't have any sub-channels");
|
|
return CastMemberID();
|
|
}
|
|
|
|
CastMemberID Channel::getSubChannelSound2() {
|
|
if (_sprite->_cast) {
|
|
if (_sprite->_cast->_type == kCastFilmLoop)
|
|
return ((FilmLoopCastMember *)_sprite->_cast)->getSubChannelSound1(_filmLoopFrame);
|
|
else if (_sprite->_cast->_type == kCastMovie)
|
|
return ((MovieCastMember *)_sprite->_cast)->getSubChannelSound2(_filmLoopFrame);
|
|
}
|
|
warning("Channel doesn't have any sub-channels");
|
|
return CastMemberID();
|
|
}
|
|
|
|
} // End of namespace Director
|