From b8c62a923ef5937274979270bb13643882fd4c20 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 Date: Mon, 10 Jan 2022 21:49:06 +0100 Subject: [PATCH] Added a few more dialogs and models --- QtGameMaker.pro | 34 ++- codeeditorwidget.cpp | 8 +- constantsmodel.cpp | 46 ++++ constantsmodel.h | 21 ++ dialogs/backgroundpropertiesdialog.cpp | 41 ++-- dialogs/backgroundpropertiesdialog.h | 2 +- dialogs/backgroundpropertiesdialog.ui | 15 ++ dialogs/codeeditordialog.cpp | 84 +++++++ dialogs/codeeditordialog.h | 30 +++ ...ropertiesdialog.ui => codeeditordialog.ui} | 11 +- dialogs/createspritedialog.cpp | 10 +- dialogs/createspritedialog.h | 2 +- dialogs/createspritedialog.ui | 3 + dialogs/editspritedialog.cpp | 104 +++++++- dialogs/editspritedialog.h | 18 +- dialogs/extensionpackagesdialog.cpp | 10 +- dialogs/fontpropertiesdialog.ui | 18 +- dialogs/gameinformationdialog.cpp | 13 +- dialogs/gameinformationdialog.ui | 13 + dialogs/globalgamesettingsdialog.cpp | 98 +++++++- dialogs/globalgamesettingsdialog.h | 10 + dialogs/globalgamesettingsdialog.ui | 2 +- dialogs/imageeditordialog.cpp | 86 ++++++- dialogs/imageeditordialog.h | 19 +- dialogs/imageeditordialog.ui | 81 +++++- dialogs/includedfilesdialog.cpp | 23 ++ dialogs/includedfilesdialog.h | 25 ++ dialogs/includedfilesdialog.ui | 112 +++++++++ dialogs/maskpropertiesdialog.cpp | 16 ++ dialogs/maskpropertiesdialog.h | 19 ++ dialogs/maskpropertiesdialog.ui | 19 ++ dialogs/objectinformationdialog.cpp | 5 +- dialogs/objectinformationdialog.h | 5 +- dialogs/objectinformationdialog.ui | 19 +- dialogs/pathpropertiesdialog.cpp | 148 ++++++++++- dialogs/pathpropertiesdialog.h | 19 +- dialogs/pathpropertiesdialog.ui | 232 +++++++++++++++++- dialogs/preferencesdialog.cpp | 18 +- dialogs/scriptpropertiesdialog.cpp | 89 +------ dialogs/scriptpropertiesdialog.h | 23 +- dialogs/soundpropertiesdialog.cpp | 35 ++- dialogs/soundpropertiesdialog.h | 2 +- dialogs/soundpropertiesdialog.ui | 33 ++- dialogs/spritepropertiesdialog.cpp | 61 +++-- dialogs/spritepropertiesdialog.h | 3 +- dialogs/spritepropertiesdialog.ui | 39 ++- dialogs/triggerconditiondialog.cpp | 12 + dialogs/triggerconditiondialog.h | 11 + dialogs/triggersdialog.cpp | 33 +++ dialogs/triggersdialog.h | 28 +++ dialogs/triggersdialog.ui | 227 +++++++++++++++++ dialogs/userdefinedconstantsdialog.cpp | 23 ++ dialogs/userdefinedconstantsdialog.h | 25 ++ dialogs/userdefinedconstantsdialog.ui | 150 +++++++++++ drawingcanvaswidget.cpp | 37 +++ drawingcanvaswidget.h | 31 +++ icons/center.png | Bin 0 -> 2953 bytes icons/constants.png | Bin 0 -> 8857 bytes icons/flip-horizontal.png | Bin 0 -> 5576 bytes icons/flip-vertical.png | Bin 0 -> 5496 bytes icons/grid.png | Bin 0 -> 6562 bytes icons/scale.png | Bin 0 -> 12801 bytes includedfilesmodel.cpp | 19 ++ includedfilesmodel.h | 19 ++ mainwindow.cpp | 129 +++++++--- mainwindow.h | 19 +- mainwindow.ui | 8 + pathpointsmodel.cpp | 100 ++++++++ pathpointsmodel.h | 29 +++ pathpointswidget.cpp | 223 +++++++++++++++++ pathpointswidget.h | 84 +++++++ projectcontainer.cpp | 22 ++ projectcontainer.h | 28 +-- projecttreemodel.cpp | 2 +- resources.qrc | 6 + spritesmodel.cpp | 34 ++- spritesmodel.h | 13 +- triggersmodel.cpp | 19 ++ triggersmodel.h | 19 ++ 79 files changed, 2748 insertions(+), 326 deletions(-) create mode 100644 constantsmodel.cpp create mode 100644 constantsmodel.h create mode 100644 dialogs/codeeditordialog.cpp create mode 100644 dialogs/codeeditordialog.h rename dialogs/{scriptpropertiesdialog.ui => codeeditordialog.ui} (98%) create mode 100644 dialogs/includedfilesdialog.cpp create mode 100644 dialogs/includedfilesdialog.h create mode 100644 dialogs/includedfilesdialog.ui create mode 100644 dialogs/maskpropertiesdialog.cpp create mode 100644 dialogs/maskpropertiesdialog.h create mode 100644 dialogs/maskpropertiesdialog.ui create mode 100644 dialogs/triggerconditiondialog.cpp create mode 100644 dialogs/triggerconditiondialog.h create mode 100644 dialogs/triggersdialog.cpp create mode 100644 dialogs/triggersdialog.h create mode 100644 dialogs/triggersdialog.ui create mode 100644 dialogs/userdefinedconstantsdialog.cpp create mode 100644 dialogs/userdefinedconstantsdialog.h create mode 100644 dialogs/userdefinedconstantsdialog.ui create mode 100644 drawingcanvaswidget.cpp create mode 100644 drawingcanvaswidget.h create mode 100644 icons/center.png create mode 100644 icons/constants.png create mode 100644 icons/flip-horizontal.png create mode 100644 icons/flip-vertical.png create mode 100644 icons/grid.png create mode 100644 icons/scale.png create mode 100644 includedfilesmodel.cpp create mode 100644 includedfilesmodel.h create mode 100644 pathpointsmodel.cpp create mode 100644 pathpointsmodel.h create mode 100644 pathpointswidget.cpp create mode 100644 pathpointswidget.h create mode 100644 triggersmodel.cpp create mode 100644 triggersmodel.h diff --git a/QtGameMaker.pro b/QtGameMaker.pro index 40540fd..ce0494b 100644 --- a/QtGameMaker.pro +++ b/QtGameMaker.pro @@ -7,18 +7,29 @@ DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 HEADERS += \ codeeditorwidget.h \ + constantsmodel.h \ + dialogs/codeeditordialog.h \ dialogs/createspritedialog.h \ dialogs/fontpropertiesdialog.h \ dialogs/imageeditordialog.h \ + dialogs/includedfilesdialog.h \ + dialogs/maskpropertiesdialog.h \ dialogs/objectinformationdialog.h \ dialogs/objectpropertiesdialog.h \ dialogs/pathpropertiesdialog.h \ dialogs/roompropertiesdialog.h \ dialogs/scriptpropertiesdialog.h \ dialogs/timelinepropertiesdialog.h \ + dialogs/triggerconditiondialog.h \ + dialogs/triggersdialog.h \ + dialogs/userdefinedconstantsdialog.h \ + drawingcanvaswidget.h \ futurecpp.h \ + includedfilesmodel.h \ jshighlighter.h \ mainwindow.h \ + pathpointsmodel.h \ + pathpointswidget.h \ projectcontainer.h \ projecttreemodel.h \ dialogs/backgroundpropertiesdialog.h \ @@ -29,21 +40,33 @@ HEADERS += \ dialogs/preferencesdialog.h \ dialogs/soundpropertiesdialog.h \ dialogs/spritepropertiesdialog.h \ - spritesmodel.h + spritesmodel.h \ + triggersmodel.h SOURCES += main.cpp \ codeeditorwidget.cpp \ + constantsmodel.cpp \ + dialogs/codeeditordialog.cpp \ dialogs/createspritedialog.cpp \ dialogs/fontpropertiesdialog.cpp \ dialogs/imageeditordialog.cpp \ + dialogs/includedfilesdialog.cpp \ + dialogs/maskpropertiesdialog.cpp \ dialogs/objectinformationdialog.cpp \ dialogs/objectpropertiesdialog.cpp \ dialogs/pathpropertiesdialog.cpp \ dialogs/roompropertiesdialog.cpp \ dialogs/scriptpropertiesdialog.cpp \ dialogs/timelinepropertiesdialog.cpp \ + dialogs/triggerconditiondialog.cpp \ + dialogs/triggersdialog.cpp \ + dialogs/userdefinedconstantsdialog.cpp \ + drawingcanvaswidget.cpp \ + includedfilesmodel.cpp \ jshighlighter.cpp \ mainwindow.cpp \ + pathpointsmodel.cpp \ + pathpointswidget.cpp \ projectcontainer.cpp \ projecttreemodel.cpp \ dialogs/backgroundpropertiesdialog.cpp \ @@ -54,18 +77,23 @@ SOURCES += main.cpp \ dialogs/preferencesdialog.cpp \ dialogs/soundpropertiesdialog.cpp \ dialogs/spritepropertiesdialog.cpp \ - spritesmodel.cpp + spritesmodel.cpp \ + triggersmodel.cpp FORMS += \ + dialogs/codeeditordialog.ui \ dialogs/createspritedialog.ui \ dialogs/fontpropertiesdialog.ui \ dialogs/imageeditordialog.ui \ + dialogs/includedfilesdialog.ui \ + dialogs/maskpropertiesdialog.ui \ dialogs/objectinformationdialog.ui \ dialogs/objectpropertiesdialog.ui \ dialogs/pathpropertiesdialog.ui \ dialogs/roompropertiesdialog.ui \ - dialogs/scriptpropertiesdialog.ui \ dialogs/timelinepropertiesdialog.ui \ + dialogs/triggersdialog.ui \ + dialogs/userdefinedconstantsdialog.ui \ mainwindow.ui \ dialogs/backgroundpropertiesdialog.ui \ dialogs/editspritedialog.ui \ diff --git a/codeeditorwidget.cpp b/codeeditorwidget.cpp index 4d367d4..9dc5cd0 100644 --- a/codeeditorwidget.cpp +++ b/codeeditorwidget.cpp @@ -3,8 +3,8 @@ #include #include -CodeEditorWidget::CodeEditorWidget(QWidget *parent) - : QPlainTextEdit{parent} +CodeEditorWidget::CodeEditorWidget(QWidget *parent) : + QPlainTextEdit{parent} { lineNumberArea = new LineNumberArea(this); @@ -105,7 +105,9 @@ void CodeEditorWidget::lineNumberAreaPaintEvent(QPaintEvent *event) } } -LineNumberArea::LineNumberArea(CodeEditorWidget *editor) : QWidget(editor), codeEditor(editor) +LineNumberArea::LineNumberArea(CodeEditorWidget *editor) : + QWidget{editor}, + codeEditor{editor} { } diff --git a/constantsmodel.cpp b/constantsmodel.cpp new file mode 100644 index 0000000..0a41401 --- /dev/null +++ b/constantsmodel.cpp @@ -0,0 +1,46 @@ +#include "constantsmodel.h" + +#include "projectcontainer.h" + +namespace { +enum { + ColumnName, + ColumnValue, + NumberOfColumns +}; +} + +ConstantsModel::ConstantsModel(ProjectContainer &project, QObject *parent) : + QAbstractTableModel{parent}, + m_project{project} +{ +} + +int ConstantsModel::rowCount(const QModelIndex &parent) const +{ + return 0; +} + +int ConstantsModel::columnCount(const QModelIndex &parent) const +{ + return NumberOfColumns; +} + +QVariant ConstantsModel::data(const QModelIndex &index, int role) const +{ + return {}; +} + +QVariant ConstantsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || (role != Qt::DisplayRole && role != Qt::EditRole)) + return {}; + + switch (section) + { + case ColumnName: return tr("Name"); + case ColumnValue: return tr("Value"); + } + + return {}; +} diff --git a/constantsmodel.h b/constantsmodel.h new file mode 100644 index 0000000..2c8c8a6 --- /dev/null +++ b/constantsmodel.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +struct ProjectContainer; + +class ConstantsModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit ConstantsModel(ProjectContainer &project, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + +private: + ProjectContainer &m_project; +}; diff --git a/dialogs/backgroundpropertiesdialog.cpp b/dialogs/backgroundpropertiesdialog.cpp index 3824188..7f49bb8 100644 --- a/dialogs/backgroundpropertiesdialog.cpp +++ b/dialogs/backgroundpropertiesdialog.cpp @@ -13,7 +13,8 @@ BackgroundPropertiesDialog::BackgroundPropertiesDialog(Background &background, P QDialog{parent}, m_ui{std::make_unique()}, m_background{background}, - m_projectModel{projectModel} + m_projectModel{projectModel}, + m_pixmap{m_background.pixmap} { m_ui->setupUi(this); @@ -27,7 +28,6 @@ BackgroundPropertiesDialog::BackgroundPropertiesDialog(Background &background, P m_ui->lineEditName->setText(m_background.name); updateSpriteInfo(); m_ui->checkBoxTileset->setChecked(m_background.tileset); - m_ui->labelPreview->setPixmap(m_background.pixmap); connect(&m_projectModel, &ProjectTreeModel::backgroundNameChanged, this, &BackgroundPropertiesDialog::backgroundNameChanged); @@ -49,6 +49,12 @@ BackgroundPropertiesDialog::~BackgroundPropertiesDialog() = default; void BackgroundPropertiesDialog::accept() { + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + if (m_background.name != m_ui->lineEditName->text()) { if (!m_projectModel.rename(m_background, m_ui->lineEditName->text())) @@ -58,8 +64,7 @@ void BackgroundPropertiesDialog::accept() } } - if (m_newPixmap) - m_background.pixmap = std::move(*m_newPixmap); + m_background.pixmap = std::move(m_pixmap); m_background.tileset = m_ui->checkBoxTileset->isChecked(); QDialog::accept(); @@ -108,20 +113,14 @@ void BackgroundPropertiesDialog::loadBackground() return; } - m_ui->labelPreview->setPixmap(pixmap); - - m_newPixmap = std::move(pixmap); - m_unsavedChanges = true; - - updateTitle(); + m_pixmap = std::move(pixmap); + changed(); updateSpriteInfo(); } void BackgroundPropertiesDialog::saveBackground() { - const auto &pixmap = m_newPixmap ? *m_newPixmap : m_background.pixmap; - - if (pixmap.isNull()) + if (m_pixmap.isNull()) { QMessageBox::warning(this, tr("No background available to save!"), tr("No background available to save!")); return; @@ -131,7 +130,7 @@ void BackgroundPropertiesDialog::saveBackground() if (path.isEmpty()) return; - if (!pixmap.save(path)) + if (!m_pixmap.save(path)) { QMessageBox::warning(this, tr("Could not save Background!"), tr("Could not save Background!")); return; @@ -140,7 +139,13 @@ void BackgroundPropertiesDialog::saveBackground() void BackgroundPropertiesDialog::editBackground() { - ImageEditorDialog{this}.exec(); + ImageEditorDialog dialog{m_pixmap, tr("Image Editor: %0").arg(m_background.name), this}; + if (dialog.exec() == QDialog::Accepted) + { + m_pixmap = dialog.pixmap(); + changed(); + updateSpriteInfo(); + } } void BackgroundPropertiesDialog::changed() @@ -175,7 +180,7 @@ void BackgroundPropertiesDialog::updateTitle() void BackgroundPropertiesDialog::updateSpriteInfo() { - const auto &pixmap = m_newPixmap ? *m_newPixmap : m_background.pixmap; - m_ui->labelWidth->setText(tr("Width: %0").arg(pixmap.width())); - m_ui->labelHeight->setText(tr("Height: %0").arg(pixmap.height())); + m_ui->labelWidth->setText(tr("Width: %0").arg(m_pixmap.width())); + m_ui->labelHeight->setText(tr("Height: %0").arg(m_pixmap.height())); + m_ui->labelPreview->setPixmap(m_pixmap); } diff --git a/dialogs/backgroundpropertiesdialog.h b/dialogs/backgroundpropertiesdialog.h index a349ea5..32b4f1e 100644 --- a/dialogs/backgroundpropertiesdialog.h +++ b/dialogs/backgroundpropertiesdialog.h @@ -41,5 +41,5 @@ private: bool m_unsavedChanges{}; - std::optional m_newPixmap; + QPixmap m_pixmap; }; diff --git a/dialogs/backgroundpropertiesdialog.ui b/dialogs/backgroundpropertiesdialog.ui index 9bd2054..0aa5732 100644 --- a/dialogs/backgroundpropertiesdialog.ui +++ b/dialogs/backgroundpropertiesdialog.ui @@ -40,6 +40,9 @@ 0 + + The name of the background + @@ -59,6 +62,9 @@ + + Load the background from a file + &Load Background @@ -70,6 +76,9 @@ + + Save the background to a file + &Save Background @@ -81,6 +90,9 @@ + + Edit the background image + &Edit Background @@ -123,6 +135,9 @@ + + Indicate whether to use this background as a tile set + &Use as tile set diff --git a/dialogs/codeeditordialog.cpp b/dialogs/codeeditordialog.cpp new file mode 100644 index 0000000..12c943a --- /dev/null +++ b/dialogs/codeeditordialog.cpp @@ -0,0 +1,84 @@ +#include "codeeditordialog.h" +#include "ui_codeeditordialog.h" + +#include +#include +#include +#include + +#include "jshighlighter.h" + +CodeEditorDialog::CodeEditorDialog(QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_labelPosition{new QLabel{this}} +{ + m_ui->setupUi(this); + + m_labelPosition->setFrameStyle(QFrame::Sunken); + m_ui->statusbar->addWidget(m_labelPosition); + + { + QFont font; + font.setFamily("Consolas"); + font.setFixedPitch(true); + font.setPointSize(10); + m_ui->codeEdit->setFont(font); + } + + new JSHighlighter{m_ui->codeEdit->document()}; + + updatePosition(); + + connect(m_ui->actionLoad, &QAction::triggered, + this, &CodeEditorDialog::load); + connect(m_ui->actionSave, &QAction::triggered, + this, &CodeEditorDialog::save); + connect(m_ui->actionPrint, &QAction::triggered, + this, &CodeEditorDialog::print); + + connect(m_ui->codeEdit, &QPlainTextEdit::textChanged, + this, &CodeEditorDialog::updatePosition); + connect(m_ui->codeEdit, &QPlainTextEdit::cursorPositionChanged, + this, &CodeEditorDialog::updatePosition); +} + +CodeEditorDialog::~CodeEditorDialog() = default; + +void CodeEditorDialog::load() +{ + +} + +void CodeEditorDialog::save() +{ + +} + +void CodeEditorDialog::print() +{ + +} + +void CodeEditorDialog::updatePosition() +{ + auto cursor = m_ui->codeEdit->textCursor(); + auto position = cursor.position(); + cursor.movePosition(QTextCursor::StartOfLine); + position -= cursor.position() - 1; + + int lines = 1; + while (cursor.positionInBlock() > 0) + { + cursor.movePosition(QTextCursor::Up); + //lines++; + } + QTextBlock block = cursor.block().previous(); + while (block.isValid()) + { + lines += 1; //block.lineCount(); + block = block.previous(); + } + + m_labelPosition->setText(tr("%0/%1: %2").arg(lines).arg(m_ui->codeEdit->blockCount()).arg(position)); +} diff --git a/dialogs/codeeditordialog.h b/dialogs/codeeditordialog.h new file mode 100644 index 0000000..2958080 --- /dev/null +++ b/dialogs/codeeditordialog.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +class QLabel; +namespace Ui { class CodeEditorDialog; } + +class CodeEditorDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CodeEditorDialog(QWidget *parent = nullptr); + ~CodeEditorDialog(); + +private slots: + void load(); + void save(); + void print(); + + void updatePosition(); + +protected: + const std::unique_ptr m_ui; + +private: + QLabel * const m_labelPosition; +}; diff --git a/dialogs/scriptpropertiesdialog.ui b/dialogs/codeeditordialog.ui similarity index 98% rename from dialogs/scriptpropertiesdialog.ui rename to dialogs/codeeditordialog.ui index 7f70b44..d99529b 100644 --- a/dialogs/scriptpropertiesdialog.ui +++ b/dialogs/codeeditordialog.ui @@ -1,7 +1,7 @@ - ScriptPropertiesDialog - + CodeEditorDialog + 0 @@ -11,7 +11,7 @@ - Script Properties + Code Editor @@ -231,6 +231,9 @@ OK + + OK, Save Changes + @@ -391,7 +394,7 @@ actionOk triggered() - ScriptPropertiesDialog + CodeEditorDialog accept() diff --git a/dialogs/createspritedialog.cpp b/dialogs/createspritedialog.cpp index d49520e..1ad7527 100644 --- a/dialogs/createspritedialog.cpp +++ b/dialogs/createspritedialog.cpp @@ -9,10 +9,10 @@ CreateSpriteDialog::CreateSpriteDialog(QWidget *parent) : { m_ui->setupUi(this); - setWindowFlags(windowFlags() - & ~Qt::Dialog - | Qt::Window - | Qt::WindowCloseButtonHint); +#ifdef Q_OS_LINUX + setWindowFlags(windowFlags() & ~Qt::Dialog | Qt::Window); +#endif + setWindowFlag(Qt::WindowCloseButtonHint); if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Ok)) button->setIcon(QIcon{":/qtgameengine/icons/ok.png"}); @@ -29,7 +29,7 @@ CreateSpriteDialog::CreateSpriteDialog(const QSize &size, QWidget *parent) : CreateSpriteDialog::~CreateSpriteDialog() = default; -QSize CreateSpriteDialog::size() const +QSize CreateSpriteDialog::spriteSize() const { return QSize{m_ui->spinBoxWidth->value(), m_ui->spinBoxHeight->value()}; } diff --git a/dialogs/createspritedialog.h b/dialogs/createspritedialog.h index aefa12a..2c143c4 100644 --- a/dialogs/createspritedialog.h +++ b/dialogs/createspritedialog.h @@ -15,7 +15,7 @@ public: explicit CreateSpriteDialog(const QSize &size, QWidget *parent = nullptr); ~CreateSpriteDialog(); - QSize size() const; + QSize spriteSize() const; private: const std::unique_ptr m_ui; diff --git a/dialogs/createspritedialog.ui b/dialogs/createspritedialog.ui index fb643b4..52a2de5 100644 --- a/dialogs/createspritedialog.ui +++ b/dialogs/createspritedialog.ui @@ -50,6 +50,9 @@ 0 + + 16384 + 32 diff --git a/dialogs/editspritedialog.cpp b/dialogs/editspritedialog.cpp index ebc1063..68c33eb 100644 --- a/dialogs/editspritedialog.cpp +++ b/dialogs/editspritedialog.cpp @@ -1,27 +1,30 @@ #include "editspritedialog.h" #include "ui_editspritedialog.h" +#include + #include "projectcontainer.h" #include "spritesmodel.h" #include "createspritedialog.h" #include "imageeditordialog.h" -EditSpriteDialog::EditSpriteDialog(Sprite &sprite, QWidget *parent) : +EditSpriteDialog::EditSpriteDialog(const std::vector &pixmaps, const QString &spriteName, QWidget *parent) : QDialog{parent}, m_ui{std::make_unique()}, - m_sprite{sprite}, - m_model{std::make_unique(this)} + m_pixmaps{pixmaps}, + m_spriteName{spriteName}, + m_model{std::make_unique(m_pixmaps, this)} { m_ui->setupUi(this); - setWindowFlags(windowFlags() - & ~Qt::Dialog - | Qt::Window - | Qt::WindowMinimizeButtonHint - | Qt::WindowMaximizeButtonHint - | Qt::WindowCloseButtonHint); +#ifdef Q_OS_LINUX + setWindowFlags(windowFlags() & ~Qt::Dialog | Qt::Window); +#endif + setWindowFlag(Qt::WindowMinimizeButtonHint); + setWindowFlag(Qt::WindowMaximizeButtonHint); + setWindowFlag(Qt::WindowCloseButtonHint); - setWindowTitle(tr("Sprite editor - %0").arg(m_sprite.name)); + updateTitle(); m_ui->actionNew->setShortcut(QKeySequence::New); m_ui->actionCreateFromFile->setShortcut(QKeySequence::Open); @@ -36,16 +39,95 @@ EditSpriteDialog::EditSpriteDialog(Sprite &sprite, QWidget *parent) : EditSpriteDialog::~EditSpriteDialog() = default; +void EditSpriteDialog::accept() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + // TODO + + QDialog::accept(); +} + +void EditSpriteDialog::reject() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + const auto result = QMessageBox::warning( + this, + tr("The Sprite has been modified."), + tr("Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, + QMessageBox::Save + ); + switch (result) + { + case QMessageBox::Save: + accept(); + return; + case QMessageBox::Discard: + QDialog::reject(); + return; + case QMessageBox::Cancel: + return; + default: + qWarning() << "unexpected dialog result" << result; + } +} + void EditSpriteDialog::newSprite() { CreateSpriteDialog dialog{this}; if (dialog.exec() == QDialog::Accepted) { + QPixmap pixmap{dialog.spriteSize()}; + pixmap.fill(Qt::white); + m_model->beginResetModel(); + m_pixmaps = std::vector { std::move(pixmap) }; + m_model->endResetModel(); + qDebug() << m_model->rowCount({}); + changed(); } } void EditSpriteDialog::doubleClicked(const QModelIndex &index) { - ImageEditorDialog{this}.exec(); + if (!index.isValid()) + { + qWarning() << "unexpected invalid index"; + return; + } + + ImageEditorDialog dialog{m_model->pixmap(index), tr("Image Editor: %0").arg(m_spriteName), this}; + if (dialog.exec() == QDialog::Accepted) + { + m_pixmaps[index.row()] = dialog.pixmap(); + emit m_model->dataChanged(index, index, {Qt::DecorationRole}); + changed(); + } +} + +void EditSpriteDialog::changed() +{ + if (!m_unsavedChanges) + { + m_unsavedChanges = true; + updateTitle(); + } +} + +void EditSpriteDialog::updateTitle() +{ + setWindowTitle(tr("Sprite editor - %0%1") + .arg(m_spriteName) + .arg(m_unsavedChanges ? tr("*") : QString{}) + ); } diff --git a/dialogs/editspritedialog.h b/dialogs/editspritedialog.h index 4f180ab..4947d78 100644 --- a/dialogs/editspritedialog.h +++ b/dialogs/editspritedialog.h @@ -1,8 +1,10 @@ #pragma once #include +#include #include +#include namespace Ui { class EditSpriteDialog; } struct Sprite; @@ -13,17 +15,29 @@ class EditSpriteDialog : public QDialog Q_OBJECT public: - explicit EditSpriteDialog(Sprite &sprite, QWidget *parent = nullptr); + explicit EditSpriteDialog(const std::vector &pixmaps, const QString &spriteName, QWidget *parent = nullptr); ~EditSpriteDialog(); + void accept() override; + void reject() override; + + const std::vector &pixmaps() const { return m_pixmaps; } + private slots: void newSprite(); void doubleClicked(const QModelIndex &index); + void changed(); + private: + void updateTitle(); + const std::unique_ptr m_ui; - Sprite &m_sprite; + std::vector m_pixmaps; + const QString m_spriteName; + + bool m_unsavedChanges{}; const std::unique_ptr m_model; }; diff --git a/dialogs/extensionpackagesdialog.cpp b/dialogs/extensionpackagesdialog.cpp index eb31f4d..4ca611e 100644 --- a/dialogs/extensionpackagesdialog.cpp +++ b/dialogs/extensionpackagesdialog.cpp @@ -7,12 +7,10 @@ ExtensionPackagesDialog::ExtensionPackagesDialog(QWidget *parent) : { m_ui->setupUi(this); - setWindowFlags(windowFlags() - & ~Qt::Dialog - | Qt::Window - | Qt::WindowMinimizeButtonHint - | Qt::WindowMaximizeButtonHint - | Qt::WindowCloseButtonHint); +#ifdef Q_OS_LINUX + setWindowFlags(windowFlags() & ~Qt::Dialog | Qt::Window); +#endif + setWindowFlag(Qt::WindowCloseButtonHint); if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Ok)) button->setIcon(QIcon{":/qtgameengine/icons/ok.png"}); diff --git a/dialogs/fontpropertiesdialog.ui b/dialogs/fontpropertiesdialog.ui index ff0e779..c767f61 100644 --- a/dialogs/fontpropertiesdialog.ui +++ b/dialogs/fontpropertiesdialog.ui @@ -31,7 +31,11 @@ - + + + The name of the font + + @@ -130,6 +134,9 @@ + + Set the character range to the normal range (32 - 127) + Normal @@ -137,6 +144,9 @@ + + Set the character range to the digits range (48 - 57) + Digits @@ -144,6 +154,9 @@ + + Set the character range to the complete set (0 - 255) + All @@ -151,6 +164,9 @@ + + Set the character range to the letters range (65 - 122) + Letters diff --git a/dialogs/gameinformationdialog.cpp b/dialogs/gameinformationdialog.cpp index 647cfe0..f7f3020 100644 --- a/dialogs/gameinformationdialog.cpp +++ b/dialogs/gameinformationdialog.cpp @@ -6,12 +6,13 @@ GameInformationDialog::GameInformationDialog(QWidget *parent) : m_ui{std::make_unique()} { m_ui->setupUi(this); - setWindowFlags(windowFlags() - & ~Qt::Dialog - | Qt::Window - | Qt::WindowMinimizeButtonHint - | Qt::WindowMaximizeButtonHint - | Qt::WindowCloseButtonHint); + +#ifdef Q_OS_LINUX + setWindowFlags(windowFlags() & ~Qt::Dialog | Qt::Window); +#endif + setWindowFlag(Qt::WindowMinimizeButtonHint); + setWindowFlag(Qt::WindowMaximizeButtonHint); + setWindowFlag(Qt::WindowCloseButtonHint); } GameInformationDialog::~GameInformationDialog() = default; diff --git a/dialogs/gameinformationdialog.ui b/dialogs/gameinformationdialog.ui index 8583a96..244b01f 100644 --- a/dialogs/gameinformationdialog.ui +++ b/dialogs/gameinformationdialog.ui @@ -13,6 +13,19 @@ Game Information + + + + 50 + 50 + 201 + 16 + + + + Not yet implemented + + diff --git a/dialogs/globalgamesettingsdialog.cpp b/dialogs/globalgamesettingsdialog.cpp index bfa8504..903699b 100644 --- a/dialogs/globalgamesettingsdialog.cpp +++ b/dialogs/globalgamesettingsdialog.cpp @@ -2,6 +2,7 @@ #include "ui_globalgamesettingsdialog.h" #include +#include GlobalGameSettingsDialog::GlobalGameSettingsDialog(QWidget *parent) : QDialog{parent}, @@ -9,17 +10,102 @@ GlobalGameSettingsDialog::GlobalGameSettingsDialog(QWidget *parent) : { m_ui->setupUi(this); - setWindowFlags(windowFlags() - & ~Qt::Dialog - | Qt::Window - | Qt::WindowMinimizeButtonHint - | Qt::WindowMaximizeButtonHint - | Qt::WindowCloseButtonHint); +#ifdef Q_OS_LINUX + setWindowFlags(windowFlags() & ~Qt::Dialog | Qt::Window); +#endif + setWindowFlag(Qt::WindowCloseButtonHint); + + updateTitle(); if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Ok)) button->setIcon(QIcon{":/qtgameengine/icons/ok.png"}); if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Cancel)) button->setIcon(QIcon{":/qtgameengine/icons/delete.png"}); + + connect(m_ui->checkBoxFullscreen, &QCheckBox::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->radioButtonFixedScale, &QCheckBox::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->radioButtonKeepAspectRatio, &QRadioButton::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->radioButtonFullScale, &QRadioButton::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->checkBoxInterpolateColors, &QRadioButton::toggled, + this, &GlobalGameSettingsDialog::changed); + // TODO toolButtonColorOutside + connect(m_ui->checkBoxAllowResize, &QCheckBox::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->checkBoxAlwaysOntop, &QCheckBox::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->checkBoxNoBorder, &QCheckBox::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->checkBoxNoWindowButtons, &QCheckBox::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->checkBoxDisplayCursor, &QCheckBox::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->checkBoxFreezeOnFocusLost, &QCheckBox::toggled, + this, &GlobalGameSettingsDialog::changed); + connect(m_ui->checkBoxDisableScreensavers, &QCheckBox::toggled, + this, &GlobalGameSettingsDialog::changed); } GlobalGameSettingsDialog::~GlobalGameSettingsDialog() = default; + +void GlobalGameSettingsDialog::accept() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + // TODO + + QDialog::accept(); +} + +void GlobalGameSettingsDialog::reject() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + const auto result = QMessageBox::warning( + this, + tr("The Global Game Settings has been modified."), + tr("Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, + QMessageBox::Save + ); + switch (result) + { + case QMessageBox::Save: + accept(); + return; + case QMessageBox::Discard: + QDialog::reject(); + return; + case QMessageBox::Cancel: + return; + default: + qWarning() << "unexpected dialog result" << result; + } +} + +void GlobalGameSettingsDialog::changed() +{ + if (!m_unsavedChanges) + { + m_unsavedChanges = true; + updateTitle(); + } +} + +void GlobalGameSettingsDialog::updateTitle() +{ + setWindowTitle(tr("Global Game Settings%0") + .arg(m_unsavedChanges ? tr("*") : QString{}) + ); +} diff --git a/dialogs/globalgamesettingsdialog.h b/dialogs/globalgamesettingsdialog.h index 22f86c4..2001a25 100644 --- a/dialogs/globalgamesettingsdialog.h +++ b/dialogs/globalgamesettingsdialog.h @@ -14,6 +14,16 @@ public: explicit GlobalGameSettingsDialog(QWidget *parent = nullptr); ~GlobalGameSettingsDialog(); + void accept() override; + void reject() override; + +private slots: + void changed(); + private: + void updateTitle(); + const std::unique_ptr m_ui; + + bool m_unsavedChanges{}; }; diff --git a/dialogs/globalgamesettingsdialog.ui b/dialogs/globalgamesettingsdialog.ui index 21e7b7b..f4fb4de 100644 --- a/dialogs/globalgamesettingsdialog.ui +++ b/dialogs/globalgamesettingsdialog.ui @@ -109,7 +109,7 @@ - + ... diff --git a/dialogs/imageeditordialog.cpp b/dialogs/imageeditordialog.cpp index 163dec5..58e72fc 100644 --- a/dialogs/imageeditordialog.cpp +++ b/dialogs/imageeditordialog.cpp @@ -1,18 +1,88 @@ #include "imageeditordialog.h" #include "ui_imageeditordialog.h" -ImageEditorDialog::ImageEditorDialog(QWidget *parent) : +#include + +ImageEditorDialog::ImageEditorDialog(const QPixmap &pixmap, const QString &title, QWidget *parent) : QDialog{parent}, - m_ui{std::make_unique()} + m_ui{std::make_unique()}, + m_pixmap{pixmap}, + m_title{title} { m_ui->setupUi(this); - setWindowFlags(windowFlags() - & ~Qt::Dialog - | Qt::Window - | Qt::WindowMinimizeButtonHint - | Qt::WindowMaximizeButtonHint - | Qt::WindowCloseButtonHint); +#ifdef Q_OS_LINUX + setWindowFlags(windowFlags() & ~Qt::Dialog | Qt::Window); +#endif + setWindowFlag(Qt::WindowMinimizeButtonHint); + setWindowFlag(Qt::WindowMaximizeButtonHint); + setWindowFlag(Qt::WindowCloseButtonHint); + + updateTitle(); + + m_ui->scrollArea->setBackgroundRole(QPalette::Dark); + + m_ui->canvas->setPixmap(m_pixmap); } ImageEditorDialog::~ImageEditorDialog() = default; + +void ImageEditorDialog::accept() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + // TODO + + QDialog::accept(); +} + +void ImageEditorDialog::reject() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + const auto result = QMessageBox::warning( + this, + tr("The Image has been modified."), + tr("Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, + QMessageBox::Save + ); + switch (result) + { + case QMessageBox::Save: + accept(); + return; + case QMessageBox::Discard: + QDialog::reject(); + return; + case QMessageBox::Cancel: + return; + default: + qWarning() << "unexpected dialog result" << result; + } +} + +void ImageEditorDialog::changed() +{ + if (!m_unsavedChanges) + { + m_unsavedChanges = true; + updateTitle(); + } +} + +void ImageEditorDialog::updateTitle() +{ + setWindowTitle(tr("%0%1") + .arg(m_title) + .arg(m_unsavedChanges ? tr("*") : QString{}) + ); +} diff --git a/dialogs/imageeditordialog.h b/dialogs/imageeditordialog.h index 2e44ffb..d2a114c 100644 --- a/dialogs/imageeditordialog.h +++ b/dialogs/imageeditordialog.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include @@ -11,9 +13,24 @@ class ImageEditorDialog : public QDialog Q_OBJECT public: - explicit ImageEditorDialog(QWidget *parent = nullptr); + explicit ImageEditorDialog(const QPixmap &pixmap, const QString &title, QWidget *parent = nullptr); ~ImageEditorDialog(); + const QPixmap &pixmap() const { return m_pixmap; } + + void accept() override; + void reject() override; + +private slots: + void changed(); + private: + void updateTitle(); + const std::unique_ptr m_ui; + + QPixmap m_pixmap; + const QString m_title; + + bool m_unsavedChanges{}; }; diff --git a/dialogs/imageeditordialog.ui b/dialogs/imageeditordialog.ui index 671fcb2..5bbc45d 100644 --- a/dialogs/imageeditordialog.ui +++ b/dialogs/imageeditordialog.ui @@ -31,7 +31,7 @@ - + &File @@ -44,7 +44,7 @@ - + &Edit @@ -61,26 +61,26 @@ - + &View - + &Transform - + &Image - - - - - + + + + + @@ -135,7 +135,40 @@ - + + + + + + + + + + + + + + + + 0 + 0 + 0 + 0 + + + + + + + + + + + + + + + @@ -264,8 +297,32 @@ + + + DrawingCanvasWidget + QWidget +
drawingcanvaswidget.h
+
+
- + + + actionCloseSavingChanges + triggered() + ImageEditorDialog + accept() + + + -1 + -1 + + + 285 + 226 + + + + diff --git a/dialogs/includedfilesdialog.cpp b/dialogs/includedfilesdialog.cpp new file mode 100644 index 0000000..a93ce5b --- /dev/null +++ b/dialogs/includedfilesdialog.cpp @@ -0,0 +1,23 @@ +#include "includedfilesdialog.h" +#include "ui_includedfilesdialog.h" + +#include "projectcontainer.h" +#include "includedfilesmodel.h" + +IncludedFilesDialog::IncludedFilesDialog(ProjectContainer &project, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_project{project}, + m_model{std::make_unique(m_project, this)} +{ + m_ui->setupUi(this); + + if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Ok)) + button->setIcon(QIcon{":/qtgameengine/icons/ok.png"}); + if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Cancel)) + button->setIcon(QIcon{":/qtgameengine/icons/delete.png"}); + + m_ui->listView->setModel(m_model.get()); +} + +IncludedFilesDialog::~IncludedFilesDialog() = default; diff --git a/dialogs/includedfilesdialog.h b/dialogs/includedfilesdialog.h new file mode 100644 index 0000000..f52e480 --- /dev/null +++ b/dialogs/includedfilesdialog.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +namespace Ui { class IncludedFilesDialog; } +struct ProjectContainer; +class IncludedFilesModel; + +class IncludedFilesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit IncludedFilesDialog(ProjectContainer &project, QWidget *parent = nullptr); + ~IncludedFilesDialog(); + +private: + const std::unique_ptr m_ui; + + ProjectContainer &m_project; + + const std::unique_ptr m_model; +}; diff --git a/dialogs/includedfilesdialog.ui b/dialogs/includedfilesdialog.ui new file mode 100644 index 0000000..aff0970 --- /dev/null +++ b/dialogs/includedfilesdialog.ui @@ -0,0 +1,112 @@ + + + IncludedFilesDialog + + + + 0 + 0 + 400 + 300 + + + + Included Files + + + + + + + + + Files to include in the stand alone executable: + + + + + + + + + + + + Add + + + + + + + Change + + + + + + + Delete + + + + + + + Clear + + + + + + + + + + + + QDialogButtonBox::Ok + + + true + + + + + + + + + buttonBox + accepted() + IncludedFilesDialog + accept() + + + 199 + 278 + + + 199 + 149 + + + + + buttonBox + rejected() + IncludedFilesDialog + reject() + + + 199 + 278 + + + 199 + 149 + + + + + diff --git a/dialogs/maskpropertiesdialog.cpp b/dialogs/maskpropertiesdialog.cpp new file mode 100644 index 0000000..389a3f2 --- /dev/null +++ b/dialogs/maskpropertiesdialog.cpp @@ -0,0 +1,16 @@ +#include "maskpropertiesdialog.h" +#include "ui_maskpropertiesdialog.h" + +MaskPropertiesDialog::MaskPropertiesDialog(QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()} +{ + m_ui->setupUi(this); + +#ifdef Q_OS_LINUX + setWindowFlags(windowFlags() & ~Qt::Dialog | Qt::Window); +#endif + setWindowFlag(Qt::WindowCloseButtonHint); +} + +MaskPropertiesDialog::~MaskPropertiesDialog() = default; diff --git a/dialogs/maskpropertiesdialog.h b/dialogs/maskpropertiesdialog.h new file mode 100644 index 0000000..7e24bdf --- /dev/null +++ b/dialogs/maskpropertiesdialog.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include + +namespace Ui { class MaskPropertiesDialog; } + +class MaskPropertiesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MaskPropertiesDialog(QWidget *parent = nullptr); + ~MaskPropertiesDialog(); + +private: + const std::unique_ptr m_ui; +}; diff --git a/dialogs/maskpropertiesdialog.ui b/dialogs/maskpropertiesdialog.ui new file mode 100644 index 0000000..a9083a2 --- /dev/null +++ b/dialogs/maskpropertiesdialog.ui @@ -0,0 +1,19 @@ + + + MaskPropertiesDialog + + + + 0 + 0 + 400 + 300 + + + + Mask Properties + + + + + diff --git a/dialogs/objectinformationdialog.cpp b/dialogs/objectinformationdialog.cpp index f01ab23..6b1adaa 100644 --- a/dialogs/objectinformationdialog.cpp +++ b/dialogs/objectinformationdialog.cpp @@ -1,9 +1,10 @@ #include "objectinformationdialog.h" #include "ui_objectinformationdialog.h" -ObjectInformationDialog::ObjectInformationDialog(QWidget *parent) : +ObjectInformationDialog::ObjectInformationDialog(ProjectContainer &project, QWidget *parent) : QDialog{parent}, - m_ui{std::make_unique()} + m_ui{std::make_unique()}, + m_project{project} { m_ui->setupUi(this); } diff --git a/dialogs/objectinformationdialog.h b/dialogs/objectinformationdialog.h index 7ce5ad1..2c7b98e 100644 --- a/dialogs/objectinformationdialog.h +++ b/dialogs/objectinformationdialog.h @@ -5,15 +5,18 @@ #include namespace Ui { class ObjectInformationDialog; } +struct ProjectContainer; class ObjectInformationDialog : public QDialog { Q_OBJECT public: - explicit ObjectInformationDialog(QWidget *parent = nullptr); + explicit ObjectInformationDialog(ProjectContainer &project, QWidget *parent = nullptr); ~ObjectInformationDialog(); private: const std::unique_ptr m_ui; + + ProjectContainer &m_project; }; diff --git a/dialogs/objectinformationdialog.ui b/dialogs/objectinformationdialog.ui index d47bb59..8a10df6 100644 --- a/dialogs/objectinformationdialog.ui +++ b/dialogs/objectinformationdialog.ui @@ -96,5 +96,22 @@ - + + + actionOk + triggered() + ObjectInformationDialog + accept() + + + -1 + -1 + + + 199 + 149 + + + + diff --git a/dialogs/pathpropertiesdialog.cpp b/dialogs/pathpropertiesdialog.cpp index 20e40df..dd1ed4d 100644 --- a/dialogs/pathpropertiesdialog.cpp +++ b/dialogs/pathpropertiesdialog.cpp @@ -1,25 +1,63 @@ #include "pathpropertiesdialog.h" #include "ui_pathpropertiesdialog.h" +#include +#include #include #include #include "projectcontainer.h" #include "projecttreemodel.h" +#include "pathpointsmodel.h" PathPropertiesDialog::PathPropertiesDialog(Path &path, ProjectTreeModel &projectModel, QWidget *parent) : QDialog{parent}, m_ui{std::make_unique()}, m_path{path}, m_projectModel{projectModel}, + m_points{path.points}, + m_pointsModel{std::make_unique(m_points, this)}, + m_spinBoxSnapX{new QSpinBox{this}}, + m_spinBoxSnapY{new QSpinBox{this}}, m_labelX{new QLabel{tr("x: %0").arg(0)}}, m_labelY{new QLabel{tr("y: %0").arg(0)}}, m_labelArea{new QLabel{tr("Area: (%0,%1)->(%2,%3)").arg(0).arg(0).arg(0).arg(0)}} { m_ui->setupUi(this); + m_ui->widget->setPoints(&m_points); + updateTitle(); + { + auto label = new QLabel{tr("Snap X:"), this}; + label->setBuddy(m_spinBoxSnapX); + m_ui->toolBar->insertWidget(m_ui->toolBar->actions().at(18), label); + } + m_spinBoxSnapX->setValue(m_ui->widget->gridX()); + m_spinBoxSnapX->setMaximumWidth(50); + m_ui->toolBar->insertWidget(m_ui->toolBar->actions().at(19), m_spinBoxSnapX); + + { + auto label = new QLabel{tr("Snap Y:"), this}; + label->setBuddy(m_spinBoxSnapY); + m_ui->toolBar->insertWidget(m_ui->toolBar->actions().at(20), label); + } + m_spinBoxSnapY->setValue(m_ui->widget->gridY()); + m_spinBoxSnapY->setMaximumWidth(50); + m_ui->toolBar->insertWidget(m_ui->toolBar->actions().at(21), m_spinBoxSnapY); + + m_ui->actionGrid->setChecked(m_ui->widget->showGrid()); + + m_ui->treeView->setModel(m_pointsModel.get()); + + m_ui->radioButtonStraight->setChecked(m_path.type == Path::Type::Straight); + m_ui->radioButtonSmooth->setChecked(m_path.type == Path::Type::Smooth); + m_ui->checkBoxClosed->setChecked(m_path.closed); + m_ui->spinBoxPrecision->setValue(m_path.precision); + + m_ui->widget->setClosed(m_path.closed); + m_labelX->setFrameStyle(QFrame::Sunken); m_ui->statusbar->addWidget(m_labelX, 1); m_labelY->setFrameStyle(QFrame::Sunken); @@ -35,9 +73,41 @@ PathPropertiesDialog::PathPropertiesDialog(Path &path, ProjectTreeModel &project m_ui->lineEditName->setText(m_path.name); + m_ui->treeView->setColumnWidth(1, 75); + + connect(m_ui->widget, &PathPointsWidget::pointInserted, + m_pointsModel.get(), &PathPointsModel::pointInserted); + connect(m_ui->widget, &PathPointsWidget::pointMoved, + m_pointsModel.get(), &PathPointsModel::pointMoved); + connect(&m_projectModel, &ProjectTreeModel::pathNameChanged, this, &PathPropertiesDialog::pathNameChanged); + connect(m_ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, + this, &PathPropertiesDialog::selectionChanged); + + connect(m_ui->widget, &PathPointsWidget::gridXChanged, + m_spinBoxSnapX, &QSpinBox::setValue); + connect(m_ui->widget, &PathPointsWidget::gridYChanged, + m_spinBoxSnapY, &QSpinBox::setValue); + connect(m_ui->widget, &PathPointsWidget::showGridChanged, + m_ui->actionGrid, &QAction::setChecked); + connect(m_ui->widget, &PathPointsWidget::closedChanged, + m_ui->checkBoxClosed, &QCheckBox::setChecked); + connect(m_ui->widget, &PathPointsWidget::cursorMoved, + this, &PathPropertiesDialog::cursorMoved); + connect(m_ui->widget, &PathPointsWidget::selectedIndexChanged, + this, &PathPropertiesDialog::selectedPointChanged); + + connect(m_spinBoxSnapX, &QSpinBox::valueChanged, + m_ui->widget, &PathPointsWidget::setGridX); + connect(m_spinBoxSnapY, &QSpinBox::valueChanged, + m_ui->widget, &PathPointsWidget::setGridY); + connect(m_ui->actionGrid, &QAction::toggled, + m_ui->widget, &PathPointsWidget::setShowGrid); + connect(m_ui->checkBoxClosed, &QCheckBox::toggled, + m_ui->widget, &PathPointsWidget::setClosed); + connect(m_ui->pushButtonAdd, &QAbstractButton::pressed, this, &PathPropertiesDialog::add); connect(m_ui->pushButtonInsert, &QAbstractButton::pressed, @@ -47,12 +117,35 @@ PathPropertiesDialog::PathPropertiesDialog(Path &path, ProjectTreeModel &project connect(m_ui->lineEditName, &QLineEdit::textChanged, this, &PathPropertiesDialog::changed); + + connect(m_pointsModel.get(), &QAbstractTableModel::rowsInserted, + this, &PathPropertiesDialog::changed); + connect(m_pointsModel.get(), &QAbstractTableModel::rowsRemoved, + this, &PathPropertiesDialog::changed); + connect(m_pointsModel.get(), &QAbstractTableModel::dataChanged, + this, &PathPropertiesDialog::dataChanged); + connect(m_pointsModel.get(), &QAbstractTableModel::dataChanged, + this, &PathPropertiesDialog::changed); + connect(m_ui->radioButtonStraight, &QRadioButton::toggled, + this, &PathPropertiesDialog::changed); + connect(m_ui->radioButtonSmooth, &QRadioButton::toggled, + this, &PathPropertiesDialog::changed); + connect(m_ui->checkBoxClosed, &QCheckBox::toggled, + this, &PathPropertiesDialog::changed); + connect(m_ui->spinBoxPrecision, &QSpinBox::valueChanged, + this, &PathPropertiesDialog::changed); } PathPropertiesDialog::~PathPropertiesDialog() = default; void PathPropertiesDialog::accept() { + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + if (m_path.name != m_ui->lineEditName->text()) { if (!m_projectModel.rename(m_path, m_ui->lineEditName->text())) @@ -62,7 +155,22 @@ void PathPropertiesDialog::accept() } } - // TODO + m_path.points = m_points; + + if (m_ui->radioButtonStraight->isChecked()) + m_path.type = Path::Type::Straight; + else if (m_ui->radioButtonSmooth->isChecked()) + m_path.type = Path::Type::Smooth; + else + { + QMessageBox::critical(this, tr("No kind selected!"), tr("No kind selected!")); + return; + } + + m_path.closed = m_ui->checkBoxClosed->isChecked(); + m_path.precision = m_ui->spinBoxPrecision->value(); + + // TODO update points QDialog::accept(); } @@ -97,6 +205,36 @@ void PathPropertiesDialog::reject() } } +void PathPropertiesDialog::selectionChanged(const QModelIndex &index) +{ + if (!index.isValid()) + { + m_ui->widget->setSelectedIndex(std::nullopt); + return; + } + + m_ui->widget->setSelectedIndex(index.row()); + + updatePointFields(); +} + +void PathPropertiesDialog::selectedPointChanged(const std::optional &index) +{ + m_ui->treeView->setCurrentIndex(index ? m_pointsModel->index(*index, 0, {}) : QModelIndex{}); +} + +void PathPropertiesDialog::dataChanged(const QModelIndex &index) +{ + if (index == m_ui->treeView->currentIndex()) + updatePointFields(); +} + +void PathPropertiesDialog::cursorMoved(const QPoint &point) +{ + m_labelX->setText(tr("X: %0").arg(point.x())); + m_labelY->setText(tr("Y: %0").arg(point.y())); +} + void PathPropertiesDialog::add() { QMessageBox::warning(this, tr("Not yet implemented"), tr("Not yet implemented")); @@ -141,3 +279,11 @@ void PathPropertiesDialog::updateTitle() .arg(m_unsavedChanges ? tr("*") : QString{}) ); } + +void PathPropertiesDialog::updatePointFields() +{ + const auto point = m_pointsModel->getPoint(m_ui->treeView->currentIndex()); + m_ui->spinBoxX->setValue(point.point.x()); + m_ui->spinBoxY->setValue(point.point.y()); + m_ui->spinBoxSp->setValue(point.sp); +} diff --git a/dialogs/pathpropertiesdialog.h b/dialogs/pathpropertiesdialog.h index 9db12a3..daada90 100644 --- a/dialogs/pathpropertiesdialog.h +++ b/dialogs/pathpropertiesdialog.h @@ -3,11 +3,15 @@ #include #include +#include +#include "projectcontainer.h" + +class QSpinBox; class QLabel; namespace Ui { class PathPropertiesDialog; } -struct Path; class ProjectTreeModel; +class PathPointsModel; class PathPropertiesDialog : public QDialog { @@ -21,6 +25,11 @@ public: void reject() override; private slots: + void selectionChanged(const QModelIndex &index); + void selectedPointChanged(const std::optional &index); + void dataChanged(const QModelIndex &index); + void cursorMoved(const QPoint &point); + void add(); void insert(); void delete_(); @@ -31,14 +40,22 @@ private slots: private: void updateTitle(); + void updatePointFields(); const std::unique_ptr m_ui; Path &m_path; ProjectTreeModel &m_projectModel; + std::vector m_points; + + const std::unique_ptr m_pointsModel; + bool m_unsavedChanges{}; + QSpinBox * const m_spinBoxSnapX; + QSpinBox * const m_spinBoxSnapY; + QLabel * const m_labelX; QLabel * const m_labelY; QLabel * const m_labelArea; diff --git a/dialogs/pathpropertiesdialog.ui b/dialogs/pathpropertiesdialog.ui index aff912a..4c9fbd7 100644 --- a/dialogs/pathpropertiesdialog.ui +++ b/dialogs/pathpropertiesdialog.ui @@ -6,8 +6,8 @@ 0 0 - 728 - 449 + 921 + 576 @@ -78,6 +78,20 @@ + + + + + + + + + + + + + +
@@ -112,7 +126,11 @@ - + + + The name of the path + + @@ -133,13 +151,19 @@ - + 0 0 + + false + + + true + @@ -170,7 +194,14 @@ - + + + X-coordinate of the point + + + 16384 + + @@ -180,7 +211,14 @@ - + + + Y-coordinate of the point + + + 16384 + + @@ -190,7 +228,14 @@ - + + + Relative speed at this point (100 = default) + + + 16384 + + @@ -211,6 +256,9 @@ + + Add the point to the path + Add @@ -218,6 +266,9 @@ + + Insert the point before the current one + Insert @@ -225,6 +276,9 @@ + + Delete the point from the path + Delete @@ -256,6 +310,9 @@ + + Indicate the type of connections between the points + connection kind @@ -287,7 +344,11 @@ - + + + Precision with which to create smooth curves + + @@ -324,7 +385,14 @@ - + + + + 300 + 0 + + + @@ -394,7 +462,153 @@ Shift the path + + + + :/qtgameengine/icons/flip-horizontal.png:/qtgameengine/icons/flip-horizontal.png + + + Mirror horizontally + + + Mirror the path horizontally + + + + + + :/qtgameengine/icons/flip-vertical.png:/qtgameengine/icons/flip-vertical.png + + + Flip vertically + + + Flip the path vertically + + + + + + :/qtgameengine/icons/rotate.png:/qtgameengine/icons/rotate.png + + + Rotate + + + Rotate the path + + + + + + :/qtgameengine/icons/scale.png:/qtgameengine/icons/scale.png + + + Scale + + + Scale the path + + + + + + :/qtgameengine/icons/arrow-left.png:/qtgameengine/icons/arrow-left.png + + + Shift Left + + + Shift the view to the left + + + + + + :/qtgameengine/icons/arrow-right.png:/qtgameengine/icons/arrow-right.png + + + Shift right + + + Shift the view to the right + + + + + + :/qtgameengine/icons/arrow-up.png:/qtgameengine/icons/arrow-up.png + + + Shift top + + + Shift the view to the top + + + + + + :/qtgameengine/icons/arrow-down.png:/qtgameengine/icons/arrow-down.png + + + Shift bottom + + + Shift the view to the bottom + + + + + + :/qtgameengine/icons/center.png:/qtgameengine/icons/center.png + + + Center view + + + Center view around the path + + + + + true + + + true + + + + :/qtgameengine/icons/grid.png:/qtgameengine/icons/grid.png + + + Grid + + + Toggle the showing of the grid + + + + + + :/qtgameengine/icons/room.png:/qtgameengine/icons/room.png + + + Room + + + Indicate the room to show as background + + + + + PathPointsWidget + QWidget +
pathpointswidget.h
+ 1 +
+
diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp index d889813..3a1fbaa 100644 --- a/dialogs/preferencesdialog.cpp +++ b/dialogs/preferencesdialog.cpp @@ -1,17 +1,23 @@ #include "preferencesdialog.h" #include "ui_preferencesdialog.h" +#include + PreferencesDialog::PreferencesDialog(QWidget *parent) : QDialog{parent}, m_ui{std::make_unique()} { m_ui->setupUi(this); - setWindowFlags(windowFlags() - & ~Qt::Dialog - | Qt::Window - | Qt::WindowMinimizeButtonHint - | Qt::WindowMaximizeButtonHint - | Qt::WindowCloseButtonHint); + +#ifdef Q_OS_LINUX + setWindowFlags(windowFlags() & ~Qt::Dialog | Qt::Window); +#endif + setWindowFlag(Qt::WindowCloseButtonHint); + + if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Ok)) + button->setIcon(QIcon{":/qtgameengine/icons/ok.png"}); + if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Cancel)) + button->setIcon(QIcon{":/qtgameengine/icons/delete.png"}); } PreferencesDialog::~PreferencesDialog() = default; diff --git a/dialogs/scriptpropertiesdialog.cpp b/dialogs/scriptpropertiesdialog.cpp index 00537a1..46384b8 100644 --- a/dialogs/scriptpropertiesdialog.cpp +++ b/dialogs/scriptpropertiesdialog.cpp @@ -1,27 +1,20 @@ #include "scriptpropertiesdialog.h" -#include "ui_scriptpropertiesdialog.h" +#include "ui_codeeditordialog.h" -#include -#include -#include #include -#include +#include #include +#include #include "projectcontainer.h" #include "projecttreemodel.h" -#include "jshighlighter.h" ScriptPropertiesDialog::ScriptPropertiesDialog(Script &script, ProjectTreeModel &projectModel, QWidget *parent) : - QDialog{parent}, - m_ui{std::make_unique()}, + CodeEditorDialog{parent}, m_script{script}, m_projectModel{projectModel}, - m_lineEditName{new QLineEdit{this}}, - m_labelPosition{new QLabel{this}} + m_lineEditName{new QLineEdit{this}} { - m_ui->setupUi(this); - updateTitle(); { @@ -32,46 +25,19 @@ ScriptPropertiesDialog::ScriptPropertiesDialog(Script &script, ProjectTreeModel m_lineEditName->setMaximumWidth(100); m_ui->toolBar->addWidget(m_lineEditName); - m_labelPosition->setFrameStyle(QFrame::Sunken); - m_ui->statusbar->addWidget(m_labelPosition); - - { - QFont font; - font.setFamily("Consolas"); - font.setFixedPitch(true); - font.setPointSize(10); - m_ui->codeEdit->setFont(font); - } - - new JSHighlighter{m_ui->codeEdit->document()}; m_lineEditName->setText(m_script.name); m_ui->codeEdit->setPlainText(m_script.script); - updatePosition(); - connect(&m_projectModel, &ProjectTreeModel::scriptNameChanged, this, &ScriptPropertiesDialog::scriptNameChanged); - connect(m_ui->actionLoad, &QAction::triggered, - this, &ScriptPropertiesDialog::load); - connect(m_ui->actionSave, &QAction::triggered, - this, &ScriptPropertiesDialog::save); - connect(m_ui->actionPrint, &QAction::triggered, - this, &ScriptPropertiesDialog::print); - connect(m_lineEditName, &QLineEdit::textChanged, this, &ScriptPropertiesDialog::changed); connect(m_ui->codeEdit, &QPlainTextEdit::textChanged, this, &ScriptPropertiesDialog::changed); - connect(m_ui->codeEdit, &QPlainTextEdit::textChanged, - this, &ScriptPropertiesDialog::updatePosition); - connect(m_ui->codeEdit, &QPlainTextEdit::cursorPositionChanged, - this, &ScriptPropertiesDialog::updatePosition); } -ScriptPropertiesDialog::~ScriptPropertiesDialog() = default; - void ScriptPropertiesDialog::accept() { if (m_script.name != m_lineEditName->text()) @@ -85,14 +51,14 @@ void ScriptPropertiesDialog::accept() m_script.script = m_ui->codeEdit->toPlainText(); - QDialog::accept(); + CodeEditorDialog::accept(); } void ScriptPropertiesDialog::reject() { if (!m_unsavedChanges) { - QDialog::reject(); + CodeEditorDialog::reject(); return; } @@ -109,7 +75,7 @@ void ScriptPropertiesDialog::reject() accept(); return; case QMessageBox::Discard: - QDialog::reject(); + CodeEditorDialog::reject(); return; case QMessageBox::Cancel: return; @@ -127,44 +93,6 @@ void ScriptPropertiesDialog::changed() } } -void ScriptPropertiesDialog::load() -{ - -} - -void ScriptPropertiesDialog::save() -{ - -} - -void ScriptPropertiesDialog::print() -{ - -} - -void ScriptPropertiesDialog::updatePosition() -{ - auto cursor = m_ui->codeEdit->textCursor(); - auto position = cursor.position(); - cursor.movePosition(QTextCursor::StartOfLine); - position -= cursor.position() - 1; - - int lines = 1; - while (cursor.positionInBlock() > 0) - { - cursor.movePosition(QTextCursor::Up); - //lines++; - } - QTextBlock block = cursor.block().previous(); - while (block.isValid()) - { - lines += 1; //block.lineCount(); - block = block.previous(); - } - - m_labelPosition->setText(tr("%0/%1: %2").arg(lines).arg(m_ui->codeEdit->blockCount()).arg(position)); -} - void ScriptPropertiesDialog::scriptNameChanged(const Script &script) { if (&script != &m_script) @@ -185,3 +113,4 @@ void ScriptPropertiesDialog::updateTitle() .arg(m_unsavedChanges ? tr("*") : QString{}) ); } + diff --git a/dialogs/scriptpropertiesdialog.h b/dialogs/scriptpropertiesdialog.h index 4ed43ab..ebc389f 100644 --- a/dialogs/scriptpropertiesdialog.h +++ b/dialogs/scriptpropertiesdialog.h @@ -1,22 +1,15 @@ -#pragma once - -#include - -#include +#include "codeeditordialog.h" class QLineEdit; -class QLabel; -namespace Ui { class ScriptPropertiesDialog; } struct Script; class ProjectTreeModel; -class ScriptPropertiesDialog : public QDialog +class ScriptPropertiesDialog : public CodeEditorDialog { Q_OBJECT public: - explicit ScriptPropertiesDialog(Script &script, ProjectTreeModel &projectModel, QWidget *parent = nullptr); - ~ScriptPropertiesDialog(); + ScriptPropertiesDialog(Script &script, ProjectTreeModel &projectModel, QWidget *parent = nullptr); void accept() override; void reject() override; @@ -24,25 +17,15 @@ public: private slots: void changed(); - void load(); - void save(); - void print(); - - void updatePosition(); - void scriptNameChanged(const Script &script); private: void updateTitle(); - const std::unique_ptr m_ui; - Script &m_script; ProjectTreeModel &m_projectModel; bool m_unsavedChanges{}; QLineEdit * const m_lineEditName; - - QLabel * const m_labelPosition; }; diff --git a/dialogs/soundpropertiesdialog.cpp b/dialogs/soundpropertiesdialog.cpp index c6276d5..49ce290 100644 --- a/dialogs/soundpropertiesdialog.cpp +++ b/dialogs/soundpropertiesdialog.cpp @@ -14,7 +14,8 @@ SoundPropertiesDialog::SoundPropertiesDialog(Sound &sound, ProjectTreeModel &pro QDialog{parent}, m_ui{std::make_unique()}, m_sound{sound}, - m_projectModel{projectModel} + m_projectModel{projectModel}, + m_path{m_sound.path} { m_ui->setupUi(this); @@ -33,6 +34,8 @@ SoundPropertiesDialog::SoundPropertiesDialog(Sound &sound, ProjectTreeModel &pro } m_ui->radioButtonNormal->setChecked(m_sound.type == Sound::Type::Sound); m_ui->radioButtonMusic->setChecked(m_sound.type == Sound::Type::Music); + m_ui->radioButton3D->setChecked(false); + m_ui->radioButtonMultimedia->setChecked(false); m_ui->checkBoxChorus->setChecked(m_sound.effects.chorus); m_ui->checkBoxFlanger->setChecked(m_sound.effects.flanger); m_ui->checkBoxGargle->setChecked(m_sound.effects.gargle); @@ -88,6 +91,12 @@ SoundPropertiesDialog::~SoundPropertiesDialog() = default; void SoundPropertiesDialog::accept() { + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + if (m_sound.name != m_ui->lineEditName->text()) { if (!m_projectModel.rename(m_sound, m_ui->lineEditName->text())) @@ -97,18 +106,23 @@ void SoundPropertiesDialog::accept() } } - if (m_newPath) - m_sound.path = std::move(*m_newPath); + m_sound.path = std::move(m_path); if (m_ui->radioButtonNormal->isChecked()) m_sound.type = Sound::Type::Sound; else if (m_ui->radioButtonMusic->isChecked()) m_sound.type = Sound::Type::Music; - else + else if (m_ui->radioButton3D->isChecked() || + m_ui->radioButtonMultimedia->isChecked()) { QMessageBox::critical(this, tr("Not implemented"), tr("This kind of sound is not yet supported!")); return; } + else + { + QMessageBox::critical(this, tr("No kind selected!"), tr("No kind selected!")); + return; + } m_sound.effects.chorus = m_ui->checkBoxChorus->isChecked(); m_sound.effects.flanger = m_ui->checkBoxFlanger->isChecked(); @@ -153,7 +167,7 @@ void SoundPropertiesDialog::reject() void SoundPropertiesDialog::loadSound() { - const auto path = QFileDialog::getOpenFileName(this, tr("Open a Sound File...")); + auto path = QFileDialog::getOpenFileName(this, tr("Open a Sound File...")); if (path.isEmpty()) return; @@ -163,16 +177,15 @@ void SoundPropertiesDialog::loadSound() return; } - m_newPath = path; + m_path = std::move(path); changed(); - m_ui->labelFilename->setText(tr("Filename: %0").arg(QFileInfo{path}.fileName())); - m_soundEffect.setSource(QUrl::fromLocalFile(path)); + m_ui->labelFilename->setText(tr("Filename: %0").arg(QFileInfo{m_path}.fileName())); + m_soundEffect.setSource(QUrl::fromLocalFile(m_path)); } void SoundPropertiesDialog::saveSound() { - const auto &path = m_newPath ? *m_newPath : m_sound.path; - if (path.isEmpty()) + if (m_path.isEmpty()) { QMessageBox::warning(this, tr("Could not save Sound!"), tr("Could not save Sound!") + "\n\n" + tr("No sound has been selected yet.")); return; @@ -182,7 +195,7 @@ void SoundPropertiesDialog::saveSound() if (savePath.isEmpty()) return; - if (!QFile::copy(path, savePath)) + if (!QFile::copy(m_path, savePath)) { QMessageBox::warning(this, tr("Could not save Sound!"), tr("Could not save Sound!")); return; diff --git a/dialogs/soundpropertiesdialog.h b/dialogs/soundpropertiesdialog.h index c361506..2ee0957 100644 --- a/dialogs/soundpropertiesdialog.h +++ b/dialogs/soundpropertiesdialog.h @@ -43,7 +43,7 @@ private: bool m_unsavedChanges{}; - std::optional m_newPath; + QString m_path; QSoundEffect m_soundEffect; }; diff --git a/dialogs/soundpropertiesdialog.ui b/dialogs/soundpropertiesdialog.ui index 571defa..dd47047 100644 --- a/dialogs/soundpropertiesdialog.ui +++ b/dialogs/soundpropertiesdialog.ui @@ -31,7 +31,11 @@
- + + + The name of the sound + +
@@ -39,6 +43,9 @@ + + Load the sound from a file + &Load Sound @@ -80,6 +87,9 @@ + + Save the sound to a file + Sa&ve Sound @@ -98,6 +108,9 @@ + + Set the kind of this sound + Kind @@ -107,9 +120,6 @@ Normal Sound - - true - @@ -138,6 +148,9 @@ + + Set the effects to apply for the sound + Effects @@ -194,6 +207,9 @@ + + Indicates the default volume for the sound + 100 @@ -217,6 +233,9 @@ + + Indicates the default pan for the sound + -100 @@ -238,6 +257,9 @@ + + Indicates whether to preload the sound when the game starts + &Preload @@ -263,6 +285,9 @@ + + Edit the sound using an external sound editor + &Edit Sound diff --git a/dialogs/spritepropertiesdialog.cpp b/dialogs/spritepropertiesdialog.cpp index a499345..c1e3c62 100644 --- a/dialogs/spritepropertiesdialog.cpp +++ b/dialogs/spritepropertiesdialog.cpp @@ -9,12 +9,14 @@ #include "projectcontainer.h" #include "projecttreemodel.h" #include "editspritedialog.h" +#include "maskpropertiesdialog.h" SpritePropertiesDialog::SpritePropertiesDialog(Sprite &sprite, ProjectTreeModel &projectModel, QWidget *parent) : QDialog{parent}, m_ui{std::make_unique()}, m_sprite{sprite}, - m_projectModel{projectModel} + m_projectModel{projectModel}, + m_pixmaps{m_sprite.pixmaps} { m_ui->setupUi(this); @@ -31,7 +33,6 @@ SpritePropertiesDialog::SpritePropertiesDialog(Sprite &sprite, ProjectTreeModel m_ui->spinBoxOriginY->setValue(m_sprite.origin.y); m_ui->checkBoxPreciseCollisionChecking->setChecked(m_sprite.preciseCollisionChecking); m_ui->checkBoxSeparateCollisionMasks->setChecked(m_sprite.separateCollisionMasks); - m_ui->labelPreview->setPixmap(m_sprite.pixmaps.empty() ? QPixmap{} : m_sprite.pixmaps.front()); connect(&m_projectModel, &ProjectTreeModel::spriteNameChanged, this, &SpritePropertiesDialog::spriteNameChanged); @@ -44,6 +45,8 @@ SpritePropertiesDialog::SpritePropertiesDialog(Sprite &sprite, ProjectTreeModel this, &SpritePropertiesDialog::editSprite); connect(m_ui->pushButtonCenterOrigin, &QAbstractButton::pressed, this, &SpritePropertiesDialog::centerOrigin); + connect(m_ui->pushButtonModifyCollisionmask, &QAbstractButton::pressed, + this, &SpritePropertiesDialog::modifyMask); connect(m_ui->lineEditName, &QLineEdit::textChanged, this, &SpritePropertiesDialog::changed); @@ -61,6 +64,12 @@ SpritePropertiesDialog::~SpritePropertiesDialog() = default; void SpritePropertiesDialog::accept() { + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + if (m_sprite.name != m_ui->lineEditName->text()) { if (!m_projectModel.rename(m_sprite, m_ui->lineEditName->text())) @@ -70,8 +79,7 @@ void SpritePropertiesDialog::accept() } } - if (m_newPixmaps) - m_sprite.pixmaps = std::move(*m_newPixmaps); + m_sprite.pixmaps = std::move(m_pixmaps); m_sprite.origin.x = m_ui->spinBoxOriginX->value(); m_sprite.origin.y = m_ui->spinBoxOriginY->value(); m_sprite.preciseCollisionChecking = m_ui->checkBoxPreciseCollisionChecking->isChecked(); @@ -123,20 +131,14 @@ void SpritePropertiesDialog::loadSprite() return; } - m_ui->labelPreview->setPixmap(pixmap); - - m_newPixmaps = std::vector{ std::move(pixmap) }; - m_unsavedChanges = true; - - updateTitle(); + m_pixmaps = std::vector{ std::move(pixmap) }; + changed(); updateSpriteInfo(); } void SpritePropertiesDialog::saveSprite() { - const auto &pixmaps = m_newPixmaps ? *m_newPixmaps : m_sprite.pixmaps; - - if (pixmaps.empty() || pixmaps.front().isNull()) + if (m_pixmaps.empty() || m_pixmaps.front().isNull()) { QMessageBox::warning(this, tr("No sprites available to save!"), tr("No sprites available to save!")); return; @@ -146,7 +148,7 @@ void SpritePropertiesDialog::saveSprite() if (path.isEmpty()) return; - if (!pixmaps.front().save(path)) + if (!m_pixmaps.front().save(path)) { QMessageBox::warning(this, tr("Could not save Sprite!"), tr("Could not save Sprite!")); return; @@ -155,20 +157,33 @@ void SpritePropertiesDialog::saveSprite() void SpritePropertiesDialog::editSprite() { - EditSpriteDialog{m_sprite}.exec(); + EditSpriteDialog dialog{m_pixmaps, m_sprite.name, this}; + if (dialog.exec() == QDialog::Accepted) + { + m_pixmaps = dialog.pixmaps(); + changed(); + updateSpriteInfo(); + } } void SpritePropertiesDialog::centerOrigin() { - const auto &pixmaps = m_newPixmaps ? *m_newPixmaps : m_sprite.pixmaps; - if (pixmaps.empty() || pixmaps.front().isNull()) + if (m_pixmaps.empty() || m_pixmaps.front().isNull()) { qDebug() << "unexpected empty pixmaps"; + m_ui->spinBoxOriginX->setValue(0); + m_ui->spinBoxOriginY->setValue(0); return; } - m_ui->spinBoxOriginX->setValue(pixmaps.front().width() / 2); - m_ui->spinBoxOriginY->setValue(pixmaps.front().height() / 2); + m_ui->spinBoxOriginX->setValue(m_pixmaps.front().width() / 2); + m_ui->spinBoxOriginY->setValue(m_pixmaps.front().height() / 2); +} + +void SpritePropertiesDialog::modifyMask() +{ + MaskPropertiesDialog dialog{this}; + dialog.exec(); } void SpritePropertiesDialog::changed() @@ -203,8 +218,8 @@ void SpritePropertiesDialog::updateTitle() void SpritePropertiesDialog::updateSpriteInfo() { - const auto &pixmaps = m_newPixmaps ? *m_newPixmaps : m_sprite.pixmaps; - m_ui->labelWidth->setText(tr("Width: %0").arg(pixmaps.empty() ? tr("n/a") : QString::number(pixmaps.front().width()))); - m_ui->labelHeight->setText(tr("Height: %0").arg(pixmaps.empty() ? tr("n/a") : QString::number(pixmaps.front().height()))); - m_ui->labelSubimages->setText(tr("Number of subimages: %0").arg(pixmaps.size())); + m_ui->labelPreview->setPixmap(m_pixmaps.empty() ? QPixmap{} : m_pixmaps.front()); + m_ui->labelWidth->setText(tr("Width: %0").arg(m_pixmaps.empty() ? tr("n/a") : QString::number(m_pixmaps.front().width()))); + m_ui->labelHeight->setText(tr("Height: %0").arg(m_pixmaps.empty() ? tr("n/a") : QString::number(m_pixmaps.front().height()))); + m_ui->labelSubimages->setText(tr("Number of subimages: %0").arg(m_pixmaps.size())); } diff --git a/dialogs/spritepropertiesdialog.h b/dialogs/spritepropertiesdialog.h index 6379bcf..36b88c3 100644 --- a/dialogs/spritepropertiesdialog.h +++ b/dialogs/spritepropertiesdialog.h @@ -26,6 +26,7 @@ private slots: void saveSprite(); void editSprite(); void centerOrigin(); + void modifyMask(); void changed(); @@ -42,5 +43,5 @@ private: bool m_unsavedChanges{}; - std::optional> m_newPixmaps; + std::vector m_pixmaps; }; diff --git a/dialogs/spritepropertiesdialog.ui b/dialogs/spritepropertiesdialog.ui index a270763..cdbf7e0 100644 --- a/dialogs/spritepropertiesdialog.ui +++ b/dialogs/spritepropertiesdialog.ui @@ -33,7 +33,11 @@ - + + + The name of the sprite + + @@ -56,6 +60,9 @@ + + Load the sprite from a file + &Load Sprite @@ -67,6 +74,9 @@ + + Save the sprite to a file + &Save Sprite @@ -78,6 +88,9 @@ + + Edit the sprite + &Edit Sprite @@ -151,7 +164,11 @@ - + + + The x-coordinate in the sprite that is used for the origin + + @@ -167,7 +184,11 @@ - + + + The y-coordinate in the sprite that is used for the origin + + @@ -188,6 +209,9 @@ + + Put the origin in the center of the image + &Center @@ -246,6 +270,9 @@ + + Perform pixel precise colliion checking + &Precise collision checking @@ -253,6 +280,9 @@ + + Have separate collision masks for the subimages + Separa&te Collision Masks @@ -260,6 +290,9 @@ + + Modify additional options for the collision mask + &Modify Mask diff --git a/dialogs/triggerconditiondialog.cpp b/dialogs/triggerconditiondialog.cpp new file mode 100644 index 0000000..e3e75c5 --- /dev/null +++ b/dialogs/triggerconditiondialog.cpp @@ -0,0 +1,12 @@ +#include "triggerconditiondialog.h" + +TriggerConditionDialog::TriggerConditionDialog(QWidget *parent) : + CodeEditorDialog{parent} +{ +#ifdef Q_OS_LINUX + setWindowFlags(windowFlags() & ~Qt::Dialog | Qt::Window); +#endif + setWindowFlag(Qt::WindowMinimizeButtonHint); + setWindowFlag(Qt::WindowMaximizeButtonHint); + setWindowFlag(Qt::WindowCloseButtonHint); +} diff --git a/dialogs/triggerconditiondialog.h b/dialogs/triggerconditiondialog.h new file mode 100644 index 0000000..95c948d --- /dev/null +++ b/dialogs/triggerconditiondialog.h @@ -0,0 +1,11 @@ +#pragma once + +#include "codeeditordialog.h" + +class TriggerConditionDialog : public CodeEditorDialog +{ + Q_OBJECT + +public: + explicit TriggerConditionDialog(QWidget *parent = nullptr); +}; diff --git a/dialogs/triggersdialog.cpp b/dialogs/triggersdialog.cpp new file mode 100644 index 0000000..a98ff6e --- /dev/null +++ b/dialogs/triggersdialog.cpp @@ -0,0 +1,33 @@ +#include "triggersdialog.h" +#include "ui_triggersdialog.h" + +#include "projectcontainer.h" +#include "triggersmodel.h" +#include "triggerconditiondialog.h" + +TriggersDialog::TriggersDialog(ProjectContainer &project, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_project{project}, + m_model{std::make_unique(project, this)} +{ + m_ui->setupUi(this); + + if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Ok)) + button->setIcon(QIcon{":/qtgameengine/icons/ok.png"}); + if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Cancel)) + button->setIcon(QIcon{":/qtgameengine/icons/delete.png"}); + + m_ui->listView->setModel(m_model.get()); + + connect(m_ui->pushButtonUseCodeEditor, &QAbstractButton::pressed, + this, &TriggersDialog::openCodeEditor); +} + +TriggersDialog::~TriggersDialog() = default; + +void TriggersDialog::openCodeEditor() +{ + TriggerConditionDialog dialog{this}; + dialog.exec(); +} diff --git a/dialogs/triggersdialog.h b/dialogs/triggersdialog.h new file mode 100644 index 0000000..9cd6017 --- /dev/null +++ b/dialogs/triggersdialog.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include + +namespace Ui { class TriggersDialog; } +struct ProjectContainer; +class TriggersModel; + +class TriggersDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TriggersDialog(ProjectContainer &project, QWidget *parent = nullptr); + ~TriggersDialog(); + +private slots: + void openCodeEditor(); + +private: + const std::unique_ptr m_ui; + + ProjectContainer &m_project; + + const std::unique_ptr m_model; +}; diff --git a/dialogs/triggersdialog.ui b/dialogs/triggersdialog.ui new file mode 100644 index 0000000..9e412b5 --- /dev/null +++ b/dialogs/triggersdialog.ui @@ -0,0 +1,227 @@ + + + TriggersDialog + + + + 0 + 0 + 622 + 515 + + + + Triggers + + + + + + + + + 0 + 0 + + + + + + + + + + Load the collection of triggers from a file. + + + &Load + + + + + + + Delete the selected trigger. Make sure no object uses it! + + + &Delete + + + + + + + Add a trigger to the list + + + &Add + + + + + + + Clear the complete list of triggers + + + &Clear + + + + + + + Save the collection of triggers to a file + + + &Save + + + + + + + + + QDialogButtonBox::Ok + + + true + + + + + + + + + true + + + + + + Name: + + + + + + + + + + Condition: + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Use code editor + + + + + + + + + Moment of checking: + + + + + + + + + Begin of step + + + + + + + Middle of step + + + + + + + End of step + + + + + + + + + Constant name: + + + + + + + + + + + + + + + buttonBox + accepted() + TriggersDialog + accept() + + + 93 + 492 + + + 310 + 257 + + + + + buttonBox + rejected() + TriggersDialog + reject() + + + 93 + 492 + + + 310 + 257 + + + + + diff --git a/dialogs/userdefinedconstantsdialog.cpp b/dialogs/userdefinedconstantsdialog.cpp new file mode 100644 index 0000000..ad9f8ee --- /dev/null +++ b/dialogs/userdefinedconstantsdialog.cpp @@ -0,0 +1,23 @@ +#include "userdefinedconstantsdialog.h" +#include "ui_userdefinedconstantsdialog.h" + +#include "projectcontainer.h" +#include "constantsmodel.h" + +UserDefinedConstantsDialog::UserDefinedConstantsDialog(ProjectContainer &project, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_project{project}, + m_model{std::make_unique(m_project, this)} +{ + m_ui->setupUi(this); + + if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Ok)) + button->setIcon(QIcon{":/qtgameengine/icons/ok.png"}); + if (auto button = m_ui->buttonBox->button(QDialogButtonBox::Cancel)) + button->setIcon(QIcon{":/qtgameengine/icons/delete.png"}); + + m_ui->treeView->setModel(m_model.get()); +} + +UserDefinedConstantsDialog::~UserDefinedConstantsDialog() = default; diff --git a/dialogs/userdefinedconstantsdialog.h b/dialogs/userdefinedconstantsdialog.h new file mode 100644 index 0000000..9f29f02 --- /dev/null +++ b/dialogs/userdefinedconstantsdialog.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +namespace Ui { class UserDefinedConstantsDialog; } +struct ProjectContainer; +class ConstantsModel; + +class UserDefinedConstantsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit UserDefinedConstantsDialog(ProjectContainer &project, QWidget *parent = nullptr); + ~UserDefinedConstantsDialog(); + +private: + const std::unique_ptr m_ui; + + ProjectContainer &m_project; + + const std::unique_ptr m_model; +}; diff --git a/dialogs/userdefinedconstantsdialog.ui b/dialogs/userdefinedconstantsdialog.ui new file mode 100644 index 0000000..aa748d1 --- /dev/null +++ b/dialogs/userdefinedconstantsdialog.ui @@ -0,0 +1,150 @@ + + + UserDefinedConstantsDialog + + + + 0 + 0 + 479 + 461 + + + + User-Defined Constants + + + + :/qtgameengine/icons/constants.png:/qtgameengine/icons/constants.png + + + + + + + + + false + + + + + + + + + &Insert + + + + + + + &Sort + + + + + + + &Delete + + + + + + + &Load + + + + + + + &Up + + + + + + + &Add + + + + + + + &Clear + + + + + + + Do&wn + + + + + + + Sa&ve + + + + + + + + + + + + QDialogButtonBox::Ok + + + true + + + + + + + + + + + buttonBox + accepted() + UserDefinedConstantsDialog + accept() + + + 239 + 439 + + + 239 + 230 + + + + + buttonBox + rejected() + UserDefinedConstantsDialog + reject() + + + 239 + 439 + + + 239 + 230 + + + + + diff --git a/drawingcanvaswidget.cpp b/drawingcanvaswidget.cpp new file mode 100644 index 0000000..5ca3630 --- /dev/null +++ b/drawingcanvaswidget.cpp @@ -0,0 +1,37 @@ +#include "drawingcanvaswidget.h" + +#include +#include + +DrawingCanvasWidget::DrawingCanvasWidget(QWidget *parent) : + QWidget{parent} +{ +} + +void DrawingCanvasWidget::setPixmap(QPixmap &pixmap) +{ + m_pixmap = &pixmap; + setFixedSize(m_pixmap->size() * m_scale); + update(); +} + +void DrawingCanvasWidget::setScale(float scale) +{ + if (m_scale == scale) + return; + emit scaleChanged(m_scale = scale); + if (m_pixmap) + setFixedSize(m_pixmap->size() * m_scale); + update(); +} + +void DrawingCanvasWidget::paintEvent(QPaintEvent *ev) +{ + QWidget::paintEvent(ev); + + if (!m_pixmap) + return; + + QPainter painter{this}; + painter.drawPixmap(QRect{QPoint{}, m_pixmap->size() * m_scale}, *m_pixmap); +} diff --git a/drawingcanvaswidget.h b/drawingcanvaswidget.h new file mode 100644 index 0000000..1409ac2 --- /dev/null +++ b/drawingcanvaswidget.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +class QPixmap; + +class DrawingCanvasWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DrawingCanvasWidget(QWidget *parent = nullptr); + + QPixmap *pixmap() { return m_pixmap; } + const QPixmap *pixmap() const { return m_pixmap; } + void setPixmap(QPixmap &pixmap); + + float scale() const { return m_scale; } + void setScale(float scale); + +signals: + void scaleChanged(float scale); + +protected: + void paintEvent(QPaintEvent *ev) override; + +private: + QPixmap *m_pixmap{}; + + float m_scale{4.f}; +}; diff --git a/icons/center.png b/icons/center.png new file mode 100644 index 0000000000000000000000000000000000000000..a48d76b4fad70c5cd942a596c339c46a3998f4f7 GIT binary patch literal 2953 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 literal 0 HcmV?d00001 diff --git a/icons/flip-horizontal.png b/icons/flip-horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..ca35497b4eb1bd275a9b8d5895223d21c79cf840 GIT binary patch literal 5576 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 T-h9<*(1@(3tDnm{r-UW|1cw^T literal 0 HcmV?d00001 diff --git a/icons/flip-vertical.png b/icons/flip-vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..108fabaabbd3aa2cdc18443726aa06d47b07922f GIT binary patch literal 5496 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 aL8?Ip>c2N%b((>Jfx*+&&t;ucLK6TEfWK=1 literal 0 HcmV?d00001 diff --git a/includedfilesmodel.cpp b/includedfilesmodel.cpp new file mode 100644 index 0000000..8d35103 --- /dev/null +++ b/includedfilesmodel.cpp @@ -0,0 +1,19 @@ +#include "includedfilesmodel.h" + +#include "projectcontainer.h" + +IncludedFilesModel::IncludedFilesModel(ProjectContainer &project, QObject *parent) : + QAbstractListModel{parent}, + m_project{project} +{ +} + +int IncludedFilesModel::rowCount(const QModelIndex &parent) const +{ + return 0; +} + +QVariant IncludedFilesModel::data(const QModelIndex &index, int role) const +{ + return {}; +} diff --git a/includedfilesmodel.h b/includedfilesmodel.h new file mode 100644 index 0000000..89f1941 --- /dev/null +++ b/includedfilesmodel.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +struct ProjectContainer; + +class IncludedFilesModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit IncludedFilesModel(ProjectContainer &project, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + +private: + ProjectContainer &m_project; +}; diff --git a/mainwindow.cpp b/mainwindow.cpp index 854d3d4..57ae83e 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include "projecttreemodel.h" +#include "dialogs/preferencesdialog.h" #include "dialogs/spritepropertiesdialog.h" #include "dialogs/soundpropertiesdialog.h" #include "dialogs/backgroundpropertiesdialog.h" @@ -17,11 +19,13 @@ #include "dialogs/timelinepropertiesdialog.h" #include "dialogs/objectpropertiesdialog.h" #include "dialogs/roompropertiesdialog.h" -#include "dialogs/preferencesdialog.h" +#include "dialogs/objectinformationdialog.h" #include "dialogs/gameinformationdialog.h" #include "dialogs/globalgamesettingsdialog.h" #include "dialogs/extensionpackagesdialog.h" -#include "dialogs/objectinformationdialog.h" +#include "dialogs/userdefinedconstantsdialog.h" +#include "dialogs/triggersdialog.h" +#include "dialogs/includedfilesdialog.h" namespace { template struct PropertiesDialogForDetail; @@ -107,6 +111,9 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_ui->actionGameInformation, &QAction::triggered, this, &MainWindow::showGameInformation); connect(m_ui->actionGlobalGameSettings, &QAction::triggered, this, &MainWindow::showGlobalGameSettings); connect(m_ui->actionExtensionPackages, &QAction::triggered, this, &MainWindow::showExtensionPackages); + connect(m_ui->actionDefineConstants, &QAction::triggered, this, &MainWindow::showDefineConstants); + connect(m_ui->actionDefineTriggers, &QAction::triggered, this, &MainWindow::showDefineTriggers); + connect(m_ui->actionIncludedFiles, &QAction::triggered, this, &MainWindow::showIncludedFiles); connect(m_ui->actionAbout, &QAction::triggered, this, &MainWindow::about); connect(m_ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt); @@ -126,10 +133,16 @@ MainWindow::MainWindow(QWidget *parent) : connect(m_projectTreeModel.get(), &ProjectTreeModel::rowsInserted, this, &MainWindow::rowsInserted); + connect(m_projectTreeModel.get(), &QAbstractTableModel::rowsInserted, + this, &MainWindow::changed); connect(m_projectTreeModel.get(), &ProjectTreeModel::rowsAboutToBeRemoved, this, &MainWindow::rowsAboutToBeRemoved); + connect(m_projectTreeModel.get(), &QAbstractTableModel::rowsRemoved, + this, &MainWindow::changed); connect(m_projectTreeModel.get(), &ProjectTreeModel::modelAboutToBeReset, this, &MainWindow::modelAboutToBeReset); + connect(m_projectTreeModel.get(), &QAbstractTableModel::dataChanged, + this, &MainWindow::changed); updateTitle(); } @@ -318,6 +331,15 @@ void MainWindow::modelErrorOccured(const QString &message) QMessageBox::warning(this, tr("Error occured!"), tr("Error occured!") + "\n\n" + message); } +void MainWindow::changed() +{ + if (!m_unsavedChanges) + { + m_unsavedChanges = true; + updateTitle(); + } +} + void MainWindow::newFile() { m_ui->mdiArea->closeAllSubWindows(); @@ -354,7 +376,7 @@ void MainWindow::newFile() m_project = {}; m_projectTreeModel->setProject(&m_project); - m_filePath = {}; + m_filePath = QString{}; m_unsavedChanges = false; updateTitle(); @@ -492,7 +514,8 @@ void MainWindow::exportResources() void MainWindow::preferences() { - PreferencesDialog{this}.exec(); + PreferencesDialog dialog{this}; + dialog.exec(); } void MainWindow::create() @@ -501,7 +524,7 @@ void MainWindow::create() if (!index.isValid()) return; - if (index == m_projectTreeModel->rootFor()) createFor(); + if (index == m_projectTreeModel->rootFor()) createFor(); else if (index == m_projectTreeModel->rootFor()) createFor(); else if (index == m_projectTreeModel->rootFor()) createFor(); else if (index == m_projectTreeModel->rootFor()) createFor(); @@ -583,35 +606,7 @@ void MainWindow::findResource() void MainWindow::showObjectInformation() { - if (m_objectInformationWindow) - m_ui->mdiArea->setActiveSubWindow(m_objectInformationWindow); - else - { - auto dialog = new ObjectInformationDialog; - auto subwindow = m_ui->mdiArea->addSubWindow(dialog); - auto action = m_ui->menuWindow->addAction(dialog->windowTitle()); - action->setCheckable(true); - connect(action, &QAction::triggered, - m_ui->mdiArea, [mdiArea=m_ui->mdiArea,subwindow,action](){ - mdiArea->setActiveSubWindow(subwindow); - action->setChecked(subwindow->windowState().testFlag(Qt::WindowActive)); - }); - connect(subwindow, &QMdiSubWindow::windowStateChanged, - action, [action](Qt::WindowStates oldState, Qt::WindowStates newState){ - action->setChecked(newState.testFlag(Qt::WindowActive)); - }); - connect(dialog, &QWidget::windowTitleChanged, action, &QAction::setText); - connect(dialog, &QDialog::finished, - this, [&objectInformationWindow=m_objectInformationWindow](){ - objectInformationWindow = nullptr; - }); - connect(dialog, &QDialog::finished, - subwindow, &QObject::deleteLater); - connect(dialog, &QDialog::finished, - action, &QObject::deleteLater); - m_objectInformationWindow = subwindow; - dialog->show(); - } + openOrActivateWindow(m_objectInformationWindow, m_project); } template @@ -633,17 +628,37 @@ template void MainWindow::createFor(); void MainWindow::showGameInformation() { - GameInformationDialog{this}.exec(); + GameInformationDialog dialog{this}; + if (dialog.exec() == QDialog::Accepted) + changed(); } void MainWindow::showGlobalGameSettings() { - GlobalGameSettingsDialog{this}.exec(); + GlobalGameSettingsDialog dialog{this}; + if (dialog.exec() == QDialog::Accepted) + changed(); } void MainWindow::showExtensionPackages() { - ExtensionPackagesDialog{this}.exec(); + ExtensionPackagesDialog dialog{this}; + dialog.exec(); +} + +void MainWindow::showDefineConstants() +{ + openOrActivateWindow(m_userDefinedConstantsWindow, m_project); +} + +void MainWindow::showDefineTriggers() +{ + openOrActivateWindow(m_triggersWindow, m_project); +} + +void MainWindow::showIncludedFiles() +{ + openOrActivateWindow(m_includedFilesWindow, m_project); } void MainWindow::about() @@ -688,11 +703,45 @@ void MainWindow::modelAboutToBeReset() void MainWindow::updateTitle() { - setWindowTitle(tr("%0 - Qt Gamemaker 1.0 Ultimate%1") + setWindowTitle(tr("%0%1 - Qt Gamemaker 1.0 Ultimate") .arg(m_filePath.isEmpty() ? "" : QFileInfo{m_filePath}.fileName()) .arg(m_unsavedChanges ? tr("*") : QString{})); } +template +void MainWindow::openOrActivateWindow(QMdiSubWindow * &ptr, Targs &&...args) +{ + if (ptr) + m_ui->mdiArea->setActiveSubWindow(ptr); + else + { + auto dialog = new T{std::forward(args)...}; + auto subwindow = m_ui->mdiArea->addSubWindow(dialog); + auto action = m_ui->menuWindow->addAction(dialog->windowTitle()); + action->setCheckable(true); + connect(action, &QAction::triggered, + m_ui->mdiArea, [mdiArea=m_ui->mdiArea,subwindow,action](){ + mdiArea->setActiveSubWindow(subwindow); + action->setChecked(subwindow->windowState().testFlag(Qt::WindowActive)); + }); + connect(subwindow, &QMdiSubWindow::windowStateChanged, + action, [action](Qt::WindowStates oldState, Qt::WindowStates newState){ + action->setChecked(newState.testFlag(Qt::WindowActive)); + }); + connect(dialog, &QWidget::windowTitleChanged, action, &QAction::setText); + connect(dialog, &QDialog::finished, + this, [&ptr](){ + ptr = nullptr; + }); + connect(dialog, &QDialog::finished, + subwindow, &QObject::deleteLater); + connect(dialog, &QDialog::finished, + action, &QObject::deleteLater); + ptr = subwindow; + dialog->show(); + } +} + template bool MainWindow::doubleClickedFor(const QModelIndex &index) { @@ -725,7 +774,9 @@ bool MainWindow::doubleClickedFor(const QModelIndex &index) }); connect(dialog, &QWidget::windowTitleChanged, action, &QAction::setText); connect(dialog, &QDialog::finished, - this, [&propertyWindows,subwindow](){ + this, [this,&propertyWindows,subwindow](int result){ + if (result == QDialog::Accepted) + changed(); for (auto iter = std::begin(propertyWindows); iter != std::end(propertyWindows); ) { if (iter->second == subwindow) diff --git a/mainwindow.h b/mainwindow.h index ad0ead9..c57bb1f 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -27,6 +27,7 @@ private slots: void doubleClicked(const QModelIndex &index); void selectionChanged(const QModelIndex &index); void modelErrorOccured(const QString &message); + void changed(); void newFile(); void openFile(); @@ -50,18 +51,12 @@ private: template void createFor(); private slots: -// void createSprite(); -// void createSound(); -// void createBackground(); -// void createPath(); -// void createScript(); -// void createFont(); -// void createTimeLine(); -// void createObject(); -// void createRoom(); void showGameInformation(); void showGlobalGameSettings(); void showExtensionPackages(); + void showDefineConstants(); + void showDefineTriggers(); + void showIncludedFiles(); void about(); void rowsInserted(const QModelIndex &parent, int first, int last); @@ -71,6 +66,9 @@ private slots: private: void updateTitle(); + template + void openOrActivateWindow(QMdiSubWindow * &ptr, Targs &&...args); + template std::map &propertyWindowsFor(); @@ -103,4 +101,7 @@ private: std::map m_roomPropertiesWindows; QMdiSubWindow *m_objectInformationWindow{}; + QMdiSubWindow *m_userDefinedConstantsWindow{}; + QMdiSubWindow *m_triggersWindow{}; + QMdiSubWindow *m_includedFilesWindow{}; }; diff --git a/mainwindow.ui b/mainwindow.ui index 5c40c3d..5b17a72 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -459,6 +459,10 @@ + + + :/qtgameengine/icons/object-file.png:/qtgameengine/icons/object-file.png + &Show Object Information @@ -608,6 +612,10 @@ + + + :/qtgameengine/icons/constants.png:/qtgameengine/icons/constants.png + Define Co&nstants... diff --git a/pathpointsmodel.cpp b/pathpointsmodel.cpp new file mode 100644 index 0000000..c27f79e --- /dev/null +++ b/pathpointsmodel.cpp @@ -0,0 +1,100 @@ +#include "pathpointsmodel.h" + +#include + +namespace { +enum { + ColumnPoint, + ColumnSp, + NumberOfColumns +}; +} + +PathPointsModel::PathPointsModel(std::vector &points, QObject *parent) : + QAbstractTableModel{parent}, + m_points{points} +{ +} + +int PathPointsModel::rowCount(const QModelIndex &parent) const +{ + return m_points.size(); +} + +int PathPointsModel::columnCount(const QModelIndex &parent) const +{ + return NumberOfColumns; +} + +QVariant PathPointsModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= m_points.size()) + { + qWarning() << "invalid row" << index.row(); + return {}; + } + + const auto &point = m_points.at(index.row()); + + switch (index.column()) + { + case ColumnPoint: + switch (role) + { + case Qt::DisplayRole: return tr("(%0,%1)").arg(point.point.x()).arg(point.point.y()); + case Qt::EditRole: return point.point; + default: return {}; + } + case ColumnSp: + switch (role) + { + case Qt::DisplayRole: + case Qt::EditRole: return point.sp; + default: return {}; + } + } + + return {}; +} + +QVariant PathPointsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || (role != Qt::DisplayRole && role != Qt::EditRole)) + return {}; + + switch (section) + { + case ColumnPoint: return tr("point"); + case ColumnSp: return tr("sp"); + } + + return {}; +} + +Path::Point PathPointsModel::getPoint(const QModelIndex &index) const +{ + if (!index.isValid()) + { + qWarning() << "unexpected invalid index" << index; + return {}; + } + + if (index.row() < 0 || index.row() >= m_points.size()) + { + qWarning() << "unexpected row" << index.row(); + return {}; + } + + return m_points.at(index.row()); +} + +void PathPointsModel::pointInserted(std::size_t index) +{ + emit beginInsertRows({}, index, index); + emit endInsertRows(); +} + +void PathPointsModel::pointMoved(std::size_t index) +{ + emit dataChanged(this->index(index, 0), this->index(index, 0), {Qt::DisplayRole, Qt::EditRole}); +} diff --git a/pathpointsmodel.h b/pathpointsmodel.h new file mode 100644 index 0000000..f813747 --- /dev/null +++ b/pathpointsmodel.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +#include "projectcontainer.h" + +class PathPointsModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit PathPointsModel(std::vector &points, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + int columnCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + Path::Point getPoint(const QModelIndex &index) const; + +public slots: + void pointInserted(std::size_t index); + void pointMoved(std::size_t index); + +private: + std::vector &m_points; +}; diff --git a/pathpointswidget.cpp b/pathpointswidget.cpp new file mode 100644 index 0000000..594a873 --- /dev/null +++ b/pathpointswidget.cpp @@ -0,0 +1,223 @@ +#include "pathpointswidget.h" + +#include +#include +#include + +#include + +PathPointsWidget::PathPointsWidget(QWidget *parent) : + QWidget{parent} +{ + setMouseTracking(true); +} + +PathPointsWidget::PathPointsWidget(std::vector *points, QWidget *parent) : + QWidget{parent}, + m_points{points} +{ +} + +void PathPointsWidget::setPoints(std::vector *points) +{ + m_points = points; + update(); +} + +void PathPointsWidget::setShowGrid(bool showGrid) +{ + if (m_showGrid == showGrid) + return; + emit showGridChanged(m_showGrid = showGrid); + update(); +} + +void PathPointsWidget::setGridX(int gridX) +{ + if (m_gridX == gridX) + return; + emit gridXChanged(m_gridX = gridX); + update(); +} + +void PathPointsWidget::setGridY(int gridY) +{ + if (m_gridY == gridY) + return; + emit gridYChanged(m_gridY = gridY); + update(); +} + +void PathPointsWidget::setClosed(bool closed) +{ + if (m_closed == closed) + return; + emit closedChanged(m_closed = closed); + update(); +} + +void PathPointsWidget::setSelectedIndex(const std::optional &selectedIndex) +{ + if (m_selectedIndex == selectedIndex) + return; + emit selectedIndexChanged(m_selectedIndex = selectedIndex); + update(); +} + +void PathPointsWidget::paintEvent(QPaintEvent *event) +{ + if (m_showGrid) + { + if (!m_gridBrush || m_gridBrush->gridX != m_gridX || m_gridBrush->gridY != m_gridY) + { + QPixmap pixmap{m_gridX, m_gridY}; + + { + QPainter painter{&pixmap}; + painter.setPen(palette().color(m_gridRole)); + painter.drawLine(0, 0, m_gridX, 0); + painter.drawLine(0, 0, 0, m_gridY); + + painter.fillRect(1, 1, m_gridX - 1, m_gridY - 1, palette().color(backgroundRole())); + } + + qDebug() << "setup brush" << pixmap.width() << pixmap.height(); + + m_gridBrush = GridBrush { + .gridX = m_gridX, + .gridY = m_gridY, + .brush = QBrush{std:: move(pixmap)} + }; + } + } + else + m_gridBrush = std::nullopt; + + QPainter painter{this}; + painter.fillRect(rect(), m_gridBrush ? m_gridBrush->brush : palette().color(backgroundRole())); + + if (!m_points) + return; + + const auto drawLine = [&](const auto &point0, const auto &point1){ + painter.setPen(QPen{Qt::black, 2}); + painter.drawLine(point0.point, point1.point); + painter.setPen(QPen{Qt::yellow, 1}); + painter.drawLine(point0.point, point1.point); + }; + + std::adjacent_find(std::cbegin(*m_points), std::cend(*m_points), + [&](const Path::Point &point0, const Path::Point &point1){ + drawLine(point0, point1); + return false; + }); + + if (m_closed && m_points->size() >= 2) + drawLine(m_points->back(), m_points->front()); + + painter.setPen(Qt::black); + std::size_t i{}; + for (const auto &point : *m_points) + { + if (i == m_selectedIndex) + painter.setBrush(Qt::red); + else if (i == 0) + painter.setBrush(Qt::green); + else + painter.setBrush(Qt::blue); + painter.drawEllipse(point.point, 2, 2); + i++; + } +} + +void PathPointsWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + + switch (event->button()) + { + case Qt::LeftButton: + if (!m_points) + return; + + if (!m_points->empty()) + { + auto points = *m_points; + const auto distance = [&](const Path::Point &point){ return QLineF{point.point, event->pos()}.length(); }; + std::sort(std::begin(points), std::end(points), [&](const Path::Point &left, const Path::Point &right){ + return distance(left) < distance(right); + }); + if (distance(points.front()) > 8) + goto createNew; + + const auto iter = std::find_if(std::cbegin(*m_points), std::cend(*m_points), + [&minPoint=points.front().point](const Path::Point &point){ return point.point == minPoint; }); + if (iter == std::cend(*m_points)) + { + qWarning() << "unexpected end"; + goto createNew; + } + + const auto index = std::distance(std::cbegin(*m_points), iter); + emit pointInserted(index); + emit selectedIndexChanged(m_selectedIndex = index); + m_dragIndex = index; + } + else + { +createNew: + m_points->push_back(Path::Point{.point = snapPoint(event->pos()), .sp=100}); + const auto index = m_points->size() - 1; + emit pointInserted(index); + emit selectedIndexChanged(m_selectedIndex = index); + m_dragIndex = index; + } + update(); + break; + } +} + +void PathPointsWidget::mouseReleaseEvent(QMouseEvent *event) +{ + QWidget::mouseReleaseEvent(event); + + switch (event->button()) + { + case Qt::LeftButton: + m_dragIndex = std::nullopt; + break; + } +} + +void PathPointsWidget::mouseMoveEvent(QMouseEvent *event) +{ + QWidget::mouseMoveEvent(event); + + emit cursorMoved(snapPoint(event->pos())); + + if (m_dragIndex) + { + if (event->buttons().testFlag(Qt::LeftButton)) + { + const auto point = snapPoint(event->pos()); + if ((*m_points)[*m_dragIndex].point != point) + { + (*m_points)[*m_dragIndex].point = point; + emit pointMoved(*m_dragIndex); + update(); + } + } + else + m_dragIndex = std::nullopt; + } +} + +QPoint PathPointsWidget::snapPoint(const QPoint &point) const +{ + if (!m_showGrid) + return point; + return QPoint{ + (point.x() + (m_gridX / 2)) / m_gridX * m_gridX, + (point.y() + (m_gridY / 2)) / m_gridY * m_gridY, + }; +} diff --git a/pathpointswidget.h b/pathpointswidget.h new file mode 100644 index 0000000..baccf77 --- /dev/null +++ b/pathpointswidget.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +#include +#include + +#include "projectcontainer.h" + +class PathPointsWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY(bool showGrid READ showGrid WRITE setShowGrid NOTIFY showGridChanged) + Q_PROPERTY(int gridX READ gridX WRITE setGridX NOTIFY gridXChanged) + Q_PROPERTY(int gridY READ gridY WRITE setGridY NOTIFY gridYChanged) + Q_PROPERTY(bool closed READ closed WRITE setClosed NOTIFY closedChanged) + +public: + explicit PathPointsWidget(QWidget *parent = nullptr); + explicit PathPointsWidget(std::vector *points, QWidget *parent = nullptr); + + void setPoints(std::vector *points); + + bool showGrid() const { return m_showGrid; } + void setShowGrid(bool showGrid); + + int gridX() const { return m_gridX; } + void setGridX(int gridX); + + int gridY() const { return m_gridY; } + void setGridY(int gridY); + + bool closed() const { return m_closed; } + void setClosed(bool closed); + + const std::optional &selectedIndex() const { return m_selectedIndex; } + void setSelectedIndex(const std::optional &selectedIndex); + + QPalette::ColorRole gridRole() const { return m_gridRole; } + void setGridRole(QPalette::ColorRole gridRole) { if (gridRole == m_gridRole) return; m_gridRole = gridRole; update(); } + +signals: + void showGridChanged(bool showGrid); + void gridXChanged(int gridX); + void gridYChanged(int gridY); + void closedChanged(bool closed); + void selectedIndexChanged(const std::optional &selectedIndex); + + void cursorMoved(const QPoint &point); + + void pointInserted(std::size_t index); + void pointMoved(std::size_t index); + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + +private: + QPoint snapPoint(const QPoint &point) const; + + std::vector *m_points{}; + + bool m_showGrid{true}; + int m_gridX{16}; + int m_gridY{16}; + + bool m_closed{true}; + + std::optional m_selectedIndex; + std::optional m_dragIndex; + + struct GridBrush { + int gridX; + int gridY; + QBrush brush; + }; + + std::optional m_gridBrush; + + QPalette::ColorRole m_gridRole{QPalette::Dark}; +}; diff --git a/projectcontainer.cpp b/projectcontainer.cpp index f0db2ae..40600f8 100644 --- a/projectcontainer.cpp +++ b/projectcontainer.cpp @@ -143,15 +143,37 @@ QDataStream &operator>>(QDataStream &ds, Background &background) return ds; } +QDataStream &operator<<(QDataStream &ds, const Path::Point &point) +{ + ds << point.point; + ds << point.sp; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, Path::Point &point) +{ + ds >> point.point; + ds >> point.sp; + return ds; +} + QDataStream &operator<<(QDataStream &ds, const Path &path) { ds << path.name; + ds << path.points; + ds << path.type; + ds << path.closed; + ds << path.precision; return ds; } QDataStream &operator>>(QDataStream &ds, Path &path) { ds >> path.name; + ds >> path.points; + ds >> path.type; + ds >> path.closed; + ds >> path.precision; return ds; } diff --git a/projectcontainer.h b/projectcontainer.h index 628df50..6339754 100644 --- a/projectcontainer.h +++ b/projectcontainer.h @@ -7,6 +7,7 @@ #include #include #include +#include struct Sprite { @@ -49,6 +50,15 @@ struct Background struct Path { QString name; + struct Point { + QPoint point; + int sp{100}; + }; + std::vector points; + enum class Type { Straight, Smooth }; + Type type{Type::Straight}; + bool closed{true}; + int precision{4}; }; struct Script @@ -98,23 +108,5 @@ struct ProjectContainer template const std::list &containerFor() const; }; -QDataStream &operator<<(QDataStream &ds, const Sprite &sprite); -QDataStream &operator>>(QDataStream &ds, Sprite &sprite); -QDataStream &operator<<(QDataStream &ds, const Sound &sound); -QDataStream &operator>>(QDataStream &ds, Sound &sound); -QDataStream &operator<<(QDataStream &ds, const Background &background); -QDataStream &operator>>(QDataStream &ds, Background &background); -QDataStream &operator<<(QDataStream &ds, const Path &path); -QDataStream &operator>>(QDataStream &ds, Path &path); -QDataStream &operator<<(QDataStream &ds, const Script &script); -QDataStream &operator>>(QDataStream &ds, Script &script); -QDataStream &operator<<(QDataStream &ds, const Font &font); -QDataStream &operator>>(QDataStream &ds, Font &font); -QDataStream &operator<<(QDataStream &ds, const TimeLine &timeLine); -QDataStream &operator>>(QDataStream &ds, TimeLine &timeLine); -QDataStream &operator<<(QDataStream &ds, const Object &object); -QDataStream &operator>>(QDataStream &ds, Object &object); -QDataStream &operator<<(QDataStream &ds, const Room &room); -QDataStream &operator>>(QDataStream &ds, Room &room); QDataStream &operator<<(QDataStream &ds, const ProjectContainer &project); QDataStream &operator>>(QDataStream &ds, ProjectContainer &project); diff --git a/projecttreemodel.cpp b/projecttreemodel.cpp index 06ab4ae..ff134fc 100644 --- a/projecttreemodel.cpp +++ b/projecttreemodel.cpp @@ -6,7 +6,7 @@ #include #include -#include "futurecpp.h" +//#include "futurecpp.h" #include "projectcontainer.h" namespace { diff --git a/resources.qrc b/resources.qrc index 4282990..7e60e01 100644 --- a/resources.qrc +++ b/resources.qrc @@ -65,5 +65,11 @@ icons/object-file.png icons/room-file.png icons/timeline-file.png + icons/flip-horizontal.png + icons/flip-vertical.png + icons/scale.png + icons/center.png + icons/grid.png + icons/constants.png diff --git a/spritesmodel.cpp b/spritesmodel.cpp index b53472a..e9fedeb 100644 --- a/spritesmodel.cpp +++ b/spritesmodel.cpp @@ -1,29 +1,53 @@ #include "spritesmodel.h" #include +#include -SpritesModel::SpritesModel(QObject *parent) - : QAbstractListModel{parent} +SpritesModel::SpritesModel(const std::vector &pixmaps, QObject *parent) : + QAbstractListModel{parent}, + m_pixmaps{pixmaps} { } int SpritesModel::rowCount(const QModelIndex &parent) const { - return 100; + return m_pixmaps.size(); } QVariant SpritesModel::data(const QModelIndex &index, int role) const { + if (index.column() != 0) + return {}; + switch (role) { case Qt::DisplayRole: case Qt::EditRole: return tr("image %0").arg(index.row()); case Qt::DecorationRole: - QPixmap pixmap{64, 64}; - pixmap.fill(Qt::black); + if (index.row() < 0 || index.row() >= m_pixmaps.size()) + { + qWarning() << "invalid row" << index.row(); + return {}; + } + + const auto &pixmap = m_pixmaps.at(index.row()); + if (pixmap.isNull()) + { + QPixmap pixmap{32, 32}; + pixmap.fill(Qt::white); + return pixmap; + } return pixmap; } return {}; } + +const QPixmap &SpritesModel::pixmap(const QModelIndex &index) const +{ + if (index.row() < 0 || index.row() >= m_pixmaps.size()) + qFatal("index out of range"); + + return m_pixmaps.at(index.row()); +} diff --git a/spritesmodel.h b/spritesmodel.h index f8acf5d..e3d8079 100644 --- a/spritesmodel.h +++ b/spritesmodel.h @@ -1,14 +1,25 @@ #pragma once #include +#include + +#include class SpritesModel : public QAbstractListModel { Q_OBJECT public: - explicit SpritesModel(QObject *parent = nullptr); + explicit SpritesModel(const std::vector &pixmaps, QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; + + const QPixmap &pixmap(const QModelIndex &index) const; + + using QAbstractListModel::beginResetModel; + using QAbstractListModel::endResetModel; + +private: + const std::vector &m_pixmaps; }; diff --git a/triggersmodel.cpp b/triggersmodel.cpp new file mode 100644 index 0000000..d511ea0 --- /dev/null +++ b/triggersmodel.cpp @@ -0,0 +1,19 @@ +#include "triggersmodel.h" + +#include "projectcontainer.h" + +TriggersModel::TriggersModel(ProjectContainer &project, QObject *parent) : + QAbstractListModel{parent}, + m_project{project} +{ +} + +int TriggersModel::rowCount(const QModelIndex &parent) const +{ + return 0; +} + +QVariant TriggersModel::data(const QModelIndex &index, int role) const +{ + return {}; +} diff --git a/triggersmodel.h b/triggersmodel.h new file mode 100644 index 0000000..01b1889 --- /dev/null +++ b/triggersmodel.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +struct ProjectContainer; + +class TriggersModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit TriggersModel(ProjectContainer &project, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + +private: + ProjectContainer &m_project; +};