mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
The original logic doesn't hold up to testing. The position/width/height of a sprite can be changed at any time without any extra flags; although the screen needs to be redrawn before you can see any change. The main thing that the sprite puppet flag does is determine if sprites are updated from the score data when the frame changes. Fixes the broken mouse cursor tracking sprite in Chop Suey. Fixes the sprite scaling in the Doc Martens minigame in Virtual Nightclub. Fixes director-tests/D4-unit/T_SPRT01.DIR
747 lines
24 KiB
C++
747 lines
24 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 "graphics/macgui/mactext.h"
|
|
#include "graphics/macgui/mactextwindow.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 (_sprite)
|
|
_sprite->updateEditable();
|
|
}
|
|
|
|
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;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
Channel::~Channel() {
|
|
if (_widget) {
|
|
if (dynamic_cast<Graphics::MacWindow *>(_widget))
|
|
g_director->_wm->removeWindow((Graphics::MacWindow *)_widget);
|
|
else
|
|
delete _widget;
|
|
}
|
|
|
|
if (_mask)
|
|
delete _mask;
|
|
if (_sprite)
|
|
delete _sprite;
|
|
}
|
|
|
|
DirectorPlotData Channel::getPlotData() {
|
|
DirectorPlotData pd(g_director, _sprite->_spriteType, _sprite->_ink, _sprite->_blendAmount, _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->_blendAmount > 0;
|
|
|
|
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;
|
|
if (!_sprite->_moveable)
|
|
isDirtyFlag |= _sprite->getPosition() != nextSprite->getPosition();
|
|
if (isStretched() && !hasTextCastMember(_sprite))
|
|
isDirtyFlag |= _sprite->_width != nextSprite->_width || _sprite->_height != nextSprite->_height;
|
|
}
|
|
|
|
return isDirtyFlag;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool Channel::isMouseIn(const Common::Point &pos) {
|
|
if (!_visible)
|
|
return false;
|
|
|
|
Common::Rect bbox = getBbox();
|
|
if (!bbox.contains(pos))
|
|
return false;
|
|
|
|
if (_sprite->_ink == kInkTypeMatte) {
|
|
if (_sprite->_cast && _sprite->_cast->_type == kCastBitmap) {
|
|
Graphics::Surface *matte = ((BitmapCastMember *)_sprite->_cast)->getMatte(bbox);
|
|
return matte ? *(byte *)(matte->getBasePtr(pos.x - bbox.left, pos.y - bbox.top)) : true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
|
|
_sprite->setCast(memberID);
|
|
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) {
|
|
// brand new film loop, reset the frame counter
|
|
_filmLoopFrame = 0;
|
|
}
|
|
}
|
|
|
|
// if the next sprite in the channel shares the cast member
|
|
if (nextSprite->_cast && _sprite->_castId == nextSprite->_castId) {
|
|
if (nextSprite->_cast->_type == kCastFilmLoop) {
|
|
FilmLoopCastMember *fl = ((FilmLoopCastMember *)nextSprite->_cast);
|
|
if (!fl->_frames.empty()) {
|
|
// increment the film loop counter
|
|
_filmLoopFrame += 1;
|
|
_filmLoopFrame %= ((FilmLoopCastMember *)nextSprite->_cast)->_frames.size();
|
|
} else {
|
|
warning("Channel::setClean(): invalid film loop in castId %s", nextSprite->_castId.asString().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
_sprite->updateEditable();
|
|
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;
|
|
|
|
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;
|
|
|
|
_sprite->updateEditable();
|
|
setEditable(_sprite->_editable);
|
|
|
|
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 everytime
|
|
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;
|
|
}
|
|
}
|
|
|
|
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]->getBbox();
|
|
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);
|
|
}
|
|
|
|
// 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) {
|
|
// Check if _widget is of type window, in which case we need to remove it from the window manager
|
|
if (dynamic_cast<Graphics::MacWindow *>(_widget))
|
|
g_director->_wm->removeWindow((Graphics::MacWindow *)_widget);
|
|
else
|
|
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;
|
|
// 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 (_widget && _widget->needsRedraw()) {
|
|
if (_sprite->_cast) {
|
|
_sprite->_cast->updateFromWidget(_widget);
|
|
}
|
|
_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;
|
|
}
|
|
|
|
// If widget is type textWindow, then we need to get the line from the window
|
|
if (dynamic_cast<Graphics::MacTextWindow *>(_widget))
|
|
return ((Graphics::MacTextWindow *)_widget)->getMouseLine(x, y);
|
|
|
|
return ((Graphics::MacText *)_widget)->getMouseLine(x, y);
|
|
}
|
|
|
|
bool Channel::hasSubChannels() {
|
|
if ((_sprite->_cast) && (_sprite->_cast->_type == kCastFilmLoop)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Common::Array<Channel> *Channel::getSubChannels() {
|
|
if ((!_sprite->_cast) || (_sprite->_cast->_type != kCastFilmLoop)) {
|
|
warning("Channel doesn't have any sub-channels");
|
|
return nullptr;
|
|
}
|
|
Common::Rect bbox = getBbox();
|
|
return ((FilmLoopCastMember *)_sprite->_cast)->getSubChannels(bbox, this);
|
|
}
|
|
|
|
} // End of namespace Director
|