/** * 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; }
/** * 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; }
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(); }
/** * 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 } }
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(); }