Example #1
0
/**
 * Returns a new stamp where all variations have been rotated in the given
 * \a direction.
 */
TileStamp TileStamp::rotated(RotateDirection direction) const
{
    TileStamp rotated(*this);
    rotated.d.detach();

    for (const TileStampVariation &variation : rotated.variations()) {
        const QRect mapRect(QPoint(), variation.map->size());
        QSize rotatedSize;

        for (auto layer : variation.map->tileLayers()) {
            TileLayer *tileLayer = static_cast<TileLayer*>(layer);

            // Synchronize tile layer size to map size (assumes map contains all layers)
            if (tileLayer->rect() != mapRect) {
                tileLayer->resize(mapRect.size(), tileLayer->position());
                tileLayer->setPosition(0, 0);
            }

            if (variation.map->orientation() == Map::Hexagonal)
                tileLayer->rotateHexagonal(direction, variation.map);
            else
                tileLayer->rotate(direction);

            rotatedSize = tileLayer->size();
        }

        variation.map->setWidth(rotatedSize.width());
        variation.map->setHeight(rotatedSize.height());
    }

    return rotated;
}
Example #2
0
/**
 * Returns a new stamp where all variations have been flipped in the given
 * \a direction.
 */
TileStamp TileStamp::flipped(FlipDirection direction) const
{
    TileStamp flipped(*this);
    flipped.d.detach();

    for (const TileStampVariation &variation : flipped.variations()) {
        const QRect mapRect(QPoint(), variation.map->size());

        for (auto layer : variation.map->tileLayers()) {
            TileLayer *tileLayer = static_cast<TileLayer*>(layer);

            // Synchronize tile layer size to map size (assumes map contains all layers)
            if (tileLayer->rect() != mapRect) {
                tileLayer->resize(mapRect.size(), tileLayer->position());
                tileLayer->setPosition(0, 0);
            }

            if (variation.map->orientation() == Map::Hexagonal)
                tileLayer->flipHexagonal(direction);
            else
                tileLayer->flip(direction);
        }

        if (variation.map->isStaggered()) {
            Map::StaggerAxis staggerAxis = variation.map->staggerAxis();

            if (staggerAxis == Map::StaggerY) {
                if ((direction == FlipVertically && !(variation.map->height() & 1)) || direction == FlipHorizontally)
                    variation.map->invertStaggerIndex();

            } else {
                if ((direction == FlipHorizontally && !(variation.map->width() & 1)) || direction == FlipVertically)
                    variation.map->invertStaggerIndex();
            }
        }
    }

    return flipped;
}
Example #3
0
void BucketFillTool::tilePositionChanged(const QPoint &tilePos)
{
    AbstractTileFillTool::tilePositionChanged(tilePos);

    if (isCapturing())
        return;

    // Skip filling if the stamp is empty and not in wangFill mode
    if (mStamp.isEmpty() && mFillMethod != WangFill)
        return;

    // Make sure that a tile layer is selected
    TileLayer *tileLayer = currentTileLayer();
    if (!tileLayer)
        return;

    bool shiftPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier;
    bool fillRegionChanged = false;

    TilePainter regionComputer(mapDocument(), tileLayer);

    // This clears the connections so we don't get callbacks
    clearConnections(mapDocument());

    // Optimization: we don't need to recalculate the fill area
    // if the new mouse position is still over the filled region
    // and the shift modifier hasn't changed.
    if (!mFillRegion.contains(tilePos) || shiftPressed != mLastShiftStatus) {

        // Clear overlay to make way for a new one
        AbstractTileFillTool::clearOverlay();

        // Cache information about how the fill region was created
        mLastShiftStatus = shiftPressed;

        // Get the new fill region
        if (!shiftPressed) {
            // If not holding shift, a region is computed from the current pos
            bool computeRegion = true;

            // If the stamp is a single tile, ignore that tile when making the region
            if (mFillMethod != WangFill && mStamp.variations().size() == 1) {
                const TileStampVariation &variation = mStamp.variations().first();
                TileLayer *stampLayer = variation.tileLayer();
                if (stampLayer->size() == QSize(1, 1) &&
                        stampLayer->cellAt(0, 0) == regionComputer.cellAt(tilePos))
                    computeRegion = false;
            }

            if (computeRegion)
                mFillRegion = regionComputer.computePaintableFillRegion(tilePos);
        } else {
            // If holding shift, the region is the selection bounds
            mFillRegion = mapDocument()->selectedArea();

            // Fill region is the whole map if there is no selection
            if (mFillRegion.isEmpty())
                mFillRegion = tileLayer->rect();

            // The mouse needs to be in the region
            if (!mFillRegion.contains(tilePos))
                mFillRegion = QRegion();
        }
        fillRegionChanged = true;
    }

    // Ensure that a fill region was created before making an overlay layer
    if (mFillRegion.isEmpty())
        return;

    if (mLastFillMethod != mFillMethod) {
        mLastFillMethod = mFillMethod;
        fillRegionChanged = true;
    }

    if (!mFillOverlay) {
        // Create a new overlay region
        const QRect fillBounds = mFillRegion.boundingRect();
        mFillOverlay = SharedTileLayer::create(QString(),
                                               fillBounds.x(),
                                               fillBounds.y(),
                                               fillBounds.width(),
                                               fillBounds.height());
    }

    // Paint the new overlay
    switch (mFillMethod) {
    case TileFill:
        if (fillRegionChanged || mStamp.variations().size() > 1) {
            fillWithStamp(*mFillOverlay, mStamp,
                          mFillRegion.translated(-mFillOverlay->position()));
            fillRegionChanged = true;
        }
        break;
    case RandomFill:
        randomFill(*mFillOverlay, mFillRegion);
        fillRegionChanged = true;
        break;
    case WangFill:
        wangFill(*mFillOverlay, *tileLayer, mFillRegion);
        fillRegionChanged = true;
        break;
    }

    if (fillRegionChanged) {
        // Update the brush item to draw the overlay
        brushItem()->setTileLayer(mFillOverlay);
    }
    // Create connections to know when the overlay should be cleared
    makeConnections();
}
Example #4
0
/**
 * Paints the tile layers present in the given \a map onto this map. Matches
 * layers by name and creates new layers when they could not be found.
 *
 * In case the \a map only contains a single tile layer, it is always painted
 * into the current tile layer. This happens also for unnamed layers. In these
 * cases, the layers are skipped when the current layer isn't a tile layer.
 *
 * If the matched target layer is locked it is also skipped.
 *
 * \a mergeable indicates whether the paint operations performed by this
 * function are mergeable with previous compatible paint operations.
 *
 * If \a missingTilesets is given, the listed tilesets will be added to the map
 * on the first paint operation. The list will then be cleared.
 *
 * If \a paintedRegions is given, then no regionEdited signal is emitted.
 * In this case it is the responsibility of the caller to emit this signal for
 * each affected tile layer.
 */
