Fix some issues with settings, canvas size, connections rendering

This commit is contained in:
GriffinR 2025-03-28 16:25:18 -04:00
parent 243a6064c2
commit cf51ca1fc4
5 changed files with 190 additions and 60 deletions

View File

@ -25,6 +25,9 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::FocusPolicy::ClickFocus</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_Options">
<item>
<layout class="QFormLayout" name="formLayout">
@ -166,7 +169,7 @@
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="checkBox_Elevation">
<widget class="QCheckBox" name="checkBox_Collision">
<property name="text">
<string>Collision</string>
</property>
@ -191,7 +194,10 @@
</property>
<layout class="QGridLayout" name="gridLayout_7">
<item row="2" column="1">
<widget class="QSpinBox" name="spinBox_TimelapseDelay">
<widget class="NoScrollSpinBox" name="spinBox_TimelapseDelay">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="specialValueText">
<string/>
</property>
@ -217,7 +223,10 @@
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="spinBox_FrameSkip">
<widget class="NoScrollSpinBox" name="spinBox_FrameSkip">
<property name="focusPolicy">
<enum>Qt::FocusPolicy::StrongFocus</enum>
</property>
<property name="suffix">
<string/>
</property>
@ -253,9 +262,22 @@
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkBox_ActualSize">
<widget class="QCheckBox" name="checkBox_DisablePreviewUpdates">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the image in the preview window will not be recreated when the settings are changed.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Preview actual size</string>
<string>Disable preview updates</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBox_DisablePreviewScaling">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the image shown in the preview window will not scale to fit into the available space. The aspect ratio of the image will never change.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Disable preview scaling</string>
</property>
</widget>
</item>
@ -266,6 +288,9 @@
<property name="text">
<string>Reset</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@ -286,6 +311,9 @@
<property name="text">
<string>Cancel</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@ -293,6 +321,9 @@
<property name="text">
<string>Save</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
@ -407,6 +438,11 @@
<extends>QComboBox</extends>
<header>noscrollcombobox.h</header>
</customwidget>
<customwidget>
<class>NoScrollSpinBox</class>
<extends>QSpinBox</extends>
<header>noscrollspinbox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@ -47,6 +47,13 @@ enum CommandId {
#define IDMask_EventType_Trigger (1 << 11)
#define IDMask_EventType_Heal (1 << 12)
#define IDMask_ConnectionDirection_Up (1 << 8)
#define IDMask_ConnectionDirection_Down (1 << 9)
#define IDMask_ConnectionDirection_Left (1 << 10)
#define IDMask_ConnectionDirection_Right (1 << 11)
#define IDMask_ConnectionDirection_Dive (1 << 12)
#define IDMask_ConnectionDirection_Emerge (1 << 13)
/// Implements a command to commit metatile paint actions
/// onto the map using the pencil tool.
class PaintMetatile : public QUndoCommand {
@ -400,7 +407,7 @@ public:
void redo() override;
bool mergeWith(const QUndoCommand *command) override;
int id() const override { return CommandId::ID_MapConnectionMove; }
int id() const override;
private:
MapConnection *connection;
@ -421,7 +428,7 @@ public:
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionChangeDirection; }
int id() const override;
private:
QPointer<MapConnection> connection;
@ -443,7 +450,7 @@ public:
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionChangeMap; }
int id() const override;
private:
QPointer<MapConnection> connection;
@ -465,7 +472,7 @@ public:
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionAdd; }
int id() const override;
private:
Map *map = nullptr;
@ -485,7 +492,7 @@ public:
void undo() override;
void redo() override;
int id() const override { return CommandId::ID_MapConnectionRemove; }
int id() const override;
private:
Map *map = nullptr;

View File

