From c83474b6bc083046842f3037e94d9ea7d70a095f Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 12 Nov 2024 01:09:43 -0500 Subject: [PATCH] redesign layout dimension change window --- forms/resizelayoutpopup.ui | 276 +++++++++++++++++++++++++++++++++ include/core/editcommands.h | 11 +- include/core/maplayout.h | 1 + include/ui/movablerect.h | 64 ++++++-- include/ui/noscrollspinbox.h | 2 + include/ui/resizelayoutpopup.h | 106 +++++++++++++ porymap.pro | 7 +- src/core/editcommands.cpp | 21 +-- src/core/maplayout.cpp | 28 ++++ src/mainwindow.cpp | 96 +++--------- src/ui/movablerect.cpp | 158 ++++++++++++++++++- src/ui/noscrollspinbox.cpp | 5 + src/ui/resizelayoutpopup.cpp | 186 ++++++++++++++++++++++ 13 files changed, 854 insertions(+), 107 deletions(-) create mode 100644 forms/resizelayoutpopup.ui create mode 100644 include/ui/resizelayoutpopup.h create mode 100644 src/ui/resizelayoutpopup.cpp diff --git a/forms/resizelayoutpopup.ui b/forms/resizelayoutpopup.ui new file mode 100644 index 00000000..c98fe10f --- /dev/null +++ b/forms/resizelayoutpopup.ui @@ -0,0 +1,276 @@ + + + ResizeLayoutPopup + + + + 0 + 0 + 598 + 378 + + + + Dialog + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Resize Layout + + + Qt::AlignCenter + + + 8 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + true + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Width + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 64 + 0 + + + + + + + + Height + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 64 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Border Width + + + + + + + + 64 + 0 + + + + + + + + Border Height + + + + + + + + 64 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + + + buttonBox + accepted() + ResizeLayoutPopup + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ResizeLayoutPopup + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/include/core/editcommands.h b/include/core/editcommands.h index 5bf99784..83e33d41 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -8,6 +8,7 @@ #include #include #include +#include class Map; class Layout; @@ -203,7 +204,12 @@ private: /// Implements a command to commit a map or border resize action. class ResizeLayout : public QUndoCommand { public: - ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, + // ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, + // const Blockdata &oldMetatiles, const Blockdata &newMetatiles, + // QSize oldBorderDimensions, QSize newBorderDimensions, + // const Blockdata &oldBorder, const Blockdata &newBorder, + // QUndoCommand *parent = nullptr); + ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QMargins newLayoutMargins, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -220,8 +226,7 @@ private: int oldLayoutWidth; int oldLayoutHeight; - int newLayoutWidth; - int newLayoutHeight; + QMargins newLayoutMargins; int oldBorderWidth; int oldBorderHeight; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index b617002f..0e9bc243 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -96,6 +96,7 @@ public: void setBlock(int x, int y, Block block, bool enableScriptCallback = false); void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); + void adjustDimensions(QMargins margins, bool setNewBlockdata = true, bool enableScriptCallback = false); void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index efd85847..c5ca00ab 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -5,12 +5,13 @@ #include #include -class MovableRect : public QGraphicsItem + + +class MovableRect : public QGraphicsRectItem { public: MovableRect(bool *enabled, int width, int height, QRgb color); - QRectF boundingRect() const override - { + QRectF boundingRect() const override { qreal penWidth = 4; return QRectF(-penWidth, -penWidth, @@ -18,21 +19,62 @@ public: 20 * 8 + penWidth * 2); } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override - { + void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { if (!(*enabled)) return; painter->setPen(this->color); - painter->drawRect(x() - 2, y() - 2, this->width + 3, this->height + 3); + painter->drawRect(this->rect().x() - 2, this->rect().y() - 2, this->rect().width() + 3, this->rect().height() + 3); painter->setPen(QColor(0, 0, 0)); - painter->drawRect(x() - 3, y() - 3, this->width + 5, this->height + 5); - painter->drawRect(x() - 1, y() - 1, this->width + 1, this->height + 1); + painter->drawRect(this->rect().x() - 3, this->rect().y() - 3, this->rect().width() + 5, this->rect().height() + 5); + painter->drawRect(this->rect().x() - 1, this->rect().y() - 1, this->rect().width() + 1, this->rect().height() + 1); } void updateLocation(int x, int y); bool *enabled; -private: - int width; - int height; + +protected: QRgb color; }; + + +/// A MovableRect with the addition of being resizable. +class ResizableRect : public QObject, public MovableRect +{ + Q_OBJECT +public: + ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color); + + QRectF boundingRect() const override { + return QRectF(this->rect() + QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); + } + + QPainterPath shape() const override { + QPainterPath path; + path.addRect(this->rect() + QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); + path.addRect(this->rect() - QMargins(lineWidth, lineWidth, lineWidth, lineWidth)); + return path; + } + + void updatePosFromRect(QRect newPos); + +protected: + void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + +private: + enum class Edge { None, Left, Right, Top, Bottom, TopLeft, BottomLeft, TopRight, BottomRight }; + ResizableRect::Edge detectEdge(int x, int y); + + // Variables for keeping state of original rect while resizing + ResizableRect::Edge clickedEdge = ResizableRect::Edge::None; + QPointF clickedPos = QPointF(); + QRect clickedRect; + + int lineWidth = 8; + +signals: + void rectUpdated(QRect rect); +}; + #endif // MOVABLERECT_H diff --git a/include/ui/noscrollspinbox.h b/include/ui/noscrollspinbox.h index 0cc11043..0615da5a 100644 --- a/include/ui/noscrollspinbox.h +++ b/include/ui/noscrollspinbox.h @@ -12,6 +12,8 @@ public: void wheelEvent(QWheelEvent *event) override; void focusOutEvent(QFocusEvent *event) override; + void setLineEditEnabled(bool enabled); + unsigned getActionId(); private: diff --git a/include/ui/resizelayoutpopup.h b/include/ui/resizelayoutpopup.h new file mode 100644 index 00000000..de48c993 --- /dev/null +++ b/include/ui/resizelayoutpopup.h @@ -0,0 +1,106 @@ +#ifndef RESIZELAYOUTPOPUP_H +#define RESIZELAYOUTPOPUP_H + +#include +#include +#include +#include +#include + +class ResizableRect; +class Editor; +namespace Ui { + class ResizeLayoutPopup; +} + + + +/// Custom scene that paints its background a gray checkered pattern. +/// Additionally there is a definable "valid" area which will paint the checkerboard green inside. +class CheckeredBgScene : public QGraphicsScene { + Q_OBJECT + +public: + CheckeredBgScene(QObject *parent = nullptr); + void setValidRect(int x, int y, int width, int height) { + this->validRect = QRect(x * this->gridSize, y * this->gridSize, width * this->gridSize, height * this->gridSize); + } + void setValidRect(QRect rect) { + this->validRect = rect; + } + +protected: + void drawBackground(QPainter *painter, const QRectF &rect) override; + +private: + int gridSize = 16; // virtual pixels + QRect validRect = QRect(); +}; + + + +/// PixmapItem subclass which allows for creating a boundary which determine whether +/// the pixmap paints normally or with a black tint. +/// This item is movable and snaps on a 16x16 grid. +class BoundedPixmapItem : public QGraphicsPixmapItem { +public: + BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent = nullptr); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override; + + void setBoundary(ResizableRect *rect) { this->boundary = rect; } + +protected: + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + +private: + ResizableRect *boundary = nullptr; + QPointF clickedPos = QPointF(); +}; + + + +/// The main (modal) dialog window for resizing layout and border dimensions. +/// The dialog itself is minimal, and is connected to the parent widget's geometry. +class ResizeLayoutPopup : public QDialog +{ + Q_OBJECT + +public: + ResizeLayoutPopup(QWidget *parent, Editor *editor); + ~ResizeLayoutPopup(); + + void setupLayoutView(); + + void resetPosition(); + + QMargins getResult(); + QSize getBorderResult(); + +protected: + void moveEvent(QMoveEvent *) override { + // Prevent the dialog from being moved + this->resetPosition(); + } + + void resizeEvent(QResizeEvent *) override { + // Prevent the dialog from being resized + this->resetPosition(); + } + +private slots: + void on_spinBox_width_valueChanged(int value); + void on_spinBox_height_valueChanged(int value); + +private: + QWidget *parent = nullptr; + Editor *editor = nullptr; + + Ui::ResizeLayoutPopup *ui; + + ResizableRect *outline = nullptr; + BoundedPixmapItem *layoutPixmap = nullptr; + + QPointer scene = nullptr; +}; + +#endif // RESIZELAYOUTPOPUP_H diff --git a/porymap.pro b/porymap.pro index 1b1c693e..ce2d1e84 100644 --- a/porymap.pro +++ b/porymap.pro @@ -22,6 +22,7 @@ VERSION = 5.4.1 DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\" SOURCES += src/core/block.cpp \ + src/ui/resizelayoutpopup.cpp \ src/core/bitpacker.cpp \ src/core/blockdata.cpp \ src/core/events.cpp \ @@ -225,7 +226,8 @@ HEADERS += include/core/block.h \ include/log.h \ include/ui/uintspinbox.h \ include/ui/updatepromoter.h \ - include/ui/wildmonchart.h + include/ui/wildmonchart.h \ + include/ui/resizelayoutpopup.h FORMS += forms/mainwindow.ui \ forms/colorinputwidget.ui \ @@ -250,7 +252,8 @@ FORMS += forms/mainwindow.ui \ forms/customscriptseditor.ui \ forms/customscriptslistitem.ui \ forms/updatepromoter.ui \ - forms/wildmonchart.ui + forms/wildmonchart.ui \ + forms/resizelayoutpopup.ui RESOURCES += \ resources/images.qrc \ diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index c500c8c0..17cec2df 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -177,7 +177,7 @@ bool ShiftMetatiles::mergeWith(const QUndoCommand *command) { ************************************************************************ ******************************************************************************/ -ResizeLayout::ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newLayoutDimensions, +ResizeLayout::ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QMargins newLayoutMargins, const Blockdata &oldMetatiles, const Blockdata &newMetatiles, QSize oldBorderDimensions, QSize newBorderDimensions, const Blockdata &oldBorder, const Blockdata &newBorder, @@ -189,8 +189,7 @@ ResizeLayout::ResizeLayout(Layout *layout, QSize oldLayoutDimensions, QSize newL this->oldLayoutWidth = oldLayoutDimensions.width(); this->oldLayoutHeight = oldLayoutDimensions.height(); - this->newLayoutWidth = newLayoutDimensions.width(); - this->newLayoutHeight = newLayoutDimensions.height(); + this->newLayoutMargins = newLayoutMargins; this->oldMetatiles = oldMetatiles; this->newMetatiles = newMetatiles; @@ -210,12 +209,14 @@ void ResizeLayout::redo() { if (!layout) return; - layout->blockdata = newMetatiles; - layout->setDimensions(newLayoutWidth, newLayoutHeight, false, true); - layout->border = newBorder; layout->setBorderDimensions(newBorderWidth, newBorderHeight, false, true); + layout->width = oldLayoutWidth; + layout->height = oldLayoutHeight; + layout->adjustDimensions(this->newLayoutMargins, false, true); + layout->blockdata = newMetatiles; + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); @@ -225,12 +226,14 @@ void ResizeLayout::redo() { void ResizeLayout::undo() { if (!layout) return; - layout->blockdata = oldMetatiles; - layout->setDimensions(oldLayoutWidth, oldLayoutHeight, false, true); - layout->border = oldBorder; layout->setBorderDimensions(oldBorderWidth, oldBorderHeight, false, true); + layout->width = oldLayoutWidth + newLayoutMargins.left() + newLayoutMargins.right(); + layout->height = oldLayoutHeight + newLayoutMargins.top() + newLayoutMargins.bottom(); + layout->adjustDimensions(-this->newLayoutMargins, false, true); + layout->blockdata = oldMetatiles; + layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 34033ac5..fa55ed77 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -183,6 +183,34 @@ void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bo emit layoutDimensionsChanged(QSize(getWidth(), getHeight())); } +void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata, bool enableScriptCallback) { + int oldWidth = this->width; + int oldHeight = this->height; + int newWidth = this->width + margins.left() + margins.right(); + int newHeight = this->height + margins.top() + margins.bottom(); + + if (setNewBlockdata) { + // Fill new blockdata TODO: replace old functions, scripting support, undo etc + Blockdata newBlockdata; + for (int y = 0; y < newHeight; y++) + for (int x = 0; x < newWidth; x++) { + if ((x < margins.left()) || (x >= newWidth - margins.right()) || (y < margins.top()) || (y >= newHeight - margins.bottom())) { + newBlockdata.append(0); + } else { + int index = (y - margins.top()) * oldWidth + (x - margins.left()); + newBlockdata.append(this->blockdata.value(index)); + } + } + this->blockdata = newBlockdata; + } + + this->width = newWidth; + this->height = newHeight; + + emit layoutChanged(this); + emit layoutDimensionsChanged(QSize(getWidth(), getHeight())); +} + void Layout::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { if (setNewBlockdata) { setNewBorderDimensionsBlockdata(newWidth, newHeight); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d9e65eb3..d1d50088 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -23,6 +23,7 @@ #include "newmapconnectiondialog.h" #include "config.h" #include "filedialog.h" +#include "resizelayoutpopup.h" #include #include @@ -2961,88 +2962,31 @@ void MainWindow::on_comboBox_SecondaryTileset_currentTextChanged(const QString & void MainWindow::on_pushButton_ChangeDimensions_clicked() { if (!editor || !editor->layout) return; - QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - dialog.setWindowTitle("Change Map Dimensions"); - dialog.setWindowModality(Qt::NonModal); - - QFormLayout form(&dialog); - - QSpinBox *widthSpinBox = new QSpinBox(); - QSpinBox *heightSpinBox = new QSpinBox(); - QSpinBox *bwidthSpinBox = new QSpinBox(); - QSpinBox *bheightSpinBox = new QSpinBox(); - widthSpinBox->setMinimum(1); - heightSpinBox->setMinimum(1); - bwidthSpinBox->setMinimum(1); - bheightSpinBox->setMinimum(1); - widthSpinBox->setMaximum(editor->project->getMaxMapWidth()); - heightSpinBox->setMaximum(editor->project->getMaxMapHeight()); - bwidthSpinBox->setMaximum(MAX_BORDER_WIDTH); - bheightSpinBox->setMaximum(MAX_BORDER_HEIGHT); - widthSpinBox->setValue(editor->layout->getWidth()); - heightSpinBox->setValue(editor->layout->getHeight()); - bwidthSpinBox->setValue(editor->layout->getBorderWidth()); - bheightSpinBox->setValue(editor->layout->getBorderHeight()); - if (projectConfig.useCustomBorderSize) { - form.addRow(new QLabel("Map Width"), widthSpinBox); - form.addRow(new QLabel("Map Height"), heightSpinBox); - form.addRow(new QLabel("Border Width"), bwidthSpinBox); - form.addRow(new QLabel("Border Height"), bheightSpinBox); - } else { - form.addRow(new QLabel("Width"), widthSpinBox); - form.addRow(new QLabel("Height"), heightSpinBox); - } - - QLabel *errorLabel = new QLabel(); - errorLabel->setStyleSheet("QLabel { color: red }"); - errorLabel->setVisible(false); - - QDialogButtonBox buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); - form.addRow(&buttonBox); - connect(&buttonBox, &QDialogButtonBox::accepted, [&dialog, &widthSpinBox, &heightSpinBox, &errorLabel, this](){ - // Ensure width and height are an acceptable size. - // The maximum number of metatiles in a map is the following: - // max = (width + 15) * (height + 14) - // This limit can be found in fieldmap.c in pokeruby/pokeemerald/pokefirered. - int numMetatiles = editor->project->getMapDataSize(widthSpinBox->value(), heightSpinBox->value()); - int maxMetatiles = editor->project->getMaxMapDataSize(); - if (numMetatiles <= maxMetatiles) { - dialog.accept(); - } else { - QString errorText = QString("Error: The specified width and height are too large.\n" - "The maximum layout width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified layout width and height was: (%2 + 15) * (%3 + 14) = %4") - .arg(maxMetatiles) - .arg(widthSpinBox->value()) - .arg(heightSpinBox->value()) - .arg(numMetatiles); - errorLabel->setText(errorText); - errorLabel->setVisible(true); - } - }); - connect(&buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); - - form.addRow(errorLabel); - - if (dialog.exec() == QDialog::Accepted) { - Layout *layout = editor->layout; - Blockdata oldMetatiles = layout->blockdata; - Blockdata oldBorder = layout->border; - QSize oldMapDimensions(layout->getWidth(), layout->getHeight()); + ResizeLayoutPopup popup(this->ui->graphicsView_Map, this->editor); + popup.show(); + popup.setupLayoutView(); + if (popup.exec() == QDialog::Accepted) { + Layout *layout = this->editor->layout; + QMargins result = popup.getResult(); + QSize borderResult = popup.getBorderResult(); + QSize oldLayoutDimensions(layout->getWidth(), layout->getHeight()); QSize oldBorderDimensions(layout->getBorderWidth(), layout->getBorderHeight()); - QSize newMapDimensions(widthSpinBox->value(), heightSpinBox->value()); - QSize newBorderDimensions(bwidthSpinBox->value(), bheightSpinBox->value()); - if (oldMapDimensions != newMapDimensions || oldBorderDimensions != newBorderDimensions) { - layout->setDimensions(newMapDimensions.width(), newMapDimensions.height(), true, true); - layout->setBorderDimensions(newBorderDimensions.width(), newBorderDimensions.height(), true, true); - editor->layout->editHistory.push(new ResizeLayout(layout, - oldMapDimensions, newMapDimensions, + if (!result.isNull() || (borderResult != oldBorderDimensions)) { + Blockdata oldMetatiles = layout->blockdata; + Blockdata oldBorder = layout->border; + + layout->adjustDimensions(result); + layout->setBorderDimensions(borderResult.width(), borderResult.height(), true, true); + layout->editHistory.push(new ResizeLayout(layout, + oldLayoutDimensions, result, oldMetatiles, layout->blockdata, - oldBorderDimensions, newBorderDimensions, + oldBorderDimensions, borderResult, oldBorder, layout->border )); } } + + return; } void MainWindow::on_checkBox_smartPaths_stateChanged(int selected) diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index 55327dba..e9e373e0 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -1,17 +1,163 @@ +#include +#include + #include "movablerect.h" MovableRect::MovableRect(bool *enabled, int width, int height, QRgb color) + : QGraphicsRectItem(0, 0, width, height) { this->enabled = enabled; - this->width = width; - this->height = height; this->color = color; this->setVisible(*enabled); } -void MovableRect::updateLocation(int x, int y) -{ - this->setX((x * 16) - this->width / 2 + 8); - this->setY((y * 16) - this->height / 2 + 8); +/// Center rect on grid position (x, y) +void MovableRect::updateLocation(int x, int y) { + this->setRect((x * 16) - this->rect().width() / 2 + 8, (y * 16) - this->rect().height() / 2 + 8, this->rect().width(), this->rect().height()); this->setVisible(*this->enabled); } + +/****************************************************************************** + ************************************************************************ + ******************************************************************************/ + +int roundUp(int numToRound, int multiple) { + return (numToRound + multiple - 1) & -multiple; +} + +ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color) + : QObject(parent), + MovableRect(enabled, width * 16, height * 16, color) +{ + setZValue(0xFFFFFFFF); // ensure on top of view + setAcceptHoverEvents(true); + setFlags(this->flags() | QGraphicsItem::ItemIsMovable); +} + +ResizableRect::Edge ResizableRect::detectEdge(int x, int y) { + QRectF edge = this->boundingRect(); + if (x <= edge.left() + this->lineWidth) { + if (y >= edge.top() + 2 * this->lineWidth) { + if (y <= edge.bottom() - 2 * this->lineWidth) { + return ResizableRect::Edge::Left; + } + else { + return ResizableRect::Edge::BottomLeft; + } + } + else { + return ResizableRect::Edge::TopLeft; + } + } + else if (x >= edge.right() - this->lineWidth) { + if (y >= edge.top() + 2 * this->lineWidth) { + if (y <= edge.bottom() - 2 * this->lineWidth) { + return ResizableRect::Edge::Right; + } + else { + return ResizableRect::Edge::BottomRight; + } + } + else { + return ResizableRect::Edge::TopRight; + } + } + else { + if (y <= edge.top() + this->lineWidth) { + return ResizableRect::Edge::Top; + } + else if (y >= edge.bottom() - this->lineWidth) { + return ResizableRect::Edge::Bottom; + } + } + return ResizableRect::Edge::None; +} + +void ResizableRect::updatePosFromRect(QRect newRect) { + prepareGeometryChange(); + this->setRect(newRect); + emit this->rectUpdated(newRect); +} + +void ResizableRect::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { + switch (this->detectEdge(event->pos().x(), event->pos().y())) { + case ResizableRect::Edge::None: + default: + break; + case ResizableRect::Edge::Left: + case ResizableRect::Edge::Right: + this->setCursor(Qt::SizeHorCursor); + break; + case ResizableRect::Edge::Top: + case ResizableRect::Edge::Bottom: + this->setCursor(Qt::SizeVerCursor); + break; + case ResizableRect::Edge::TopRight: + case ResizableRect::Edge::BottomLeft: + this->setCursor(Qt::SizeBDiagCursor); + break; + case ResizableRect::Edge::TopLeft: + case ResizableRect::Edge::BottomRight: + this->setCursor(Qt::SizeFDiagCursor); + break; + } +} + +void ResizableRect::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { + this->unsetCursor(); +} + +void ResizableRect::mousePressEvent(QGraphicsSceneMouseEvent *event) { + int x = event->pos().x(); + int y = event->pos().y(); + this->clickedPos = event->scenePos(); + this->clickedRect = this->rect().toAlignedRect(); + this->clickedEdge = this->detectEdge(x, y); +} + +void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + int dx = roundUp(event->scenePos().x() - this->clickedPos.x(), 16); + int dy = roundUp(event->scenePos().y() - this->clickedPos.y(), 16); + + QRect resizedRect = this->clickedRect; + + switch (this->clickedEdge) { + case ResizableRect::Edge::None: + default: + return; + case ResizableRect::Edge::Left: + resizedRect.adjust(dx, 0, 0, 0); + break; + case ResizableRect::Edge::Right: + resizedRect.adjust(0, 0, dx, 0); + break; + case ResizableRect::Edge::Top: + resizedRect.adjust(0, dy, 0, 0); + break; + case ResizableRect::Edge::Bottom: + resizedRect.adjust(0, 0, 0, dy); + break; + case ResizableRect::Edge::TopRight: + resizedRect.adjust(0, dy, dx, 0); + break; + case ResizableRect::Edge::BottomLeft: + resizedRect.adjust(dx, 0, 0, dy); + break; + case ResizableRect::Edge::TopLeft: + resizedRect.adjust(dx, dy, 0, 0); + break; + case ResizableRect::Edge::BottomRight: + resizedRect.adjust(0, 0, dx, dy); + break; + } + + // lower bounds limits + if (resizedRect.width() < 16) + resizedRect.setWidth(16); + if (resizedRect.height() < 16) + resizedRect.setHeight(16); + + // TODO: upper bound limits + + this->updatePosFromRect(resizedRect); +} diff --git a/src/ui/noscrollspinbox.cpp b/src/ui/noscrollspinbox.cpp index f8d1d444..3493d4ee 100644 --- a/src/ui/noscrollspinbox.cpp +++ b/src/ui/noscrollspinbox.cpp @@ -1,5 +1,6 @@ #include "noscrollspinbox.h" #include +#include unsigned actionId = 0xffff; @@ -25,6 +26,10 @@ void NoScrollSpinBox::focusOutEvent(QFocusEvent *event) { QSpinBox::focusOutEvent(event); } +void NoScrollSpinBox::setLineEditEnabled(bool enabled) { + this->lineEdit()->setReadOnly(!enabled); +} + unsigned NoScrollSpinBox::getActionId() { return actionId; } diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp new file mode 100644 index 00000000..c6c48db7 --- /dev/null +++ b/src/ui/resizelayoutpopup.cpp @@ -0,0 +1,186 @@ +#include "resizelayoutpopup.h" +#include "editor.h" +#include "movablerect.h" +#include "config.h" + +#include "ui_resizelayoutpopup.h" + +// TODO: put this in a util file or something +extern int roundUp(int, int); + +CheckeredBgScene::CheckeredBgScene(QObject *parent) : QGraphicsScene(parent) { } + +void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) { + QRect r = rect.toRect(); + int xMin = r.left() - r.left() % this->gridSize - this->gridSize; + int yMin = r.top() - r.top() % this->gridSize - this->gridSize; + int xMax = r.right() - r.right() % this->gridSize + this->gridSize; + int yMax = r.bottom() - r.bottom() % this->gridSize + this->gridSize; + + // draw grid 16x16 from top to bottom of scene + QColor paintColor(0x00ff00); + for (int x = xMin, xTile = 0; x <= xMax; x += this->gridSize, xTile++) { + for (int y = yMin, yTile = 0; y <= yMax; y += this->gridSize, yTile++) { + if (!((xTile ^ yTile) & 1)) { // tile numbers have same parity (evenness) + if (this->validRect.contains(x, y)) // check if inside validRect + paintColor = QColor(132, 217, 165); // green light color + else + paintColor = 0xbcbcbc; // normal light color + } + else { + if (this->validRect.contains(x, y)) // check if inside validRect + paintColor = QColor(76, 178, 121); // green dark color + else + paintColor = 0x969696; // normal dark color + } + painter->fillRect(QRect(x, y, this->gridSize, this->gridSize), paintColor); + } + } +} + +/****************************************************************************** + ************************************************************************ + ******************************************************************************/ + +BoundedPixmapItem::BoundedPixmapItem(const QPixmap &pixmap, QGraphicsItem *parent) : QGraphicsPixmapItem(pixmap, parent) { + setFlags(this->flags() | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges | QGraphicsItem::ItemIsSelectable); +} + +void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * item, QWidget *widget) { + // Draw the pixmap darkened in the background + painter->fillRect(this->boundingRect().toAlignedRect(), QColor(0x444444)); + painter->setCompositionMode(QPainter::CompositionMode_Multiply); + painter->drawPixmap(this->boundingRect().toAlignedRect(), this->pixmap()); + + // draw the normal pixmap on top, cropping to validRect as needed + painter->setCompositionMode(QPainter::CompositionMode_SourceOver); + QRect intersection = this->mapRectFromScene(this->boundary->rect()).toAlignedRect() & this->boundingRect().toAlignedRect(); + QPixmap cropped = this->pixmap().copy(intersection); + painter->drawPixmap(intersection, cropped); +} + +QVariant BoundedPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) { + if (change == ItemPositionChange && scene()) { + QPointF newPos = value.toPointF(); + return QPointF(roundUp(newPos.x(), 16), roundUp(newPos.y(), 16)); + } + else + return QGraphicsItem::itemChange(change, value); +} + +/****************************************************************************** + ************************************************************************ + ******************************************************************************/ + +ResizeLayoutPopup::ResizeLayoutPopup(QWidget *parent, Editor *editor) : + QDialog(parent), + parent(parent), + editor(editor), + ui(new Ui::ResizeLayoutPopup) +{ + ui->setupUi(this); + this->resetPosition(); + this->setWindowFlags(this->windowFlags() | Qt::FramelessWindowHint); + this->setWindowModality(Qt::ApplicationModal); + + this->scene = new CheckeredBgScene(this); + //this->ui->graphicsView->setAlignment(Qt::AlignTop|Qt::AlignLeft); + this->ui->graphicsView->setScene(this->scene); + this->ui->graphicsView->setRenderHints(QPainter::Antialiasing); + this->ui->graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); +} + +ResizeLayoutPopup::~ResizeLayoutPopup() +{ + delete ui; +} + +/// Reset position of the dialog to cover the MainWindow's layout metatile scene +void ResizeLayoutPopup::resetPosition() { + this->setGeometry(QRect(parent->mapToGlobal(QPoint(0, 0)), parent->size())); +} + +/// Custom scene contains +/// (1) pixmap representing the current layout / not resizable / drag-movable +/// (1) layout outline / resizable / not movable +void ResizeLayoutPopup::setupLayoutView() { + if (!this->editor || !this->editor->layout) return; + // TODO: this should be a more robust check probably + + // Border stuff + bool bordersEnabled = projectConfig.useCustomBorderSize; + if (bordersEnabled) { + this->ui->spinBox_borderWidth->setMinimum(1); + this->ui->spinBox_borderHeight->setMinimum(1); + this->ui->spinBox_borderWidth->setMaximum(MAX_BORDER_WIDTH); + this->ui->spinBox_borderHeight->setMaximum(MAX_BORDER_HEIGHT); + this->ui->spinBox_borderWidth->setLineEditEnabled(false); + this->ui->spinBox_borderHeight->setLineEditEnabled(false); + } else { + this->ui->frame_border->setVisible(false); + } + this->ui->spinBox_borderWidth->setValue(this->editor->layout->getBorderWidth()); + this->ui->spinBox_borderHeight->setValue(this->editor->layout->getBorderHeight()); + + // Layout stuff + QPixmap pixmap = this->editor->layout->pixmap; + this->layoutPixmap = new BoundedPixmapItem(pixmap); + this->scene->addItem(layoutPixmap); + int maxWidth = this->editor->project->getMaxMapWidth(); + int maxHeight = this->editor->project->getMaxMapHeight(); + QGraphicsRectItem *cover = new QGraphicsRectItem(-maxWidth * 8, -maxHeight * 8, maxWidth * 16, maxHeight * 16); + this->scene->addItem(cover); + + this->ui->spinBox_width->setMinimum(1); + this->ui->spinBox_width->setMaximum(maxWidth); + this->ui->spinBox_height->setMinimum(1); + this->ui->spinBox_height->setMaximum(maxHeight); + + this->ui->spinBox_width->setLineEditEnabled(false); + this->ui->spinBox_height->setLineEditEnabled(false); + + static bool layoutSizeRectVisible = true; + + this->outline = new ResizableRect(this, &layoutSizeRectVisible, this->editor->layout->getWidth(), this->editor->layout->getHeight(), qRgb(255, 0, 255)); + connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){ + this->scene->setValidRect(rect); + this->ui->spinBox_width->setValue(rect.width() / 16); + this->ui->spinBox_height->setValue(rect.height() / 16); + }); + scene->addItem(outline); + + layoutPixmap->setBoundary(outline); + this->outline->rectUpdated(outline->rect().toAlignedRect()); + + this->ui->graphicsView->scale(0.5, 0.5); + this->ui->graphicsView->centerOn(layoutPixmap); + // this->ui->graphicsView->fitInView(cover->rect(), Qt::KeepAspectRatio); +} + +void ResizeLayoutPopup::on_spinBox_width_valueChanged(int value) { + if (!this->outline) return; + QRectF rect = this->outline->rect(); + this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), value * 16, rect.height())); +} + +void ResizeLayoutPopup::on_spinBox_height_valueChanged(int value) { + if (!this->outline) return; + QRectF rect = this->outline->rect(); + this->outline->updatePosFromRect(QRect(rect.x(), rect.y(), rect.width(), value * 16)); +} + +/// Result is the number of metatiles to add (or subtract) to each side of the map after dimension changes +QMargins ResizeLayoutPopup::getResult() { + QMargins result = QMargins(); + + result.setLeft(this->layoutPixmap->x() - this->outline->rect().left()); + result.setTop(this->layoutPixmap->y() - this->outline->rect().top()); + result.setRight(this->outline->rect().right() - (this->layoutPixmap->x() + this->layoutPixmap->pixmap().width())); + result.setBottom(this->outline->rect().bottom() - (this->layoutPixmap->y() + this->layoutPixmap->pixmap().height())); + + return result / 16; +} + +QSize ResizeLayoutPopup::getBorderResult() { + return QSize(this->ui->spinBox_borderWidth->value(), this->ui->spinBox_borderHeight->value()); +}