diff --git a/.gitignore b/.gitignore index f147edf..fab7372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,73 @@ -# C++ objects and libs -*.slo -*.lo -*.o +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave *.a -*.la -*.lai +*.core +*.moc +*.o +*.obj +*.orig +*.rej *.so *.so.* -*.dll -*.dylib - -# Qt-es -object_script.*.Release -object_script.*.Debug -*_plugin_import.cpp +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc /.qmake.cache /.qmake.stash -*.pro.user -*.pro.user.* -*.qbs.user -*.qbs.user.* -*.moc -moc_*.cpp -moc_*.h -qrc_*.cpp -ui_*.h -*.qmlc -*.jsc -Makefile* -*build-* -*.qm -*.prl -# Qt unit tests -target_wrapper.* +# qtcreator generated files +*.pro.user* -# QtCreator -*.autosave +# xemacs temporary files +*.flc -# QtCreator Qml -*.qmlproject.user -*.qmlproject.user.* +# Vim temporary files +.*.swp -# QtCreator CMake -CMakeLists.txt.user* +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* -# QtCreator 4.8< compilation database -compile_commands.json +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe -# QtCreator local machine specific files for imported projects -*creator.user* diff --git a/QtGameMaker.pro b/QtGameMaker.pro new file mode 100644 index 0000000..a505795 --- /dev/null +++ b/QtGameMaker.pro @@ -0,0 +1,60 @@ +QT = core gui widgets multimedia + +CONFIG += c++latest +QMAKE_CXXFLAGS += -std=c++23 + +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 + +HEADERS += \ + codeeditorwidget.h \ + dialogs/fontpropertiesdialog.h \ + dialogs/pathpropertiesdialog.h \ + dialogs/scriptpropertiesdialog.h \ + futurecpp.h \ + jshighlighter.h \ + mainwindow.h \ + projectcontainer.h \ + projecttreemodel.h \ + dialogs/backgroundpropertiesdialog.h \ + dialogs/editspritedialog.h \ + dialogs/extensionpackagesdialog.h \ + dialogs/gameinformationdialog.h \ + dialogs/globalgamesettingsdialog.h \ + dialogs/preferencesdialog.h \ + dialogs/soundpropertiesdialog.h \ + dialogs/spritepropertiesdialog.h + +SOURCES += main.cpp \ + codeeditorwidget.cpp \ + dialogs/fontpropertiesdialog.cpp \ + dialogs/pathpropertiesdialog.cpp \ + dialogs/scriptpropertiesdialog.cpp \ + jshighlighter.cpp \ + mainwindow.cpp \ + projectcontainer.cpp \ + projecttreemodel.cpp \ + dialogs/backgroundpropertiesdialog.cpp \ + dialogs/editspritedialog.cpp \ + dialogs/extensionpackagesdialog.cpp \ + dialogs/gameinformationdialog.cpp \ + dialogs/globalgamesettingsdialog.cpp \ + dialogs/preferencesdialog.cpp \ + dialogs/soundpropertiesdialog.cpp \ + dialogs/spritepropertiesdialog.cpp + +FORMS += \ + dialogs/fontpropertiesdialog.ui \ + dialogs/pathpropertiesdialog.ui \ + dialogs/scriptpropertiesdialog.ui \ + mainwindow.ui \ + dialogs/backgroundpropertiesdialog.ui \ + dialogs/editspritedialog.ui \ + dialogs/extensionpackagesdialog.ui \ + dialogs/gameinformationdialog.ui \ + dialogs/globalgamesettingsdialog.ui \ + dialogs/preferencesdialog.ui \ + dialogs/soundpropertiesdialog.ui \ + dialogs/spritepropertiesdialog.ui + +RESOURCES += \ + resources.qrc diff --git a/codeeditorwidget.cpp b/codeeditorwidget.cpp new file mode 100644 index 0000000..4d367d4 --- /dev/null +++ b/codeeditorwidget.cpp @@ -0,0 +1,120 @@ +#include "codeeditorwidget.h" + +#include +#include + +CodeEditorWidget::CodeEditorWidget(QWidget *parent) + : QPlainTextEdit{parent} +{ + lineNumberArea = new LineNumberArea(this); + + connect(this, &CodeEditorWidget::blockCountChanged, + this, &CodeEditorWidget::updateLineNumberAreaWidth); + connect(this, &CodeEditorWidget::updateRequest, + this, &CodeEditorWidget::updateLineNumberArea); + connect(this, &CodeEditorWidget::cursorPositionChanged, + this, &CodeEditorWidget::highlightCurrentLine); + + updateLineNumberAreaWidth(0); + highlightCurrentLine(); +} + +void CodeEditorWidget::updateLineNumberAreaWidth(int newBlockCount) +{ + setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); +} + +int CodeEditorWidget::lineNumberAreaWidth() +{ + int digits = 1; + int max = qMax(1, blockCount()); + while (max >= 10) + { + max /= 10; + ++digits; + } + + int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; + + return std::max(space, 40); +} + +void CodeEditorWidget::resizeEvent(QResizeEvent *e) +{ + QPlainTextEdit::resizeEvent(e); + + QRect cr = contentsRect(); + lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); +} + +void CodeEditorWidget::updateLineNumberArea(const QRect &rect, int dy) +{ + if (dy) + lineNumberArea->scroll(0, dy); + else + lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); + + if (rect.contains(viewport()->rect())) + updateLineNumberAreaWidth(0); +} + +void CodeEditorWidget::highlightCurrentLine() +{ + QList extraSelections; + + if (!isReadOnly()) + { + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(Qt::yellow).lighter(160); + + selection.format.setBackground(lineColor); + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + selection.cursor = textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + } + + setExtraSelections(extraSelections); +} + +void CodeEditorWidget::lineNumberAreaPaintEvent(QPaintEvent *event) +{ + QPainter painter(lineNumberArea); + painter.fillRect(event->rect(), Qt::lightGray); + + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int top = qRound(blockBoundingGeometry(block).translated(contentOffset()).top()); + int bottom = top + qRound(blockBoundingRect(block).height()); + + while (block.isValid() && top <= event->rect().bottom()) + { + if (block.isVisible() && bottom >= event->rect().top()) + { + QString number = QString::number(blockNumber + 1); + painter.setPen(Qt::black); + painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(), + Qt::AlignRight, number); + } + + block = block.next(); + top = bottom; + bottom = top + qRound(blockBoundingRect(block).height()); + ++blockNumber; + } +} + +LineNumberArea::LineNumberArea(CodeEditorWidget *editor) : QWidget(editor), codeEditor(editor) +{ +} + +QSize LineNumberArea::sizeHint() const +{ + return QSize(codeEditor->lineNumberAreaWidth(), 0); +} + +void LineNumberArea::paintEvent(QPaintEvent *event) +{ + codeEditor->lineNumberAreaPaintEvent(event); +} diff --git a/codeeditorwidget.h b/codeeditorwidget.h new file mode 100644 index 0000000..a9abcb1 --- /dev/null +++ b/codeeditorwidget.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +class CodeEditorWidget : public QPlainTextEdit +{ + Q_OBJECT + +public: + explicit CodeEditorWidget(QWidget *parent = nullptr); + + void lineNumberAreaPaintEvent(QPaintEvent *event); + int lineNumberAreaWidth(); + +protected: + void resizeEvent(QResizeEvent *event) override; + +private slots: + void updateLineNumberAreaWidth(int newBlockCount); + void highlightCurrentLine(); + void updateLineNumberArea(const QRect &rect, int dy); + +private: + QWidget *lineNumberArea; +}; + +class LineNumberArea : public QWidget +{ + Q_OBJECT + +public: + LineNumberArea(CodeEditorWidget *editor); + + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + CodeEditorWidget *codeEditor; +}; diff --git a/dialogs/backgroundpropertiesdialog.cpp b/dialogs/backgroundpropertiesdialog.cpp new file mode 100644 index 0000000..a3b1515 --- /dev/null +++ b/dialogs/backgroundpropertiesdialog.cpp @@ -0,0 +1,104 @@ +#include "backgroundpropertiesdialog.h" +#include "ui_backgroundpropertiesdialog.h" + +#include +#include +#include + +#include "projectcontainer.h" + +BackgroundPropertiesDialog::BackgroundPropertiesDialog(Background &background, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_background{background} +{ + m_ui->setupUi(this); + + setWindowTitle(tr("Background Properties: %0").arg(m_background.name)); + + m_ui->lineEditName->setText(m_background.name); + m_ui->labelWidth->setText(tr("Width: %0").arg(m_background.pixmap.width())); + m_ui->labelHeight->setText(tr("Height: %0").arg(m_background.pixmap.height())); + m_ui->labelPreview->setPixmap(m_background.pixmap); + + connect(m_ui->pushButtonLoad, &QAbstractButton::pressed, + this, &BackgroundPropertiesDialog::loadBackground); + connect(m_ui->pushButtonSave, &QAbstractButton::pressed, + this, &BackgroundPropertiesDialog::saveBackground); + connect(m_ui->pushButtonEdit, &QAbstractButton::pressed, + this, &BackgroundPropertiesDialog::editBackground); + + connect(m_ui->lineEditName, &QLineEdit::textChanged, + this, &BackgroundPropertiesDialog::changed); + connect(m_ui->checkBoxTileset, &QCheckBox::stateChanged, + this, &BackgroundPropertiesDialog::changed); +} + +BackgroundPropertiesDialog::~BackgroundPropertiesDialog() = default; + +void BackgroundPropertiesDialog::accept() +{ + if (m_background.name != m_ui->lineEditName->text()) + { + QMessageBox::critical(this, tr("Not implemented"), tr("Changing the name is not yet implemented!")); + return; + } + + // TODO + + QDialog::accept(); +} + +void BackgroundPropertiesDialog::reject() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + const auto result = QMessageBox::warning( + this, + tr("The Background 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 BackgroundPropertiesDialog::loadBackground() +{ + QFileDialog::getOpenFileName(this, tr("Open a Background Image...")); +} + +void BackgroundPropertiesDialog::saveBackground() +{ + QFileDialog::getSaveFileName(this, tr("Save a Background Image..."), m_background.name + ".png", tr("PNG Files (*.png)")); +} + +void BackgroundPropertiesDialog::editBackground() +{ + +} + +void BackgroundPropertiesDialog::changed() +{ + if (!m_unsavedChanges) + { + setWindowTitle(tr("Background Properties: %0*").arg(m_background.name)); + m_unsavedChanges = true; + } +} diff --git a/dialogs/backgroundpropertiesdialog.h b/dialogs/backgroundpropertiesdialog.h new file mode 100644 index 0000000..670e7ab --- /dev/null +++ b/dialogs/backgroundpropertiesdialog.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include + +namespace Ui { class BackgroundPropertiesDialog; } +struct Background; + +class BackgroundPropertiesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit BackgroundPropertiesDialog(Background &background, QWidget *parent = nullptr); + ~BackgroundPropertiesDialog(); + + void accept() override; + void reject() override; + +private slots: + void loadBackground(); + void saveBackground(); + void editBackground(); + + void changed(); + +private: + const std::unique_ptr m_ui; + + Background &m_background; + + bool m_unsavedChanges{}; +}; diff --git a/dialogs/backgroundpropertiesdialog.ui b/dialogs/backgroundpropertiesdialog.ui new file mode 100644 index 0000000..7e0e022 --- /dev/null +++ b/dialogs/backgroundpropertiesdialog.ui @@ -0,0 +1,197 @@ + + + BackgroundPropertiesDialog + + + + 0 + 0 + 573 + 221 + + + + Dialog + + + + :/qtgameengine/icons/dialogs/background-file.png:/qtgameengine/icons/dialogs/background-file.png + + + + + + + + + + Name: + + + lineEditName + + + + + + + + 0 + 0 + + + + + + + + + + &Load Background + + + + :/qtgameengine/icons/actions/open.png:/qtgameengine/icons/actions/open.png + + + + + + + &Save Background + + + + :/qtgameengine/icons/actions/save.png:/qtgameengine/icons/actions/save.png + + + + + + + &Edit Background + + + + :/qtgameengine/icons/buttons/edit.png:/qtgameengine/icons/buttons/edit.png + + + + + + + + + Width: + + + + + + + Height: + + + + + + + + + &Use as tile set + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 40 + 20 + + + + + + + + &OK + + + + :/qtgameengine/icons/buttons/ok.png:/qtgameengine/icons/buttons/ok.png + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 40 + 20 + + + + + + + + + + + + true + + + + + 0 + 0 + 371 + 201 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + + + pushButtonOk + pressed() + BackgroundPropertiesDialog + accept() + + + 124 + 197 + + + 286 + 110 + + + + + diff --git a/dialogs/editspritedialog.cpp b/dialogs/editspritedialog.cpp new file mode 100644 index 0000000..3b2482d --- /dev/null +++ b/dialogs/editspritedialog.cpp @@ -0,0 +1,22 @@ +#include "editspritedialog.h" +#include "ui_editspritedialog.h" + +#include "projectcontainer.h" + +EditSpriteDialog::EditSpriteDialog(Sprite &sprite, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_sprite{sprite} +{ + m_ui->setupUi(this); + setWindowFlags(windowFlags() + & ~Qt::Dialog + | Qt::Window + | Qt::WindowMinimizeButtonHint + | Qt::WindowMaximizeButtonHint + | Qt::WindowCloseButtonHint); + + setWindowTitle(tr("Sprite editor - %0").arg(m_sprite.name)); +} + +EditSpriteDialog::~EditSpriteDialog() = default; diff --git a/dialogs/editspritedialog.h b/dialogs/editspritedialog.h new file mode 100644 index 0000000..678a66a --- /dev/null +++ b/dialogs/editspritedialog.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +namespace Ui { class EditSpriteDialog; } +struct Sprite; + +class EditSpriteDialog : public QDialog +{ + Q_OBJECT + +public: + explicit EditSpriteDialog(Sprite &sprite, QWidget *parent = nullptr); + ~EditSpriteDialog(); + +private: + const std::unique_ptr m_ui; + + Sprite &m_sprite; +}; diff --git a/dialogs/editspritedialog.ui b/dialogs/editspritedialog.ui new file mode 100644 index 0000000..99983df --- /dev/null +++ b/dialogs/editspritedialog.ui @@ -0,0 +1,206 @@ + + + EditSpriteDialog + + + + 0 + 0 + 491 + 500 + + + + Sprite Editor + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + &File + + + + + + + + + + + + + + + &Edit + + + + + &Transform + + + + + &Images + + + + + &Animation + + + + + + + + + + + + + QFrame::Panel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 16 + 16 + + + + false + + + + + + + + + + + + + + + + + + + + + + + + :/qtgameengine/icons/actions/new.png:/qtgameengine/icons/actions/new.png + + + &New... + + + + + + :/qtgameengine/icons/actions/open.png:/qtgameengine/icons/actions/open.png + + + &Create from file... + + + + + + :/qtgameengine/icons/actions/create-group.png:/qtgameengine/icons/actions/create-group.png + + + &Add from file... + + + + + &Save as PNG File... + + + + + Create from Strip... + + + + + Add from Stri&p... + + + + + + :/qtgameengine/icons/actions/ok.png:/qtgameengine/icons/actions/ok.png + + + Close Saving Changes + + + + + + + + + actionCloseSavingChanges + triggered() + EditSpriteDialog + accept() + + + -1 + -1 + + + 245 + 249 + + + + + diff --git a/dialogs/extensionpackagesdialog.cpp b/dialogs/extensionpackagesdialog.cpp new file mode 100644 index 0000000..769d5e4 --- /dev/null +++ b/dialogs/extensionpackagesdialog.cpp @@ -0,0 +1,17 @@ +#include "extensionpackagesdialog.h" +#include "ui_extensionpackagesdialog.h" + +ExtensionPackagesDialog::ExtensionPackagesDialog(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); +} + +ExtensionPackagesDialog::~ExtensionPackagesDialog() = default; diff --git a/dialogs/extensionpackagesdialog.h b/dialogs/extensionpackagesdialog.h new file mode 100644 index 0000000..28d09e4 --- /dev/null +++ b/dialogs/extensionpackagesdialog.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include + +namespace Ui { class ExtensionPackagesDialog; } + +class ExtensionPackagesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ExtensionPackagesDialog(QWidget *parent = nullptr); + ~ExtensionPackagesDialog(); + +private: + const std::unique_ptr m_ui; +}; diff --git a/dialogs/extensionpackagesdialog.ui b/dialogs/extensionpackagesdialog.ui new file mode 100644 index 0000000..ae35576 --- /dev/null +++ b/dialogs/extensionpackagesdialog.ui @@ -0,0 +1,212 @@ + + + ExtensionPackagesDialog + + + + 0 + 0 + 400 + 461 + + + + Extension Packages + + + + + + + + + + + + + ⬅️ + + + + + + + ➡️ + + + + + + + + + Used Packages: + + + + + + + + + + Available Packages: + + + + + + + + + Information + + + + + + + 75 + true + + + + Author: + + + + + + + + 75 + true + + + + Version: + + + + + + + + 75 + true + + + + Date: + + + + + + + + 75 + true + + + + License: + + + + + + + + 75 + true + + + + Description: + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ExtensionPackagesDialog + accept() + + + 199 + 278 + + + 199 + 149 + + + + + buttonBox + rejected() + ExtensionPackagesDialog + reject() + + + 199 + 278 + + + 199 + 149 + + + + + diff --git a/dialogs/fontpropertiesdialog.cpp b/dialogs/fontpropertiesdialog.cpp new file mode 100644 index 0000000..4d51d19 --- /dev/null +++ b/dialogs/fontpropertiesdialog.cpp @@ -0,0 +1,144 @@ +#include "fontpropertiesdialog.h" +#include "ui_fontpropertiesdialog.h" + +#include +#include + +#include "projectcontainer.h" + +FontPropertiesDialog::FontPropertiesDialog(Font &font, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_font{font} +{ + m_ui->setupUi(this); + + setWindowTitle(tr("Font Properties: %0").arg(m_font.name)); + + m_ui->lineEditName->setText(m_font.name); + m_ui->fontComboBox->setCurrentFont(m_font.font); + m_ui->spinBoxSize->setValue(m_font.font.pointSize()); + m_ui->checkBoxBold->setChecked(m_font.font.bold()); + m_ui->checkBoxItalic->setChecked(m_font.font.italic()); + m_ui->spinBoxCharRangeFrom->setValue(m_font.range.from); + m_ui->spinBoxCharRangeTo->setValue(m_font.range.to); + + m_ui->labelPreview->setFont(currentFont()); + + connect(m_ui->pushButtonNormal, &QAbstractButton::pressed, + this, &FontPropertiesDialog::normalRange); + connect(m_ui->pushButtonDigits, &QAbstractButton::pressed, + this, &FontPropertiesDialog::digitsRange); + connect(m_ui->pushButtonAll, &QAbstractButton::pressed, + this, &FontPropertiesDialog::allRange); + connect(m_ui->pushButtonLetters, &QAbstractButton::pressed, + this, &FontPropertiesDialog::lettersRange); + + connect(m_ui->lineEditName, &QLineEdit::textChanged, + this, &FontPropertiesDialog::changed); + connect(m_ui->fontComboBox, &QFontComboBox::currentFontChanged, + this, &FontPropertiesDialog::changed); + connect(m_ui->spinBoxSize, &QSpinBox::valueChanged, + this, &FontPropertiesDialog::changed); + connect(m_ui->checkBoxBold, &QCheckBox::toggled, + this, &FontPropertiesDialog::changed); + connect(m_ui->checkBoxItalic, &QCheckBox::toggled, + this, &FontPropertiesDialog::changed); + connect(m_ui->spinBoxCharRangeFrom, &QSpinBox::valueChanged, + this, &FontPropertiesDialog::changed); + connect(m_ui->spinBoxCharRangeTo, &QSpinBox::valueChanged, + this, &FontPropertiesDialog::changed); +} + +FontPropertiesDialog::~FontPropertiesDialog() = default; + +void FontPropertiesDialog::accept() +{ + if (m_font.name != m_ui->lineEditName->text()) + { + QMessageBox::critical(this, tr("Not implemented"), tr("Changing the name is not yet implemented!")); + return; + } + + m_font.font = currentFont(); + m_font.range = { + .from = m_ui->spinBoxCharRangeFrom->value(), + .to = m_ui->spinBoxCharRangeTo->value(), + }; + + QDialog::accept(); +} + +void FontPropertiesDialog::reject() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + const auto result = QMessageBox::warning( + this, + tr("The Font 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 FontPropertiesDialog::normalRange() +{ + m_ui->spinBoxCharRangeFrom->setValue(32); + m_ui->spinBoxCharRangeFrom->setValue(127); +} + +void FontPropertiesDialog::digitsRange() +{ + m_ui->spinBoxCharRangeFrom->setValue(48); + m_ui->spinBoxCharRangeFrom->setValue(57); +} + +void FontPropertiesDialog::allRange() +{ + m_ui->spinBoxCharRangeFrom->setValue(0); + m_ui->spinBoxCharRangeFrom->setValue(255); +} + +void FontPropertiesDialog::lettersRange() +{ + m_ui->spinBoxCharRangeFrom->setValue(65); + m_ui->spinBoxCharRangeFrom->setValue(122); +} + +void FontPropertiesDialog::changed() +{ + m_ui->labelPreview->setFont(currentFont()); + if (!m_unsavedChanges) + { + setWindowTitle(tr("Font Properties: %0*").arg(m_font.name)); + m_unsavedChanges = true; + } +} + +QFont FontPropertiesDialog::currentFont() const +{ + QFont font; + font.setFamily(m_ui->fontComboBox->currentFont().family()); + font.setPointSize(m_ui->spinBoxSize->value()); + font.setBold(m_ui->checkBoxBold->isChecked()); + font.setItalic(m_ui->checkBoxItalic->isChecked()); + return font; +} diff --git a/dialogs/fontpropertiesdialog.h b/dialogs/fontpropertiesdialog.h new file mode 100644 index 0000000..2943e9a --- /dev/null +++ b/dialogs/fontpropertiesdialog.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +namespace Ui { class FontPropertiesDialog; } +struct Font; + +class FontPropertiesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit FontPropertiesDialog(Font &font, QWidget *parent = nullptr); + ~FontPropertiesDialog(); + + void accept() override; + void reject() override; + +private slots: + void normalRange(); + void digitsRange(); + void allRange(); + void lettersRange(); + + void changed(); + +private: + QFont currentFont() const; + + const std::unique_ptr m_ui; + + Font &m_font; + + bool m_unsavedChanges{}; +}; diff --git a/dialogs/fontpropertiesdialog.ui b/dialogs/fontpropertiesdialog.ui new file mode 100644 index 0000000..02b45f5 --- /dev/null +++ b/dialogs/fontpropertiesdialog.ui @@ -0,0 +1,258 @@ + + + FontPropertiesDialog + + + + 0 + 0 + 338 + 451 + + + + Font Properties + + + + :/qtgameengine/icons/dialogs/font-file.png:/qtgameengine/icons/dialogs/font-file.png + + + + + + + + Name: + + + lineEditName + + + + + + + + + + Font: + + + fontComboBox + + + + + + + Size: + + + spinBoxSize + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 40 + + + + + + + + + + + + Bold + + + + + + + Italic + + + + + + + + + Character Range + + + + + + till + + + + + + + 255 + + + 32 + + + + + + + 255 + + + 127 + + + + + + + Normal + + + + + + + Digits + + + + + + + All + + + + + + + Letters + + + + + + + + + + + 0 + 100 + + + + QFrame::StyledPanel + + + AaBbCcDd + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &OK + + + + :/qtgameengine/icons/buttons/ok.png:/qtgameengine/icons/buttons/ok.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + pushButtonOk + pressed() + FontPropertiesDialog + accept() + + + 168 + 428 + + + 168 + 225 + + + + + diff --git a/dialogs/gameinformationdialog.cpp b/dialogs/gameinformationdialog.cpp new file mode 100644 index 0000000..647cfe0 --- /dev/null +++ b/dialogs/gameinformationdialog.cpp @@ -0,0 +1,17 @@ +#include "gameinformationdialog.h" +#include "ui_gameinformationdialog.h" + +GameInformationDialog::GameInformationDialog(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); +} + +GameInformationDialog::~GameInformationDialog() = default; diff --git a/dialogs/gameinformationdialog.h b/dialogs/gameinformationdialog.h new file mode 100644 index 0000000..9ef9ed8 --- /dev/null +++ b/dialogs/gameinformationdialog.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include + +namespace Ui { class GameInformationDialog; } + +class GameInformationDialog : public QDialog +{ + Q_OBJECT + +public: + explicit GameInformationDialog(QWidget *parent = nullptr); + ~GameInformationDialog(); + +private: + const std::unique_ptr m_ui; +}; diff --git a/dialogs/gameinformationdialog.ui b/dialogs/gameinformationdialog.ui new file mode 100644 index 0000000..8583a96 --- /dev/null +++ b/dialogs/gameinformationdialog.ui @@ -0,0 +1,19 @@ + + + GameInformationDialog + + + + 0 + 0 + 400 + 300 + + + + Game Information + + + + + diff --git a/dialogs/globalgamesettingsdialog.cpp b/dialogs/globalgamesettingsdialog.cpp new file mode 100644 index 0000000..8e275db --- /dev/null +++ b/dialogs/globalgamesettingsdialog.cpp @@ -0,0 +1,17 @@ +#include "globalgamesettingsdialog.h" +#include "ui_globalgamesettingsdialog.h" + +GlobalGameSettingsDialog::GlobalGameSettingsDialog(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); +} + +GlobalGameSettingsDialog::~GlobalGameSettingsDialog() = default; diff --git a/dialogs/globalgamesettingsdialog.h b/dialogs/globalgamesettingsdialog.h new file mode 100644 index 0000000..22f86c4 --- /dev/null +++ b/dialogs/globalgamesettingsdialog.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include + +namespace Ui { class GlobalGameSettingsDialog; } + +class GlobalGameSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit GlobalGameSettingsDialog(QWidget *parent = nullptr); + ~GlobalGameSettingsDialog(); + +private: + const std::unique_ptr m_ui; +}; diff --git a/dialogs/globalgamesettingsdialog.ui b/dialogs/globalgamesettingsdialog.ui new file mode 100644 index 0000000..21e7b7b --- /dev/null +++ b/dialogs/globalgamesettingsdialog.ui @@ -0,0 +1,268 @@ + + + GlobalGameSettingsDialog + + + + 0 + 0 + 370 + 528 + + + + Global Game Settings + + + + + + 0 + + + + Graphics + + + + + + Start in full-screen mode + + + + + + + + + Scaling + + + + + + + + Fixed scale (in %) + + + + + + + 100 + + + 100 + + + + + + + + + Keep aspect ratio + + + + + + + Full scale + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Interpolate colors between pixels + + + + + + + + + Color outside the room region: + + + + + + + ... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Allow the player to resize the game window + + + + + + + Let the game window always stay on top + + + + + + + Don't draw a border in windowed mode + + + + + + + Don't show the buttons in the window caption + + + + + + + Display the cursor + + + + + + + Freeze the game when the form loses focus + + + + + + + Disable screensavers and power saving actions + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Resolution + + + + + Other + + + + + Loading + + + + + Errors + + + + + Info + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + GlobalGameSettingsDialog + accept() + + + 184 + 506 + + + 184 + 263 + + + + + buttonBox + rejected() + GlobalGameSettingsDialog + reject() + + + 184 + 506 + + + 184 + 263 + + + + + diff --git a/dialogs/pathpropertiesdialog.cpp b/dialogs/pathpropertiesdialog.cpp new file mode 100644 index 0000000..fbf3f98 --- /dev/null +++ b/dialogs/pathpropertiesdialog.cpp @@ -0,0 +1,92 @@ +#include "pathpropertiesdialog.h" +#include "ui_pathpropertiesdialog.h" + +#include +#include + +#include "projectcontainer.h" + +PathPropertiesDialog::PathPropertiesDialog(Path &path, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_path{path}, + 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); + + setWindowTitle(tr("Path Properties: %0").arg(m_path.name)); + + m_labelX->setFrameStyle(QFrame::Sunken); + m_ui->statusbar->addWidget(m_labelX, 1); + m_labelY->setFrameStyle(QFrame::Sunken); + m_ui->statusbar->addWidget(m_labelY, 1); + m_labelArea->setFrameStyle(QFrame::Sunken); + m_ui->statusbar->addWidget(m_labelArea, 4); + + { + auto frame = new QFrame{this}; + frame->setFrameStyle(QFrame::Sunken); + m_ui->statusbar->addPermanentWidget(frame, 2); + } + + m_ui->lineEditName->setText(m_path.name); + + connect(m_ui->lineEditName, &QLineEdit::textChanged, + this, &PathPropertiesDialog::changed); +} + +PathPropertiesDialog::~PathPropertiesDialog() = default; + +void PathPropertiesDialog::accept() +{ + if (m_path.name != m_ui->lineEditName->text()) + { + QMessageBox::critical(this, tr("Not implemented"), tr("Changing the name is not yet implemented!")); + return; + } + + // TODO + + QDialog::accept(); +} + +void PathPropertiesDialog::reject() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + const auto result = QMessageBox::warning( + this, + tr("The Path 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 PathPropertiesDialog::changed() +{ + if (!m_unsavedChanges) + { + setWindowTitle(tr("Path Properties: %0*").arg(m_path.name)); + m_unsavedChanges = true; + } +} diff --git a/dialogs/pathpropertiesdialog.h b/dialogs/pathpropertiesdialog.h new file mode 100644 index 0000000..bbe8897 --- /dev/null +++ b/dialogs/pathpropertiesdialog.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +class QLabel; +namespace Ui { class PathPropertiesDialog; } +struct Path; + +class PathPropertiesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PathPropertiesDialog(Path &path, QWidget *parent = nullptr); + ~PathPropertiesDialog(); + + void accept() override; + void reject() override; + +private slots: + void changed(); + +private: + const std::unique_ptr m_ui; + + Path &m_path; + + QLabel * const m_labelX; + QLabel * const m_labelY; + QLabel * const m_labelArea; + + bool m_unsavedChanges{}; +}; diff --git a/dialogs/pathpropertiesdialog.ui b/dialogs/pathpropertiesdialog.ui new file mode 100644 index 0000000..0daf26e --- /dev/null +++ b/dialogs/pathpropertiesdialog.ui @@ -0,0 +1,333 @@ + + + PathPropertiesDialog + + + + 0 + 0 + 728 + 449 + + + + Path Properties + + + + :/qtgameengine/icons/dialogs/path-file.png:/qtgameengine/icons/dialogs/path-file.png + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::Panel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + toolBar + + + false + + + + 16 + 16 + + + + + + + + + + + + + + + + + + + + + + Name: + + + lineEditName + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + X: + + + + + + + + + + Y: + + + + + + + + + + sp: + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Add + + + + + + + Insert + + + + + + + Delete + + + + + + + + + + + + + + + connection kind + + + + + + Straight lines + + + + + + + Smooth curves + + + + + + + + + + + + Precision: + + + + + + + + + + + + + + + + Closed + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + :/qtgameengine/icons/actions/ok.png:/qtgameengine/icons/actions/ok.png + + + OK + + + Close the form, saving the changes + + + + + + :/qtgameengine/icons/actions/undo.png:/qtgameengine/icons/actions/undo.png + + + Undo + + + Undo the last changes in the path + + + + + + :/qtgameengine/icons/actions/new.png:/qtgameengine/icons/actions/new.png + + + Clear + + + Clear the path + + + + + + + + + actionOk + triggered() + PathPropertiesDialog + accept() + + + -1 + -1 + + + 363 + 224 + + + + + diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp new file mode 100644 index 0000000..d889813 --- /dev/null +++ b/dialogs/preferencesdialog.cpp @@ -0,0 +1,17 @@ +#include "preferencesdialog.h" +#include "ui_preferencesdialog.h" + +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); +} + +PreferencesDialog::~PreferencesDialog() = default; diff --git a/dialogs/preferencesdialog.h b/dialogs/preferencesdialog.h new file mode 100644 index 0000000..e76862c --- /dev/null +++ b/dialogs/preferencesdialog.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include + +namespace Ui { class PreferencesDialog; } + +class PreferencesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PreferencesDialog(QWidget *parent = nullptr); + ~PreferencesDialog(); + +private: + const std::unique_ptr m_ui; +}; diff --git a/dialogs/preferencesdialog.ui b/dialogs/preferencesdialog.ui new file mode 100644 index 0000000..e412bb5 --- /dev/null +++ b/dialogs/preferencesdialog.ui @@ -0,0 +1,85 @@ + + + PreferencesDialog + + + + 0 + 0 + 400 + 300 + + + + Preferences + + + + + + + General + + + + + Forms + + + + + Scripts and Code + + + + + Editors + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + PreferencesDialog + accept() + + + 199 + 278 + + + 199 + 149 + + + + + buttonBox + rejected() + PreferencesDialog + reject() + + + 199 + 278 + + + 199 + 149 + + + + + diff --git a/dialogs/scriptpropertiesdialog.cpp b/dialogs/scriptpropertiesdialog.cpp new file mode 100644 index 0000000..8efbc9c --- /dev/null +++ b/dialogs/scriptpropertiesdialog.cpp @@ -0,0 +1,158 @@ +#include "scriptpropertiesdialog.h" +#include "ui_scriptpropertiesdialog.h" + +#include +#include +#include +#include +#include +#include + +#include "projectcontainer.h" +#include "jshighlighter.h" + +ScriptPropertiesDialog::ScriptPropertiesDialog(Script &script, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_script{script}, + m_lineEditName{new QLineEdit{this}}, + m_labelPosition{new QLabel{this}} +{ + m_ui->setupUi(this); + + setWindowTitle(tr("Script Properties: %0").arg(m_script.name)); + + { + auto label = new QLabel{tr("Name:"), this}; + label->setBuddy(m_lineEditName); + m_ui->toolBar->addWidget(label); + } + 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_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()) + { + QMessageBox::critical(this, tr("Not implemented"), tr("Changing the name is not yet implemented!")); + return; + } + + m_script.script = m_ui->codeEdit->toPlainText(); + + QDialog::accept(); +} + +void ScriptPropertiesDialog::reject() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + const auto result = QMessageBox::warning( + this, + tr("The Script 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 ScriptPropertiesDialog::changed() +{ + if (!m_unsavedChanges) + { + setWindowTitle(tr("Script Properties: %0*").arg(m_script.name)); + m_unsavedChanges = true; + } +} + +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)); +} diff --git a/dialogs/scriptpropertiesdialog.h b/dialogs/scriptpropertiesdialog.h new file mode 100644 index 0000000..46c514a --- /dev/null +++ b/dialogs/scriptpropertiesdialog.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include + +class QLineEdit; +class QLabel; +namespace Ui { class ScriptPropertiesDialog; } +struct Script; + +class ScriptPropertiesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ScriptPropertiesDialog(Script &script, QWidget *parent = nullptr); + ~ScriptPropertiesDialog(); + + void accept() override; + void reject() override; + +private slots: + void changed(); + + void load(); + void save(); + void print(); + + void updatePosition(); + +private: + const std::unique_ptr m_ui; + + Script &m_script; + + QLineEdit * const m_lineEditName; + + QLabel * const m_labelPosition; + + bool m_unsavedChanges{}; +}; diff --git a/dialogs/scriptpropertiesdialog.ui b/dialogs/scriptpropertiesdialog.ui new file mode 100644 index 0000000..0eac0e0 --- /dev/null +++ b/dialogs/scriptpropertiesdialog.ui @@ -0,0 +1,568 @@ + + + ScriptPropertiesDialog + + + + 0 + 0 + 540 + 497 + + + + Script Properties + + + + :/qtgameengine/icons/dialogs/script-file.png:/qtgameengine/icons/dialogs/script-file.png + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + toolBar + + + false + + + + 16 + 16 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + + + + + + false + + + + + + Find + + + + + + true + + + + + + + Case Sensitive + + + + + + + Whole word only + + + + + + + + + First + + + + + + + Previous + + + + + + + Next + + + + + + + Last + + + + + + + + + + + + Replace + + + + + + true + + + + + + + + + First + + + + + + + Previous + + + + + + + Next + + + + + + + Last + + + + + + + + + Replace All + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + :/qtgameengine/icons/actions/ok.png:/qtgameengine/icons/actions/ok.png + + + OK + + + + + + :/qtgameengine/icons/actions/open.png:/qtgameengine/icons/actions/open.png + + + Load + + + Load the code from a text file + + + + + + :/qtgameengine/icons/actions/save.png:/qtgameengine/icons/actions/save.png + + + Save + + + Save the code to a text file + + + + + + :/qtgameengine/icons/actions/print.png:/qtgameengine/icons/actions/print.png + + + Print + + + Print the code + + + + + false + + + + :/qtgameengine/icons/actions/undo.png:/qtgameengine/icons/actions/undo.png + + + Undo + + + Undo the last change + + + + + false + + + + :/qtgameengine/icons/actions/redo.png:/qtgameengine/icons/actions/redo.png + + + Redo + + + Redo the last undo + + + + + false + + + + :/qtgameengine/icons/actions/cut.png:/qtgameengine/icons/actions/cut.png + + + Cut + + + Cut the selected text into clipboard + + + + + false + + + + :/qtgameengine/icons/actions/copy.png:/qtgameengine/icons/actions/copy.png + + + Copy + + + Copy the selected text to the clipboard + + + + + + :/qtgameengine/icons/actions/paste.png:/qtgameengine/icons/actions/paste.png + + + Paste + + + Paste the text from the clipboard + + + + + true + + + false + + + + :/qtgameengine/icons/actions/find.png:/qtgameengine/icons/actions/find.png + + + Find + + + Show the find and replace panel + + + + + true + + + true + + + + :/qtgameengine/icons/actions/check.png:/qtgameengine/icons/actions/check.png + + + Check + + + Check the script for syntax errors + + + + + + CodeEditorWidget + QPlainTextEdit +
codeeditorwidget.h
+
+
+ + + + + + actionOk + triggered() + ScriptPropertiesDialog + accept() + + + -1 + -1 + + + 269 + 248 + + + + + actionUndo + triggered() + codeEdit + undo() + + + -1 + -1 + + + 269 + 263 + + + + + actionRedo + triggered() + codeEdit + redo() + + + -1 + -1 + + + 269 + 263 + + + + + codeEdit + undoAvailable(bool) + actionUndo + setEnabled(bool) + + + 269 + 263 + + + -1 + -1 + + + + + codeEdit + redoAvailable(bool) + actionRedo + setEnabled(bool) + + + 269 + 263 + + + -1 + -1 + + + + + actionCut + triggered() + codeEdit + cut() + + + -1 + -1 + + + 269 + 263 + + + + + actionCopy + triggered() + codeEdit + copy() + + + -1 + -1 + + + 269 + 263 + + + + + actionPaste + triggered() + codeEdit + paste() + + + -1 + -1 + + + 269 + 263 + + + + + codeEdit + copyAvailable(bool) + actionCut + setEnabled(bool) + + + 269 + 263 + + + -1 + -1 + + + + + codeEdit + copyAvailable(bool) + actionCopy + setEnabled(bool) + + + 269 + 263 + + + -1 + -1 + + + + + actionFind + toggled(bool) + widgetSearch + setVisible(bool) + + + -1 + -1 + + + 499 + 263 + + + + +
diff --git a/dialogs/soundpropertiesdialog.cpp b/dialogs/soundpropertiesdialog.cpp new file mode 100644 index 0000000..8246c9e --- /dev/null +++ b/dialogs/soundpropertiesdialog.cpp @@ -0,0 +1,167 @@ +#include "soundpropertiesdialog.h" +#include "ui_soundpropertiesdialog.h" + +#include +#include +#include +#include + +#include "projectcontainer.h" + +SoundPropertiesDialog::SoundPropertiesDialog(Sound &sound, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_sound{sound} +{ + m_ui->setupUi(this); + + setWindowTitle(tr("Sound Properties: %0").arg(m_sound.name)); + + m_ui->lineEditName->setText(m_sound.name); + m_ui->labelFilename->setText(tr("Filename: %0").arg(QFileInfo{m_sound.path}.fileName())); + m_ui->radioButtonNormal->setChecked(m_sound.type == Sound::Type::Sound); + m_ui->radioButtonMusic->setChecked(m_sound.type == Sound::Type::Music); + m_ui->checkBoxChorus->setChecked(m_sound.effects.chorus); + m_ui->checkBoxFlanger->setChecked(m_sound.effects.flanger); + m_ui->checkBoxGargle->setChecked(m_sound.effects.gargle); + m_ui->checkBoxEcho->setChecked(m_sound.effects.echo); + m_ui->checkBoxReverb->setChecked(m_sound.effects.reverb); + m_ui->horizontalSliderVolume->setValue(m_sound.volume); + m_ui->horizontalSliderPan->setValue(m_sound.pan); + m_ui->checkBoxPreload->setChecked(m_sound.preload); + + connect(m_ui->pushButtonLoad, &QAbstractButton::pressed, + this, &SoundPropertiesDialog::loadSound); + connect(m_ui->pushButtonPlay, &QAbstractButton::pressed, + this, &SoundPropertiesDialog::playSound); + connect(m_ui->pushButtonStop, &QAbstractButton::pressed, + this, &SoundPropertiesDialog::stopSound); + connect(m_ui->pushButtonSave, &QAbstractButton::pressed, + this, &SoundPropertiesDialog::saveSound); + connect(m_ui->pushButtonEdit, &QAbstractButton::pressed, + this, &SoundPropertiesDialog::editSound); + + connect(m_ui->lineEditName, &QLineEdit::textChanged, + this, &SoundPropertiesDialog::changed); + connect(m_ui->radioButtonNormal, &QRadioButton::toggled, + this, &SoundPropertiesDialog::changed); + connect(m_ui->radioButtonMusic, &QRadioButton::toggled, + this, &SoundPropertiesDialog::changed); + connect(m_ui->radioButton3D, &QRadioButton::toggled, + this, &SoundPropertiesDialog::changed); + connect(m_ui->radioButtonMultimedia, &QRadioButton::toggled, + this, &SoundPropertiesDialog::changed); + connect(m_ui->checkBoxChorus, &QCheckBox::stateChanged, + this, &SoundPropertiesDialog::changed); + connect(m_ui->checkBoxFlanger, &QCheckBox::stateChanged, + this, &SoundPropertiesDialog::changed); + connect(m_ui->checkBoxGargle, &QCheckBox::stateChanged, + this, &SoundPropertiesDialog::changed); + connect(m_ui->checkBoxEcho, &QCheckBox::stateChanged, + this, &SoundPropertiesDialog::changed); + connect(m_ui->checkBoxReverb, &QCheckBox::stateChanged, + this, &SoundPropertiesDialog::changed); + connect(m_ui->horizontalSliderVolume, &QSlider::valueChanged, + this, &SoundPropertiesDialog::changed); + connect(m_ui->horizontalSliderPan, &QSlider::valueChanged, + this, &SoundPropertiesDialog::changed); + connect(m_ui->checkBoxPreload, &QCheckBox::stateChanged, + this, &SoundPropertiesDialog::changed); +} + +SoundPropertiesDialog::~SoundPropertiesDialog() = default; + +void SoundPropertiesDialog::accept() +{ + if (m_sound.name != m_ui->lineEditName->text()) + { + QMessageBox::critical(this, tr("Not implemented"), tr("Changing the name is not yet implemented!")); + return; + } + + if (m_ui->radioButtonNormal->isChecked()) + m_sound.type = Sound::Type::Sound; + else if (m_ui->radioButtonMusic->isChecked()) + m_sound.type = Sound::Type::Music; + else + { + QMessageBox::critical(this, tr("Not implemented"), tr("This kind of sound is not yet supported!")); + return; + } + + m_sound.effects.chorus = m_ui->checkBoxChorus->isChecked(); + m_sound.effects.flanger = m_ui->checkBoxFlanger->isChecked(); + m_sound.effects.gargle = m_ui->checkBoxGargle->isChecked(); + m_sound.effects.echo = m_ui->checkBoxEcho->isChecked(); + m_sound.effects.reverb = m_ui->checkBoxReverb->isChecked(); + m_sound.volume = m_ui->horizontalSliderVolume->value(); + m_sound.pan = m_ui->horizontalSliderPan->value(); + + QDialog::accept(); +} + +void SoundPropertiesDialog::reject() +{ + if (!m_unsavedChanges) + { + QDialog::reject(); + return; + } + + const auto result = QMessageBox::warning( + this, + tr("The Sound 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 SoundPropertiesDialog::loadSound() +{ + QFileDialog::getOpenFileName(this, tr("Open a Sound File...")); +} + +void SoundPropertiesDialog::playSound() +{ + m_soundEffect.setSource(QUrl::fromLocalFile(m_sound.path)); + m_soundEffect.stop(); + m_soundEffect.play(); +} + +void SoundPropertiesDialog::stopSound() +{ + m_soundEffect.stop(); +} + +void SoundPropertiesDialog::saveSound() +{ + QFileDialog::getSaveFileName(this, tr("Save a Sound File..."), m_sound.name + ".wav", tr("WAV Files (*.wav)")); +} + +void SoundPropertiesDialog::editSound() +{ + QMessageBox::critical(this, tr("Setup not complete"), tr("No valid external editor has been indicated for this type of sound. You can specify this editor in the Preferences.")); +} + +void SoundPropertiesDialog::changed() +{ + if (!m_unsavedChanges) + { + setWindowTitle(tr("Sound Properties: %0*").arg(m_sound.name)); + m_unsavedChanges = true; + } +} diff --git a/dialogs/soundpropertiesdialog.h b/dialogs/soundpropertiesdialog.h new file mode 100644 index 0000000..d263991 --- /dev/null +++ b/dialogs/soundpropertiesdialog.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Ui { class SoundPropertiesDialog; } +struct Sound; + +class SoundPropertiesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SoundPropertiesDialog(Sound &sound, QWidget *parent = nullptr); + ~SoundPropertiesDialog(); + + void accept() override; + void reject() override; + +private slots: + void loadSound(); + void playSound(); + void stopSound(); + void saveSound(); + void editSound(); + + void changed(); + +private: + const std::unique_ptr m_ui; + + Sound &m_sound; + + bool m_unsavedChanges{}; + + QSoundEffect m_soundEffect; +}; diff --git a/dialogs/soundpropertiesdialog.ui b/dialogs/soundpropertiesdialog.ui new file mode 100644 index 0000000..f89088d --- /dev/null +++ b/dialogs/soundpropertiesdialog.ui @@ -0,0 +1,354 @@ + + + SoundPropertiesDialog + + + + 0 + 0 + 239 + 534 + + + + Sound Properties + + + + :/qtgameengine/icons/dialogs/sound-file.png:/qtgameengine/icons/dialogs/sound-file.png + + + + + + + + Name: + + + lineEditName + + + + + + + + + + + + + + &Load Sound + + + + :/qtgameengine/icons/actions/open.png:/qtgameengine/icons/actions/open.png + + + + + + + Play the sound + + + + + + + :/qtgameengine/icons/actions/run.png:/qtgameengine/icons/actions/run.png + + + + + + + Stop the sound + + + + + + + :/qtgameengine/icons/actions/exit.png:/qtgameengine/icons/actions/exit.png + + + + + + + + + Sa&ve Sound + + + + :/qtgameengine/icons/actions/save.png:/qtgameengine/icons/actions/save.png + + + + + + + Filename: + + + + + + + Kind + + + + + + Normal Sound + + + true + + + + + + + Background Music + + + + + + + 3D Sound + + + + + + + Use multimedia player + + + + + + + + + + Effects + + + + + + Flanger + + + + + + + Chorus + + + + + + + Gargle + + + + + + + Echo + + + + + + + Reverb + + + + + + + + + + + + Volume: + + + horizontalSliderVolume + + + + + + + 100 + + + 100 + + + Qt::Horizontal + + + + + + + Pan: + + + horizontalSliderPan + + + + + + + -100 + + + 100 + + + Qt::Horizontal + + + false + + + false + + + + + + + + + &Preload + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Edit Sound + + + + :/qtgameengine/icons/actions/sound.png:/qtgameengine/icons/actions/sound.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &OK + + + + :/qtgameengine/icons/buttons/ok.png:/qtgameengine/icons/buttons/ok.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + pushButtonOk + pressed() + SoundPropertiesDialog + accept() + + + 119 + 534 + + + 119 + 279 + + + + + diff --git a/dialogs/spritepropertiesdialog.cpp b/dialogs/spritepropertiesdialog.cpp new file mode 100644 index 0000000..0d4e27a --- /dev/null +++ b/dialogs/spritepropertiesdialog.cpp @@ -0,0 +1,133 @@ +#include "spritepropertiesdialog.h" +#include "ui_spritepropertiesdialog.h" + +#include +#include +#include + +#include "projectcontainer.h" +#include "editspritedialog.h" + +SpritePropertiesDialog::SpritePropertiesDialog(Sprite &sprite, QWidget *parent) : + QDialog{parent}, + m_ui{std::make_unique()}, + m_sprite{sprite} +{ + m_ui->setupUi(this); + + setWindowTitle(tr("Sprite Properties: %0").arg(m_sprite.name)); + + m_ui->lineEditName->setText(m_sprite.name); + m_ui->labelWidth->setText(tr("Width: %0").arg(m_sprite.pixmaps.empty() ? tr("n/a") : QString::number(m_sprite.pixmaps.front().width()))); + m_ui->labelHeight->setText(tr("Height: %0").arg(m_sprite.pixmaps.empty() ? tr("n/a") : QString::number(m_sprite.pixmaps.front().height()))); + m_ui->labelSubimages->setText(tr("Number of subimages: %0").arg(m_sprite.pixmaps.size())); + m_ui->spinBoxOriginX->setValue(m_sprite.origin.x); + 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_ui->pushButtonLoad, &QAbstractButton::pressed, + this, &SpritePropertiesDialog::loadSprite); + connect(m_ui->pushButtonSave, &QAbstractButton::pressed, + this, &SpritePropertiesDialog::saveSprite); + connect(m_ui->pushButtonEdit, &QAbstractButton::pressed, + this, &SpritePropertiesDialog::editSprite); + connect(m_ui->pushButtonCenterOrigin, &QAbstractButton::pressed, + this, &SpritePropertiesDialog::centerOrigin); + + connect(m_ui->lineEditName, &QLineEdit::textChanged, + this, &SpritePropertiesDialog::changed); + connect(m_ui->spinBoxOriginX, &QSpinBox::valueChanged, + this, &SpritePropertiesDialog::changed); + connect(m_ui->spinBoxOriginY, &QSpinBox::valueChanged, + this, &SpritePropertiesDialog::changed); + connect(m_ui->checkBoxPreciseCollisionChecking, &QCheckBox::stateChanged, + this, &SpritePropertiesDialog::changed); + connect(m_ui->checkBoxSeparateCollisionMasks, &QCheckBox::stateChanged, + this, &SpritePropertiesDialog::changed); +} + +SpritePropertiesDialog::~SpritePropertiesDialog() = default; + +void SpritePropertiesDialog::accept() +{ + if (m_sprite.name != m_ui->lineEditName->text()) + { + QMessageBox::critical(this, tr("Not implemented"), tr("Changing the name is not yet implemented!")); + return; + } + + m_sprite.origin.x = m_ui->spinBoxOriginX->value(); + m_sprite.origin.y = m_ui->spinBoxOriginY->value(); + m_sprite.preciseCollisionChecking = m_ui->checkBoxPreciseCollisionChecking->isChecked(); + m_sprite.separateCollisionMasks = m_ui->checkBoxSeparateCollisionMasks->isChecked(); + + QDialog::accept(); +} + +void SpritePropertiesDialog::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 SpritePropertiesDialog::loadSprite() +{ + QFileDialog::getOpenFileName(this, tr("Open a Sprite Image...")); +} + +void SpritePropertiesDialog::saveSprite() +{ + QFileDialog::getSaveFileName(this, tr("Save a Sprite Image..."), m_sprite.name + ".png", tr("PNG Files (*.png)")); +} + +void SpritePropertiesDialog::editSprite() +{ + EditSpriteDialog{m_sprite}.exec(); +} + +void SpritePropertiesDialog::centerOrigin() +{ + if (m_sprite.pixmaps.empty()) + { + qDebug() << "unexpected empty pixmaps"; + return; + } + + m_ui->spinBoxOriginX->setValue(m_sprite.pixmaps.front().width() / 2); + m_ui->spinBoxOriginY->setValue(m_sprite.pixmaps.front().height() / 2); +} + +void SpritePropertiesDialog::changed() +{ + if (!m_unsavedChanges) + { + setWindowTitle(tr("Sprite Properties: %0*").arg(m_sprite.name)); + m_unsavedChanges = true; + } +} diff --git a/dialogs/spritepropertiesdialog.h b/dialogs/spritepropertiesdialog.h new file mode 100644 index 0000000..f078d7b --- /dev/null +++ b/dialogs/spritepropertiesdialog.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include + +namespace Ui { class SpritePropertiesDialog; } +struct Sprite; + +class SpritePropertiesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SpritePropertiesDialog(Sprite &sprite, QWidget *parent = nullptr); + ~SpritePropertiesDialog(); + + void accept() override; + void reject() override; + +private slots: + void loadSprite(); + void saveSprite(); + void editSprite(); + void centerOrigin(); + + void changed(); + +private: + const std::unique_ptr m_ui; + + Sprite &m_sprite; + + bool m_unsavedChanges{}; +}; diff --git a/dialogs/spritepropertiesdialog.ui b/dialogs/spritepropertiesdialog.ui new file mode 100644 index 0000000..e5fb632 --- /dev/null +++ b/dialogs/spritepropertiesdialog.ui @@ -0,0 +1,360 @@ + + + SpritePropertiesDialog + + + + 0 + 0 + 560 + 344 + + + + Sprite Properties + + + + :/qtgameengine/icons/dialogs/sprite-file.png:/qtgameengine/icons/dialogs/sprite-file.png + + + + + + + + + + Name: + + + lineEditName + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + &Load Sprite + + + + :/qtgameengine/icons/actions/open.png:/qtgameengine/icons/actions/open.png + + + + + + + &Save Sprite + + + + :/qtgameengine/icons/actions/save.png:/qtgameengine/icons/actions/save.png + + + + + + + &Edit Sprite + + + + :/qtgameengine/icons/buttons/edit.png:/qtgameengine/icons/buttons/edit.png + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Height: + + + + + + + Width: + + + + + + + + + Number of subimages: + + + + + + + Origin + + + + + + + + X: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + spinBoxOriginX + + + + + + + + + + Y: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + spinBoxOriginY + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Center + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Ok + + + + :/qtgameengine/icons/buttons/ok.png:/qtgameengine/icons/buttons/ok.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + Collision Checking + + + + + + &Precise collision checking + + + + + + + Separa&te Collision Masks + + + + + + + &Modify Mask + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + true + + + + + 0 + 0 + 124 + 324 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + + + pushButtonOk + pressed() + SpritePropertiesDialog + accept() + + + 106 + 320 + + + 279 + 171 + + + + + diff --git a/futurecpp.h b/futurecpp.h new file mode 100644 index 0000000..b67578c --- /dev/null +++ b/futurecpp.h @@ -0,0 +1,30 @@ +#pragma once + +// system includes +#include +#include +#include + +// C++20 backports (until espressif finally updates their aged compiler suite) + +namespace std { +template +typename std::enable_if_t< + sizeof(To) == sizeof(From) && std::is_trivially_copyable_v && std::is_trivially_copyable_v, + To> +// constexpr support needs compiler magic +bit_cast(const From& src) noexcept +{ + static_assert(std::is_trivially_constructible_v, + "This implementation additionally requires destination type to be trivially constructible"); + + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; +} + +template {}>> +constexpr std::underlying_type_t to_underlying(EnumT e) noexcept { + return static_cast>(e); +} +} // namespace std diff --git a/icons/actions/background.png b/icons/actions/background.png new file mode 100644 index 0000000..6a9ca6b Binary files /dev/null and b/icons/actions/background.png differ diff --git a/icons/actions/cascade.png b/icons/actions/cascade.png new file mode 100644 index 0000000..8f1891e Binary files /dev/null and b/icons/actions/cascade.png differ diff --git a/icons/actions/check.png b/icons/actions/check.png new file mode 100644 index 0000000..c4ac85f Binary files /dev/null and b/icons/actions/check.png differ diff --git a/icons/actions/copy.png b/icons/actions/copy.png new file mode 100644 index 0000000..5e7f82a Binary files /dev/null and b/icons/actions/copy.png differ diff --git a/icons/actions/create-executable.png b/icons/actions/create-executable.png new file mode 100644 index 0000000..3b69651 Binary files /dev/null and b/icons/actions/create-executable.png differ diff --git a/icons/actions/create-group.png b/icons/actions/create-group.png new file mode 100644 index 0000000..5dc4d3c Binary files /dev/null and b/icons/actions/create-group.png differ diff --git a/icons/actions/create.png b/icons/actions/create.png new file mode 100644 index 0000000..90fd9fd Binary files /dev/null and b/icons/actions/create.png differ diff --git a/icons/actions/cut.png b/icons/actions/cut.png new file mode 100644 index 0000000..5598995 Binary files /dev/null and b/icons/actions/cut.png differ diff --git a/icons/actions/debug.png b/icons/actions/debug.png new file mode 100644 index 0000000..370c11d Binary files /dev/null and b/icons/actions/debug.png differ diff --git a/icons/actions/delete.png b/icons/actions/delete.png new file mode 100644 index 0000000..2f78744 Binary files /dev/null and b/icons/actions/delete.png differ diff --git a/icons/actions/duplicate.png b/icons/actions/duplicate.png new file mode 100644 index 0000000..cd10056 Binary files /dev/null and b/icons/actions/duplicate.png differ diff --git a/icons/actions/exit.png b/icons/actions/exit.png new file mode 100644 index 0000000..6d63678 Binary files /dev/null and b/icons/actions/exit.png differ diff --git a/icons/actions/export-resources.png b/icons/actions/export-resources.png new file mode 100644 index 0000000..b45e0e2 Binary files /dev/null and b/icons/actions/export-resources.png differ diff --git a/icons/actions/extension-packages.png b/icons/actions/extension-packages.png new file mode 100644 index 0000000..5ff8629 Binary files /dev/null and b/icons/actions/extension-packages.png differ diff --git a/icons/actions/find.png b/icons/actions/find.png new file mode 100644 index 0000000..4cc4ec6 Binary files /dev/null and b/icons/actions/find.png differ diff --git a/icons/actions/font.png b/icons/actions/font.png new file mode 100644 index 0000000..f31c5f2 Binary files /dev/null and b/icons/actions/font.png differ diff --git a/icons/actions/game-information.png b/icons/actions/game-information.png new file mode 100644 index 0000000..d607b07 Binary files /dev/null and b/icons/actions/game-information.png differ diff --git a/icons/actions/global-game-settings.png b/icons/actions/global-game-settings.png new file mode 100644 index 0000000..7a6c875 Binary files /dev/null and b/icons/actions/global-game-settings.png differ diff --git a/icons/actions/help.png b/icons/actions/help.png new file mode 100644 index 0000000..0f3948c Binary files /dev/null and b/icons/actions/help.png differ diff --git a/icons/actions/import-resources.png b/icons/actions/import-resources.png new file mode 100644 index 0000000..1d66975 Binary files /dev/null and b/icons/actions/import-resources.png differ diff --git a/icons/actions/new.png b/icons/actions/new.png new file mode 100644 index 0000000..c4e0380 Binary files /dev/null and b/icons/actions/new.png differ diff --git a/icons/actions/object.png b/icons/actions/object.png new file mode 100644 index 0000000..9643847 Binary files /dev/null and b/icons/actions/object.png differ diff --git a/icons/actions/ok.png b/icons/actions/ok.png new file mode 100644 index 0000000..04c2ed6 Binary files /dev/null and b/icons/actions/ok.png differ diff --git a/icons/actions/open.png b/icons/actions/open.png new file mode 100644 index 0000000..8751d77 Binary files /dev/null and b/icons/actions/open.png differ diff --git a/icons/actions/paste.png b/icons/actions/paste.png new file mode 100644 index 0000000..e6c19c3 Binary files /dev/null and b/icons/actions/paste.png differ diff --git a/icons/actions/path.png b/icons/actions/path.png new file mode 100644 index 0000000..d4d1699 Binary files /dev/null and b/icons/actions/path.png differ diff --git a/icons/actions/preferences.png b/icons/actions/preferences.png new file mode 100644 index 0000000..a55ed51 Binary files /dev/null and b/icons/actions/preferences.png differ diff --git a/icons/actions/print.png b/icons/actions/print.png new file mode 100644 index 0000000..24bb842 Binary files /dev/null and b/icons/actions/print.png differ diff --git a/icons/actions/properties.png b/icons/actions/properties.png new file mode 100644 index 0000000..6b20817 Binary files /dev/null and b/icons/actions/properties.png differ diff --git a/icons/actions/publish-game.png b/icons/actions/publish-game.png new file mode 100644 index 0000000..b040277 Binary files /dev/null and b/icons/actions/publish-game.png differ diff --git a/icons/actions/redo.png b/icons/actions/redo.png new file mode 100644 index 0000000..d1be333 Binary files /dev/null and b/icons/actions/redo.png differ diff --git a/icons/actions/rename.png b/icons/actions/rename.png new file mode 100644 index 0000000..42ff6db Binary files /dev/null and b/icons/actions/rename.png differ diff --git a/icons/actions/room.png b/icons/actions/room.png new file mode 100644 index 0000000..efac2f0 Binary files /dev/null and b/icons/actions/room.png differ diff --git a/icons/actions/run.png b/icons/actions/run.png new file mode 100644 index 0000000..635aa95 Binary files /dev/null and b/icons/actions/run.png differ diff --git a/icons/actions/save-as.png b/icons/actions/save-as.png new file mode 100644 index 0000000..fb64d76 Binary files /dev/null and b/icons/actions/save-as.png differ diff --git a/icons/actions/save.png b/icons/actions/save.png new file mode 100644 index 0000000..637ec45 Binary files /dev/null and b/icons/actions/save.png differ diff --git a/icons/actions/script.png b/icons/actions/script.png new file mode 100644 index 0000000..91c1bd3 Binary files /dev/null and b/icons/actions/script.png differ diff --git a/icons/actions/sound.png b/icons/actions/sound.png new file mode 100644 index 0000000..90bc880 Binary files /dev/null and b/icons/actions/sound.png differ diff --git a/icons/actions/sprite.png b/icons/actions/sprite.png new file mode 100644 index 0000000..3a407fb Binary files /dev/null and b/icons/actions/sprite.png differ diff --git a/icons/actions/tile.png b/icons/actions/tile.png new file mode 100644 index 0000000..6684933 Binary files /dev/null and b/icons/actions/tile.png differ diff --git a/icons/actions/timeline.png b/icons/actions/timeline.png new file mode 100644 index 0000000..f56755e Binary files /dev/null and b/icons/actions/timeline.png differ diff --git a/icons/actions/undo.png b/icons/actions/undo.png new file mode 100644 index 0000000..9fb738d Binary files /dev/null and b/icons/actions/undo.png differ diff --git a/icons/buttons/edit.png b/icons/buttons/edit.png new file mode 100644 index 0000000..b6939e9 Binary files /dev/null and b/icons/buttons/edit.png differ diff --git a/icons/buttons/ok.png b/icons/buttons/ok.png new file mode 100644 index 0000000..04c2ed6 Binary files /dev/null and b/icons/buttons/ok.png differ diff --git a/icons/dialogs/background-file.png b/icons/dialogs/background-file.png new file mode 100644 index 0000000..6c5e4d9 Binary files /dev/null and b/icons/dialogs/background-file.png differ diff --git a/icons/dialogs/font-file.png b/icons/dialogs/font-file.png new file mode 100644 index 0000000..860370a Binary files /dev/null and b/icons/dialogs/font-file.png differ diff --git a/icons/dialogs/path-file.png b/icons/dialogs/path-file.png new file mode 100644 index 0000000..148e749 Binary files /dev/null and b/icons/dialogs/path-file.png differ diff --git a/icons/dialogs/script-file.png b/icons/dialogs/script-file.png new file mode 100644 index 0000000..91c1bd3 Binary files /dev/null and b/icons/dialogs/script-file.png differ diff --git a/icons/dialogs/sound-file.png b/icons/dialogs/sound-file.png new file mode 100644 index 0000000..57f0af9 Binary files /dev/null and b/icons/dialogs/sound-file.png differ diff --git a/icons/dialogs/sprite-file.png b/icons/dialogs/sprite-file.png new file mode 100644 index 0000000..2e19e63 Binary files /dev/null and b/icons/dialogs/sprite-file.png differ diff --git a/icons/tree/extension-packages.png b/icons/tree/extension-packages.png new file mode 100644 index 0000000..f55f62f Binary files /dev/null and b/icons/tree/extension-packages.png differ diff --git a/icons/tree/folder.png b/icons/tree/folder.png new file mode 100644 index 0000000..337d4e7 Binary files /dev/null and b/icons/tree/folder.png differ diff --git a/icons/tree/font-file.png b/icons/tree/font-file.png new file mode 100644 index 0000000..860370a Binary files /dev/null and b/icons/tree/font-file.png differ diff --git a/icons/tree/game-information.png b/icons/tree/game-information.png new file mode 100644 index 0000000..6d835ce Binary files /dev/null and b/icons/tree/game-information.png differ diff --git a/icons/tree/global-game-settings.png b/icons/tree/global-game-settings.png new file mode 100644 index 0000000..98777ef Binary files /dev/null and b/icons/tree/global-game-settings.png differ diff --git a/icons/tree/music-file.png b/icons/tree/music-file.png new file mode 100644 index 0000000..35af82c Binary files /dev/null and b/icons/tree/music-file.png differ diff --git a/icons/tree/path-file.png b/icons/tree/path-file.png new file mode 100644 index 0000000..148e749 Binary files /dev/null and b/icons/tree/path-file.png differ diff --git a/icons/tree/script-file.png b/icons/tree/script-file.png new file mode 100644 index 0000000..91c1bd3 Binary files /dev/null and b/icons/tree/script-file.png differ diff --git a/icons/tree/sound-file.png b/icons/tree/sound-file.png new file mode 100644 index 0000000..4dd4e84 Binary files /dev/null and b/icons/tree/sound-file.png differ diff --git a/jshighlighter.cpp b/jshighlighter.cpp new file mode 100644 index 0000000..7ffa68e --- /dev/null +++ b/jshighlighter.cpp @@ -0,0 +1,449 @@ +#include "jshighlighter.h" + +class JSBlockData : public QTextBlockUserData +{ +public: + QList bracketPositions; +}; + +JSHighlighter::JSHighlighter(QTextDocument *parent) : + QSyntaxHighlighter{parent}, + m_colors { + // default color scheme + { Normal, QColor("#000000") }, + { Comment, QColor("#808080") }, + { Number, QColor("#008000") }, + { String, QColor("#800000") }, + { Operator, QColor("#808000") }, + { Identifier, QColor("#000020") }, + { Keyword, QColor("#000080") }, + { BuiltIn, QColor("#008080") }, + { Marker, QColor("#ffff00") }, + }, + m_keywords { + // https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words + "break", + "case", + "catch", + "continue", + "default", + "delete", + "do", + "else", + "finally", + "for", + "function", + "if", + "in", + "instanceof", + "new", + "return", + "switch", + "this", + "throw", + "try", + "typeof", + "var", + "void", + "while", + "with", + + "true", + "false", + "null", + }, + m_knownIds { + // built-in and other popular objects + properties + "Object", + "prototype", + "create", + "defineProperty", + "defineProperties", + "getOwnPropertyDescriptor", + "keys", + "getOwnPropertyNames", + "constructor", + "__parent__", + "__proto__", + "__defineGetter__", + "__defineSetter__", + "eval", + "hasOwnProperty", + "isPrototypeOf", + "__lookupGetter__", + "__lookupSetter__", + "__noSuchMethod__", + "propertyIsEnumerable", + "toSource", + "toLocaleString", + "toString", + "unwatch", + "valueOf", + "watch", + + "Function", + "arguments", + "arity", + "caller", + "constructor", + "length", + "name", + "apply", + "bind", + "call", + + "String", + "fromCharCode", + "length", + "charAt", + "charCodeAt", + "concat", + "indexOf", + "lastIndexOf", + "localCompare", + "match", + "quote", + "replace", + "search", + "slice", + "split", + "substr", + "substring", + "toLocaleLowerCase", + "toLocaleUpperCase", + "toLowerCase", + "toUpperCase", + "trim", + "trimLeft", + "trimRight", + + "Array", + "isArray", + "index", + "input", + "pop", + "push", + "reverse", + "shift", + "sort", + "splice", + "unshift", + "concat", + "join", + "filter", + "forEach", + "every", + "map", + "some", + "reduce", + "reduceRight", + + "RegExp", + "global", + "ignoreCase", + "lastIndex", + "multiline", + "source", + "exec", + "test", + + "JSON", + "parse", + "stringify", + + "decodeURI", + "decodeURIComponent", + "encodeURI", + "encodeURIComponent", + "eval", + "isFinite", + "isNaN", + "parseFloat", + "parseInt", + "Infinity", + "NaN", + "undefined", + + "Math", + "E", + "LN2", + "LN10", + "LOG2E", + "LOG10E", + "PI", + "SQRT1_2", + "SQRT2", + "abs", + "acos", + "asin", + "atan", + "atan2", + "ceil", + "cos", + "exp", + "floor", + "log", + "max", + "min", + "pow", + "random", + "round", + "sin", + "sqrt", + "tan", + + "document", + "window", + "navigator", + "userAgent", + }, + m_markCaseSensitivity{Qt::CaseInsensitive} +{ +} + +void JSHighlighter::setColor(ColorComponent component, const QColor &color) +{ + m_colors[component] = color; + rehighlight(); +} + +void JSHighlighter::highlightBlock(const QString &text) +{ + // parsing state + enum { + Start = 0, + Number = 1, + Identifier = 2, + String = 3, + Comment = 4, + Regex = 5 + }; + + QList bracketPositions; + + int blockState = previousBlockState(); + int bracketLevel = blockState >> 4; + int state = blockState & 15; + if (blockState < 0) + { + bracketLevel = 0; + state = Start; + } + + int start = 0; + int i = 0; + while (i <= text.length()) + { + QChar ch = (i < text.length()) ? text.at(i) : QChar(); + QChar next = (i < text.length() - 1) ? text.at(i + 1) : QChar(); + + switch (state) + { + case Start: + start = i; + if (ch.isSpace()) + { + ++i; + } + else if (ch.isDigit()) + { + ++i; + state = Number; + } + else if (ch.isLetter() || ch == '_') + { + ++i; + state = Identifier; + } + else if (ch == '\'' || ch == '\"') + { + ++i; + state = String; + } + else if (ch == '/' && next == '*') + { + ++i; + ++i; + state = Comment; + } + else if (ch == '/' && next == '/') + { + i = text.length(); + setFormat(start, text.length(), m_colors[ColorComponent::Comment]); + } + else if (ch == '/' && next != '*') + { + ++i; + state = Regex; + } + else + { + if (!QString("(){}[]").contains(ch)) + setFormat(start, 1, m_colors[Operator]); + if (ch =='{' || ch == '}') + { + bracketPositions += i; + if (ch == '{') + bracketLevel++; + else + bracketLevel--; + } + ++i; + state = Start; + } + break; + + case Number: + if (ch.isSpace() || !ch.isDigit()) + { + setFormat(start, i - start, m_colors[ColorComponent::Number]); + state = Start; + } + else + { + ++i; + } + break; + + case Identifier: + if (ch.isSpace() || !(ch.isDigit() || ch.isLetter() || ch == '_')) + { + QString token = text.mid(start, i - start).trimmed(); + if (m_keywords.contains(token)) + setFormat(start, i - start, m_colors[Keyword]); + else if (m_knownIds.contains(token)) + setFormat(start, i - start, m_colors[BuiltIn]); + state = Start; + } + else + { + ++i; + } + break; + + case String: + if (ch == text.at(start)) { + QChar prev = (i > 0) ? text.at(i - 1) : QChar(); + if (prev != '\\') { + ++i; + setFormat(start, i - start, m_colors[ColorComponent::String]); + state = Start; + } else { + ++i; + } + } else { + ++i; + } + break; + + case Comment: + if (ch == '*' && next == '/') + { + ++i; + ++i; + setFormat(start, i - start, m_colors[ColorComponent::Comment]); + state = Start; + } + else + { + ++i; + } + break; + + case Regex: + if (ch == '/') + { + QChar prev = (i > 0) ? text.at(i - 1) : QChar(); + if (prev != '\\') + { + ++i; + setFormat(start, i - start, m_colors[ColorComponent::String]); + state = Start; + } + else + { + ++i; + } + } + else + { + ++i; + } + break; + + default: + state = Start; + break; + } + } + + if (state == Comment) + setFormat(start, text.length(), m_colors[ColorComponent::Comment]); + else + state = Start; + + if (!m_markString.isEmpty()) + { + int pos = 0; + int len = m_markString.length(); + QTextCharFormat markerFormat; + markerFormat.setBackground(m_colors[Marker]); + markerFormat.setForeground(m_colors[Normal]); + for (;;) + { + pos = text.indexOf(m_markString, pos, m_markCaseSensitivity); + if (pos < 0) + break; + setFormat(pos, len, markerFormat); + ++pos; + } + } + + if (!bracketPositions.isEmpty()) + { + JSBlockData *blockData = reinterpret_cast(currentBlock().userData()); + if (!blockData) + { + blockData = new JSBlockData; + currentBlock().setUserData(blockData); + } + blockData->bracketPositions = bracketPositions; + } + + blockState = (state & 15) | (bracketLevel << 4); + setCurrentBlockState(blockState); +} + +void JSHighlighter::mark(const QString &str, Qt::CaseSensitivity caseSensitivity) +{ + m_markString = str; + m_markCaseSensitivity = caseSensitivity; + rehighlight(); +} + +QStringList JSHighlighter::keywords() const +{ + return QStringList{std::begin(m_keywords), std::end(m_keywords)}; +} + +void JSHighlighter::setKeywords(std::set &&keywords) +{ + m_keywords = std::move(keywords); + rehighlight(); +} + +void JSHighlighter::setKeywords(const std::set &keywords) +{ + m_keywords = keywords; + rehighlight(); +} + +struct BlockInfo +{ + int position; + int number; + bool foldable: 1; + bool folded : 1; +}; + +Q_DECLARE_TYPEINFO(BlockInfo, Q_PRIMITIVE_TYPE); diff --git a/jshighlighter.h b/jshighlighter.h new file mode 100644 index 0000000..6e3100f --- /dev/null +++ b/jshighlighter.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include +#include + +enum ColorComponent { + Background, + Normal, + Comment, + Number, + String, + Operator, + Identifier, + Keyword, + BuiltIn, + Sidebar, + LineNumber, + Cursor, + Marker, + BracketMatch, + BracketError, + FoldIndicator +}; + +class JSHighlighter : public QSyntaxHighlighter +{ +public: + JSHighlighter(QTextDocument *parent = 0); + void setColor(ColorComponent component, const QColor &color); + void mark(const QString &str, Qt::CaseSensitivity caseSensitivity); + + QStringList keywords() const; + void setKeywords(std::set &&keywords); + void setKeywords(const std::set &keywords); + +protected: + void highlightBlock(const QString &text); + +private: + std::unordered_map m_colors; + std::set m_keywords; + std::set m_knownIds; + + QString m_markString; + Qt::CaseSensitivity m_markCaseSensitivity; +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..8546f97 --- /dev/null +++ b/main.cpp @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} " + "[" + "%{if-debug}D%{endif}" + "%{if-info}I%{endif}" + "%{if-warning}W%{endif}" + "%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}" + "] " + "%{function}(): " + "%{message}")); + + Q_INIT_RESOURCE(resources); + + QApplication app(argc, argv); + + QApplication::setStyle(QStyleFactory::create("Windows")); + + MainWindow mainWindow; + mainWindow.show(); + + return app.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..7e95d35 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,978 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include +#include + +#include "projecttreemodel.h" +#include "dialogs/spritepropertiesdialog.h" +#include "dialogs/soundpropertiesdialog.h" +#include "dialogs/backgroundpropertiesdialog.h" +#include "dialogs/pathpropertiesdialog.h" +#include "dialogs/scriptpropertiesdialog.h" +#include "dialogs/fontpropertiesdialog.h" +#include "dialogs/preferencesdialog.h" +#include "dialogs/gameinformationdialog.h" +#include "dialogs/globalgamesettingsdialog.h" +#include "dialogs/extensionpackagesdialog.h" + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow{parent}, + m_ui{std::make_unique()}, + m_projectTreeModel{std::make_unique(&m_project, this)} +{ + m_ui->setupUi(this); + + { + QList sizes; + sizes.append(0.5 * m_ui->splitter->width()); + sizes.append(0.5 * m_ui->splitter->width()); + m_ui->splitter->setSizes(sizes); + } + + m_ui->actionNew->setShortcut(QKeySequence::New); + m_ui->actionOpen->setShortcut(QKeySequence::Open); + m_ui->actionSave->setShortcut(QKeySequence::Save); + m_ui->actionSaveAs->setShortcut(QKeySequence::SaveAs); + m_ui->actionExit->setShortcut(QKeySequence::Quit); + m_ui->actionFindResource->setShortcut(QKeySequence::Find); + m_ui->actionPrevious->setShortcut(QKeySequence::PreviousChild); + m_ui->actionNext->setShortcut(QKeySequence::NextChild); + m_ui->actionContents->setShortcut(QKeySequence::HelpContents); + + { + const auto updateViewActions = [this](){ + m_ui->actionSubWindowedView->setChecked(m_ui->mdiArea->viewMode() == QMdiArea::SubWindowView); + m_ui->actionTabbedView->setChecked(m_ui->mdiArea->viewMode() == QMdiArea::TabbedView); + }; + updateViewActions(); + connect(m_ui->actionSubWindowedView, &QAction::triggered, m_ui->mdiArea, [mdiArea=m_ui->mdiArea,updateViewActions](){ + mdiArea->setViewMode(QMdiArea::SubWindowView); + updateViewActions(); + }); + connect(m_ui->actionTabbedView, &QAction::triggered, m_ui->mdiArea, [mdiArea=m_ui->mdiArea,updateViewActions](){ + mdiArea->setViewMode(QMdiArea::TabbedView); + updateViewActions(); + }); + } + + connect(m_ui->actionNew, &QAction::triggered, this, &MainWindow::newFile); + connect(m_ui->actionOpen, &QAction::triggered, this, &MainWindow::openFile); + connect(m_ui->actionSave, &QAction::triggered, this, &MainWindow::saveFile); + connect(m_ui->actionSaveAs, &QAction::triggered, this, &MainWindow::saveFileAs); + connect(m_ui->actionCreateExecutable, &QAction::triggered, this, &MainWindow::createExecutable); + connect(m_ui->actionPublishGame, &QAction::triggered, this, &MainWindow::publishGame); + connect(m_ui->actionImportResources, &QAction::triggered, this, &MainWindow::importResources); + connect(m_ui->actionExportResources, &QAction::triggered, this, &MainWindow::exportResources); + connect(m_ui->actionPreferences, &QAction::triggered, this, &MainWindow::preferences); + connect(m_ui->actionCreate, &QAction::triggered, this, &MainWindow::create); + connect(m_ui->actionDuplicate, &QAction::triggered, this, &MainWindow::duplicate); + connect(m_ui->actionCreateGroup, &QAction::triggered, this, &MainWindow::createGroup); + connect(m_ui->actionSortByName, &QAction::triggered, this, &MainWindow::sortByName); + connect(m_ui->actionDelete, &QAction::triggered, this, &MainWindow::delete_); + connect(m_ui->actionRename, &QAction::triggered, this, &MainWindow::rename); + connect(m_ui->actionProperties, &QAction::triggered, this, &MainWindow::showProperties); + connect(m_ui->actionCreateSprite, &QAction::triggered, this, &MainWindow::createSprite); + connect(m_ui->actionCreateSound, &QAction::triggered, this, &MainWindow::createSound); + connect(m_ui->actionCreateBackground, &QAction::triggered, this, &MainWindow::createBackground); + connect(m_ui->actionCreatePath, &QAction::triggered, this, &MainWindow::createPath); + connect(m_ui->actionCreateScript, &QAction::triggered, this, &MainWindow::createScript); + connect(m_ui->actionCreateFont, &QAction::triggered, this, &MainWindow::createFont); + 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->actionAbout, &QAction::triggered, this, &MainWindow::about); + connect(m_ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt); + + m_ui->treeView->setModel(m_projectTreeModel.get()); + + connect(m_ui->treeView, &QTreeView::customContextMenuRequested, + this, &MainWindow::contextMenuRequested); + + connect(m_ui->treeView, &QTreeView::doubleClicked, + this, &MainWindow::doubleClicked); + + connect(m_ui->treeView->selectionModel(), &QItemSelectionModel::currentChanged, + this, &MainWindow::selectionChanged); + + connect(m_projectTreeModel.get(), &ProjectTreeModel::rowsInserted, + this, &MainWindow::rowsInserted); + connect(m_projectTreeModel.get(), &ProjectTreeModel::rowsAboutToBeRemoved, + this, &MainWindow::rowsAboutToBeRemoved); + connect(m_projectTreeModel.get(), &ProjectTreeModel::modelAboutToBeReset, + this, &MainWindow::modelAboutToBeReset); + + updateTitle(); +} + +void MainWindow::closeEvent(QCloseEvent *event) +{ + m_ui->mdiArea->closeAllSubWindows(); + if (!m_ui->mdiArea->subWindowList().empty()) + { + event->ignore(); + return; + } + + if (m_unsavedChanges) + { + const auto result = QMessageBox::warning( + this, + tr("The Game has been modified."), + tr("Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, + QMessageBox::Save + ); + switch (result) + { + case QMessageBox::Save: + saveFile(); + if (m_unsavedChanges) + { + event->ignore(); + return; + } + else + break; + case QMessageBox::Discard: + break; + case QMessageBox::Cancel: + event->ignore(); + return; + default: + qWarning() << "unexpected dialog result" << result; + event->ignore(); + return; + } + } + + QMainWindow::closeEvent(event); +} + +MainWindow::~MainWindow() = default; + +void MainWindow::contextMenuRequested(const QPoint &pos) +{ + QMenu menu{this}; + menu.addAction(m_ui->actionCreate); + menu.addAction(m_ui->actionDuplicate); + menu.addSeparator(); + menu.addAction(m_ui->actionCreateGroup); + menu.addSeparator(); + menu.addAction(m_ui->actionSortByName); + menu.addSeparator(); + menu.addAction(m_ui->actionDelete); + menu.addSeparator(); + menu.addAction(m_ui->actionRename); + menu.addSeparator(); + menu.addAction(m_ui->actionProperties); + menu.exec(m_ui->treeView->viewport()->mapToGlobal(pos)); +} + +void MainWindow::doubleClicked(const QModelIndex &index) +{ + switch (m_projectTreeModel->nodeType(index)) + { + case ProjectTreeModel::NodeType::Root: + if (index == m_projectTreeModel->gameInformationRoot()) + showGameInformation(); + else if (index == m_projectTreeModel->globalGameSettingsRoot()) + showGlobalGameSettings(); + else if (index == m_projectTreeModel->extensionPackagesRoot()) + showExtensionPackages(); + break; + case ProjectTreeModel::NodeType::Sprite: + { + auto sprite = m_projectTreeModel->getSprite(index); + if (!sprite) + break; + + if (const auto iter = m_spritePropertiesDialogs.find(sprite); iter != std::cend(m_spritePropertiesDialogs)) + { + m_ui->mdiArea->setActiveSubWindow(iter->second); + } + else + { + auto dialog = new SpritePropertiesDialog{*sprite}; + 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, [&spritePropertiesDialogs=m_spritePropertiesDialogs,subwindow](){ + for (auto iter = std::begin(spritePropertiesDialogs); iter != std::end(spritePropertiesDialogs); ) + { + if (iter->second == subwindow) + iter = spritePropertiesDialogs.erase(iter); + else + iter++; + } + }); + connect(dialog, &QDialog::finished, + subwindow, &QObject::deleteLater); + connect(dialog, &QDialog::finished, + action, &QObject::deleteLater); + m_spritePropertiesDialogs[sprite] = subwindow; + dialog->show(); + } + break; + } + case ProjectTreeModel::NodeType::Sound: + { + auto sound = m_projectTreeModel->getSound(index); + if (!sound) + break; + + if (const auto iter = m_soundPropertiesDialogs.find(sound); iter != std::cend(m_soundPropertiesDialogs)) + { + m_ui->mdiArea->setActiveSubWindow(iter->second); + } + else + { + auto dialog = new SoundPropertiesDialog{*sound}; + 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, [&soundPropertiesDialogs=m_soundPropertiesDialogs,subwindow](){ + for (auto iter = std::begin(soundPropertiesDialogs); iter != std::end(soundPropertiesDialogs); ) + { + if (iter->second == subwindow) + iter = soundPropertiesDialogs.erase(iter); + else + iter++; + } + }); + connect(dialog, &QDialog::finished, + subwindow, &QObject::deleteLater); + connect(dialog, &QDialog::finished, + action, &QObject::deleteLater); + m_soundPropertiesDialogs[sound] = subwindow; + dialog->show(); + } + break; + } + case ProjectTreeModel::NodeType::Background: + { + auto background = m_projectTreeModel->getBackground(index); + if (!background) + break; + + if (const auto iter = m_backgroundPropertiesDialogs.find(background); iter != std::cend(m_backgroundPropertiesDialogs)) + { + m_ui->mdiArea->setActiveSubWindow(iter->second); + } + else + { + auto dialog = new BackgroundPropertiesDialog{*background}; + 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, [&backgroundPropertiesDialogs=m_backgroundPropertiesDialogs,subwindow](){ + for (auto iter = std::begin(backgroundPropertiesDialogs); iter != std::end(backgroundPropertiesDialogs); ) + { + if (iter->second == subwindow) + iter = backgroundPropertiesDialogs.erase(iter); + else + iter++; + } + }); + connect(dialog, &QDialog::finished, + subwindow, &QObject::deleteLater); + connect(dialog, &QDialog::finished, + action, &QObject::deleteLater); + m_backgroundPropertiesDialogs[background] = subwindow; + dialog->show(); + } + break; + } + case ProjectTreeModel::NodeType::Path: + { + auto path = m_projectTreeModel->getPath(index); + if (!path) + break; + + if (const auto iter = m_pathPropertiesDialogs.find(path); iter != std::cend(m_pathPropertiesDialogs)) + { + m_ui->mdiArea->setActiveSubWindow(iter->second); + } + else + { + auto dialog = new PathPropertiesDialog{*path}; + 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, [&pathPropertiesDialogs=m_pathPropertiesDialogs,subwindow](){ + for (auto iter = std::begin(pathPropertiesDialogs); iter != std::end(pathPropertiesDialogs); ) + { + if (iter->second == subwindow) + iter = pathPropertiesDialogs.erase(iter); + else + iter++; + } + }); + connect(dialog, &QDialog::finished, + subwindow, &QObject::deleteLater); + connect(dialog, &QDialog::finished, + action, &QObject::deleteLater); + m_pathPropertiesDialogs[path] = subwindow; + dialog->show(); + } + break; + } + case ProjectTreeModel::NodeType::Script: + { + auto script = m_projectTreeModel->getScript(index); + if (!script) + break; + + if (const auto iter = m_scriptPropertiesDialogs.find(script); iter != std::cend(m_scriptPropertiesDialogs)) + { + m_ui->mdiArea->setActiveSubWindow(iter->second); + } + else + { + auto dialog = new ScriptPropertiesDialog{*script}; + 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, [&scriptPropertiesDialogs=m_scriptPropertiesDialogs,subwindow](){ + for (auto iter = std::begin(scriptPropertiesDialogs); iter != std::end(scriptPropertiesDialogs); ) + { + if (iter->second == subwindow) + iter = scriptPropertiesDialogs.erase(iter); + else + iter++; + } + }); + connect(dialog, &QDialog::finished, + subwindow, &QObject::deleteLater); + connect(dialog, &QDialog::finished, + action, &QObject::deleteLater); + m_scriptPropertiesDialogs[script] = subwindow; + dialog->show(); + } + break; + } + case ProjectTreeModel::NodeType::Font: + { + auto font = m_projectTreeModel->getFont(index); + if (!font) + break; + + if (const auto iter = m_fontPropertiesDialogs.find(font); iter != std::cend(m_fontPropertiesDialogs)) + { + m_ui->mdiArea->setActiveSubWindow(iter->second); + } + else + { + auto dialog = new FontPropertiesDialog{*font}; + 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, [&fontPropertiesDialogs=m_fontPropertiesDialogs,subwindow](){ + for (auto iter = std::begin(fontPropertiesDialogs); iter != std::end(fontPropertiesDialogs); ) + { + if (iter->second == subwindow) + iter = fontPropertiesDialogs.erase(iter); + else + iter++; + } + }); + connect(dialog, &QDialog::finished, + subwindow, &QObject::deleteLater); + connect(dialog, &QDialog::finished, + action, &QObject::deleteLater); + m_fontPropertiesDialogs[font] = subwindow; + dialog->show(); + } + break; + } + } +} + +void MainWindow::selectionChanged(const QModelIndex &index) +{ + switch (const auto nodeType = m_projectTreeModel->nodeType(index)) + { + case ProjectTreeModel::NodeType::Root: + if (index == m_projectTreeModel->spritesRoot() || + index == m_projectTreeModel->soundsRoot() || + index == m_projectTreeModel->backgroundsRoot() || + index == m_projectTreeModel->pathsRoot() || + index == m_projectTreeModel->scriptsRoot() || + index == m_projectTreeModel->fontsRoot()) + { + m_ui->actionCreate->setEnabled(true); + m_ui->actionCreateGroup->setText(tr("Cr&eate Group")); + m_ui->actionCreateGroup->setEnabled(true); + m_ui->actionSortByName->setEnabled(true); + + if (index == m_projectTreeModel->spritesRoot()) + m_ui->actionCreate->setText(tr("&Create Sprite")); + else if (index == m_projectTreeModel->soundsRoot()) + m_ui->actionCreate->setText(tr("&Create Sound")); + else if (index == m_projectTreeModel->backgroundsRoot()) + m_ui->actionCreate->setText(tr("&Create Background")); + else if (index == m_projectTreeModel->pathsRoot()) + m_ui->actionCreate->setText(tr("&Create Path")); + else if (index == m_projectTreeModel->scriptsRoot()) + m_ui->actionCreate->setText(tr("&Create Script")); + else if (index == m_projectTreeModel->fontsRoot()) + m_ui->actionCreate->setText(tr("&Create Font")); + } + else + { + m_ui->actionCreate->setEnabled(false); + m_ui->actionCreate->setText(tr("&Create")); + m_ui->actionCreateGroup->setText(tr("Cr&eate Group")); + m_ui->actionCreateGroup->setEnabled(false); + m_ui->actionSortByName->setEnabled(false); + } + + m_ui->actionDuplicate->setEnabled(false); + m_ui->actionDelete->setEnabled(false); + m_ui->actionRename->setEnabled(false); + m_ui->actionProperties->setEnabled( + index == m_projectTreeModel->gameInformationRoot() || + index == m_projectTreeModel->globalGameSettingsRoot() || + index == m_projectTreeModel->extensionPackagesRoot()); + break; + case ProjectTreeModel::NodeType::Sprite: + case ProjectTreeModel::NodeType::Sound: + case ProjectTreeModel::NodeType::Background: + case ProjectTreeModel::NodeType::Path: + case ProjectTreeModel::NodeType::Script: + case ProjectTreeModel::NodeType::Font: + m_ui->actionCreate->setEnabled(true); + switch (nodeType) + { + case ProjectTreeModel::NodeType::Sprite: m_ui->actionCreate->setText(tr("&Insert Sprite")); break; + case ProjectTreeModel::NodeType::Sound: m_ui->actionCreate->setText(tr("&Insert Sound")); break; + case ProjectTreeModel::NodeType::Background: m_ui->actionCreate->setText(tr("&Insert Background")); break; + case ProjectTreeModel::NodeType::Path: m_ui->actionCreate->setText(tr("&Insert Path")); break; + case ProjectTreeModel::NodeType::Script: m_ui->actionCreate->setText(tr("&Insert Script")); break; + case ProjectTreeModel::NodeType::Font: m_ui->actionCreate->setText(tr("&Insert Font")); break; + default: + __builtin_unreachable(); + } + m_ui->actionDuplicate->setEnabled(true); + m_ui->actionCreateGroup->setText(tr("I&nsert Group")); + m_ui->actionCreateGroup->setEnabled(true); + m_ui->actionSortByName->setEnabled(false); + m_ui->actionDelete->setEnabled(true); + m_ui->actionRename->setEnabled(true); + m_ui->actionProperties->setEnabled(true); + break; + default: + m_ui->actionCreate->setEnabled(false); + m_ui->actionCreate->setText(tr("&Create")); + m_ui->actionDuplicate->setEnabled(false); + m_ui->actionCreateGroup->setText(tr("Cr&eate Group")); + m_ui->actionCreateGroup->setEnabled(false); + m_ui->actionSortByName->setEnabled(false); + m_ui->actionDelete->setEnabled(false); + m_ui->actionRename->setEnabled(false); + m_ui->actionProperties->setEnabled(false); + } +} + +void MainWindow::newFile() +{ + m_ui->mdiArea->closeAllSubWindows(); + if (!m_ui->mdiArea->subWindowList().empty()) + return; + + if (m_unsavedChanges) + { + const auto result = QMessageBox::warning( + this, + tr("The Game has been modified."), + tr("Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, + QMessageBox::Save + ); + switch (result) + { + case QMessageBox::Save: + saveFile(); + if (m_unsavedChanges) + return; + else + break; + case QMessageBox::Discard: + break; + case QMessageBox::Cancel: + return; + default: + qWarning() << "unexpected dialog result" << result; + return; + } + } + + m_project = {}; + m_projectTreeModel->setProject(&m_project); + + m_filePath = {}; + m_unsavedChanges = false; + + updateTitle(); +} + +void MainWindow::openFile() +{ + auto path = QFileDialog::getOpenFileName(this, tr("Load a game"), {}, tr("GMK Files (*.gmk)")); + if (path.isEmpty()) + return; + + ProjectContainer project; + + { + QFile file{path}; + if (!file.open(QIODevice::ReadOnly)) + { + QMessageBox::warning(this, tr("Could not load game!"), tr("Could not load game!") + "\n\n" + file.errorString()); + return; + } + + QDataStream dataStream{&file}; + dataStream >> project; + } + + m_ui->mdiArea->closeAllSubWindows(); + if (!m_ui->mdiArea->subWindowList().empty()) + return; + + if (m_unsavedChanges) + { + const auto result = QMessageBox::warning( + this, + tr("The Game has been modified."), + tr("Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, + QMessageBox::Save + ); + switch (result) + { + case QMessageBox::Save: + saveFile(); + if (m_unsavedChanges) + return; + else + break; + case QMessageBox::Discard: + break; + case QMessageBox::Cancel: + return; + default: + qWarning() << "unexpected dialog result" << result; + return; + } + } + + m_project = std::move(project); + m_projectTreeModel->setProject(&m_project); + + m_filePath = path; + m_unsavedChanges = false; + + updateTitle(); +} + +void MainWindow::saveFile() +{ + if (m_filePath.isEmpty()) + { + saveFileAs(); + return; + } + + { + QFile file{m_filePath}; + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + QMessageBox::warning(this, tr("Could not save game!"), tr("Could not save game!") + "\n\n" + file.errorString()); + saveFileAs(); + return; + } + + QDataStream dataStream{&file}; + dataStream << m_project; + } + + m_unsavedChanges = false; + + updateTitle(); +} + +void MainWindow::saveFileAs() +{ + auto path = QFileDialog::getSaveFileName(this, tr("Save the game"), "mygame.gmk", tr("GMK Files (*.gmk)")); + if (path.isEmpty()) + return; + + { + QFile file{path}; + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + QMessageBox::warning(this, tr("Could not save game!"), tr("Could not save game!") + "\n\n" + file.errorString()); + return; + } + + QDataStream dataStream{&file}; + dataStream << m_project; + } + + m_filePath = path; + m_unsavedChanges = false; + + updateTitle(); +} + +void MainWindow::createExecutable() +{ + QMessageBox::warning(this, tr("Not yet implemented"), tr("Not yet implemented")); +} + +void MainWindow::publishGame() +{ + QMessageBox::warning(this, tr("Not yet implemented"), tr("Not yet implemented")); +} + +void MainWindow::importResources() +{ + QMessageBox::warning(this, tr("Not yet implemented"), tr("Not yet implemented")); +} + +void MainWindow::exportResources() +{ + QMessageBox::warning(this, tr("Not yet implemented"), tr("Not yet implemented")); +} + +void MainWindow::preferences() +{ + PreferencesDialog{this}.exec(); +} + +void MainWindow::create() +{ + const auto index = m_ui->treeView->currentIndex(); + if (!index.isValid()) + return; + + if (index == m_projectTreeModel->spritesRoot()) + createSprite(); + else if (index == m_projectTreeModel->soundsRoot()) + createSound(); + else if (index == m_projectTreeModel->backgroundsRoot()) + createBackground(); + else if (index == m_projectTreeModel->pathsRoot()) + createPath(); + else if (index == m_projectTreeModel->scriptsRoot()) + createScript(); + else if (index == m_projectTreeModel->fontsRoot()) + createFont(); + else + { + switch (m_projectTreeModel->nodeType(index)) + { + case ProjectTreeModel::NodeType::Sprite: + case ProjectTreeModel::NodeType::Sound: + case ProjectTreeModel::NodeType::Background: + case ProjectTreeModel::NodeType::Path: + case ProjectTreeModel::NodeType::Script: + case ProjectTreeModel::NodeType::Font: + if (!m_projectTreeModel->insertRows(index.row(), 1, index.parent())) + QMessageBox::warning(this, tr("Inserting failed!"), tr("Inserting failed!")); + break; + } + } +} + +void MainWindow::duplicate() +{ + QMessageBox::warning(this, tr("Not yet implemented"), tr("Not yet implemented")); +} + +void MainWindow::createGroup() +{ + QMessageBox::warning(this, tr("Not yet implemented"), tr("Not yet implemented")); +} + +void MainWindow::sortByName() +{ + QMessageBox::warning(this, tr("Not yet implemented"), tr("Not yet implemented")); +} + +void MainWindow::delete_() +{ + const auto index = m_ui->treeView->currentIndex(); + if (!index.isValid()) + return; + if (!m_projectTreeModel->removeRow(index.row(), index.parent())) + QMessageBox::warning(this, tr("Deleting failed!"), tr("Deleting failed!")); +} + +void MainWindow::rename() +{ + m_ui->treeView->edit(m_ui->treeView->currentIndex()); +} + +void MainWindow::showProperties() +{ + const auto index = m_ui->treeView->currentIndex(); + if (!index.isValid()) + { + qWarning() << "unexpected invalid index"; + return; + } + + doubleClicked(index); +} + +void MainWindow::createSprite() +{ + if (!m_projectTreeModel->insertRows(m_project.sprites.size(), 1, m_projectTreeModel->spritesRoot())) + QMessageBox::warning(this, tr("Creating Sprite failed!"), tr("Creating Sprite failed!")); +} + +void MainWindow::createSound() +{ + if (!m_projectTreeModel->insertRows(m_project.sounds.size(), 1, m_projectTreeModel->soundsRoot())) + QMessageBox::warning(this, tr("Creating Sound failed!"), tr("Creating Sound failed!")); +} + +void MainWindow::createBackground() +{ + if (!m_projectTreeModel->insertRows(m_project.backgrounds.size(), 1, m_projectTreeModel->backgroundsRoot())) + QMessageBox::warning(this, tr("Creating Background failed!"), tr("Creating Background failed!")); +} + +void MainWindow::createPath() +{ + if (!m_projectTreeModel->insertRows(m_project.paths.size(), 1, m_projectTreeModel->pathsRoot())) + QMessageBox::warning(this, tr("Creating Path failed!"), tr("Creating Path failed!")); +} + +void MainWindow::createScript() +{ + if (!m_projectTreeModel->insertRows(m_project.scripts.size(), 1, m_projectTreeModel->scriptsRoot())) + QMessageBox::warning(this, tr("Creating Script failed!"), tr("Creating Script failed!")); +} + +void MainWindow::createFont() +{ + if (!m_projectTreeModel->insertRows(m_project.fonts.size(), 1, m_projectTreeModel->fontsRoot())) + QMessageBox::warning(this, tr("Creating Font failed!"), tr("Creating Font failed!")); +} + +void MainWindow::showGameInformation() +{ + GameInformationDialog{this}.exec(); +} + +void MainWindow::showGlobalGameSettings() +{ + GlobalGameSettingsDialog{this}.exec(); +} + +void MainWindow::showExtensionPackages() +{ + ExtensionPackagesDialog{this}.exec(); +} + +void MainWindow::about() +{ + QMessageBox::about(this, tr("About Qt Game Maker"), + tr("This About-Dialog is not finished.")); +} + +void MainWindow::rowsInserted(const QModelIndex &parent, int first, int last) +{ + m_ui->treeView->expand(parent); + const auto index = m_projectTreeModel->index(first, 0, parent); + m_ui->treeView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); + doubleClicked(index); +} + +void MainWindow::rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) +{ + if (parent == m_projectTreeModel->spritesRoot()) + { + for (int row = first; row <= last; row++) + { + if (const auto sprite = m_projectTreeModel->getSprite(m_projectTreeModel->index(row, 0, parent))) + { + if (const auto iter = m_spritePropertiesDialogs.find(sprite); iter != std::end(m_spritePropertiesDialogs)) + { + delete iter->second; + m_spritePropertiesDialogs.erase(iter); + } + } + } + } + else if (parent == m_projectTreeModel->soundsRoot()) + { + for (int row = first; row <= last; row++) + { + if (const auto sound = m_projectTreeModel->getSound(m_projectTreeModel->index(row, 0, parent))) + { + if (const auto iter = m_soundPropertiesDialogs.find(sound); iter != std::end(m_soundPropertiesDialogs)) + { + delete iter->second; + m_soundPropertiesDialogs.erase(iter); + } + } + } + } + else if (parent == m_projectTreeModel->backgroundsRoot()) + { + for (int row = first; row <= last; row++) + { + if (const auto background = m_projectTreeModel->getBackground(m_projectTreeModel->index(row, 0, parent))) + { + if (const auto iter = m_backgroundPropertiesDialogs.find(background); iter != std::end(m_backgroundPropertiesDialogs)) + { + delete iter->second; + m_backgroundPropertiesDialogs.erase(iter); + } + } + } + } + else if (parent == m_projectTreeModel->pathsRoot()) + { + for (int row = first; row <= last; row++) + { + if (const auto path = m_projectTreeModel->getPath(m_projectTreeModel->index(row, 0, parent))) + { + if (const auto iter = m_pathPropertiesDialogs.find(path); iter != std::end(m_pathPropertiesDialogs)) + { + delete iter->second; + m_pathPropertiesDialogs.erase(iter); + } + } + } + } + else if (parent == m_projectTreeModel->scriptsRoot()) + { + for (int row = first; row <= last; row++) + { + if (const auto script = m_projectTreeModel->getScript(m_projectTreeModel->index(row, 0, parent))) + { + if (const auto iter = m_scriptPropertiesDialogs.find(script); iter != std::end(m_scriptPropertiesDialogs)) + { + delete iter->second; + m_scriptPropertiesDialogs.erase(iter); + } + } + } + } + else if (parent == m_projectTreeModel->fontsRoot()) + { + for (int row = first; row <= last; row++) + { + if (const auto font = m_projectTreeModel->getFont(m_projectTreeModel->index(row, 0, parent))) + { + if (const auto iter = m_fontPropertiesDialogs.find(font); iter != std::end(m_fontPropertiesDialogs)) + { + delete iter->second; + m_fontPropertiesDialogs.erase(iter); + } + } + } + } +} + +void MainWindow::modelAboutToBeReset() +{ + for (const auto &pair : m_spritePropertiesDialogs) + delete pair.second; + m_spritePropertiesDialogs.clear(); + for (const auto &pair : m_soundPropertiesDialogs) + delete pair.second; + m_soundPropertiesDialogs.clear(); + for (const auto &pair : m_backgroundPropertiesDialogs) + delete pair.second; + m_backgroundPropertiesDialogs.clear(); + for (const auto &pair : m_pathPropertiesDialogs) + delete pair.second; + m_pathPropertiesDialogs.clear(); + for (const auto &pair : m_scriptPropertiesDialogs) + delete pair.second; + m_scriptPropertiesDialogs.clear(); + for (const auto &pair : m_fontPropertiesDialogs) + delete pair.second; + m_fontPropertiesDialogs.clear(); +} + +void MainWindow::updateTitle() +{ + setWindowTitle(tr("%0 - Qt Gamemaker 1.0 Ultimate%1") + .arg(m_filePath.isEmpty() ? "" : QFileInfo{m_filePath}.fileName()) + .arg(m_unsavedChanges ? tr("*") : QString{})); +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..bd21b2b --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#include +#include + +#include "projectcontainer.h" + +class QMdiSubWindow; +namespace Ui { class MainWindow; } +class ProjectTreeModel; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +protected: + void closeEvent(QCloseEvent *event) override; + +private slots: + void contextMenuRequested(const QPoint &pos); + void doubleClicked(const QModelIndex &index); + void selectionChanged(const QModelIndex &index); + + void newFile(); + void openFile(); + void saveFile(); + void saveFileAs(); + void createExecutable(); + void publishGame(); + void importResources(); + void exportResources(); + void preferences(); + void create(); + void duplicate(); + void createGroup(); + void sortByName(); + void delete_(); + void rename(); + void showProperties(); + void createSprite(); + void createSound(); + void createBackground(); + void createPath(); + void createScript(); + void createFont(); + void showGameInformation(); + void showGlobalGameSettings(); + void showExtensionPackages(); + void about(); + + void rowsInserted(const QModelIndex &parent, int first, int last); + void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); + void modelAboutToBeReset(); + +private: + void updateTitle(); + + const std::unique_ptr m_ui; + + ProjectContainer m_project; + + QString m_filePath; + bool m_unsavedChanges{}; + + const std::unique_ptr m_projectTreeModel; + + std::map m_spritePropertiesDialogs; + std::map m_soundPropertiesDialogs; + std::map m_backgroundPropertiesDialogs; + std::map m_pathPropertiesDialogs; + std::map m_scriptPropertiesDialogs; + std::map m_fontPropertiesDialogs; +}; diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..46000c1 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,967 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + Qt Gamemaker 1.0 Ultimate + + + + Qt::Horizontal + + + + Qt::CustomContextMenu + + + QAbstractItemView::EditKeyPressed + + + true + + + + 16 + 16 + + + + true + + + true + + + + + + + 159 + 159 + 159 + + + + + + + + + 0 + 0 + 800 + 20 + + + + + &File + + + + &Recent Files + + + + + + + + + + + + + + + + + + + + + + + + &Edit + + + + + + + + + + + + + + + + + + + + + &Resources + + + + + + + + + + + + + + + + + + + + + + &Scripts + + + + + + + + + + + + + + + + + + R&un + + + + + + + &Window + + + + + + + + + + + + + + + + + &Help + + + + &Tutorials + + + + + + &Extension Packages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + toolBar + + + false + + + + 16 + 16 + + + + TopToolBarArea + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + :/qtgameengine/icons/actions/new.png:/qtgameengine/icons/actions/new.png + + + &New + + + + + + :/qtgameengine/icons/actions/open.png:/qtgameengine/icons/actions/open.png + + + &Open + + + + + + :/qtgameengine/icons/actions/save.png:/qtgameengine/icons/actions/save.png + + + &Save + + + + + + :/qtgameengine/icons/actions/save-as.png:/qtgameengine/icons/actions/save-as.png + + + Save &As... + + + + + + :/qtgameengine/icons/actions/create-executable.png:/qtgameengine/icons/actions/create-executable.png + + + &Create Executable... + + + + + + :/qtgameengine/icons/actions/publish-game.png:/qtgameengine/icons/actions/publish-game.png + + + P&ublish your Game... + + + + + + :/qtgameengine/icons/actions/import-resources.png:/qtgameengine/icons/actions/import-resources.png + + + &Import Resources... + + + + + + :/qtgameengine/icons/actions/export-resources.png:/qtgameengine/icons/actions/export-resources.png + + + &Export Resources... + + + + + true + + + true + + + Ad&vanced Mode + + + + + + :/qtgameengine/icons/actions/preferences.png:/qtgameengine/icons/actions/preferences.png + + + &Preferences... + + + QAction::PreferencesRole + + + + + + :/qtgameengine/icons/actions/exit.png:/qtgameengine/icons/actions/exit.png + + + E&xit + + + QAction::QuitRole + + + + + false + + + (none) + + + + + + :/qtgameengine/icons/actions/create.png:/qtgameengine/icons/actions/create.png + + + &Create + + + + + + :/qtgameengine/icons/actions/duplicate.png:/qtgameengine/icons/actions/duplicate.png + + + D&uplicate + + + Alt+Ins + + + + + + :/qtgameengine/icons/actions/create-group.png:/qtgameengine/icons/actions/create-group.png + + + Cr&eate Group + + + + + &Sort by Name + + + + + + :/qtgameengine/icons/actions/delete.png:/qtgameengine/icons/actions/delete.png + + + &Delete + + + + + + :/qtgameengine/icons/actions/rename.png:/qtgameengine/icons/actions/rename.png + + + &Rename + + + + + + :/qtgameengine/icons/actions/properties.png:/qtgameengine/icons/actions/properties.png + + + &Properties + + + Alt+Return + + + + + + :/qtgameengine/icons/actions/find.png:/qtgameengine/icons/actions/find.png + + + &Find Resource + + + + + E&xpand Resource Tree + + + + + C&ollapse Resource Tree + + + + + &Show Object Information + + + + + + :/qtgameengine/icons/actions/sprite.png:/qtgameengine/icons/actions/sprite.png + + + Create &Sprite + + + Ctrl+Shift+S + + + + + + :/qtgameengine/icons/actions/sound.png:/qtgameengine/icons/actions/sound.png + + + Create So&und + + + Ctrl+Shift+U + + + + + + :/qtgameengine/icons/actions/background.png:/qtgameengine/icons/actions/background.png + + + Create &Background + + + Ctrl+Shift+B + + + + + + :/qtgameengine/icons/actions/path.png:/qtgameengine/icons/actions/path.png + + + Create &Path + + + Ctrl+Shift+P + + + + + + :/qtgameengine/icons/actions/script.png:/qtgameengine/icons/actions/script.png + + + Create S&cript + + + Ctrl+Shift+C + + + + + + :/qtgameengine/icons/actions/font.png:/qtgameengine/icons/actions/font.png + + + Create &Font + + + Ctrl+Shift+F + + + + + + :/qtgameengine/icons/actions/timeline.png:/qtgameengine/icons/actions/timeline.png + + + Create &Time Line + + + Ctrl+Shift+T + + + + + + :/qtgameengine/icons/actions/object.png:/qtgameengine/icons/actions/object.png + + + Create &Object + + + Ctrl+Shift+O + + + + + + :/qtgameengine/icons/actions/room.png:/qtgameengine/icons/actions/room.png + + + Create &Room + + + Ctrl+Shift+R + + + + + + :/qtgameengine/icons/actions/game-information.png:/qtgameengine/icons/actions/game-information.png + + + Change Game &Information + + + Ctrl+Shift+I + + + + + + :/qtgameengine/icons/actions/global-game-settings.png:/qtgameengine/icons/actions/global-game-settings.png + + + Change &Global Game Settings + + + Ctrl+Shift+G + + + + + + :/qtgameengine/icons/actions/extension-packages.png:/qtgameengine/icons/actions/extension-packages.png + + + Select &Extension Packages + + + Ctrl+Shift+E + + + + + Define Co&nstants... + + + Ctrl+Shift+N + + + + + &Define Triggers... + + + Ctrl+Shift+D + + + + + Inc&luded Files... + + + Ctrl+Shift+L + + + + + + :/qtgameengine/icons/actions/run.png:/qtgameengine/icons/actions/run.png + + + &Run normally + + + F5 + + + + + + :/qtgameengine/icons/actions/debug.png:/qtgameengine/icons/actions/debug.png + + + Run in &Debug mode + + + F6 + + + + + &Import Scripts... + + + + + + :/qtgameengine/icons/actions/help.png:/qtgameengine/icons/actions/help.png + + + &Contents + + + + + &News... + + + + + &Book... + + + + + &More Tutorials... + + + + + &Website... + + + + + &Forum... + + + + + W&iki... + + + + + &About Game Maker... + + + QAction::AboutRole + + + + + About &Qt... + + + QAction::AboutQtRole + + + + + &Export All Scripts... + + + + + Show Built-in &Variables + + + + + Show Build-in &Functions + + + + + S&how Extension Functions + + + + + Show &Constants + + + + + &Show Resource Names + + + + + Search i&n Scripts... + + + + + Check &Resource Names + + + + + Check &All Scripts + + + + + Cl&ose + + + + + Close &All + + + + + + :/qtgameengine/icons/actions/tile.png:/qtgameengine/icons/actions/tile.png + + + &Tile + + + + + + :/qtgameengine/icons/actions/cascade.png:/qtgameengine/icons/actions/cascade.png + + + &Cascade + + + + + Ne&xt + + + + + Pre&vious + + + + + true + + + Sub-Windowed view + + + + + true + + + Tabbed view + + + + + + + + + actionExit + triggered() + MainWindow + close() + + + -1 + -1 + + + 399 + 299 + + + + + actionExpandResourceTree + triggered() + treeView + expandAll() + + + -1 + -1 + + + 66 + 314 + + + + + actionCollapseResourceTree + triggered() + treeView + collapseAll() + + + -1 + -1 + + + 66 + 314 + + + + + actionClose + triggered() + mdiArea + closeActiveSubWindow() + + + -1 + -1 + + + 468 + 314 + + + + + actionCloseAll + triggered() + mdiArea + closeAllSubWindows() + + + -1 + -1 + + + 468 + 314 + + + + + actionTile + triggered() + mdiArea + tileSubWindows() + + + -1 + -1 + + + 468 + 314 + + + + + actionCascade + triggered() + mdiArea + cascadeSubWindows() + + + -1 + -1 + + + 468 + 314 + + + + + actionPrevious + triggered() + mdiArea + activatePreviousSubWindow() + + + -1 + -1 + + + 468 + 314 + + + + + actionNext + triggered() + mdiArea + activateNextSubWindow() + + + -1 + -1 + + + 468 + 314 + + + + + diff --git a/projdata/backgrounds/background0.png b/projdata/backgrounds/background0.png new file mode 100644 index 0000000..6a036b2 Binary files /dev/null and b/projdata/backgrounds/background0.png differ diff --git a/projdata/sounds/background_music.mid b/projdata/sounds/background_music.mid new file mode 100644 index 0000000..5e3a30b Binary files /dev/null and b/projdata/sounds/background_music.mid differ diff --git a/projdata/sounds/collision.wav b/projdata/sounds/collision.wav new file mode 100644 index 0000000..ef3fc7e Binary files /dev/null and b/projdata/sounds/collision.wav differ diff --git a/projdata/sounds/gas_sound.wav b/projdata/sounds/gas_sound.wav new file mode 100644 index 0000000..7b3696f Binary files /dev/null and b/projdata/sounds/gas_sound.wav differ diff --git a/projdata/sounds/horn.wav b/projdata/sounds/horn.wav new file mode 100644 index 0000000..37ced8b Binary files /dev/null and b/projdata/sounds/horn.wav differ diff --git a/projdata/sounds/sirens.wav b/projdata/sounds/sirens.wav new file mode 100644 index 0000000..e9629d0 Binary files /dev/null and b/projdata/sounds/sirens.wav differ diff --git a/projdata/sprites/apple.png b/projdata/sprites/apple.png new file mode 100644 index 0000000..9278ec2 Binary files /dev/null and b/projdata/sprites/apple.png differ diff --git a/projdata/sprites/banana.png b/projdata/sprites/banana.png new file mode 100644 index 0000000..1c4cd06 Binary files /dev/null and b/projdata/sprites/banana.png differ diff --git a/projdata/sprites/bomb.png b/projdata/sprites/bomb.png new file mode 100644 index 0000000..3ff5666 Binary files /dev/null and b/projdata/sprites/bomb.png differ diff --git a/projdata/sprites/cherry.png b/projdata/sprites/cherry.png new file mode 100644 index 0000000..ff7b397 Binary files /dev/null and b/projdata/sprites/cherry.png differ diff --git a/projdata/sprites/strawberry.png b/projdata/sprites/strawberry.png new file mode 100644 index 0000000..2d07aca Binary files /dev/null and b/projdata/sprites/strawberry.png differ diff --git a/projdata/sprites/wall.png b/projdata/sprites/wall.png new file mode 100644 index 0000000..81cf74e Binary files /dev/null and b/projdata/sprites/wall.png differ diff --git a/projectcontainer.cpp b/projectcontainer.cpp new file mode 100644 index 0000000..27703ae --- /dev/null +++ b/projectcontainer.cpp @@ -0,0 +1,237 @@ +#include "projectcontainer.h" + +QDataStream &operator<<(QDataStream &ds, const Sprite &sprite) +{ + ds << sprite.name; + { + int pixmaps = sprite.pixmaps.size(); + ds << pixmaps; + } + for (const auto &pixmap : sprite.pixmaps) + ds << pixmap; + ds << sprite.origin.x; + ds << sprite.origin.y; + ds << sprite.preciseCollisionChecking; + ds << sprite.separateCollisionMasks; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, Sprite &sprite) +{ + ds >> sprite.name; + { + int pixmaps; + ds >> pixmaps; + + for (int i = 0; i < pixmaps; i++) + { + QPixmap pixmap; + ds >> pixmap; + sprite.pixmaps.emplace_back(std::move(pixmap)); + } + } + ds >> sprite.origin.x; + ds >> sprite.origin.y; + ds >> sprite.preciseCollisionChecking; + ds >> sprite.separateCollisionMasks; + return ds; +} + +QDataStream &operator<<(QDataStream &ds, const Sound &sound) +{ + ds << sound.name; + ds << sound.type; + ds << sound.path; + ds << sound.effects.chorus; + ds << sound.effects.flanger; + ds << sound.effects.gargle; + ds << sound.effects.echo; + ds << sound.effects.reverb; + ds << sound.volume; + ds << sound.pan; + ds << sound.preload; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, Sound &sound) +{ + ds >> sound.name; + ds >> sound.type; + ds >> sound.path; + ds >> sound.effects.chorus; + ds >> sound.effects.flanger; + ds >> sound.effects.gargle; + ds >> sound.effects.echo; + ds >> sound.effects.reverb; + ds >> sound.volume; + ds >> sound.pan; + ds >> sound.preload; + return ds; +} + +QDataStream &operator<<(QDataStream &ds, const Background &background) +{ + ds << background.name; + ds << background.pixmap; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, Background &background) +{ + ds >> background.name; + ds >> background.pixmap; + return ds; +} + +QDataStream &operator<<(QDataStream &ds, const Path &path) +{ + ds << path.name; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, Path &path) +{ + ds >> path.name; + return ds; +} + +QDataStream &operator<<(QDataStream &ds, const Script &script) +{ + ds << script.name; + ds << script.script; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, Script &script) +{ + ds >> script.name; + ds >> script.script; + return ds; +} + +QDataStream &operator<<(QDataStream &ds, const Font &font) +{ + ds << font.name; + ds << font.font; + return ds; +} + +QDataStream &operator>>(QDataStream &ds, Font &font) +{ + ds >> font.name; + ds >> font.font; + return ds; +} + +QDataStream &operator<<(QDataStream &ds, const ProjectContainer &project) +{ + { + int sprites = project.sprites.size(); + ds << sprites; + for (const auto &sprite : project.sprites) + ds << sprite; + } + { + int sounds = project.sounds.size(); + ds << sounds; + for (const auto &sound : project.sounds) + ds << sound; + } + { + int backgrounds = project.backgrounds.size(); + ds << backgrounds; + for (const auto &background : project.backgrounds) + ds << background; + } + { + int paths = project.paths.size(); + ds << paths; + for (const auto &path : project.paths) + ds << path; + } + { + int scripts = project.scripts.size(); + ds << scripts; + for (const auto &script : project.scripts) + ds << script; + } + { + int fonts = project.fonts.size(); + ds << fonts; + for (const auto &font : project.fonts) + ds << font; + } + return ds; +} + +QDataStream &operator>>(QDataStream &ds, ProjectContainer &project) +{ + { + int sprites; + ds >> sprites; + + for (int i = 0; i < sprites; i++) + { + Sprite sprite; + ds >> sprite; + project.sprites.emplace_back(std::move(sprite)); + } + } + { + int sounds; + ds >> sounds; + + for (int i = 0; i < sounds; i++) + { + Sound sound; + ds >> sound; + project.sounds.emplace_back(std::move(sound)); + } + } + { + int backgrounds; + ds >> backgrounds; + + for (int i = 0; i < backgrounds; i++) + { + Background background; + ds >> background; + project.backgrounds.emplace_back(std::move(background)); + } + } + { + int paths; + ds >> paths; + + for (int i = 0; i < paths; i++) + { + Path path; + ds >> path; + project.paths.emplace_back(std::move(path)); + } + } + { + int scripts; + ds >> scripts; + + for (int i = 0; i < scripts; i++) + { + Script script; + ds >> script; + project.scripts.emplace_back(std::move(script)); + } + } + { + int fonts; + ds >> fonts; + + for (int i = 0; i < fonts; i++) + { + Font font; + ds >> font; + project.fonts.emplace_back(std::move(font)); + } + } + return ds; +} + diff --git a/projectcontainer.h b/projectcontainer.h new file mode 100644 index 0000000..a17671b --- /dev/null +++ b/projectcontainer.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +struct Sprite +{ + QString name; + std::vector pixmaps; + struct { + int x{}; + int y{}; + } origin; + bool preciseCollisionChecking{true}; + bool separateCollisionMasks{true}; +}; + +struct Sound +{ + QString name; + enum class Type { Sound, Music }; + Type type; + QString path; + struct { + bool chorus{}; + bool flanger{}; + bool gargle{}; + bool echo{}; + bool reverb{}; + } effects; + + int volume{100}; + int pan{}; + bool preload{true}; +}; + +struct Background +{ + QString name; + QPixmap pixmap; +}; + +struct Path +{ + QString name; +}; + +struct Script +{ + QString name; + QString script; +}; + +struct Font +{ + QString name; + QFont font; + struct { + int from{32}; + int to{127}; + } range; +}; + +struct ProjectContainer +{ + std::list sprites; + std::list sounds; + std::list backgrounds; + std::list paths; + std::list