@ -21,10 +21,12 @@ struct ImageExporterSettings {
bool showGrid = false;
bool showBorder = false;
bool showCollision = false;
bool previewActualSize = false;
bool disablePreviewScaling = false;
bool disablePreviewUpdates = false;
int timelapseSkipAmount = 1;
int timelapseDelayMs = 200;
QColor fillColor = Qt::transparent; // Not exposed as a setting in the UI atm.
// Not exposed as a setting in the UI atm (our color input widget has no alpha channel).
QColor fillColor = Qt::transparent;
};
class MapImageExporter : public QDialog
@ -63,9 +65,10 @@ private:
void setModeSpecificUi();
void setSelectionText(const QString &text);
void updateMapSelection();
void resetSettings();
QString getTitle(ImageExporterMode mode);
QString getDescription(ImageExporterMode mode);
void updatePreview();
void updatePreview(bool forceUpdate = false);
void scalePreview();
bool eventsEnabled();
void setEventGroupEnabled(Event::Group group, bool enable);
@ -81,7 +84,7 @@ private:
void paintEvents(QPainter *painter, const Map *map);
void paintGrid(QPainter *painter, const Layout *layout = nullptr);
QMargins getMargins(const Map *map);
QPixmap getExpandedPixmap(const QPixmap &pixmap, const QSize &minSize, const QColor &fillColor);
QPixmap getExpandedPixmap(const QPixmap &pixmap, const QSize &targetSize, const QColor &fillColor);
bool currentHistoryAppliesToFrame(QUndoStack *historyStack);
protected:
@ -102,15 +105,16 @@ private slots:
void on_checkBox_ConnectionRight_stateChanged(int state);
void on_checkBox_AllConnections_stateChanged(int state);
void on_checkBox_Elevation_stateChanged(int state);
void on_checkBox_Collision_stateChanged(int state);
void on_checkBox_Grid_stateChanged(int state);
void on_checkBox_Border_stateChanged(int state);
void on_pushButton_Reset_pressed();
void on_spinBox_TimelapseDelay_valueChanged(int delayMs);
void on_spinBox_FrameSkip_valueChanged(int skip);
void on_spinBox_TimelapseDelay_editingFinished();
void on_spinBox_FrameSkip_editingFinished();
void on_checkBox_ActualSize_stateChanged(int state);
void on_checkBox_DisablePreviewScaling_stateChanged(int state);
void on_checkBox_DisablePreviewUpdates_stateChanged(int state);
};
#endif // MAPIMAGEEXPORTER_H

View File