void MapDocument::paintTileLayers(const Map *map, bool mergeable,
                                  QVector<SharedTileset> *missingTilesets,
                                  QHash<TileLayer*, QRegion> *paintedRegions)
{
    TileLayer *currentTileLayer = mCurrentLayer ? mCurrentLayer->asTileLayer() : nullptr;

    LayerIterator it(map, Layer::TileLayerType);
    const bool isMultiLayer = it.next() && it.next();

    it.toFront();
    while (auto tileLayer = static_cast<TileLayer*>(it.next())) {
        TileLayer *targetLayer = currentTileLayer;
        bool addLayer = false;

        // When the map contains only a single layer, always paint it into
        // the current layer. This makes sure you can still take pieces from
        // one layer and draw them into another.
        if (isMultiLayer && !tileLayer->name().isEmpty()) {
            targetLayer = static_cast<TileLayer*>(mMap->findLayer(tileLayer->name(), Layer::TileLayerType));
            if (!targetLayer) {
                // Create a layer with this name
                targetLayer = new TileLayer(tileLayer->name(), 0, 0,
                                            mMap->width(),
                                            mMap->height());
                addLayer = true;
            }
        }

        if (!targetLayer)
            continue;
        if (!targetLayer->isUnlocked())
            continue;
        if (!mMap->infinite() && !targetLayer->rect().intersects(tileLayer->bounds()))
            continue;

        PaintTileLayer *paint = new PaintTileLayer(this,
                                                   targetLayer,
                                                   tileLayer->x(),
                                                   tileLayer->y(),
                                                   tileLayer);

        if (missingTilesets && !missingTilesets->isEmpty()) {
            for (const SharedTileset &tileset : *missingTilesets) {
                if (!mMap->tilesets().contains(tileset))
                    new AddTileset(this, tileset, paint);
            }

            missingTilesets->clear();
        }

        if (addLayer) {
            new AddLayer(this,
                         mMap->layerCount(), targetLayer, nullptr,
                         paint);
        }

        paint->setMergeable(mergeable);
        undoStack()->push(paint);

        const QRegion editedRegion = tileLayer->region();
        if (paintedRegions)
            (*paintedRegions)[targetLayer] |= editedRegion;
        else
            emit regionEdited(editedRegion, targetLayer);

        mergeable = true; // further paints are always mergeable
    }
}
Example #5
0
void BucketFillTool::tilePositionChanged(const QPoint &tilePos)
{
    AbstractTileFillTool::tilePositionChanged(tilePos);

    if (isCapturing())
        return;

    // Skip filling if the stamp is empty and not in wangFill mode
    if (mStamp.isEmpty() && mFillMethod != WangFill)
        return;

    // Make sure that a tile layer is selected
    TileLayer *tileLayer = currentTileLayer();
    if (!tileLayer)
        return;

    bool shiftPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier;
    bool fillRegionChanged = false;

    TilePainter regionComputer(mapDocument(), tileLayer);

    // This clears the connections so we don't get callbacks
    clearConnections(mapDocument());

    // Optimization: we don't need to recalculate the fill area
    // if the new mouse position is still over the filled region
    // and the shift modifier hasn't changed.
    if (!mFillRegion.contains(tilePos) || shiftPressed != mLastShiftStatus) {

        // Clear overlay to make way for a new one
        AbstractTileFillTool::clearOverlay();

        // Cache information about how the fill region was created
        mLastShiftStatus = shiftPressed;

        // Get the new fill region
        if (!shiftPressed) {
            // If not holding shift, a region is computed from the current pos
            bool computeRegion = true;

            // If the stamp is a single layer with a single tile, ignore that tile when making the region
            if (mFillMethod != WangFill && mStamp.variations().size() == 1) {
                const TileStampVariation &variation = mStamp.variations().first();
                if (variation.map->layerCount() == 1) {
                    auto stampLayer = static_cast<TileLayer*>(variation.map->layerAt(0));
                    if (stampLayer->size() == QSize(1, 1) &&
                            stampLayer->cellAt(0, 0) == regionComputer.cellAt(tilePos))
                        computeRegion = false;
                }
            }

            if (computeRegion)
                mFillRegion = regionComputer.computePaintableFillRegion(tilePos);
        } else {
            // If holding shift, the region is the selection bounds
            mFillRegion = mapDocument()->selectedArea();

            // Fill region is the whole map if there is no selection
            if (mFillRegion.isEmpty())
                mFillRegion = tileLayer->rect();

            // The mouse needs to be in the region
            if (!mFillRegion.contains(tilePos))
                mFillRegion = QRegion();
        }
        fillRegionChanged = true;
    }

    // Ensure that a fill region was created before making an overlay layer
    if (mFillRegion.isEmpty())
        return;

    if (mLastFillMethod != mFillMethod) {
        mLastFillMethod = mFillMethod;
        fillRegionChanged = true;
    }

    bool hasRandom = mFillMethod == RandomFill || mFillMethod == WangFill;
    if (mFillMethod == TileFill)
        hasRandom = mStamp.variations().size() > 1;

    if (fillRegionChanged || hasRandom)
        updatePreview(mFillRegion);

    // Create connections to know when the overlay should be cleared
    makeConnections();
}