void EditPolygonTool::startMoving() { // Move only the clicked handle, if it was not part of the selection if (!mSelectedHandles.contains(mClickedHandle)) setSelectedHandle(mClickedHandle); mMode = Moving; MapRenderer *renderer = mapDocument()->renderer(); // Remember the current object positions mOldHandlePositions.clear(); mOldPolygons.clear(); mAlignPosition = renderer->screenToPixelCoords((*mSelectedHandles.begin())->pos()); const auto &selectedHandles = mSelectedHandles; for (PointHandle *handle : selectedHandles) { const QPointF pos = renderer->screenToPixelCoords(handle->pos()); mOldHandlePositions.append(handle->pos()); if (pos.x() < mAlignPosition.x()) mAlignPosition.setX(pos.x()); if (pos.y() < mAlignPosition.y()) mAlignPosition.setY(pos.y()); MapObject *mapObject = handle->mapObject(); if (!mOldPolygons.contains(mapObject)) mOldPolygons.insert(mapObject, mapObject->polygon()); } }
void MapObjectItem::syncWithMapObject() { // Update the whole object when the name or type has changed if (mObject->name() != mName || mObject->type() != mType) { mName = mObject->name(); mType = mObject->type(); update(); mResizeHandle->update(); } QString toolTip = mName; if (!mType.isEmpty()) toolTip += QLatin1String(" (") + mType + QLatin1String(")"); setToolTip(toolTip); MapRenderer *renderer = mMapDocument->renderer(); const QPointF pixelPos = renderer->tileToPixelCoords(mObject->position()); QRectF bounds = renderer->boundingRect(mObject); bounds.translate(-pixelPos); mSyncing = true; setPos(pixelPos); if (mBoundingRect != bounds) { // Notify the graphics scene about the geometry change in advance prepareGeometryChange(); mBoundingRect = bounds; const QPointF bottomRight = mObject->bounds().bottomRight(); const QPointF handlePos = renderer->tileToPixelCoords(bottomRight); mResizeHandle->setPos(handlePos - pixelPos); } mSyncing = false; }
QVariant ResizeHandle::itemChange(GraphicsItemChange change, const QVariant &value) { if (!mMapObjectItem->mSyncing) { MapRenderer *renderer = mMapObjectItem->mapDocument()->renderer(); if (change == ItemPositionChange) { // Calculate the absolute pixel position const QPointF itemPos = mMapObjectItem->pos(); QPointF pixelPos = value.toPointF() + itemPos; // Calculate the new coordinates in tiles QPointF tileCoords = renderer->pixelToTileCoords(pixelPos); const QPointF objectPos = mMapObjectItem->mapObject()->position(); tileCoords -= objectPos; tileCoords.setX(qMax(tileCoords.x(), qreal(0))); tileCoords.setY(qMax(tileCoords.y(), qreal(0))); if (QApplication::keyboardModifiers() & Qt::ControlModifier) tileCoords = tileCoords.toPoint(); tileCoords += objectPos; return renderer->tileToPixelCoords(tileCoords) - itemPos; } else if (change == ItemPositionHasChanged) { // Update the size of the map object const QPointF newPos = value.toPointF() + mMapObjectItem->pos(); QPointF tileCoords = renderer->pixelToTileCoords(newPos); tileCoords -= mMapObjectItem->mapObject()->position(); mMapObjectItem->resize(QSizeF(tileCoords.x(), tileCoords.y())); } } return QGraphicsItem::itemChange(change, value); }
void ImageMovementTool::mouseMoved(const QPointF &pos, Qt::KeyboardModifiers modifiers) { AbstractImageTool::mouseMoved(pos, modifiers); if (mMousePressed) { if (ImageLayer *layer = currentImageLayer()) { QPointF diff = pos - mMouseStart; bool snapToGrid = Preferences::instance()->snapToGrid(); bool snapToFineGrid = Preferences::instance()->snapToFineGrid(); if (modifiers & Qt::ControlModifier) { snapToGrid = !snapToGrid; snapToFineGrid = false; } if (snapToGrid || snapToFineGrid) { MapRenderer *renderer = mapDocument()->renderer(); int scale = snapToFineGrid ? Preferences::instance()->gridFine() : 1; const QPointF alignScreenPos = renderer->pixelToScreenCoords(mLayerStart); const QPointF newAlignPixelPos = alignScreenPos + diff; // Snap the position to the grid QPointF newTileCoords = (renderer->screenToTileCoords(newAlignPixelPos) * scale).toPoint(); newTileCoords /= scale; diff = renderer->tileToScreenCoords(newTileCoords) - alignScreenPos; } layer->setPosition(mLayerStart + diff.toPoint()); mapDocument()->emitImageLayerChanged(layer); } } }
void ImageLayerItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) { // TODO: Display a border around the layer when selected MapRenderer *renderer = mMapDocument->renderer(); renderer->drawImageLayer(painter, imageLayer(), option->exposedRect); }
/** * Creates and removes handle instances as necessary to adapt to a new object * selection. */ void EditPolygonTool::updateHandles() { const QSet<MapObjectItem*> &selection = mapScene()->selectedObjectItems(); // First destroy the handles for objects that are no longer selected QMutableMapIterator<MapObjectItem*, QList<PointHandle*> > i(mHandles); while (i.hasNext()) { i.next(); if (!selection.contains(i.key())) { const auto &handles = i.value(); for (PointHandle *handle : handles) { if (handle->isSelected()) mSelectedHandles.remove(handle); delete handle; } i.remove(); } } MapRenderer *renderer = mapDocument()->renderer(); for (MapObjectItem *item : selection) { const MapObject *object = item->mapObject(); if (!object->cell().isEmpty()) continue; QPolygonF polygon = object->polygon(); polygon.translate(object->position()); QList<PointHandle*> pointHandles = mHandles.value(item); // Create missing handles while (pointHandles.size() < polygon.size()) { PointHandle *handle = new PointHandle(item, pointHandles.size()); pointHandles.append(handle); mapScene()->addItem(handle); } // Remove superfluous handles while (pointHandles.size() > polygon.size()) { PointHandle *handle = pointHandles.takeLast(); if (handle->isSelected()) mSelectedHandles.remove(handle); delete handle; } // Update the position of all handles for (int i = 0; i < pointHandles.size(); ++i) { const QPointF &point = polygon.at(i); const QPointF handlePos = renderer->pixelToScreenCoords(point); const QPointF internalHandlePos = handlePos - item->pos(); pointHandles.at(i)->setPos(item->mapToScene(internalHandlePos)); } mHandles.insert(item, pointHandles); } }
void TileSelectionItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) { const QRegion &selection = mMapDocument->selectedArea(); QColor highlight = QApplication::palette().highlight().color(); highlight.setAlpha(128); MapRenderer *renderer = mMapDocument->renderer(); renderer->drawTileSelection(painter, selection, highlight, option->exposedRect); }
void MapScene::setMapDocument(MapDocument *mapDocument) { if (mMapDocument) { mMapDocument->disconnect(this); if (!mSelectedObjectItems.isEmpty()) { mSelectedObjectItems.clear(); emit selectedObjectItemsChanged(); } } mMapDocument = mapDocument; if (mMapDocument) { MapRenderer *renderer = mMapDocument->renderer(); renderer->setObjectLineWidth(mObjectLineWidth); renderer->setFlag(ShowTileObjectOutlines, mShowTileObjectOutlines); connect(mMapDocument, SIGNAL(mapChanged()), this, SLOT(mapChanged())); connect(mMapDocument, &MapDocument::regionChanged, this, &MapScene::repaintRegion); connect(mMapDocument, SIGNAL(tileLayerDrawMarginsChanged(TileLayer*)), this, SLOT(tileLayerDrawMarginsChanged(TileLayer*))); connect(mMapDocument, SIGNAL(layerAdded(int)), this, SLOT(layerAdded(int))); connect(mMapDocument, SIGNAL(layerRemoved(int)), this, SLOT(layerRemoved(int))); connect(mMapDocument, SIGNAL(layerChanged(int)), this, SLOT(layerChanged(int))); connect(mMapDocument, SIGNAL(objectGroupChanged(ObjectGroup*)), this, SLOT(objectGroupChanged(ObjectGroup*))); connect(mMapDocument, SIGNAL(imageLayerChanged(ImageLayer*)), this, SLOT(imageLayerChanged(ImageLayer*))); connect(mMapDocument, SIGNAL(currentLayerIndexChanged(int)), this, SLOT(currentLayerIndexChanged())); connect(mMapDocument, SIGNAL(tilesetTileOffsetChanged(Tileset*)), this, SLOT(tilesetTileOffsetChanged(Tileset*))); connect(mMapDocument, SIGNAL(objectsInserted(ObjectGroup*,int,int)), this, SLOT(objectsInserted(ObjectGroup*,int,int))); connect(mMapDocument, SIGNAL(objectsRemoved(QList<MapObject*>)), this, SLOT(objectsRemoved(QList<MapObject*>))); connect(mMapDocument, SIGNAL(objectsChanged(QList<MapObject*>)), this, SLOT(objectsChanged(QList<MapObject*>))); connect(mMapDocument, SIGNAL(objectsIndexChanged(ObjectGroup*,int,int)), this, SLOT(objectsIndexChanged(ObjectGroup*,int,int))); connect(mMapDocument, SIGNAL(selectedObjectsChanged()), this, SLOT(updateSelectedObjectItems())); } refreshScene(); }
void MapObjectItem::syncWithMapObject() { // Update the whole object when the name or polygon has changed if (mObject->name() != mName || mObject->polygon() != mPolygon) { mName = mObject->name(); mPolygon = mObject->polygon(); update(); } const QColor color = objectColor(mObject); if (mColor != color) { mColor = color; update(); mResizeHandle->update(); } QString toolTip = mName; const QString &type = mObject->type(); if (!type.isEmpty()) toolTip += QLatin1String(" (") + type + QLatin1String(")"); setToolTip(toolTip); MapRenderer *renderer = mMapDocument->renderer(); const QPointF pixelPos = renderer->tileToPixelCoords(mObject->position()); QRectF bounds = renderer->boundingRect(mObject); bounds.translate(-pixelPos); setPos(pixelPos); setZValue(pixelPos.y()); setRotation(mObject->rotation()); setTransformOriginPoint(objectCenter()); mSyncing = true; if (mBoundingRect != bounds) { // Notify the graphics scene about the geometry change in advance prepareGeometryChange(); mBoundingRect = bounds; const QPointF bottomRight = mObject->bounds().bottomRight(); const QPointF handlePos = renderer->tileToPixelCoords(bottomRight); mResizeHandle->setPos(handlePos - pixelPos); } mSyncing = false; setVisible(mObject->isVisible()); }
static void extendMapRect(QRect &mapBoundingRect, const MapRenderer &renderer) { // Start with the basic map size QRectF rect(mapBoundingRect); // Take into account large tiles extending beyond their cell for (const Layer *layer : renderer.map()->tileLayers()) { const TileLayer *tileLayer = static_cast<const TileLayer*>(layer); const QPointF offset = tileLayer->totalOffset(); for (int y = 0; y < tileLayer->height(); ++y) { for (int x = 0; x < tileLayer->width(); ++x) { const Cell &cell = tileLayer->cellAt(x, y); if (!cell.isEmpty()) { QRectF r = cellRect(renderer, cell, QPointF(x, y)); r.translate(offset); rect |= r; } } } } mapBoundingRect = rect.toAlignedRect(); }
void MapObjectItem::syncWithMapObject() { const QColor color = mObject->effectiveColor(); // Update the whole object when the name, polygon or color has changed if (mPolygon != mObject->polygon() || mColor != color) { mPolygon = mObject->polygon(); mColor = color; update(); } QString toolTip = mObject->name(); const QString &type = mObject->type(); if (!type.isEmpty()) toolTip += QLatin1String(" (") + type + QLatin1String(")"); setToolTip(toolTip); MapRenderer *renderer = mMapDocument->renderer(); const QPointF pixelPos = renderer->pixelToScreenCoords(mObject->position()); QRectF bounds = renderer->boundingRect(mObject); bounds.translate(-pixelPos); if (renderer->flags().testFlag(ShowTileCollisionShapes)) expandBoundsToCoverTileCollisionObjects(bounds); setPos(pixelPos); setRotation(mObject->rotation()); if (ObjectGroup *objectGroup = mObject->objectGroup()) { if (objectGroup->drawOrder() == ObjectGroup::TopDownOrder) setZValue(pixelPos.y()); if (mIsHoveredIndicator) { auto totalOffset = objectGroup->totalOffset(); setTransform(QTransform::fromTranslate(totalOffset.x(), totalOffset.y())); } } if (mBoundingRect != bounds) { // Notify the graphics scene about the geometry change in advance prepareGeometryChange(); mBoundingRect = bounds; } setVisible(mObject->isVisible()); }
void EditPolygonTool::updateMovingItems(const QPointF &pos, Qt::KeyboardModifiers modifiers) { MapRenderer *renderer = mapDocument()->renderer(); QPointF diff = pos - mStart; SnapHelper snapHelper(renderer, modifiers); if (snapHelper.snaps()) { const QPointF alignScreenPos = renderer->pixelToScreenCoords(mAlignPosition); const QPointF newAlignScreenPos = alignScreenPos + diff; QPointF newAlignPixelPos = renderer->screenToPixelCoords(newAlignScreenPos); snapHelper.snap(newAlignPixelPos); diff = renderer->pixelToScreenCoords(newAlignPixelPos) - alignScreenPos; } const auto &selectedHandles = mSelectedHandles; int i = 0; for (PointHandle *handle : selectedHandles) { // update handle position const QPointF newScreenPos = mOldHandlePositions.at(i) + diff; handle->setPos(newScreenPos); // calculate new pixel position of polygon node const MapObjectItem *item = handle->mapObjectItem(); const QPointF newInternalPos = item->mapFromScene(newScreenPos); const QPointF newScenePos = item->pos() + newInternalPos; const QPointF newPixelPos = renderer->screenToPixelCoords(newScenePos); // update the polygon MapObject *mapObject = item->mapObject(); QPolygonF polygon = mapObject->polygon(); polygon[handle->pointIndex()] = newPixelPos - mapObject->position(); mapDocument()->mapObjectModel()->setObjectPolygon(mapObject, polygon); ++i; } }
QVariant MapObjectItem::itemChange(GraphicsItemChange change, const QVariant &value) { if (!mSyncing) { MapRenderer *renderer = mMapDocument->renderer(); if (change == ItemPositionChange && (QApplication::keyboardModifiers() & Qt::ControlModifier)) { const QPointF pixelDiff = value.toPointF() - mOldItemPos; const QPointF newPixelPos = renderer->tileToPixelCoords(mOldObjectPos) + pixelDiff; // Snap the position to the grid const QPointF newTileCoords = renderer->pixelToTileCoords(newPixelPos).toPoint(); return renderer->tileToPixelCoords(newTileCoords); } else if (change == ItemPositionHasChanged) { // Update the position of the map object const QPointF pixelDiff = value.toPointF() - mOldItemPos; const QPointF newPixelPos = renderer->tileToPixelCoords(mOldObjectPos) + pixelDiff; mObject->setPosition(renderer->pixelToTileCoords(newPixelPos)); } } return QGraphicsItem::itemChange(change, value); }
static QRectF cellRect(const MapRenderer &renderer, const Cell &cell, const QPointF &tileCoords) { const Tile *tile = cell.tile(); if (!tile) return QRectF(); QPointF pixelCoords = renderer.tileToScreenCoords(tileCoords); QPointF offset = tile->offset(); QSize size = tile->size(); if (cell.flippedAntiDiagonally()) std::swap(size.rwidth(), size.rheight()); // This is a correction needed because tileToScreenCoords does not return // the bottom-left origin of the tile image, but rather the top-left // corner of the cell. pixelCoords.ry() += renderer.map()->tileHeight() - size.height(); return QRectF(pixelCoords, size).translated(offset); }
void SaveAsImageDialog::accept() { const QString fileName = ui->name->text(); if (fileName.isEmpty()) return; if (QFile::exists(fileName)) { const QMessageBox::StandardButton button = QMessageBox::warning(this, tr("Save as Image"), tr("%1 already exists.\n" "Do you want to replace it?") .arg(QFileInfo(fileName).fileName()), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (button != QMessageBox::Yes) return; } const bool visibleLayersOnly = ui->visibleLayers->isChecked(); const bool useCurrentScale = ui->currentZoomLevel->isChecked(); const bool drawTileGrid = ui->showGrid->isChecked(); MapRenderer *renderer = mMapEditor->renderer(); //remember the current render flags const Aurora::RenderFlags renderFlags = renderer->flags(); renderer->setFlag(ShowTileObjectOutlines, false); QSize mapSize = renderer->mapSize(); if (useCurrentScale) mapSize *= mCurrentScale; QImage image(mapSize, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter painter(&image); if (useCurrentScale && mCurrentScale != qreal(1)) { painter.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing); painter.setTransform(QTransform::fromScale(mCurrentScale, mCurrentScale)); } foreach (const Layer *layer, mMapEditor->map()->layers()) { if (visibleLayersOnly && !layer->isVisible()) continue; painter.setOpacity(layer->opacity()); const Tilelayer *tilelayer = dynamic_cast<const Tilelayer*>(layer); if (tilelayer) { renderer->drawTilelayer(&painter, tilelayer); } } if (drawTileGrid) { renderer->drawGrid(&painter, QRectF(QPointF(), renderer->mapSize())); } //restore the previous render flags renderer->setFlags(renderFlags); image.save(fileName); mPath = QFileInfo(fileName).path(); //store settings for next time QSettings *s = Preferences::instance()->settings(); s->setValue(QLatin1String(VISIBLE_ONLY_KEY), visibleLayersOnly); s->setValue(QLatin1String(CURRENT_SCALE_KEY), useCurrentScale); s->setValue(QLatin1String(DRAW_GRID_KEY), drawTileGrid); QDialog::accept(); }
void SaveAsImageDialog::accept() { const QString fileName = mUi->fileNameEdit->text(); if (fileName.isEmpty()) return; if (QFile::exists(fileName)) { const QMessageBox::StandardButton button = QMessageBox::warning(this, tr("Save as Image"), tr("%1 already exists.\n" "Do you want to replace it?") .arg(QFileInfo(fileName).fileName()), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (button != QMessageBox::Yes) return; } const bool visibleLayersOnly = mUi->visibleLayersOnly->isChecked(); const bool useCurrentScale = mUi->currentZoomLevel->isChecked(); const bool drawTileGrid = mUi->drawTileGrid->isChecked(); MapRenderer *renderer = mMapDocument->renderer(); QSize mapSize = renderer->mapSize(); if (useCurrentScale) mapSize *= mCurrentScale; QImage image(mapSize, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter painter(&image); if (useCurrentScale && mCurrentScale != qreal(1)) { painter.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing); painter.setTransform(QTransform::fromScale(mCurrentScale, mCurrentScale)); } foreach (const Layer *layer, mMapDocument->map()->layers()) { if (visibleLayersOnly && !layer->isVisible()) continue; painter.setOpacity(layer->opacity()); const TileLayer *tileLayer = dynamic_cast<const TileLayer*>(layer); const ObjectGroup *objGroup = dynamic_cast<const ObjectGroup*>(layer); if (tileLayer) { renderer->drawTileLayer(&painter, tileLayer); } else if (objGroup) { QColor color = objGroup->color(); if (!color.isValid()) color = Qt::gray; // TODO: Support colors for different object types foreach (const MapObject *object, objGroup->objects()) renderer->drawMapObject(&painter, object, color); } } if (drawTileGrid) { QVector<GridStyle> gridStyles = QVector<GridStyle>() << GridStyle(1, QColor(Qt::black), Qt::CustomDashLine); renderer->drawGrid(&painter, QRectF(QPointF(), renderer->mapSize()), gridStyles); } image.save(fileName); mPath = QFileInfo(fileName).path(); // Store settings for next time QSettings *s = Preferences::instance()->settings(); s->setValue(QLatin1String(VISIBLE_ONLY_KEY), visibleLayersOnly); s->setValue(QLatin1String(CURRENT_SCALE_KEY), useCurrentScale); s->setValue(QLatin1String(DRAW_GRID_KEY), drawTileGrid); QDialog::accept(); }
void ExportAsImageDialog::accept() { const QString fileName = mUi->fileNameEdit->text(); if (fileName.isEmpty()) return; if (QFile::exists(fileName)) { const QMessageBox::StandardButton button = QMessageBox::warning(this, tr("Export as Image"), tr("%1 already exists.\n" "Do you want to replace it?") .arg(QFileInfo(fileName).fileName()), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (button != QMessageBox::Yes) return; } const bool visibleLayersOnly = mUi->visibleLayersOnly->isChecked(); const bool useCurrentScale = mUi->currentZoomLevel->isChecked(); const bool drawTileGrid = mUi->drawTileGrid->isChecked(); const bool includeBackgroundColor = mUi->includeBackgroundColor->isChecked(); MapRenderer *renderer = mMapDocument->renderer(); // Remember the current render flags const Tiled::RenderFlags renderFlags = renderer->flags(); renderer->setFlag(ShowTileObjectOutlines, false); QSize mapSize = renderer->mapSize(); QMargins margins = mMapDocument->map()->computeLayerOffsetMargins(); mapSize.setWidth(mapSize.width() + margins.left() + margins.right()); mapSize.setHeight(mapSize.height() + margins.top() + margins.bottom()); if (useCurrentScale) mapSize *= mCurrentScale; QImage image; try { image = QImage(mapSize, QImage::Format_ARGB32_Premultiplied); if (includeBackgroundColor) { if (mMapDocument->map()->backgroundColor().isValid()) image.fill(mMapDocument->map()->backgroundColor()); else image.fill(Qt::gray); } else { image.fill(Qt::transparent); } } catch (const std::bad_alloc &) { QMessageBox::critical(this, tr("Out of Memory"), tr("Could not allocate sufficient memory for the image. " "Try reducing the zoom level or using a 64-bit version of Tiled.")); return; } if (image.isNull()) { const size_t gigabyte = 1073741824; const size_t memory = size_t(mapSize.width()) * size_t(mapSize.height()) * 4; const double gigabytes = (double) memory / gigabyte; QMessageBox::critical(this, tr("Image too Big"), tr("The resulting image would be %1 x %2 pixels and take %3 GB of memory. " "Tiled is unable to create such an image. Try reducing the zoom level.") .arg(mapSize.width()) .arg(mapSize.height()) .arg(gigabytes, 0, 'f', 2)); return; } QPainter painter(&image); if (useCurrentScale) { if (smoothTransform(mCurrentScale)) painter.setRenderHints(QPainter::SmoothPixmapTransform); painter.setTransform(QTransform::fromScale(mCurrentScale, mCurrentScale)); renderer->setPainterScale(mCurrentScale); } else { renderer->setPainterScale(1); } painter.translate(margins.left(), margins.top()); foreach (const Layer *layer, mMapDocument->map()->layers()) { if (visibleLayersOnly && !layer->isVisible()) continue; painter.setOpacity(layer->opacity()); painter.translate(layer->offset()); const TileLayer *tileLayer = dynamic_cast<const TileLayer*>(layer); const ObjectGroup *objGroup = dynamic_cast<const ObjectGroup*>(layer); const ImageLayer *imageLayer = dynamic_cast<const ImageLayer*>(layer); if (tileLayer) { renderer->drawTileLayer(&painter, tileLayer); } else if (objGroup) { QList<MapObject*> objects = objGroup->objects(); if (objGroup->drawOrder() == ObjectGroup::TopDownOrder) qStableSort(objects.begin(), objects.end(), objectLessThan); foreach (const MapObject *object, objects) { if (object->isVisible()) { if (object->rotation() != qreal(0)) { QPointF origin = renderer->pixelToScreenCoords(object->position()); painter.save(); painter.translate(origin); painter.rotate(object->rotation()); painter.translate(-origin); } const QColor color = MapObjectItem::objectColor(object); renderer->drawMapObject(&painter, object, color); if (object->rotation() != qreal(0)) painter.restore(); } } } else if (imageLayer) { renderer->drawImageLayer(&painter, imageLayer); } painter.translate(-layer->offset()); }