@ -5,7 +5,7 @@
#include <QDebug>
int getEventTypeMask(QList<Event *> events) {
int getEventTypeMask(const QList<Event *> &events) {
int eventTypeMask = 0;
for (auto event : events) {
Event::Group groupType = event->getEventGroup();
@ -24,6 +24,26 @@ int getEventTypeMask(QList<Event *> events) {
return eventTypeMask;
}
int getConnectionDirectionMask(const QList<QString> &directions) {
int mask = 0;
for (auto direction : directions) {
if (direction == "up") {
mask |= IDMask_ConnectionDirection_Up;
} else if (direction == "down") {
mask |= IDMask_ConnectionDirection_Down;
} else if (direction == "left") {
mask |= IDMask_ConnectionDirection_Left;
} else if (direction == "right") {
mask |= IDMask_ConnectionDirection_Right;
} else if (direction == "dive") {
mask |= IDMask_ConnectionDirection_Dive;
} else if (direction == "emerge") {
mask |= IDMask_ConnectionDirection_Emerge;
}
}
return mask;
}
void renderBlocks(Layout *layout, bool ignoreCache = false) {
layout->layoutItem->draw(ignoreCache);
layout->collisionItem->draw(ignoreCache);
@ -587,6 +607,10 @@ bool MapConnectionMove::mergeWith(const QUndoCommand *command) {
return true;
}
int MapConnectionMove::id() const {
return CommandId::ID_MapConnectionMove | getConnectionDirectionMask({this->connection->direction()});
}
/******************************************************************************
************************************************************************
******************************************************************************/
@ -629,6 +653,10 @@ void MapConnectionChangeDirection::undo() {
QUndoCommand::undo();
}
int MapConnectionChangeDirection::id() const {
return CommandId::ID_MapConnectionChangeDirection | getConnectionDirectionMask({this->oldDirection, this->newDirection});
}
/******************************************************************************
************************************************************************
******************************************************************************/
@ -664,6 +692,10 @@ void MapConnectionChangeMap::undo() {
QUndoCommand::undo();
}
int MapConnectionChangeMap::id() const {
return CommandId::ID_MapConnectionChangeMap | getConnectionDirectionMask({this->connection->direction()});
}
/******************************************************************************
************************************************************************
******************************************************************************/
@ -708,6 +740,10 @@ void MapConnectionAdd::undo() {
QUndoCommand::undo();
}
int MapConnectionAdd::id() const {
return CommandId::ID_MapConnectionAdd | getConnectionDirectionMask({this->connection->direction()});
}
/******************************************************************************
************************************************************************
******************************************************************************/
@ -745,3 +781,7 @@ void MapConnectionRemove::undo() {
QUndoCommand::undo();
}
int MapConnectionRemove::id() const {
return CommandId::ID_MapConnectionRemove | getConnectionDirectionMask({this->connection->direction()});
}

View File

@ -96,22 +96,26 @@ void MapImageExporter::setModeSpecificUi() {
}
if (m_mode == ImageExporterMode::Timelapse) {
// At the moment edit history for events (and the DraggablePixmapItem class)
// depend on the editor and assume their map is the current map.
// Until this is resolved, the selected map and the editor's map must remain the same.
// TODO: At the moment edit history for events (and the DraggablePixmapItem class)
// explicitly depend on the editor and assume their map is currently open.
// Other edit commands rely on this more subtly, like triggering API callbacks or
// spending time rendering their layout (which can make creating timelapses very slow).
// Until this is resolved, the selected map/layout must remain the same as in the editor.
// We enforce this here by disabling the selector, and in MainWindow by programmatically
// changing the exporter's map/layout selection if the user opens a new one in the editor.
ui->comboBox_MapSelection->setEnabled(false);
ui->label_MapSelection->setEnabled(false);
// Timelapse gif has artifacts with transparency, make sure it's disabled.
m_settings.fillColor.setAlpha(255);
}
// Update for any mode-specific default settings
resetSettings();
}
// Allow the window to open before displaying the preview.
void MapImageExporter::showEvent(QShowEvent *event) {
QWidget::showEvent(event);
if (!event->spontaneous())
QTimer::singleShot(0, this, &MapImageExporter::updatePreview);
QTimer::singleShot(0, this, [this](){ updatePreview(); });
}
void MapImageExporter::resizeEvent(QResizeEvent *event) {
@ -166,12 +170,12 @@ void MapImageExporter::updateMapSelection() {
}
void MapImageExporter::saveImage() {
// If the preview is empty it's because progress was canceled.
// Try again to create it, and if it's canceled again we'll stop the export.
if (m_preview->pixmap().isNull()) {
updatePreview();
// If the preview is empty (because progress was canceled) or if updates were disabled
// then we should ensure the image in the preview is up-to-date before exporting.
if (m_preview->pixmap().isNull() || m_settings.disablePreviewUpdates) {
updatePreview(true);
if (m_preview->pixmap().isNull())
return;
return; // Canceled
}
if (m_mode == ImageExporterMode::Timelapse && !m_timelapseGifImage) {
// Shouldn't happen. We have a preview for the timelapse, but no timelapse image.
@ -237,8 +241,15 @@ bool MapImageExporter::currentHistoryAppliesToFrame(QUndoStack *historyStack) {
case CommandId::ID_MapConnectionChangeDirection:
case CommandId::ID_MapConnectionChangeMap:
case CommandId::ID_MapConnectionAdd:
case CommandId::ID_MapConnectionRemove:
return connectionsEnabled();
case CommandId::ID_MapConnectionRemove: {
if (!connectionsEnabled())
return false;
if (command->id() & IDMask_ConnectionDirection_Up) return m_settings.showConnections.contains("up");
if (command->id() & IDMask_ConnectionDirection_Down) return m_settings.showConnections.contains("down");
if (command->id() & IDMask_ConnectionDirection_Left) return m_settings.showConnections.contains("left");
if (command->id() & IDMask_ConnectionDirection_Right) return m_settings.showConnections.contains("right");
return false;
}
case CommandId::ID_EventMove:
case CommandId::ID_EventShift:
case CommandId::ID_EventCreate:
@ -257,15 +268,18 @@ bool MapImageExporter::currentHistoryAppliesToFrame(QUndoStack *historyStack) {
}
}
QPixmap MapImageExporter::getExpandedPixmap(const QPixmap &pixmap, const QSize &minSize, const QColor &fillColor) {
if (pixmap.width() >= minSize.width() && pixmap.height() >= minSize.height())
QPixmap MapImageExporter::getExpandedPixmap(const QPixmap &pixmap, const QSize &targetSize, const QColor &fillColor) {
if (pixmap.width() >= targetSize.width() && pixmap.height() >= targetSize.height())
return pixmap;
QPixmap resizedPixmap = QPixmap(minSize);
QPixmap resizedPixmap = QPixmap(targetSize);
QPainter painter(&resizedPixmap);
resizedPixmap.fill(fillColor);
painter.drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap);
painter.end();
// Center the old pixmap in the new resized one.
int x = (targetSize.width() - pixmap.width()) / 2;
int y = (targetSize.height() - pixmap.height()) / 2;
painter.drawPixmap(x, y, pixmap.width(), pixmap.height(), pixmap);
return resizedPixmap;
}
@ -278,13 +292,11 @@ struct TimelapseStep {
QGifImage* MapImageExporter::createTimelapseGifImage(QProgressDialog *progress) {
// TODO: Timelapse will play in order of layout changes then map changes (events, connections). Potentially update in the future?
QList<TimelapseStep> steps;
if (m_layout) {
steps.append({
.historyStack = &m_layout->editHistory,
.initialStackIndex = m_layout->editHistory.index(),
.name = "layout",
});
}
steps.append({
.historyStack = &m_layout->editHistory,
.initialStackIndex = m_layout->editHistory.index(),
.name = "layout",
});
if (m_map) {
steps.append({
.historyStack = m_map->editHistory(),
@ -301,8 +313,8 @@ QGifImage* MapImageExporter::createTimelapseGifImage(QProgressDialog *progress)
progress->setMaximum(step.initialStackIndex);
progress->setValue(progress->minimum());
do {
if (currentHistoryAppliesToFrame(step.historyStack)) {
// This command is relevant, record the size of the map at this point.
if (currentHistoryAppliesToFrame(step.historyStack) || step.historyStack->index() == step.initialStackIndex) {
// Either this is relevant edit history, or it's the final frame (which is always rendered). Record the size of the map at this point.
QMargins margins = getMargins(m_map);
canvasSize = canvasSize.expandedTo(QSize(m_layout->getWidth() * 16 + margins.left() + margins.right(),
m_layout->getHeight() * 16 + margins.top() + margins.bottom()));
@ -489,7 +501,10 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress) {
return stitchedPixmap;
}
void MapImageExporter::updatePreview() {
void MapImageExporter::updatePreview(bool forceUpdate) {
if (m_settings.disablePreviewUpdates && !forceUpdate)
return;
QProgressDialog progress("", "Cancel", 0, 1, this);
progress.setAutoClose(true);
progress.setWindowModality(Qt::WindowModal);
@ -535,7 +550,7 @@ void MapImageExporter::updatePreview() {
}
void MapImageExporter::scalePreview() {
if (!m_preview || m_settings.previewActualSize)
if (!m_preview || m_settings.disablePreviewScaling)
return;
ui->graphicsView_Preview->fitInView(m_preview, Qt::KeepAspectRatioByExpanding);
}
@ -689,7 +704,7 @@ void MapImageExporter::setEventGroupEnabled(Event::Group group, bool enable) {
}
bool MapImageExporter::connectionsEnabled() {
return !m_settings.showConnections.isEmpty();
return !m_settings.showConnections.isEmpty() && m_mode != ImageExporterMode::Stitch;
}
void MapImageExporter::setConnectionDirectionEnabled(const QString &dir, bool enable) {
@ -700,7 +715,7 @@ void MapImageExporter::setConnectionDirectionEnabled(const QString &dir, bool en
}
}
void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) {
void MapImageExporter::on_checkBox_Collision_stateChanged(int state) {
m_settings.showCollision = (state == Qt::Checked);
updatePreview();
}
@ -819,21 +834,37 @@ void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) {
updatePreview();
}
void MapImageExporter::on_checkBox_ActualSize_stateChanged(int state) {
m_settings.previewActualSize = (state == Qt::Checked);
if (m_settings.previewActualSize) {
void MapImageExporter::on_checkBox_DisablePreviewScaling_stateChanged(int state) {
m_settings.disablePreviewScaling = (state == Qt::Checked);
if (m_settings.disablePreviewScaling) {
ui->graphicsView_Preview->resetTransform();
} else {
scalePreview();
}
}
void MapImageExporter::on_checkBox_DisablePreviewUpdates_stateChanged(int state) {
m_settings.disablePreviewUpdates = (state == Qt::Checked);
if (m_settings.disablePreviewUpdates) {
if (m_timelapseMovie) {
m_timelapseMovie->stop();
}
} else {
updatePreview();
}
}
void MapImageExporter::on_pushButton_Reset_pressed() {
m_settings = {};
resetSettings();
updatePreview();
}
void MapImageExporter::resetSettings() {
m_settings = {};
for (auto widget : this->findChildren<QCheckBox *>()) {
const QSignalBlocker b(widget); // Prevent calls to updatePreview
widget->setChecked(false);
widget->setChecked(false); // This assumes the default state of all checkboxes settings is false.
}
const QSignalBlocker b_TimelapseDelay(ui->spinBox_TimelapseDelay);
@ -842,15 +873,27 @@ void MapImageExporter::on_pushButton_Reset_pressed() {
const QSignalBlocker b_FrameSkip(ui->spinBox_FrameSkip);
ui->spinBox_FrameSkip->setValue(m_settings.timelapseSkipAmount);
updatePreview();
if (m_mode == ImageExporterMode::Timelapse) {
// Timelapse gif has artifacts with transparency, make sure it's disabled.
m_settings.fillColor.setAlpha(255);
}
}
void MapImageExporter::on_spinBox_TimelapseDelay_valueChanged(int delayMs) {
// These spin boxes can be changed rapidly, so we wait for editing to finish before updating the preview.
void MapImageExporter::on_spinBox_TimelapseDelay_editingFinished() {
int delayMs = ui->spinBox_TimelapseDelay->value();
if (delayMs == m_settings.timelapseDelayMs)
return;
m_settings.timelapseDelayMs = delayMs;
updatePreview();
}
void MapImageExporter::on_spinBox_FrameSkip_valueChanged(int skip) {
m_settings.timelapseSkipAmount = skip;
void MapImageExporter::on_spinBox_FrameSkip_editingFinished() {
int skipAmount = ui->spinBox_FrameSkip->value();
if (skipAmount == m_settings.timelapseSkipAmount)
return;
m_settings.timelapseSkipAmount = skipAmount;
updatePreview();
}