From 4f8224359e5eec405f62b5c35a71807c5f96ce90 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 14:34:10 -0500 Subject: [PATCH] Use collapsible section for header data in new map dialog --- forms/newmapdialog.ui | 286 ++++++++++++++++--------------- include/config.h | 2 + include/lib/collapsiblesection.h | 36 ++-- include/ui/newmapdialog.h | 4 +- src/config.cpp | 3 + src/lib/collapsiblesection.cpp | 104 +++++++---- src/mainwindow.cpp | 36 ++-- src/ui/newmapdialog.cpp | 12 +- 8 files changed, 273 insertions(+), 210 deletions(-) diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index dcf4f052..3ddba1dc 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -25,7 +25,7 @@ 0 0 427 - 526 + 520 @@ -39,24 +39,23 @@ - - + + + + false + + + color: rgb(255, 0, 0) + - ID + - - - - - - <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> - - + true - + false @@ -72,7 +71,7 @@ - + Border Dimensions @@ -127,115 +126,14 @@ - - + + - Group + ID - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - - - Can Fly To - - - - - - - Tilesets - - - - - - Primary - - - - - - - <html><head/><body><p>The primary tileset for the new map.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - Secondary - - - - - - - <html><head/><body><p>The secondary tileset for the new map.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - - - + Map Dimensions @@ -306,14 +204,102 @@ - + + + + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + + true + + + + + + + Tilesets + + + + + + Primary + + + + + + + <html><head/><body><p>The primary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Secondary + + + + + + + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + false @@ -329,28 +315,45 @@ - - - - Header Data + + + + Qt::Orientation::Vertical - - + + + 20 + 40 + + + - - - - <html><head/><body><p>The name of the group this map will be added to.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert + + + + Group - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> @@ -360,6 +363,13 @@ + + + + Can Fly To + + + diff --git a/include/config.h b/include/config.h index 2a93a793..0dcbfe66 100644 --- a/include/config.h +++ b/include/config.h @@ -70,6 +70,7 @@ public: this->showTilesetEditorLayerGrid = true; this->monitorFiles = true; this->tilesetCheckerboardFill = true; + this->newMapHeaderSectionExpanded = false; this->theme = "default"; this->wildMonChartTheme = ""; this->textEditorOpenFolder = ""; @@ -121,6 +122,7 @@ public: bool showTilesetEditorLayerGrid; bool monitorFiles; bool tilesetCheckerboardFill; + bool newMapHeaderSectionExpanded; QString theme; QString wildMonChartTheme; QString textEditorOpenFolder; diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h index 3a1b3023..584e44cb 100644 --- a/include/lib/collapsiblesection.h +++ b/include/lib/collapsiblesection.h @@ -16,6 +16,10 @@ You should have received a copy of the GNU General Public License along with Elypson/qt-collapsible-section. If not, see . + + + PORYMAP NOTE: Modified to support having the section expanded by default, to stop the contents + squashing during the collapse animation, and to add some guard rails against crashes. */ #ifndef COLLAPSIBLESECTION_H @@ -24,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -31,32 +36,33 @@ class CollapsibleSection : public QWidget { Q_OBJECT + +public: + explicit CollapsibleSection(const QString& title = "", const bool expanded = false, const int animationDuration = 0, QWidget* parent = 0); + + void setContentLayout(QLayout* contentLayout); + void setTitle(QString title); + bool isExpanded() const { return this->expanded; } + +public slots: + void toggle(bool collapsed); private: QGridLayout* mainLayout; QToolButton* toggleButton; QFrame* headerLine; QParallelAnimationGroup* toggleAnimation; + QSet sectionAnimations; + QPropertyAnimation* contentAnimation; QScrollArea* contentArea; int animationDuration; int collapsedHeight; - bool isExpanded = false; - -public slots: - void toggle(bool collapsed); + bool expanded; -public: - // initialize section - explicit CollapsibleSection(const QString& title = "", const int animationDuration = 0, QWidget* parent = 0); + void updateToggleButton(); + void updateAnimationTargets(); + int getContentHeight() const; - // set layout of content - void setContentLayout(QLayout& contentLayout); - - // set title - void setTitle(QString title); - - // update animations and their heights - void updateHeights(); }; #endif // COLLAPSIBLESECTION_H diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 090fcb96..72b0b5de 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -7,6 +7,7 @@ #include "project.h" #include "map.h" #include "mapheaderform.h" +#include "lib/collapsiblesection.h" namespace Ui { class NewMapDialog; @@ -24,7 +25,7 @@ public: bool importedMap; QString layoutId; void init(); - //void initUi(); + //void initUi();//TODO void init(int tabIndex, QString data); void init(Layout *); static void setDefaultSettings(Project *project); @@ -35,6 +36,7 @@ signals: private: Ui::NewMapDialog *ui; Project *project; + CollapsibleSection *headerSection; MapHeaderForm *headerData; bool validateMapDimensions(); diff --git a/src/config.cpp b/src/config.cpp index a1987203..316c9ea3 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -371,6 +371,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->monitorFiles = getConfigBool(key, value); } else if (key == "tileset_checkerboard_fill") { this->tilesetCheckerboardFill = getConfigBool(key, value); + } else if (key == "new_map_header_section_expanded") { + this->newMapHeaderSectionExpanded = getConfigBool(key, value); } else if (key == "theme") { this->theme = value; } else if (key == "wild_mon_chart_theme") { @@ -453,6 +455,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("show_tileset_editor_layer_grid", this->showTilesetEditorLayerGrid ? "1" : "0"); map.insert("monitor_files", this->monitorFiles ? "1" : "0"); map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0"); + map.insert("new_map_header_section_expanded", this->newMapHeaderSectionExpanded ? "1" : "0"); map.insert("theme", this->theme); map.insert("wild_mon_chart_theme", this->wildMonChartTheme); map.insert("text_editor_open_directory", this->textEditorOpenFolder); diff --git a/src/lib/collapsiblesection.cpp b/src/lib/collapsiblesection.cpp index 000e822e..34dfb780 100644 --- a/src/lib/collapsiblesection.cpp +++ b/src/lib/collapsiblesection.cpp @@ -16,13 +16,15 @@ You should have received a copy of the GNU General Public License along with Elypson/qt-collapsible-section. If not, see . + + + PORYMAP NOTE: Modified to support having the section expanded by default, to stop the contents + squashing during the collapse animation, and to add some guard rails against crashes. */ -#include - #include "collapsiblesection.h" -CollapsibleSection::CollapsibleSection(const QString& title, const int animationDuration, QWidget* parent) - : QWidget(parent), animationDuration(animationDuration) +CollapsibleSection::CollapsibleSection(const QString& title, const bool expanded, const int animationDuration, QWidget* parent) + : QWidget(parent), animationDuration(animationDuration), expanded(expanded) { toggleButton = new QToolButton(this); headerLine = new QFrame(this); @@ -32,25 +34,24 @@ CollapsibleSection::CollapsibleSection(const QString& title, const int animation toggleButton->setStyleSheet("QToolButton {border: none;}"); toggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - toggleButton->setArrowType(Qt::ArrowType::RightArrow); toggleButton->setText(title); toggleButton->setCheckable(true); - toggleButton->setChecked(false); + updateToggleButton(); headerLine->setFrameShape(QFrame::HLine); headerLine->setFrameShadow(QFrame::Sunken); headerLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - // start out collapsed contentArea->setMaximumHeight(0); contentArea->setMinimumHeight(0); - // let the entire widget grow and shrink with its content - toggleAnimation->addAnimation(new QPropertyAnimation(this, "maximumHeight")); - toggleAnimation->addAnimation(new QPropertyAnimation(this, "minimumHeight")); - toggleAnimation->addAnimation(new QPropertyAnimation(contentArea, "maximumHeight")); + sectionAnimations.insert(new QPropertyAnimation(this, "minimumHeight")); + sectionAnimations.insert(new QPropertyAnimation(this, "maximumHeight")); + for (const auto &anim : sectionAnimations) + toggleAnimation->addAnimation(anim); + contentAnimation = new QPropertyAnimation(contentArea, "maximumHeight"); + toggleAnimation->addAnimation(contentAnimation); mainLayout->setVerticalSpacing(0); mainLayout->setContentsMargins(0, 0, 0, 0); @@ -64,22 +65,58 @@ CollapsibleSection::CollapsibleSection(const QString& title, const int animation connect(toggleButton, &QToolButton::toggled, this, &CollapsibleSection::toggle); } -void CollapsibleSection::toggle(bool expanded) +void CollapsibleSection::updateToggleButton() { - toggleButton->setArrowType(expanded ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); - toggleAnimation->setDirection(expanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); - toggleAnimation->start(); - - this->isExpanded = expanded; + toggleButton->setChecked(this->expanded); + toggleButton->setArrowType(this->expanded ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); } -void CollapsibleSection::setContentLayout(QLayout& contentLayout) +void CollapsibleSection::toggle(bool expand) { + if (toggleAnimation->state() != QAbstractAnimation::Stopped) + return; + if (this->expanded == expand) + return; + this->expanded = expand; + + updateToggleButton(); + + if (expand) { + // Opening animation. Set the contents to their maximum size immediately, + // and they will be revealed slowly by the section animation. + int contentHeight = getContentHeight(); + contentArea->setMinimumHeight(contentHeight); + contentArea->setMaximumHeight(contentHeight); + toggleAnimation->setDirection(QAbstractAnimation::Forward); + } else { + // Closing animation. Keep the contents at their current size, allowing + // them to be hidden slowly by the section animation, then change their size + // once the animation is complete so they aren't visible just below the title. + auto ctx = new QObject(); + connect(toggleAnimation, &QAbstractAnimation::finished, ctx, [this, ctx]() { + // This is a single-shot connection. Qt6 has built-in support for this kind of thing. + contentArea->setMinimumHeight(0); + contentArea->setMaximumHeight(0); + ctx->deleteLater(); + }); + toggleAnimation->setDirection(QAbstractAnimation::Backward); + } + toggleAnimation->start(); +} + +void CollapsibleSection::setContentLayout(QLayout* contentLayout) +{ + if (contentArea->layout() == contentLayout) + return; delete contentArea->layout(); - contentArea->setLayout(&contentLayout); + contentArea->setLayout(contentLayout); collapsedHeight = sizeHint().height() - contentArea->maximumHeight(); - - updateHeights(); + + int contentHeight = this->expanded ? getContentHeight() : 0; + contentArea->setMinimumHeight(contentHeight); + contentArea->setMaximumHeight(contentHeight); + + updateAnimationTargets(); } void CollapsibleSection::setTitle(QString title) @@ -87,23 +124,20 @@ void CollapsibleSection::setTitle(QString title) toggleButton->setText(std::move(title)); } -void CollapsibleSection::updateHeights() +int CollapsibleSection::getContentHeight() const { - int contentHeight = contentArea->layout()->sizeHint().height(); + return contentArea->layout() ? contentArea->layout()->sizeHint().height() : 0; +} - for (int i = 0; i < toggleAnimation->animationCount() - 1; ++i) - { - QPropertyAnimation* SectionAnimation = static_cast(toggleAnimation->animationAt(i)); - SectionAnimation->setDuration(animationDuration); - SectionAnimation->setStartValue(collapsedHeight); - SectionAnimation->setEndValue(collapsedHeight + contentHeight); +void CollapsibleSection::updateAnimationTargets() +{ + const int contentHeight = getContentHeight(); + for (auto anim : sectionAnimations) { + anim->setDuration(animationDuration); + anim->setStartValue(collapsedHeight); + anim->setEndValue(collapsedHeight + contentHeight); } - - QPropertyAnimation* contentAnimation = static_cast(toggleAnimation->animationAt(toggleAnimation->animationCount() - 1)); contentAnimation->setDuration(animationDuration); contentAnimation->setStartValue(0); contentAnimation->setEndValue(contentHeight); - - toggleAnimation->setDirection(isExpanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); - toggleAnimation->start(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d47dfef2..50c4b4d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -86,11 +86,27 @@ MainWindow::MainWindow(QWidget *parent) : MainWindow::~MainWindow() { + // Some config settings are updated as subwindows are destroyed (e.g. their geometry), + // so we need to ensure that the configs are saved after this happens. + saveGlobalConfigs(); + delete label_MapRulerStatus; delete editor; delete ui; } +void MainWindow::saveGlobalConfigs() { + porymapConfig.setMainGeometry( + this->saveGeometry(), + this->saveState(), + this->ui->splitter_map->saveState(), + this->ui->splitter_main->saveState(), + this->ui->splitter_Metatiles->saveState() + ); + porymapConfig.save(); + shortcutsConfig.save(); +} + void MainWindow::setWindowDisabled(bool disabled) { for (auto action : findChildren()) action->setDisabled(disabled); @@ -1739,12 +1755,14 @@ void MainWindow::on_action_Save_Project_triggered() { editor->saveProject(); updateWindowTitle(); updateMapList(); + saveGlobalConfigs(); } void MainWindow::on_action_Save_triggered() { editor->save(); updateWindowTitle(); updateMapList(); + saveGlobalConfigs(); } void MainWindow::duplicate() { @@ -3282,24 +3300,9 @@ bool MainWindow::closeProject() { return true; } -void MainWindow::saveGlobalConfigs() { - porymapConfig.setMainGeometry( - this->saveGeometry(), - this->saveState(), - this->ui->splitter_map->saveState(), - this->ui->splitter_main->saveState(), - this->ui->splitter_Metatiles->saveState() - ); - porymapConfig.save(); - shortcutsConfig.save(); -} - void MainWindow::on_action_Exit_triggered() { if (!closeProject()) return; - - saveGlobalConfigs(); - QApplication::quit(); } @@ -3308,8 +3311,5 @@ void MainWindow::closeEvent(QCloseEvent *event) { event->ignore(); return; } - - saveGlobalConfigs(); - QMainWindow::closeEvent(event); } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index d3096a86..ff4cb209 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -8,8 +8,6 @@ #include #include -// TODO: Make ui->groupBox_HeaderData collapsible - const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; struct NewMapDialog::Settings NewMapDialog::settings = {}; @@ -31,8 +29,15 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : ui->lineEdit_Name->setValidator(validator); ui->lineEdit_ID->setValidator(validator); + // Create a collapsible section that has all the map header data. this->headerData = new MapHeaderForm(); - ui->layout_HeaderData->addWidget(this->headerData); + auto sectionLayout = new QVBoxLayout(); + sectionLayout->addWidget(this->headerData); + + this->headerSection = new CollapsibleSection("Header Data", porymapConfig.newMapHeaderSectionExpanded, 150, this); + this->headerSection->setContentLayout(sectionLayout); + ui->layout_HeaderData->addWidget(this->headerSection); + ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); @@ -162,6 +167,7 @@ void NewMapDialog::saveSettings() { settings.allowEscaping = this->headerData->ui->checkBox_AllowEscaping->isChecked(); settings.floorNumber = this->headerData->ui->spinBox_FloorNumber->value(); settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } void NewMapDialog::useLayoutSettings(Layout *layout) {