diff --git a/engines/director/events.cpp b/engines/director/events.cpp index 05909b5656f..6af7ef8ed40 100644 --- a/engines/director/events.cpp +++ b/engines/director/events.cpp @@ -62,7 +62,6 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) { warning("processEvents: request to access frame %d of %d", sc->getCurrentFrame(), sc->_frames.size() - 1); return; } - Frame *currentFrame = sc->_frames[sc->getCurrentFrame()]; uint16 spriteId = 0; Common::Point pos; @@ -82,7 +81,7 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) { sc->_lastRollTime = sc->_lastEventTime; if (_draggingSprite) { - Sprite *draggedSprite = currentFrame->_sprites[_draggingSpriteId]; + Sprite *draggedSprite = sc->_sprites[_draggingSpriteId]; if (draggedSprite->_moveable) { pos = g_system->getEventManager()->getMousePos(); Common::Point delta = pos - _draggingSpritePos; @@ -101,7 +100,7 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) { // D3 doesn't have both mouse up and down. // But we still want to know if the mouse is down for press effects. - spriteId = currentFrame->getSpriteIDFromPos(pos); + spriteId = sc->getSpriteIDFromPos(pos); sc->_currentMouseDownSpriteId = spriteId; sc->_mouseIsDown = true; @@ -111,7 +110,7 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) { debugC(3, kDebugEvents, "event: Button Down @(%d, %d), sprite id: %d", pos.x, pos.y, spriteId); _lingo->registerEvent(kEventMouseDown); - if (currentFrame->_sprites[spriteId]->_moveable) + if (sc->_sprites[spriteId]->_moveable) g_director->setDraggedSprite(spriteId); break; @@ -119,7 +118,7 @@ void DirectorEngine::processEvents(bool bufferLingoEvents) { case Common::EVENT_LBUTTONUP: pos = g_system->getEventManager()->getMousePos(); - spriteId = currentFrame->getSpriteIDFromPos(pos); + spriteId = sc->getSpriteIDFromPos(pos); debugC(3, kDebugEvents, "event: Button Up @(%d, %d), sprite id: %d", pos.x, pos.y, spriteId); diff --git a/engines/director/frame.cpp b/engines/director/frame.cpp index f506fd00e9c..782a39f64b3 100644 --- a/engines/director/frame.cpp +++ b/engines/director/frame.cpp @@ -23,13 +23,9 @@ #include "common/system.h" #include "common/substream.h" -#include "graphics/macgui/macfontmanager.h" -#include "graphics/macgui/macwindowmanager.h" -#include "graphics/macgui/maceditabletext.h" #include "graphics/primitives.h" #include "director/director.h" -#include "director/cachedmactext.h" #include "director/cast.h" #include "director/frame.h" #include "director/score.h" @@ -525,662 +521,4 @@ void Frame::readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offse } -void Frame::prepareFrame(Score *score, bool updateStageOnly) { - renderSprites(*score->_surface, false); - renderSprites(*score->_trailSurface, true); - - if (!updateStageOnly) { - score->renderZoomBox(); - - _vm->_wm->draw(); - - if (_transType != 0) - // TODO Handle changing area case - playTransition(score); - - if (_sound1 != 0 || _sound2 != 0) { - playSoundChannel(); - } - - if (_vm->getCurrentScore()->haveZoomBox()) - score->_backSurface->copyFrom(*score->_surface); - } - - g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, score->_surface->getBounds().width(), score->_surface->getBounds().height()); -} - -void Frame::playSoundChannel() { - debug(0, "STUB: playSoundChannel(), Sound1 %d Sound2 %d", _sound1, _sound2); -} - -void Frame::renderSprites(Graphics::ManagedSurface &surface, bool renderTrail) { - for (uint16 i = 0; i <= _numChannels; i++) { - if (!_sprites[i]->_enabled) - continue; - - if ((_sprites[i]->_trails == 0 && renderTrail) || (_sprites[i]->_trails == 1 && !renderTrail)) - continue; - - CastType castType = _sprites[i]->_castType; - if (castType == kCastTypeNull) - continue; - - // this needs precedence to be hit first... D3 does something really tricky with cast IDs for shapes. - // I don't like this implementation 100% as the 'cast' above might not actually hit a member and be null? - debugC(1, kDebugImages, "Frame::renderSprites(): Channel: %d castType: %d", i, castType); - - _sprites[i]->_currentBbox = _sprites[i]->_dirtyBbox; - - if (castType == kCastShape) { - renderShape(surface, i); - } else if (castType == kCastText || castType == kCastRTE) { - renderText(surface, i, NULL); - } else if (castType == kCastButton) { - renderButton(surface, i); - } else { - if (!_sprites[i]->_cast || _sprites[i]->_cast->_type != kCastBitmap) { - warning("Frame::renderSprites(): No cast ID for sprite %d", i); - continue; - } - if (_sprites[i]->_cast->_surface == nullptr) { - warning("Frame::renderSprites(): No cast surface for sprite %d", i); - continue; - } - - renderBitmap(surface, i); - } - } -} - -void Frame::renderShape(Graphics::ManagedSurface &surface, uint16 spriteId) { - Sprite *sp = _sprites[spriteId]; - - InkType ink = sp->_ink; - byte spriteType = sp->_spriteType; - byte foreColor = sp->_foreColor; - byte backColor = sp->_backColor; - int lineSize = sp->_thickness & 0x3; - - if (_vm->getVersion() >= 3 && spriteType == kCastMemberSprite) { - if (!sp->_cast) { - warning("Frame::renderShape(): kCastMemberSprite has no cast defined"); - return; - } - switch (sp->_cast->_type) { - case kCastShape: - { - ShapeCast *sc = (ShapeCast *)sp->_cast; - switch (sc->_shapeType) { - case kShapeRectangle: - spriteType = sc->_fillType ? kRectangleSprite : kOutlinedRectangleSprite; - break; - case kShapeRoundRect: - spriteType = sc->_fillType ? kRoundedRectangleSprite : kOutlinedRoundedRectangleSprite; - break; - case kShapeOval: - spriteType = sc->_fillType ? kOvalSprite : kOutlinedOvalSprite; - break; - case kShapeLine: - spriteType = sc->_lineDirection == 6 ? kLineBottomTopSprite : kLineTopBottomSprite; - break; - default: - break; - } - if (_vm->getVersion() > 3) { - foreColor = sc->_fgCol; - backColor = sc->_bgCol; - lineSize = sc->_lineThickness; - ink = sc->_ink; - } - // shapes should be rendered with transparency by default - if (ink == kInkTypeCopy) { - ink = kInkTypeTransparent; - } - } - break; - default: - warning("Frame::renderShape(): Unhandled cast type: %d", sp->_cast->_type); - break; - } - } - - // for outlined shapes, line thickness of 1 means invisible. - lineSize -= 1; - - Common::Rect shapeRect = sp->_currentBbox; - - Graphics::ManagedSurface tmpSurface, maskSurface; - tmpSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8()); - tmpSurface.clear(backColor); - - maskSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8()); - maskSurface.clear(0); - - // Draw fill - Common::Rect fillRect((int)shapeRect.width(), (int)shapeRect.height()); - Graphics::MacPlotData plotFill(&tmpSurface, &maskSurface, &_vm->getPatterns(), sp->getPattern(), -shapeRect.left, -shapeRect.top, 1, backColor); - switch (spriteType) { - case kRectangleSprite: - Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill); - break; - case kRoundedRectangleSprite: - Graphics::drawRoundRect(fillRect, 12, foreColor, true, Graphics::macDrawPixel, &plotFill); - break; - case kOvalSprite: - Graphics::drawEllipse(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, foreColor, true, Graphics::macDrawPixel, &plotFill); - break; - case kCastMemberSprite: // Face kit D3 - Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill); - break; - default: - break; - } - - // Draw stroke - Common::Rect strokeRect(MAX((int)shapeRect.width() - lineSize, 0), MAX((int)shapeRect.height() - lineSize, 0)); - Graphics::MacPlotData plotStroke(&tmpSurface, &maskSurface, &_vm->getPatterns(), 1, -shapeRect.left, -shapeRect.top, lineSize, backColor); - switch (spriteType) { - case kLineTopBottomSprite: - Graphics::drawLine(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, Graphics::macDrawPixel, &plotStroke); - break; - case kLineBottomTopSprite: - Graphics::drawLine(strokeRect.left, strokeRect.bottom, strokeRect.right, strokeRect.top, foreColor, Graphics::macDrawPixel, &plotStroke); - break; - case kRectangleSprite: - // fall through - case kOutlinedRectangleSprite: // this is actually a mouse-over shape? I don't think it's a real button. - Graphics::drawRect(strokeRect, foreColor, Graphics::macDrawPixel, &plotStroke); - //tmpSurface.fillRect(Common::Rect(shapeRect.width(), shapeRect.height()), (_vm->getCurrentScore()->_currentMouseDownSpriteId == spriteId ? 0 : 0xff)); - break; - case kRoundedRectangleSprite: - // fall through - case kOutlinedRoundedRectangleSprite: - Graphics::drawRoundRect(strokeRect, 12, foreColor, false, Graphics::macDrawPixel, &plotStroke); - break; - case kOvalSprite: - // fall through - case kOutlinedOvalSprite: - Graphics::drawEllipse(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, false, Graphics::macDrawPixel, &plotStroke); - break; - default: - break; - } - - inkBasedBlit(surface, &maskSurface, tmpSurface, ink, shapeRect, spriteId); -} - -void Frame::renderButton(Graphics::ManagedSurface &surface, uint16 spriteId) { - uint16 castId = _sprites[spriteId]->_castId; - - // This may not be a button cast. It could be a textcast with the channel forcing it - // to be a checkbox or radio button! - Cast *member = _vm->getCastMember(castId); - if (!member) { - warning("renderButton: unknown cast id %d", castId); - } else if (member->_type != kCastButton) { - warning("renderButton: cast id %d not of type kCastButton", castId); - return; - } - ButtonCast *button = (ButtonCast *)member; - - // Sometimes, at least in the D3 Workshop Examples, these buttons are just TextCast. - // If they are, then we just want to use the spriteType as the button type. - // If they are full-bown Cast members, then use the actual cast member type. - int buttonType = _sprites[spriteId]->_spriteType; - if (buttonType == kCastMemberSprite) { - switch (button->_buttonType) { - case kTypeCheckBox: - buttonType = kCheckboxSprite; - break; - case kTypeButton: - buttonType = kButtonSprite; - break; - case kTypeRadio: - buttonType = kRadioButtonSprite; - break; - } - } - - bool invert = spriteId == _vm->getCurrentScore()->_currentMouseDownSpriteId; - - // TODO: review all cases to confirm if we should use text height. - // height = textRect.height(); - - Common::Rect _rect = _sprites[spriteId]->_currentBbox; - int16 x = _rect.left; - int16 y = _rect.top; - - Common::Rect textRect(0, 0, _rect.width(), _rect.height()); - - // WORKAROUND, HACK - // Because we're not drawing text with transparency - // We swap drawing depending on whether the button is - // inverted or not, to prevent destroying the border - if (!invert) - renderText(surface, spriteId, &textRect); - - Graphics::MacPlotData plotStroke(&surface, nullptr, &_vm->getPatterns(), 1, 0, 0, 1, 0); - - switch (buttonType) { - case kCheckboxSprite: - surface.frameRect(_rect, 0); - break; - case kButtonSprite: { - Graphics::MacPlotData pd(&surface, nullptr, &_vm->getMacWindowManager()->getPatterns(), Graphics::MacGUIConstants::kPatternSolid, 0, 0, 1, invert ? Graphics::kColorBlack : Graphics::kColorWhite); - - Graphics::drawRoundRect(_rect, 4, 0, invert, Graphics::macDrawPixel, &pd); - } - break; - case kRadioButtonSprite: - Graphics::drawEllipse(x, y + 2, x + 11, y + 13, 0, false, Graphics::macDrawPixel, &plotStroke); - break; - default: - warning("renderButton: Unknown buttonType"); - break; - } - - if (invert) - renderText(surface, spriteId, &textRect); -} - -void Frame::renderText(Graphics::ManagedSurface &surface, uint16 spriteId, Common::Rect *textRect) { - TextCast *textCast = (TextCast*)_sprites[spriteId]->_cast; - if (textCast == nullptr) { - warning("Frame::renderText(): TextCast #%d is a nullptr", spriteId); - return; - } - - Score *score = _vm->getCurrentScore(); - Sprite *sprite = _sprites[spriteId]; - - Common::Rect bbox = sprite->_currentBbox; - int width = bbox.width(); - int height = bbox.height(); - int x = bbox.left; - int y = bbox.top; - - if (_vm->getCurrentScore()->_fontMap.contains(textCast->_fontId)) { - // We need to make sure that the Shared Cast fonts have been loaded in? - // might need a mapping table here of our own. - // textCast->fontId = _vm->_wm->_fontMan->getFontIdByName(_vm->getCurrentScore()->_fontMap[textCast->fontId]); - } - - if (width == 0 || height == 0) { - warning("Frame::renderText(): Requested to draw on an empty surface: %d x %d", width, height); - return; - } - - if (sprite->_editable) { - if (!textCast->_widget) { - warning("Creating MacEditableText with '%s'", toPrintable(textCast->_ftext).c_str()); - textCast->_widget = new Graphics::MacEditableText(score->_window, x, y, width, height, g_director->_wm, textCast->_ftext, new Graphics::MacFont(), 0, 255, width); - warning("Finished creating MacEditableText"); - } - - textCast->_widget->draw(); - - InkType ink = sprite->_ink; - - if (spriteId == score->_currentMouseDownSpriteId) - ink = kInkTypeReverse; - - inkBasedBlit(surface, nullptr, textCast->_widget->getSurface()->rawSurface(), ink, Common::Rect(x, y, x + width, y + height), spriteId); - - return; - } - - debugC(3, kDebugText, "renderText: sprite: %d x: %d y: %d w: %d h: %d fontId: '%d' text: '%s'", spriteId, x, y, width, height, textCast->_fontId, Common::toPrintable(textCast->_ftext).c_str()); - - uint16 boxShadow = (uint16)textCast->_boxShadow; - uint16 borderSize = (uint16)textCast->_borderSize; - if (textRect != NULL) - borderSize = 0; - uint16 padding = (uint16)textCast->_gutterSize; - uint16 textShadow = (uint16)textCast->_textShadow; - - //uint32 rectLeft = textCast->initialRect.left; - //uint32 rectTop = textCast->initialRect.top; - - textCast->_cachedMacText->clip(width); - const Graphics::ManagedSurface *textSurface = textCast->_cachedMacText->getSurface(); - - if (!textSurface) - return; - - height = textSurface->h; - if (textRect != NULL) { - // TODO: this offset could be due to incorrect fonts loaded! - textRect->bottom = height + textCast->_cachedMacText->getLineCount(); - } - - uint16 textX = 0, textY = 0; - - if (textRect == NULL) { - if (borderSize > 0) { - if (_vm->getVersion() <= 3) { - height += (borderSize * 2); - textX += (borderSize + 2); - } else { - height += borderSize; - textX += (borderSize + 1); - } - textY += borderSize; - } else { - x += 1; - } - - if (padding > 0) { - width += padding * 2; - height += padding; - textY += padding / 2; - } - - if (textCast->_textAlign == kTextAlignRight) - textX -= 1; - - if (textShadow > 0) - textX--; - } else { - x++; - if (width % 2 != 0) - x++; - - if (sprite->_spriteType != kCastMemberSprite) { - y += 2; - switch (sprite->_spriteType) { - case kCheckboxSprite: - textX += 16; - break; - case kRadioButtonSprite: - textX += 17; - break; - default: - break; - } - } else { - ButtonType buttonType = ((ButtonCast*)textCast)->_buttonType; - switch (buttonType) { - case kTypeCheckBox: - width += 4; - textX += 16; - break; - case kTypeRadio: - width += 4; - textX += 17; - break; - case kTypeButton: - width += 4; - y += 2; - break; - default: - warning("Frame::renderText(): Expected button but got unexpected button type: %d", buttonType); - y += 2; - break; - } - } - } - - switch (textCast->_textAlign) { - case kTextAlignLeft: - default: - break; - case kTextAlignCenter: - textX = (width / 2) - (textSurface->w / 2) + (padding / 2) + borderSize; - break; - case kTextAlignRight: - textX = width - (textSurface->w + 1) + (borderSize * 2) - (textShadow * 2) - (padding); - break; - } - - Graphics::ManagedSurface textWithFeatures(width + (borderSize * 2) + boxShadow + textShadow, height + borderSize + boxShadow + textShadow); - textWithFeatures.fillRect(Common::Rect(textWithFeatures.w, textWithFeatures.h), score->getStageColor()); - - if (textRect == NULL && boxShadow > 0) { - textWithFeatures.fillRect(Common::Rect(boxShadow, boxShadow, textWithFeatures.w + boxShadow, textWithFeatures.h), 0); - } - - if (textRect == NULL && borderSize != kSizeNone) { - for (int bb = 0; bb < borderSize; bb++) { - Common::Rect borderRect(bb, bb, textWithFeatures.w - bb - boxShadow - textShadow, textWithFeatures.h - bb - boxShadow - textShadow); - textWithFeatures.fillRect(borderRect, 0xff); - textWithFeatures.frameRect(borderRect, 0); - } - } - - if (textShadow > 0) - textWithFeatures.transBlitFrom(textSurface->rawSurface(), Common::Point(textX + textShadow, textY + textShadow), 0xff); - - textWithFeatures.transBlitFrom(textSurface->rawSurface(), Common::Point(textX, textY), 0xff); - - InkType ink = sprite->_ink; - - if (spriteId == score->_currentMouseDownSpriteId) - ink = kInkTypeReverse; - - inkBasedBlit(surface, nullptr, textWithFeatures, ink, Common::Rect(x, y, x + width, y + height), spriteId); -} - -void Frame::renderBitmap(Graphics::ManagedSurface &surface, uint16 spriteId) { - InkType ink; - Sprite *sprite = _sprites[spriteId]; - - if (spriteId == _vm->getCurrentScore()->_currentMouseDownSpriteId) - ink = kInkTypeReverse; - else - ink = sprite->_ink; - - BitmapCast *bc = (BitmapCast *)sprite->_cast; - Common::Rect drawRect = sprite->_currentBbox; - - inkBasedBlit(surface, nullptr, *(bc->_surface), ink, drawRect, spriteId); -} - -void Frame::inkBasedBlit(Graphics::ManagedSurface &targetSurface, const Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId) { - // drawRect could be bigger than the spriteSurface. Clip it - Common::Rect t(spriteSurface.w, spriteSurface.h); - t.moveTo(drawRect.left, drawRect.top); - drawRect.clip(t); - - switch (ink) { - case kInkTypeCopy: - if (maskSurface) - targetSurface.transBlitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top), *maskSurface); - else - targetSurface.blitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top)); - break; - case kInkTypeTransparent: - // FIXME: is it always white (last entry in pallette)? - targetSurface.transBlitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top), _vm->getPaletteColorCount() - 1); - break; - case kInkTypeBackgndTrans: - drawBackgndTransSprite(targetSurface, spriteSurface, drawRect, spriteId); - break; - case kInkTypeMatte: - drawMatteSprite(targetSurface, spriteSurface, drawRect); - break; - case kInkTypeGhost: - drawGhostSprite(targetSurface, spriteSurface, drawRect); - break; - case kInkTypeReverse: - drawReverseSprite(targetSurface, spriteSurface, drawRect, spriteId); - break; - default: - warning("Frame::inkBasedBlit(): Unhandled ink type %d", ink); - targetSurface.blitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top)); - break; - } -} - -void Frame::drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect, int spriteId) { - byte skipColor = _sprites[spriteId]->_backColor; - Common::Rect srcRect(sprite.w, sprite.h); - - if (!target.clip(srcRect, drawRect)) - return; // Out of screen - - for (int ii = 0; ii < srcRect.height(); ii++) { - const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii); - byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii); - - for (int j = 0; j < srcRect.width(); j++) { - if (*src != skipColor) - *dst = *src; - - src++; - dst++; - } - } -} - -void Frame::drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { - Common::Rect srcRect(sprite.w, sprite.h); - - if (!target.clip(srcRect, drawRect)) - return; // Out of screen - - uint8 skipColor = _vm->getPaletteColorCount() - 1; - for (int ii = 0; ii < srcRect.height(); ii++) { - const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii); - byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii); - - for (int j = 0; j < srcRect.width(); j++) { - if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0) && (*src != skipColor)) - *dst = (_vm->getPaletteColorCount() - 1) - *src; // Oposite color - - src++; - dst++; - } - } -} - -void Frame::drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId) { - Common::Rect srcRect(sprite.w, sprite.h); - - if (!target.clip(srcRect, drawRect)) - return; // Out of screen - - uint8 skipColor = _vm->getPaletteColorCount() - 1; - for (int ii = 0; ii < srcRect.height(); ii++) { - const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii); - byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii); - byte srcColor = *src; - - for (int j = 0; j < srcRect.width(); j++) { - if (_sprites[spriteId]->_cast->_type == kCastShape) - srcColor = 0x0; - else - srcColor = *src; - uint16 targetSprite = getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)); - if ((targetSprite != 0)) { - // TODO: This entire reverse colour attempt needs a lot more testing on - // a lot more colour depths. - if (srcColor != skipColor) { - if (_sprites[targetSprite]->_cast->_type != kCastBitmap) { - if (*dst == 0 || *dst == 255) { - *dst = _vm->transformColor(*dst); - } else if (srcColor == 255 || srcColor == 0) { - *dst = _vm->transformColor(*dst - 40); - } else { - *dst = _vm->transformColor(*src - 40); - } - } else { - if (*dst == 0 && _vm->getVersion() == 3 && - ((BitmapCast*)_sprites[spriteId]->_cast)->_bitsPerPixel > 1) { - *dst = _vm->transformColor(*src - 40); - } else { - *dst ^= _vm->transformColor(srcColor); - } - } - } - } else if (srcColor != skipColor) { - *dst = _vm->transformColor(srcColor); - } - src++; - dst++; - } - } -} - -void Frame::drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) { - // Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent - Graphics::Surface tmp; - tmp.copyFrom(sprite); - Common::Rect srcRect(sprite.w, sprite.h); - - if (!target.clip(srcRect, drawRect)) - return; // Out of screen - - // Searching white color in the corners - int whiteColor = -1; - - for (int y = 0; y < tmp.h; y++) { - for (int x = 0; x < tmp.w; x++) { - byte color = *(byte *)tmp.getBasePtr(x, y); - - if (_vm->getPalette()[color * 3 + 0] == 0xff && - _vm->getPalette()[color * 3 + 1] == 0xff && - _vm->getPalette()[color * 3 + 2] == 0xff) { - whiteColor = color; - break; - } - } - } - - if (whiteColor == -1) { - debugC(1, kDebugImages, "Frame::drawMatteSprite(): No white color for Matte image"); - - for (int yy = 0; yy < srcRect.height(); yy++) { - const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy); - byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy); - - for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++) - *dst = *src; - } - } else { - Graphics::FloodFill ff(&tmp, whiteColor, 0, true); - - for (int yy = 0; yy < tmp.h; yy++) { - ff.addSeed(0, yy); - ff.addSeed(tmp.w - 1, yy); - } - - for (int xx = 0; xx < tmp.w; xx++) { - ff.addSeed(xx, 0); - ff.addSeed(xx, tmp.h - 1); - } - ff.fillMask(); - - for (int yy = 0; yy < srcRect.height(); yy++) { - const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy); - const byte *mask = (const byte *)ff.getMask()->getBasePtr(srcRect.left, srcRect.top + yy); - byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy); - - for (int xx = 0; xx < srcRect.width(); xx++, src++, dst++, mask++) - if (*mask == 0) - *dst = *src; - } - } - - tmp.free(); -} - -uint16 Frame::getSpriteIDFromPos(Common::Point pos) { - for (int i = _sprites.size() - 1; i >= 0; i--) - if (_sprites[i]->_currentBbox.contains(pos)) - return i; - - return 0; -} - -bool Frame::checkSpriteIntersection(uint16 spriteId, Common::Point pos) { - if (_sprites[spriteId]->_currentBbox.contains(pos)) - return true; - - return false; -} - -Common::Rect *Frame::getSpriteRect(uint16 spriteId) { - return &_sprites[spriteId]->_currentBbox; -} - } // End of namespace Director diff --git a/engines/director/frame.h b/engines/director/frame.h index 5ab89bd330d..684488841d3 100644 --- a/engines/director/frame.h +++ b/engines/director/frame.h @@ -72,31 +72,19 @@ public: ~Frame(); void readChannels(Common::ReadStreamEndian *stream); void readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size); - void prepareFrame(Score *score, bool updateStageOnly = false); - uint16 getSpriteIDFromPos(Common::Point pos); - bool checkSpriteIntersection(uint16 spriteId, Common::Point pos); - Common::Rect *getSpriteRect(uint16 spriteId); void executeImmediateScripts(); -private: void playTransition(Score *score); - void playSoundChannel(); - void renderSprites(Graphics::ManagedSurface &surface, bool renderTrail); - void renderText(Graphics::ManagedSurface &surface, uint16 spriteId, Common::Rect *textSize); - void renderShape(Graphics::ManagedSurface &surface, uint16 spriteId); - void renderButton(Graphics::ManagedSurface &surface, uint16 spriteId); - void renderBitmap(Graphics::ManagedSurface &surface, uint16 spriteId); + +private: + void readPaletteInfo(Common::SeekableSubReadStreamEndian &stream); void readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size); void readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size); Image::ImageDecoder *getImageFrom(uint16 spriteId); Common::String readTextStream(Common::SeekableSubReadStreamEndian *textStream, TextCast *textCast); - void drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect, int spriteId); - void drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); - void drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect); - void drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId); - void inkBasedBlit(Graphics::ManagedSurface &targetSurface, const Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId); + public: int _numChannels; diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp index f0ef57eaa0e..17877d00fb6 100644 --- a/engines/director/lingo/lingo-builtins.cpp +++ b/engines/director/lingo/lingo-builtins.cpp @@ -1715,21 +1715,21 @@ void LB::b_rollOver(int nargs) { Datum res(0); int arg = d.asInt(); - if (!g_director->getCurrentScore()) { + Score *score = g_director->getCurrentScore(); + + if (!score) { warning("b_rollOver: Reference to an empty score"); return; } - Frame *frame = g_director->getCurrentScore()->_frames[g_director->getCurrentScore()->getCurrentFrame()]; - - if (arg >= (int32) frame->_sprites.size()) { + if (arg >= (int32) score->_sprites.size()) { g_lingo->push(res); return; } Common::Point pos = g_system->getEventManager()->getMousePos(); - if (frame->checkSpriteIntersection(arg, pos)) + if (score->checkSpriteIntersection(arg, pos)) res.u.i = 1; // TRUE g_lingo->push(res); @@ -1775,9 +1775,8 @@ void LB::b_zoomBox(int nargs) { Score *score = g_director->getCurrentScore(); uint16 curFrame = score->getCurrentFrame(); - Frame *frame = score->_frames[curFrame]; - Common::Rect *startRect = frame->getSpriteRect(startSprite); + Common::Rect *startRect = score->getSpriteRect(startSprite); if (!startRect) { warning("b_zoomBox: unknown start sprite #%d", startSprite); return; @@ -1785,10 +1784,10 @@ void LB::b_zoomBox(int nargs) { // Looks for endSprite in the current frame, otherwise // Looks for endSprite in the next frame - Common::Rect *endRect = frame->getSpriteRect(endSprite); + Common::Rect *endRect = score->getSpriteRect(endSprite); if (!endRect) { if ((uint)curFrame + 1 < score->_frames.size()) - score->_frames[curFrame + 1]->getSpriteRect(endSprite); + endRect = &score->_frames[curFrame + 1]->_sprites[endSprite]->_currentBbox; } if (!endRect) { @@ -1818,11 +1817,7 @@ void LB::b_updateStage(int nargs) { return; } - uint16 curFrame = score->getCurrentFrame(); - Frame *frame = score->_frames[curFrame]; - - frame->prepareFrame(score, true); - g_director->processEvents(true); + score->renderFrame( score->getCurrentFrame(), false, true); } diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp index 5459c5f44d4..0383d7057d2 100644 --- a/engines/director/lingo/lingo-the.cpp +++ b/engines/director/lingo/lingo-the.cpp @@ -427,10 +427,9 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) { { Common::Point pos = g_system->getEventManager()->getMousePos(); Score *sc = _vm->getCurrentScore(); - Frame *currentFrame = sc->_frames[sc->getCurrentFrame()]; - uint16 spriteId = currentFrame->getSpriteIDFromPos(pos); + uint16 spriteId = sc->getSpriteIDFromPos(pos); d.type = INT; - d.u.i = currentFrame->_sprites[spriteId]->_castId; + d.u.i = sc->_sprites[spriteId]->_castId; if (d.u.i == 0) d.u.i = -1; } @@ -735,6 +734,10 @@ void Lingo::setTheSprite(Datum &id1, int field, Datum &d) { break; case kThePuppet: sprite->_puppet = d.asInt(); + if (!d.u.i) { + sprite->_currentPoint = sprite->_startPoint; + sprite->_dirtyBbox = sprite->_startBbox; + } break; case kTheStartTime: sprite->_startTime = d.asInt(); diff --git a/engines/director/score.cpp b/engines/director/score.cpp index b04454bc209..70258152df7 100644 --- a/engines/director/score.cpp +++ b/engines/director/score.cpp @@ -31,6 +31,8 @@ #include "graphics/primitives.h" #include "graphics/macgui/macfontmanager.h" #include "graphics/macgui/macwindowmanager.h" +#include "graphics/macgui/maceditabletext.h" +#include "director/cachedmactext.h" #include "image/bmp.h" #include "director/director.h" @@ -69,7 +71,7 @@ const char *scriptType2str(ScriptType scr) { Score::Score(DirectorEngine *vm) { _vm = vm; _surface = nullptr; - _trailSurface = nullptr; + _maskSurface = nullptr; _backSurface = nullptr; _backSurface2 = nullptr; _lingo = _vm->getLingo(); @@ -454,8 +456,8 @@ void Score::loadSpriteSounds(bool isSharedCast) { Score::~Score() { - if (_trailSurface && _trailSurface->w) - _trailSurface->free(); + if (_maskSurface && _maskSurface->w) + _maskSurface->free(); if (_backSurface && _backSurface->w) _backSurface->free(); @@ -465,7 +467,7 @@ Score::~Score() { delete _backSurface; delete _backSurface2; - delete _trailSurface; + delete _maskSurface; if (_window) _vm->_wm->removeWindow(_window); @@ -1706,14 +1708,16 @@ void Score::startLoop() { _window->resize(_movieRect.width(), _movieRect.height()); _surface = _window->getWindowSurface(); - _trailSurface = new Graphics::ManagedSurface; + _maskSurface = new Graphics::ManagedSurface; _backSurface = new Graphics::ManagedSurface; _backSurface2 = new Graphics::ManagedSurface; - _trailSurface->create(_movieRect.width(), _movieRect.height()); + _maskSurface->create(_movieRect.width(), _movieRect.height()); _backSurface->create(_movieRect.width(), _movieRect.height()); _backSurface2->create(_movieRect.width(), _movieRect.height()); + _sprites.resize(_frames[0]->_sprites.size()); + if (_vm->_backSurface.w > 0) { // Persist screen between the movies // TODO: this is a workaround until the rendering pipeline is reworked @@ -1728,7 +1732,7 @@ void Score::startLoop() { _vm->_wm->setScreen(_surface); - _trailSurface->clear(_stageColor); + _surface->clear(_stageColor); _currentFrame = 0; _stopPlay = false; @@ -1736,7 +1740,8 @@ void Score::startLoop() { _lingo->processEvent(kEventStartMovie); - _frames[_currentFrame]->prepareFrame(this); + _sprites = _frames[_currentFrame]->_sprites; + renderFrame(_currentFrame, true); while (!_stopPlay) { if (_currentFrame >= _frames.size()) { @@ -1818,8 +1823,8 @@ void Score::update() { _vm->_newMovieStarted = false; - _surface->clear(_stageColor); - _surface->copyFrom(*_trailSurface); + // _surface->clear(_stageColor); + // _surface->copyFrom(*_trailSurface); _lingo->executeImmediateScripts(_frames[_currentFrame]); @@ -1830,7 +1835,7 @@ void Score::update() { // TODO: Director 6 step: send prepareFrame event to all sprites and the script channel in upcoming frame } - _frames[_currentFrame]->prepareFrame(this); + renderFrame(_currentFrame); // Stage is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell, p.100) // Enter and exit from previous frame (Director 4) @@ -1880,19 +1885,724 @@ void Score::update() { _framesRan++; } +void Score::renderFrame(uint16 frameId, bool forceUpdate, bool updateStageOnly) { + _maskSurface->clear(0); + + Frame *currentFrame = _frames[frameId]; + + for (uint16 i = 0; i < _sprites.size(); i++) { + Sprite *currentSprite = _sprites[i]; + Sprite *nextSprite; + + if (currentSprite->_puppet) + nextSprite = currentSprite; + else + nextSprite = currentFrame->_sprites[i]; + + bool needsUpdate = (currentSprite->_currentBbox != nextSprite->_currentBbox || currentSprite->_currentBbox != currentSprite->_dirtyBbox); + + if (needsUpdate || forceUpdate) + unrenderSprite(i); + + _sprites[i] = nextSprite; + } + + for (uint i = 0; i < _sprites.size(); i++) + renderSprite(i); + + if (!updateStageOnly) { + renderZoomBox(); + + _vm->_wm->draw(); + + if (currentFrame->_transType != 0) + // TODO Handle changing area case + currentFrame->playTransition(this); + + if (currentFrame->_sound1 != 0 || currentFrame->_sound2 != 0) { + playSoundChannel(frameId); + } + + if (_vm->getCurrentScore()->haveZoomBox()) + _backSurface->copyFrom(*_surface); + } + + g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, _surface->getBounds().width(), _surface->getBounds().height()); +} + +void Score::unrenderSprite(uint16 spriteId) { + Sprite *currentSprite = _sprites[spriteId]; + + if (!currentSprite->_trails) { + _maskSurface->fillRect(currentSprite->_currentBbox, 1); + _surface->fillRect(currentSprite->_currentBbox, _stageColor); + } + + currentSprite->_currentBbox = currentSprite->_dirtyBbox; +} + +void Score::renderSprite(uint16 id) { + Sprite *sprite = _sprites[id]; + + if (!sprite) + return; + + CastType castType = sprite->_castType; + + _maskSurface->fillRect(sprite->_currentBbox, 1); + + if (castType == kCastTypeNull) + return; + + debugC(1, kDebugImages, "Score::renderFrame(): channel: %d, castType: %d", id, castType); + // this needs precedence to be hit first... D3 does something really tricky + // with cast IDs for shapes. I don't like this implementation 100% as the + // 'cast' above might not actually hit a member and be null? + if (castType == kCastShape) { + renderShape(id); + } else if (castType == kCastText || castType == kCastRTE) { + renderText(id, NULL); + } else if (castType == kCastButton) { + renderButton(id); + } else { + if (!sprite->_cast || sprite->_cast->_type != kCastBitmap) { + warning("Score::renderFrame(): No cast ID for sprite %d", id); + return; + } + if (sprite->_cast->_surface == nullptr) { + warning("Score::renderFrame(): No cast surface for sprite %d", id); + return; + } + + renderBitmap(id); + } +} + +void Score::renderShape(uint16 spriteId) { + Sprite *sp = _sprites[spriteId]; + + InkType ink = sp->_ink; + byte spriteType = sp->_spriteType; + byte foreColor = sp->_foreColor; + byte backColor = sp->_backColor; + int lineSize = sp->_thickness & 0x3; + + if (_vm->getVersion() >= 3 && spriteType == kCastMemberSprite) { + if (!sp->_cast) { + warning("Frame::renderShape(): kCastMemberSprite has no cast defined"); + return; + } + switch (sp->_cast->_type) { + case kCastShape: + { + ShapeCast *sc = (ShapeCast *)sp->_cast; + switch (sc->_shapeType) { + case kShapeRectangle: + spriteType = sc->_fillType ? kRectangleSprite : kOutlinedRectangleSprite; + break; + case kShapeRoundRect: + spriteType = sc->_fillType ? kRoundedRectangleSprite : kOutlinedRoundedRectangleSprite; + break; + case kShapeOval: + spriteType = sc->_fillType ? kOvalSprite : kOutlinedOvalSprite; + break; + case kShapeLine: + spriteType = sc->_lineDirection == 6 ? kLineBottomTopSprite : kLineTopBottomSprite; + break; + default: + break; + } + if (_vm->getVersion() > 3) { + foreColor = sc->_fgCol; + backColor = sc->_bgCol; + lineSize = sc->_lineThickness; + ink = sc->_ink; + } + // shapes should be rendered with transparency by default + if (ink == kInkTypeCopy) { + ink = kInkTypeTransparent; + } + } + break; + default: + warning("Frame::renderShape(): Unhandled cast type: %d", sp->_cast->_type); + break; + } + } + + // for outlined shapes, line thickness of 1 means invisible. + lineSize -= 1; + + Common::Rect shapeRect = sp->_currentBbox; + + Graphics::ManagedSurface tmpSurface, maskSurface; + tmpSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8()); + tmpSurface.clear(backColor); + + maskSurface.create(shapeRect.width(), shapeRect.height(), Graphics::PixelFormat::createFormatCLUT8()); + maskSurface.clear(0); + + // Draw fill + Common::Rect fillRect((int)shapeRect.width(), (int)shapeRect.height()); + Graphics::MacPlotData plotFill(&tmpSurface, &maskSurface, &_vm->getPatterns(), sp->getPattern(), -shapeRect.left, -shapeRect.top, 1, backColor); + switch (spriteType) { + case kRectangleSprite: + Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill); + break; + case kRoundedRectangleSprite: + Graphics::drawRoundRect(fillRect, 12, foreColor, true, Graphics::macDrawPixel, &plotFill); + break; + case kOvalSprite: + Graphics::drawEllipse(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, foreColor, true, Graphics::macDrawPixel, &plotFill); + break; + case kCastMemberSprite: // Face kit D3 + Graphics::drawFilledRect(fillRect, foreColor, Graphics::macDrawPixel, &plotFill); + break; + default: + break; + } + + // Draw stroke + Common::Rect strokeRect(MAX((int)shapeRect.width() - lineSize, 0), MAX((int)shapeRect.height() - lineSize, 0)); + Graphics::MacPlotData plotStroke(&tmpSurface, &maskSurface, &_vm->getPatterns(), 1, -shapeRect.left, -shapeRect.top, lineSize, backColor); + switch (spriteType) { + case kLineTopBottomSprite: + Graphics::drawLine(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, Graphics::macDrawPixel, &plotStroke); + break; + case kLineBottomTopSprite: + Graphics::drawLine(strokeRect.left, strokeRect.bottom, strokeRect.right, strokeRect.top, foreColor, Graphics::macDrawPixel, &plotStroke); + break; + case kRectangleSprite: + // fall through + case kOutlinedRectangleSprite: // this is actually a mouse-over shape? I don't think it's a real button. + Graphics::drawRect(strokeRect, foreColor, Graphics::macDrawPixel, &plotStroke); + //tmpSurface.fillRect(Common::Rect(shapeRect.width(), shapeRect.height()), (_vm->getCurrentScore()->_currentMouseDownSpriteId == spriteId ? 0 : 0xff)); + break; + case kRoundedRectangleSprite: + // fall through + case kOutlinedRoundedRectangleSprite: + Graphics::drawRoundRect(strokeRect, 12, foreColor, false, Graphics::macDrawPixel, &plotStroke); + break; + case kOvalSprite: + // fall through + case kOutlinedOvalSprite: + Graphics::drawEllipse(strokeRect.left, strokeRect.top, strokeRect.right, strokeRect.bottom, foreColor, false, Graphics::macDrawPixel, &plotStroke); + break; + default: + break; + } + + inkBasedBlit(&maskSurface, tmpSurface, ink, shapeRect, spriteId); +} + + +void Score::renderButton(uint16 spriteId) { + uint16 castId = _sprites[spriteId]->_castId; + + // This may not be a button cast. It could be a textcast with the channel forcing it + // to be a checkbox or radio button! + Cast *member = _vm->getCastMember(castId); + if (!member) { + warning("renderButton: unknown cast id %d", castId); + } else if (member->_type != kCastButton) { + warning("renderButton: cast id %d not of type kCastButton", castId); + return; + } + ButtonCast *button = (ButtonCast *)member; + + // Sometimes, at least in the D3 Workshop Examples, these buttons are just TextCast. + // If they are, then we just want to use the spriteType as the button type. + // If they are full-bown Cast members, then use the actual cast member type. + int buttonType = _sprites[spriteId]->_spriteType; + if (buttonType == kCastMemberSprite) { + switch (button->_buttonType) { + case kTypeCheckBox: + buttonType = kCheckboxSprite; + break; + case kTypeButton: + buttonType = kButtonSprite; + break; + case kTypeRadio: + buttonType = kRadioButtonSprite; + break; + } + } + + bool invert = spriteId == _vm->getCurrentScore()->_currentMouseDownSpriteId; + + // TODO: review all cases to confirm if we should use text height. + // height = textRect.height(); + + Common::Rect _rect = _sprites[spriteId]->_currentBbox; + int16 x = _rect.left; + int16 y = _rect.top; + + Common::Rect textRect(0, 0, _rect.width(), _rect.height()); + + // WORKAROUND, HACK + // Because we're not drawing text with transparency + // We swap drawing depending on whether the button is + // inverted or not, to prevent destroying the border + if (!invert) + renderText(spriteId, &textRect); + + Graphics::MacPlotData plotStroke(_surface, nullptr, &_vm->getPatterns(), 1, 0, 0, 1, 0); + + switch (buttonType) { + case kCheckboxSprite: + _surface->frameRect(_rect, 0); + break; + case kButtonSprite: { + Graphics::MacPlotData pd(_surface, nullptr, &_vm->getMacWindowManager()->getPatterns(), Graphics::MacGUIConstants::kPatternSolid, 0, 0, 1, invert ? Graphics::kColorBlack : Graphics::kColorWhite); + + Graphics::drawRoundRect(_rect, 4, 0, invert, Graphics::macDrawPixel, &pd); + } + break; + case kRadioButtonSprite: + Graphics::drawEllipse(x, y + 2, x + 11, y + 13, 0, false, Graphics::macDrawPixel, &plotStroke); + break; + default: + warning("renderButton: Unknown buttonType"); + break; + } + + if (invert) + renderText(spriteId, &textRect); +} + +void Score::renderText(uint16 spriteId, Common::Rect *textRect) { + TextCast *textCast = (TextCast*)_sprites[spriteId]->_cast; + if (textCast == nullptr) { + warning("Frame::renderText(): TextCast #%d is a nullptr", spriteId); + return; + } + + Score *score = _vm->getCurrentScore(); + Sprite *sprite = _sprites[spriteId]; + + Common::Rect bbox = sprite->_currentBbox; + int width = bbox.width(); + int height = bbox.height(); + int x = bbox.left; + int y = bbox.top; + + if (_vm->getCurrentScore()->_fontMap.contains(textCast->_fontId)) { + // We need to make sure that the Shared Cast fonts have been loaded in? + // might need a mapping table here of our own. + // textCast->fontId = _vm->_wm->_fontMan->getFontIdByName(_vm->getCurrentScore()->_fontMap[textCast->fontId]); + } + + if (width == 0 || height == 0) { + warning("Frame::renderText(): Requested to draw on an empty surface: %d x %d", width, height); + return; + } + + if (sprite->_editable) { + if (!textCast->_widget) { + warning("Creating MacEditableText with '%s'", toPrintable(textCast->_ftext).c_str()); + textCast->_widget = new Graphics::MacEditableText(score->_window, x, y, width, height, g_director->_wm, textCast->_ftext, new Graphics::MacFont(), 0, 255, width); + warning("Finished creating MacEditableText"); + } + + textCast->_widget->draw(); + + InkType ink = sprite->_ink; + + if (spriteId == score->_currentMouseDownSpriteId) + ink = kInkTypeReverse; + + inkBasedBlit(nullptr, textCast->_widget->getSurface()->rawSurface(), ink, Common::Rect(x, y, x + width, y + height), spriteId); + + return; + } + + debugC(3, kDebugText, "renderText: sprite: %d x: %d y: %d w: %d h: %d fontId: '%d' text: '%s'", spriteId, x, y, width, height, textCast->_fontId, Common::toPrintable(textCast->_ftext).c_str()); + + uint16 boxShadow = (uint16)textCast->_boxShadow; + uint16 borderSize = (uint16)textCast->_borderSize; + if (textRect != NULL) + borderSize = 0; + uint16 padding = (uint16)textCast->_gutterSize; + uint16 textShadow = (uint16)textCast->_textShadow; + + //uint32 rectLeft = textCast->initialRect.left; + //uint32 rectTop = textCast->initialRect.top; + + textCast->_cachedMacText->clip(width); + const Graphics::ManagedSurface *textSurface = textCast->_cachedMacText->getSurface(); + + if (!textSurface) + return; + + height = textSurface->h; + if (textRect != NULL) { + // TODO: this offset could be due to incorrect fonts loaded! + textRect->bottom = height + textCast->_cachedMacText->getLineCount(); + } + + uint16 textX = 0, textY = 0; + + if (textRect == NULL) { + if (borderSize > 0) { + if (_vm->getVersion() <= 3) { + height += (borderSize * 2); + textX += (borderSize + 2); + } else { + height += borderSize; + textX += (borderSize + 1); + } + textY += borderSize; + } else { + x += 1; + } + + if (padding > 0) { + width += padding * 2; + height += padding; + textY += padding / 2; + } + + if (textCast->_textAlign == kTextAlignRight) + textX -= 1; + + if (textShadow > 0) + textX--; + } else { + x++; + if (width % 2 != 0) + x++; + + if (sprite->_spriteType != kCastMemberSprite) { + y += 2; + switch (sprite->_spriteType) { + case kCheckboxSprite: + textX += 16; + break; + case kRadioButtonSprite: + textX += 17; + break; + default: + break; + } + } else { + ButtonType buttonType = ((ButtonCast*)textCast)->_buttonType; + switch (buttonType) { + case kTypeCheckBox: + width += 4; + textX += 16; + break; + case kTypeRadio: + width += 4; + textX += 17; + break; + case kTypeButton: + width += 4; + y += 2; + break; + default: + warning("Frame::renderText(): Expected button but got unexpected button type: %d", buttonType); + y += 2; + break; + } + } + } + + switch (textCast->_textAlign) { + case kTextAlignLeft: + default: + break; + case kTextAlignCenter: + textX = (width / 2) - (textSurface->w / 2) + (padding / 2) + borderSize; + break; + case kTextAlignRight: + textX = width - (textSurface->w + 1) + (borderSize * 2) - (textShadow * 2) - (padding); + break; + } + + Graphics::ManagedSurface textWithFeatures(width + (borderSize * 2) + boxShadow + textShadow, height + borderSize + boxShadow + textShadow); + textWithFeatures.fillRect(Common::Rect(textWithFeatures.w, textWithFeatures.h), score->getStageColor()); + + if (textRect == NULL && boxShadow > 0) { + textWithFeatures.fillRect(Common::Rect(boxShadow, boxShadow, textWithFeatures.w + boxShadow, textWithFeatures.h), 0); + } + + if (textRect == NULL && borderSize != kSizeNone) { + for (int bb = 0; bb < borderSize; bb++) { + Common::Rect borderRect(bb, bb, textWithFeatures.w - bb - boxShadow - textShadow, textWithFeatures.h - bb - boxShadow - textShadow); + textWithFeatures.fillRect(borderRect, 0xff); + textWithFeatures.frameRect(borderRect, 0); + } + } + + if (textShadow > 0) + textWithFeatures.transBlitFrom(textSurface->rawSurface(), Common::Point(textX + textShadow, textY + textShadow), 0xff); + + textWithFeatures.transBlitFrom(textSurface->rawSurface(), Common::Point(textX, textY), 0xff); + + InkType ink = sprite->_ink; + + if (spriteId == score->_currentMouseDownSpriteId) + ink = kInkTypeReverse; + + inkBasedBlit(nullptr, textWithFeatures, ink, Common::Rect(x, y, x + width, y + height), spriteId); +} + +void Score::renderBitmap(uint16 spriteId) { + InkType ink; + Sprite *sprite = _sprites[spriteId]; + + if (spriteId == _vm->getCurrentScore()->_currentMouseDownSpriteId) + ink = kInkTypeReverse; + else + ink = sprite->_ink; + + BitmapCast *bc = (BitmapCast *)sprite->_cast; + Common::Rect drawRect = sprite->_currentBbox; + + inkBasedBlit(nullptr, *(bc->_surface), ink, drawRect, spriteId); +} + +void Score::inkBasedBlit(Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId) { + // drawRect could be bigger than the spriteSurface. Clip it + Common::Rect t(spriteSurface.w, spriteSurface.h); + t.moveTo(drawRect.left, drawRect.top); + bool nullMask = false; + + // combine the given mask with the maskSurface + if (!maskSurface) { + nullMask = true; + maskSurface = new Graphics::ManagedSurface; + maskSurface->create(spriteSurface.w, spriteSurface.h, Graphics::PixelFormat::createFormatCLUT8()); + maskSurface->clear(0); + } + + maskSurface->blitFrom(*_maskSurface, drawRect, Common::Point(0, 0)); + + drawRect.clip(t); + + switch (ink) { + case kInkTypeCopy: + if (maskSurface) + _surface->transBlitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top), *maskSurface); + else + _surface->blitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top)); + break; + case kInkTypeTransparent: + // FIXME: is it always white (last entry in pallette)? + _surface->transBlitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top), _vm->getPaletteColorCount() - 1); + break; + case kInkTypeBackgndTrans: + drawBackgndTransSprite(spriteSurface, drawRect, spriteId); + break; + case kInkTypeMatte: + drawMatteSprite(spriteSurface, drawRect); + break; + case kInkTypeGhost: + drawGhostSprite(spriteSurface, drawRect); + break; + case kInkTypeReverse: + drawReverseSprite(spriteSurface, drawRect, spriteId); + break; + default: + warning("Frame::inkBasedBlit(): Unhandled ink type %d", ink); + _surface->blitFrom(spriteSurface, Common::Point(drawRect.left, drawRect.top)); + break; + } + + if (nullMask) + delete maskSurface; +} + +void Score::drawBackgndTransSprite(const Graphics::Surface &sprite, Common::Rect &drawRect, int spriteId) { + byte skipColor = _sprites[spriteId]->_backColor; + Common::Rect srcRect(sprite.w, sprite.h); + + if (!_surface->clip(srcRect, drawRect)) + return; // Out of screen + + for (int ii = 0; ii < srcRect.height(); ii++) { + const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii); + byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + ii); + + for (int j = 0; j < srcRect.width(); j++) { + if (*src != skipColor) + *dst = *src; + + src++; + dst++; + } + } +} + +void Score::drawGhostSprite(const Graphics::Surface &sprite, Common::Rect &drawRect) { + Common::Rect srcRect(sprite.w, sprite.h); + + if (!_surface->clip(srcRect, drawRect)) + return; // Out of screen + + uint8 skipColor = _vm->getPaletteColorCount() - 1; + for (int ii = 0; ii < srcRect.height(); ii++) { + const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii); + byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + ii); + + for (int j = 0; j < srcRect.width(); j++) { + if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0) && (*src != skipColor)) + *dst = (_vm->getPaletteColorCount() - 1) - *src; // Oposite color + + src++; + dst++; + } + } +} + +void Score::drawReverseSprite(const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId) { + Common::Rect srcRect(sprite.w, sprite.h); + + if (!_surface->clip(srcRect, drawRect)) + return; // Out of screen + + uint8 skipColor = _vm->getPaletteColorCount() - 1; + for (int ii = 0; ii < srcRect.height(); ii++) { + const byte *src = (const byte *)sprite.getBasePtr(srcRect.left, srcRect.top + ii); + byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + ii); + byte srcColor = *src; + + for (int j = 0; j < srcRect.width(); j++) { + if (_sprites[spriteId]->_cast->_type == kCastShape) + srcColor = 0x0; + else + srcColor = *src; + uint16 targetSprite = getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)); + if ((targetSprite != 0)) { + // TODO: This entire reverse colour attempt needs a lot more testing on + // a lot more colour depths. + if (srcColor != skipColor) { + if (_sprites[targetSprite]->_cast->_type != kCastBitmap) { + if (*dst == 0 || *dst == 255) { + *dst = _vm->transformColor(*dst); + } else if (srcColor == 255 || srcColor == 0) { + *dst = _vm->transformColor(*dst - 40); + } else { + *dst = _vm->transformColor(*src - 40); + } + } else { + if (*dst == 0 && _vm->getVersion() == 3 && + ((BitmapCast*)_sprites[spriteId]->_cast)->_bitsPerPixel > 1) { + *dst = _vm->transformColor(*src - 40); + } else { + *dst ^= _vm->transformColor(srcColor); + } + } + } + } else if (srcColor != skipColor) { + *dst = _vm->transformColor(srcColor); + } + src++; + dst++; + } + } +} + +void Score::drawMatteSprite(const Graphics::Surface &sprite, Common::Rect &drawRect) { + // Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent + Graphics::Surface tmp; + tmp.copyFrom(sprite); + Common::Rect srcRect(sprite.w, sprite.h); + + if (!_surface->clip(srcRect, drawRect)) + return; // Out of screen + + // Searching white color in the corners + int whiteColor = -1; + + for (int y = 0; y < tmp.h; y++) { + for (int x = 0; x < tmp.w; x++) { + byte color = *(byte *)tmp.getBasePtr(x, y); + + if (_vm->getPalette()[color * 3 + 0] == 0xff && + _vm->getPalette()[color * 3 + 1] == 0xff && + _vm->getPalette()[color * 3 + 2] == 0xff) { + whiteColor = color; + break; + } + } + } + + if (whiteColor == -1) { + debugC(1, kDebugImages, "Frame::drawMatteSprite(): No white color for Matte image"); + + for (int yy = 0; yy < srcRect.height(); yy++) { + const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy); + byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + yy); + + for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++) + *dst = *src; + } + } else { + Graphics::FloodFill ff(&tmp, whiteColor, 0, true); + + for (int yy = 0; yy < tmp.h; yy++) { + ff.addSeed(0, yy); + ff.addSeed(tmp.w - 1, yy); + } + + for (int xx = 0; xx < tmp.w; xx++) { + ff.addSeed(xx, 0); + ff.addSeed(xx, tmp.h - 1); + } + ff.fillMask(); + + for (int yy = 0; yy < srcRect.height(); yy++) { + const byte *src = (const byte *)tmp.getBasePtr(srcRect.left, srcRect.top + yy); + const byte *mask = (const byte *)ff.getMask()->getBasePtr(srcRect.left, srcRect.top + yy); + byte *dst = (byte *)_surface->getBasePtr(drawRect.left, drawRect.top + yy); + + for (int xx = 0; xx < srcRect.width(); xx++, src++, dst++, mask++) + if (*mask == 0) + *dst = *src; + } + } + + tmp.free(); +} + +uint16 Score::getSpriteIDFromPos(Common::Point pos) { + for (int i = _sprites.size() - 1; i >= 0; i--) + if (_sprites[i]->_currentBbox.contains(pos)) + return i; + + return 0; +} + +bool Score::checkSpriteIntersection(uint16 spriteId, Common::Point pos) { + if (_sprites[spriteId]->_currentBbox.contains(pos)) + return true; + + return false; +} + +Common::Rect *Score::getSpriteRect(uint16 spriteId) { + return &_sprites[spriteId]->_currentBbox; +} + Sprite *Score::getSpriteById(uint16 id) { - if (_currentFrame >= _frames.size() || id >= _frames[_currentFrame]->_sprites.size()) { + if (id >= _sprites.size()) { warning("Score::getSpriteById(%d): out of bounds. frame: %d", id, _currentFrame); return nullptr; } - if (_frames[_currentFrame]->_sprites[id]) { - return _frames[_currentFrame]->_sprites[id]; + if (_sprites[id]) { + return _sprites[id]; } else { warning("Sprite on frame %d width id %d not found", _currentFrame, id); return nullptr; } } +void Score::playSoundChannel(uint16 frameId) { + Frame *frame = _frames[frameId]; + debug(0, "STUB: playSoundChannel(), Sound1 %d Sound2 %d", frame->_sound1, frame->_sound2); +} + void Score::addZoomBox(ZoomBox *box) { _zoomBoxes.push_back(box); } diff --git a/engines/director/score.h b/engines/director/score.h index 4648ff06041..75476300163 100644 --- a/engines/director/score.h +++ b/engines/director/score.h @@ -102,6 +102,10 @@ public: int getCurrentLabelNumber(); int getNextLabelNumber(int referenceFrame); + uint16 getSpriteIDFromPos(Common::Point pos); + bool checkSpriteIntersection(uint16 spriteId, Common::Point pos); + Common::Rect *getSpriteRect(uint16 spriteId); + void addZoomBox(ZoomBox *box); void renderZoomBox(bool redraw = false); bool haveZoomBox() { return !_zoomBoxes.empty(); } @@ -109,9 +113,25 @@ public: int32 getStageColor() { return _stageColor; } Cast *getCastMember(int castId); + void renderFrame(uint16 frameId, bool forceUpdate = false, bool updateStageOnly = false); + void renderSprite(uint16 id); + void unrenderSprite(uint16 spriteId); private: void update(); + void renderText(uint16 spriteId, Common::Rect *textSize); + void renderShape(uint16 spriteId); + void renderButton(uint16 spriteId); + void renderBitmap(uint16 spriteId); + + void inkBasedBlit(Graphics::ManagedSurface *maskSurface, const Graphics::Surface &spriteSurface, InkType ink, Common::Rect drawRect, uint spriteId); + void drawBackgndTransSprite(const Graphics::Surface &sprite, Common::Rect &drawRect, int spriteId); + void drawMatteSprite(const Graphics::Surface &sprite, Common::Rect &drawRect); + void drawGhostSprite(const Graphics::Surface &sprite, Common::Rect &drawRect); + void drawReverseSprite(const Graphics::Surface &sprite, Common::Rect &drawRect, uint16 spriteId); + + void playSoundChannel(uint16 frameId); + void readVersion(uint32 rid); void loadPalette(Common::SeekableSubReadStreamEndian &stream); void loadFrames(Common::SeekableSubReadStreamEndian &stream); @@ -128,6 +148,7 @@ private: public: Common::Array _frames; + Common::Array _sprites; Common::HashMap _castsInfo; Common::HashMap _castsNames; Common::SortedArray