-
extern const uint8_t data_unknown_base_color[];
extern const size_t png_size;
diff --git a/README-en.md b/README-en.md
index 8f13c44f..e73d8cb0 100644
--- a/README-en.md
+++ b/README-en.md
@@ -1,13 +1,14 @@
[中文](README.md) | **English**
-SlopeCraft
+
+
Get your Minecraft pixel painting in multiple kinds of forms!
-
+
@@ -15,8 +16,8 @@
-
-
+
+
@@ -24,27 +25,34 @@
SlopeCraft is created using Qt + Eigen + zlib for creating 3D pixel map arts within Minecraft.
-The difference between this program and SpriteCraft is that this program focuses on pixel art on maps. Its purpose is to record the pixel art with the map, then display it in an item frame (the map here refers to the in-game item, and not the saving files).
+The difference between this program and SpriteCraft is that this program focuses on pixel art on maps. Its purpose is to
+record the pixel art with the map, then display it in an item frame (the map here refers to the in-game item, and not
+the saving files).
-The color modification module is targeted towards the map. The pixel art is meant to resemble the art in the "point of view" of the map - not of the player.
+The color modification module is targeted towards the map. The pixel art is meant to resemble the art in the "point of
+view" of the map - not of the player.
-Since the color within the map is related to the relative height of the block, the pixel art created by SlopeCraft is usually in 3D, which I call 3D Pixel Art.
+Since the color within the map is related to the relative height of the block, the pixel art created by SlopeCraft is
+usually in 3D, which I call 3D Pixel Art.
In summary, SlopeCraft is created for map pixel art.
-This is why maps made by SlopeCraft have higher quality than exporting a picture from SpriteCraft then recording it with a map - because SlopeCraft is specifically designed for map pixel art.
+This is why maps made by SlopeCraft have higher quality than exporting a picture from SpriteCraft then recording it with
+a map - because SlopeCraft is specifically designed for map pixel art.
## ⚙️ Installation
-1. Download the latest version of SlopeCraft from the [Release](https://github.com/SlopeCraft/SlopeCraft/releases/latest) page.
+1. Download the latest version of SlopeCraft from
+ the [Release](https://github.com/SlopeCraft/SlopeCraft/releases/latest) page.
2. Run SlopeCraft
- - Windows Users: Download `SlopeCraft-x.x.x-win.zip`, unzip and run `SlopeCraft.exe`
- - macOS Users: Download `SlopeCraft-x.x.x-mac.zip`, unzip and drag `SlopeCraft.app` into the Applications folder and run `SlopeCraft`
- - Linux Users: Download `SlopeCraft-x.x.x-linux.tar.xz`, unzip and run `SlopeCraft`
+ - Windows Users: Download `SlopeCraft-x.x.x-win.zip`, unzip and run `SlopeCraft.exe`
+ - macOS Users: Download `SlopeCraft-x.x.x-mac.zip`, unzip and drag `SlopeCraft.app` into the Applications folder and
+ run `SlopeCraft`
+ - Linux Users: Download `SlopeCraft-x.x.x-linux.tar.xz`, unzip and run `SlopeCraft`
- ::: tips
- The `x.x.x` mentioned above is the version number of SlopeCraft, for example, `5.0.0`.
+ ::: tips
+ The `x.x.x` mentioned above is the version number of SlopeCraft, for example, `5.0.0`.
3. Make sure you have read the [FAQ](https://slopecraft.readthedocs.io/en/faq/) and the tutorial before doing anything.
@@ -65,13 +73,16 @@ This is why maps made by SlopeCraft have higher quality than exporting a picture
### Compilation Guide
-- If you want to compile SlopeCraft yourself, you can refer to the [Compilation Guide](https://slopecraft.readthedocs.io/en/compilation-guide/) for operation.
+- If you want to compile SlopeCraft yourself, you can refer to
+ the [Compilation Guide](https://slopecraft.readthedocs.io/en/compilation-guide/) for operation.
## 🛠️ Other Related Repositories
* [NBTWriter](https://github.com/ToKiNoBug/NBTWriter-of-Toki) - Lib for writing NBT files.
* [SlopeCraftTutorial](https://github.com/ToKiNoBug/SlopeCraftTutorial) - Tutorials
-* [SlopeCraftCompressLib](https://github.com/ToKiNoBug/SlopeCraftCompressLib) - Lib for building height map and lossless compression lib.
-* [SlopeCraftLossyCompression](https://github.com/ToKiNoBug/SlopeCraftLossyCompression) - Lossy compression lib, based on SlopeCraftCompressLib.
+* [SlopeCraftCompressLib](https://github.com/ToKiNoBug/SlopeCraftCompressLib) - Lib for building height map and lossless
+ compression lib.
+* [SlopeCraftLossyCompression](https://github.com/ToKiNoBug/SlopeCraftLossyCompression) - Lossy compression lib, based
+ on SlopeCraftCompressLib.
* [SlopeCraftGlassBuilder](https://github.com/ToKiNoBug/SlopeCraftGlassBuilder) - Glass bridge building lib.
* [HeuristicFlow](https://github.com/TokiNoBug/HeuristicFlow) - GA implementation.
diff --git a/README.md b/README.md
index 9280233f..557a920d 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
**中文** | [English](README-en.md)
-SlopeCraft
+
生成多种样式的 Minecraft 地图画!
-
+
@@ -15,8 +15,8 @@
-
-
+
+
@@ -39,12 +39,12 @@ SlopeCraft 是一款基于 Qt + Eigen + zlib 开发的,用于在 Minecraft 中
1. 从 [Release](https://github.com/SlopeCraft/SlopeCraft/releases/latest) 页面下载最新版本的 SlopeCraft。
2. 运行 SlopeCraft
- - Windows 用户:下载 `SlopeCraft-x.x.x-win.zip`,解压后运行 `SlopeCraft.exe`
- - macOS 用户:下载 `SlopeCraft-x.x.x-mac.zip`,解压后将 `SlopeCraft.app` 拖入应用程序文件夹并运行 `SlopeCraft`
- - Linux 用户:下载 `SlopeCraft-x.x.x-linux.tar.xz`,解压后运行 `SlopeCraft`
+ - Windows 用户:下载 `SlopeCraft-x.x.x-win.zip`,解压后运行 `SlopeCraft.exe`
+ - macOS 用户:下载 `SlopeCraft-x.x.x-mac.zip`,解压后将 `SlopeCraft.app` 拖入应用程序文件夹并运行 `SlopeCraft`
+ - Linux 用户:下载 `SlopeCraft-x.x.x-linux.tar.xz`,解压后运行 `SlopeCraft`
- ::: tips
- 此处的 `x.x.x` 为 SlopeCraft 的版本号,例如 `5.0.0`。
+ ::: tips
+ 此处的 `x.x.x` 为 SlopeCraft 的版本号,例如 `5.0.0`。
3. 在进行任何操作前,请确保你已经阅读了 [常见问题](https://slopecraft.readthedocs.io/faq/) 和使用教程。
diff --git a/SlopeCraft-install-macOS.zsh b/SlopeCraft-install-macOS.zsh
new file mode 100755
index 00000000..3f21631b
--- /dev/null
+++ b/SlopeCraft-install-macOS.zsh
@@ -0,0 +1,55 @@
+#!/usr/bin/env zsh
+
+if [[ $OSTYPE != darwin* ]]; then
+ echo "This script can ONLY be used on macOS!"
+ exit 1
+fi
+
+# Check if Homebrew is installed
+which -s brew
+
+if [[ $? != 0 ]] ; then
+ # Install Homebrew
+ echo "Installing Homebrew"
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
+else
+ echo "Updating Homebrew"
+ brew update
+fi
+
+echo "Installing dependencies"
+brew install git
+brew install llvm
+
+if [[ $CPUTYPE == arm64 ]]; then
+ echo "Configuring LLVM Clang for Apple Silicon Macs"
+ alias clang=/opt/homebrew/opt/llvm/bin/clang
+ alias clang++=/opt/homebrew/opt/llvm/bin/clang++
+else
+ echo "Configuring LLVM Clang for Intel Macs"
+ alias clang=/usr/local/opt/llvm/bin/clang
+ alias clang++=/usr/local/opt/llvm/bin/clang++
+fi
+
+brew install cmake ninja
+brew install qt
+brew install zlib libpng libzip xsimd
+
+echo "Cloning git repo"
+cd ~
+git clone https://github.com/SlopeCraft/SlopeCraft.git && cd SlopeCraft
+
+echo "Configuring CMake... This step might take a while."
+cmake -S . -B ./build -G "Ninja" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=./build/install -DCMAKE_BUILD_TYPE=Release
+
+echo "Building... This step might take a while."
+cd build
+cmake --build . --parallel
+
+echo "Installing and Deploying"
+cmake --install .
+cd install
+macdeployqt *.app
+
+echo "Get your app files at $(pwd)!"
+
diff --git a/SlopeCraft/AdaptiveListView.cpp b/SlopeCraft/AdaptiveListView.cpp
new file mode 100644
index 00000000..7021ac97
--- /dev/null
+++ b/SlopeCraft/AdaptiveListView.cpp
@@ -0,0 +1,20 @@
+#include "AdaptiveListView.h"
+
+inline QSize compute_size(QSize widget_size, double ratio = 0.8) noexcept {
+ const int new_w = widget_size.width() * ratio;
+ return {new_w, new_w};
+}
+
+AdaptiveListView::AdaptiveListView(QWidget* parent) : QListView(parent) {
+ this->setIconSize(compute_size(this->size()));
+ this->setDragEnabled(true);
+ this->setAcceptDrops(true);
+ this->setDropIndicatorShown(true);
+}
+
+AdaptiveListView::~AdaptiveListView() {}
+
+void AdaptiveListView::resizeEvent(QResizeEvent* event) {
+ this->setIconSize(compute_size(event->size()));
+ QListView::resizeEvent(event);
+}
\ No newline at end of file
diff --git a/SlopeCraft/AdaptiveListView.h b/SlopeCraft/AdaptiveListView.h
new file mode 100644
index 00000000..818845fa
--- /dev/null
+++ b/SlopeCraft/AdaptiveListView.h
@@ -0,0 +1,14 @@
+#ifndef SLOPECRAFT_SLOPECRAFT_ADAPTIVELISTVIEW_H
+#define SLOPECRAFT_SLOPECRAFT_ADAPTIVELISTVIEW_H
+#include
+#include
+
+class AdaptiveListView : public QListView {
+ public:
+ explicit AdaptiveListView(QWidget* parent = nullptr);
+ ~AdaptiveListView();
+
+ void resizeEvent(QResizeEvent* event) override;
+};
+
+#endif // SLOPECRAFT_SLOPECRAFT_ADAPTIVELISTVIEW_H
diff --git a/SlopeCraft/AiCvterParameterDialog.cpp b/SlopeCraft/AiCvterParameterDialog.cpp
new file mode 100644
index 00000000..660785fc
--- /dev/null
+++ b/SlopeCraft/AiCvterParameterDialog.cpp
@@ -0,0 +1,66 @@
+/*
+ Copyright © 2021-2026 TokiNoBug
+This file is part of SlopeCraft.
+
+ SlopeCraft is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SlopeCraft is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SlopeCraft. If not, see .
+
+ Contact with me:
+ github:https://github.com/SlopeCraft/SlopeCraft
+ bilibili:https://space.bilibili.com/351429231
+*/
+
+#include "AiCvterParameterDialog.h"
+#include "ui_AiCvterParameterDialog.h"
+
+#include "SCWind.h"
+
+using namespace SlopeCraft;
+AiCvterParameterDialog::AiCvterParameterDialog(SCWind* parent)
+ : QDialog(parent), ui(new Ui::AiCvterParameterDialog) {
+ this->ui->setupUi(this);
+
+ SlopeCraft::GA_converter_option opt =
+ dynamic_cast(this->parent())->GA_option;
+ opt.caller_api_version = SC_VERSION_U64;
+
+ this->ui->sb_pop_size->setValue(opt.popSize);
+ this->ui->sb_max_gen->setValue(opt.maxGeneration);
+ this->ui->sb_max_early_stop->setValue(opt.maxFailTimes);
+ this->ui->dsb_crossover_prob->setValue(opt.crossoverProb);
+ this->ui->dsb_mutate_prob->setValue(opt.mutationProb);
+}
+
+AiCvterParameterDialog::~AiCvterParameterDialog() { delete this->ui; }
+
+SlopeCraft::GA_converter_option AiCvterParameterDialog::current_option()
+ const noexcept {
+ SlopeCraft::GA_converter_option ret{
+ .caller_api_version = SC_VERSION_U64,
+ .popSize = static_cast(this->ui->sb_pop_size->value()),
+ .maxGeneration = static_cast(this->ui->sb_max_gen->value()),
+ .maxFailTimes = static_cast(this->ui->sb_max_early_stop->value()),
+ .crossoverProb = this->ui->dsb_crossover_prob->value(),
+ .mutationProb = this->ui->dsb_mutate_prob->value(),
+ };
+ return ret;
+}
+
+void AiCvterParameterDialog::on_buttonBox_accepted() noexcept {
+ auto opt = this->current_option();
+ dynamic_cast(this->parent())->GA_option = opt;
+}
+
+void AiCvterParameterDialog::on_buttonBox_rejected() noexcept {
+ this->deleteLater();
+}
diff --git a/SlopeCraftMain/AiCvterParameterDialog.h b/SlopeCraft/AiCvterParameterDialog.h
similarity index 72%
rename from SlopeCraftMain/AiCvterParameterDialog.h
rename to SlopeCraft/AiCvterParameterDialog.h
index 450c8686..ed594cf1 100644
--- a/SlopeCraftMain/AiCvterParameterDialog.h
+++ b/SlopeCraft/AiCvterParameterDialog.h
@@ -1,5 +1,5 @@
/*
- Copyright © 2021-2023 TokiNoBug
+ Copyright © 2021-2026 TokiNoBug
This file is part of SlopeCraft.
SlopeCraft is free software: you can redistribute it and/or modify
@@ -25,30 +25,33 @@ This file is part of SlopeCraft.
#include
#include
+#include
+
+class AiCvterParameterDialog;
namespace Ui {
class AiCvterParameterDialog;
}
-class MainWindow;
+class SCWind;
class AiCvterParameterDialog : public QDialog {
Q_OBJECT
-public:
- explicit AiCvterParameterDialog(QWidget *parent);
+ public:
+ explicit AiCvterParameterDialog(SCWind *parent);
~AiCvterParameterDialog();
-protected:
- void closeEvent(QCloseEvent *event) override;
-private slots:
- void updateMaxFailTimes();
- void on_buttonBox_accepted();
+ SlopeCraft::GA_converter_option current_option() const noexcept;
+
+ private slots:
+ // void updateMaxFailTimes();
+ void on_buttonBox_accepted() noexcept;
- void on_buttonBox_clicked(QAbstractButton *button);
+ void on_buttonBox_rejected() noexcept;
-private:
+ private:
Ui::AiCvterParameterDialog *ui;
};
-#endif // AICVTERPARAMETERDIALOG_H
+#endif // AICVTERPARAMETERDIALOG_H
diff --git a/SlopeCraftMain/AiCvterParameterDialog.ui b/SlopeCraft/AiCvterParameterDialog.ui
similarity index 87%
rename from SlopeCraftMain/AiCvterParameterDialog.ui
rename to SlopeCraft/AiCvterParameterDialog.ui
index 78bc0e41..74e2665f 100644
--- a/SlopeCraftMain/AiCvterParameterDialog.ui
+++ b/SlopeCraft/AiCvterParameterDialog.ui
@@ -14,18 +14,8 @@
Ai转化器参数
- -
-
-
- 允许提前收敛
-
-
- true
-
-
-
-
-
+
最大提前收敛代数:
@@ -33,7 +23,7 @@
10
- 100
+ 3000
10
@@ -41,7 +31,7 @@
-
-
+
交叉概率:
@@ -60,7 +50,7 @@
-
-
+
150
@@ -88,7 +78,7 @@
-
-
+
150
@@ -113,7 +103,7 @@
-
-
+
变异概率:
diff --git a/SlopeCraft/BlockListDialog.cpp b/SlopeCraft/BlockListDialog.cpp
new file mode 100644
index 00000000..87415872
--- /dev/null
+++ b/SlopeCraft/BlockListDialog.cpp
@@ -0,0 +1,322 @@
+//
+// Created by Joseph on 2024/4/9.
+//
+
+#include
+#include
+#include
+#include "BlockListDialog.h"
+#include "ui_BlockListDialog.h"
+#include "SCWind.h"
+
+int BLD_block_list_provider::rowCount(const QModelIndex &parent) const {
+ if (parent.isValid()) {
+ return 0;
+ }
+ return this->available_block_lists().size();
+}
+
+QVariant BLD_block_list_provider::data(const QModelIndex &index,
+ int role) const {
+ if (not index.isValid()) {
+ return {};
+ }
+ if (role not_eq Qt::ItemDataRole::DisplayRole) {
+ return {};
+ }
+ const auto block_lists = this->available_block_lists();
+ const int idx = index.row();
+
+ if (idx >= block_lists.size() or idx < 0) {
+ return {};
+ }
+ if (block_lists[idx].second == nullptr) {
+ return tr("SlopeCraft 内部错误,方块列表的列表中出现 nullptr");
+ }
+ return block_lists[idx].first;
+}
+
+std::vector
+BLD_block_provider::available_blocks() const noexcept {
+ auto bl = this->available_block_list();
+ if (bl == nullptr) {
+ return {};
+ }
+ const size_t num = bl->size();
+ std::vector ret;
+ ret.resize(num);
+ [[maybe_unused]] const size_t num_ =
+ bl->get_blocks(ret.data(), nullptr, ret.size());
+ assert(num == num_);
+ return ret;
+}
+
+int BLD_block_provider::rowCount(const QModelIndex &parent) const {
+ if (parent.isValid()) {
+ return 0;
+ }
+ auto bl = this->available_block_list();
+ if (bl == nullptr) {
+ return 0;
+ }
+ return bl->size();
+}
+QVariant BLD_block_provider::data(const QModelIndex &index, int role) const {
+ if (not index.isValid()) {
+ return {};
+ }
+ if (role not_eq Qt::ItemDataRole::DisplayRole) {
+ return {};
+ }
+ const auto blocks = this->available_blocks();
+ const int idx = index.row();
+
+ if (idx >= blocks.size() or idx < 0) {
+ return {};
+ }
+ if (blocks[idx] == nullptr) {
+ return tr("SlopeCraft 内部错误,方块列表中出现 nullptr");
+ }
+ if (this->current_lang() == SCL_language::Chinese) {
+ return QString::fromUtf8(blocks[idx]->getNameZH());
+ }
+ return QString::fromUtf8(blocks[idx]->getNameEN());
+}
+
+int BLD_block_info_provider::rowCount(const QModelIndex &qmi) const {
+ if (qmi.isValid()) {
+ return 0;
+ }
+ return 6;
+}
+int BLD_block_info_provider::columnCount(const QModelIndex &qmi) const {
+ if (qmi.isValid()) {
+ return 0;
+ }
+ return 2;
+}
+
+QString BLD_block_info_provider::key_name(int index) noexcept {
+ const std::array keys{tr("最低版本"), tr("依附方块"),
+ tr("发光"), tr("末影人可搬走"),
+ tr("可燃"), tr("一组数量")};
+ if (index < 0 or index >= keys.size()) {
+ return {};
+ }
+ return keys[index];
+}
+/*
+ * 0 -> version
+ * 1 -> need glass
+ * 2 -> do glow
+ * 3 -> enderman pickable
+ * 4 -> burnable
+ * 5 -> stack size
+ * */
+QVariant BLD_block_info_provider::value_of_attribute(
+ const SlopeCraft::mc_block_interface &blk, int index) noexcept {
+ auto bool_to_str = [](bool val) {
+ if (val)
+ return "Yes";
+ else
+ return "No";
+ };
+ switch (index) {
+ case 0: { // version
+ const auto ver = blk.getVersion();
+ if (ver < uint8_t(SCL_gameVersion::MC12)) {
+ return tr("远古版本");
+ }
+ if (ver > (uint8_t)SlopeCraft::SCL_maxAvailableVersion()) {
+ return tr("未来版本");
+ }
+ return QStringLiteral("1.%1").arg(int(ver));
+ }
+ case 1: // need glass
+ return bool_to_str(blk.getNeedGlass());
+ case 2:
+ return bool_to_str(blk.getDoGlow());
+ case 3:
+ return bool_to_str(blk.getEndermanPickable());
+ case 4:
+ return bool_to_str(blk.getBurnable());
+ case 5:
+ return blk.getStackSize();
+ }
+ return {};
+}
+
+QVariant BLD_block_info_provider::data(const QModelIndex &qmi,
+ int role) const noexcept {
+ if (not qmi.isValid()) {
+ return {};
+ }
+ if (role not_eq Qt::ItemDataRole::DisplayRole) {
+ return {};
+ }
+ const auto current_block = this->selected_block();
+ switch (qmi.column()) {
+ case 0:
+ return key_name(qmi.row());
+ case 1: {
+ if (current_block == nullptr) {
+ return {};
+ }
+ return value_of_attribute(*current_block, qmi.row());
+ }
+ }
+ return {};
+}
+
+BlockListDialog::BlockListDialog(SCWind *parent, BlockListManager *blm)
+ : QDialog{parent}, ui{new Ui::BlockListDialog}, block_list_manager{blm} {
+ this->ui->setupUi(this);
+
+ {
+ auto get_block_lists = [blm]()
+ -> std::vector<
+ std::pair> {
+ return blm->get_block_lists();
+ };
+ this->block_list_provider.reset(
+ new BLD_block_list_provider{this, get_block_lists});
+ this->ui->lv_block_lists->setModel(this->block_list_provider.get());
+ }
+ {
+ auto get_selected_block_list =
+ [this]() -> const SlopeCraft::block_list_interface * {
+ const auto qmi = this->ui->lv_block_lists->currentIndex();
+ if (not qmi.isValid()) {
+ return nullptr;
+ }
+ const int idx = qmi.row();
+ const auto available_lists =
+ this->block_list_provider->available_block_lists();
+ if (idx < 0 or idx >= available_lists.size()) {
+ return nullptr;
+ }
+ return available_lists[idx].second;
+ };
+ auto get_lang = [parent]() -> SCL_language { return parent->lang(); };
+ this->block_provider.reset(
+ new BLD_block_provider{this, get_selected_block_list, get_lang});
+ this->ui->lv_blocks->setModel(this->block_provider.get());
+ }
+ {
+ auto get_selected_block =
+ [this]() -> const SlopeCraft::mc_block_interface * {
+ const auto qmi = this->ui->lv_blocks->currentIndex();
+ if (not qmi.isValid()) {
+ return nullptr;
+ }
+ const int idx = qmi.row();
+ const auto available_blocks = this->block_provider->available_blocks();
+ if (idx < 0 or idx >= available_blocks.size()) {
+ return nullptr;
+ }
+ return available_blocks[idx];
+ };
+ this->block_info_provider.reset(
+ new BLD_block_info_provider{this, get_selected_block});
+ this->ui->tv_block_props->setModel(this->block_info_provider.get());
+ }
+
+ connect(this->ui->lv_block_lists->selectionModel(),
+ &QItemSelectionModel::selectionChanged, [this]() {
+ this->block_provider->dataChanged({}, {},
+ {Qt::ItemDataRole::DisplayRole});
+ });
+ connect(this->ui->lv_blocks->selectionModel(),
+ &QItemSelectionModel::selectionChanged, [this]() {
+ this->block_info_provider->dataChanged(
+ {}, {}, {Qt::ItemDataRole::DisplayRole});
+
+ auto blk = this->block_info_provider->selected_block();
+ this->update_info(blk);
+ });
+
+ connect(this->ui->pb_ok, &QPushButton::clicked, this, &QDialog::accept);
+}
+
+BlockListDialog::~BlockListDialog() {
+ // delete this->ui;
+}
+
+void BlockListDialog::update_info(
+ const SlopeCraft::mc_block_interface *blk) noexcept {
+ if (blk == nullptr) {
+ this->ui->lb_image->setPixmap({});
+ this->ui->le_id->clear();
+ this->ui->le_id_old->clear();
+ this->ui->le_name_cn->clear();
+ this->ui->le_name_en->clear();
+ return;
+ }
+ {
+ QImage img{blk->imageCols(), blk->imageRows(), QImage::Format_ARGB32};
+ blk->getImage(reinterpret_cast(img.scanLine(0)));
+ img = img.scaledToHeight(64);
+ this->ui->lb_image->setPixmap(QPixmap::fromImage(img));
+ }
+ this->ui->le_id->setText(blk->getId());
+ this->ui->le_id_old->setText(blk->getIdOld());
+ this->ui->le_name_cn->setText(QString::fromUtf8(blk->getNameZH()));
+ this->ui->le_name_en->setText(QString::fromUtf8(blk->getNameEN()));
+}
+
+void BlockListDialog::on_pb_add_block_list_clicked() noexcept {
+ const auto files = QFileDialog::getOpenFileNames(
+ this, tr("选择方块列表"),
+ QStringLiteral("%1/Blocks").arg(QCoreApplication::applicationDirPath()),
+ "*.zip");
+ if (files.empty()) {
+ return;
+ }
+
+ for (auto &file : files) {
+ this->block_list_manager->add_blocklist(file);
+ }
+ this->block_list_manager->finish_blocklist();
+ this->block_list_provider->dataChanged({}, {});
+}
+
+void BlockListDialog::on_pb_remove_block_list_clicked() noexcept {
+ const auto selected_indices =
+ this->ui->lv_block_lists->selectionModel()->selectedIndexes();
+ if (selected_indices.empty()) {
+ return;
+ }
+ std::vector names;
+ for (auto &qmi : selected_indices) {
+ if (not qmi.isValid()) {
+ continue;
+ }
+ names.emplace_back(
+ this->block_list_provider->available_block_lists()[qmi.row()].first);
+ }
+
+ int num_lists = 0;
+ size_t remove_counter = 0;
+ for (auto &name : names) {
+ if (name == "FixedBlocks.zip") {
+ QMessageBox::warning(this, tr("不能删除基础方块列表"),
+ tr("FixedBlocks.zip 是基础方块列表,不允许移除。"));
+ continue;
+ }
+ auto res = this->block_list_manager->remove_blocklist(name);
+ if (not res) {
+ QMessageBox::warning(this, tr("删除方块列表 %1 失败").arg(name),
+ res.error());
+ } else {
+ remove_counter += res.value();
+ num_lists++;
+ }
+ }
+ if (num_lists > 0) {
+ QMessageBox::information(this, tr("删除方块列表成功"),
+ tr("删除了 %1 个方块列表,移除了 %2 个方块")
+ .arg(num_lists)
+ .arg(remove_counter));
+ }
+ this->block_list_provider->dataChanged({}, {});
+}
\ No newline at end of file
diff --git a/SlopeCraft/BlockListDialog.h b/SlopeCraft/BlockListDialog.h
new file mode 100644
index 00000000..1c3beca3
--- /dev/null
+++ b/SlopeCraft/BlockListDialog.h
@@ -0,0 +1,110 @@
+//
+// Created by Joseph on 2024/4/9.
+//
+
+#ifndef SLOPECRAFT_BLOCKLISTDIALOG_H
+#define SLOPECRAFT_BLOCKLISTDIALOG_H
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include "BlockListManager.h"
+
+class BlockListDialog;
+
+namespace Ui {
+class BlockListDialog;
+}
+
+class BLD_block_list_provider : public QAbstractListModel {
+ Q_OBJECT
+ public:
+ const std::function>()>
+ available_block_lists;
+
+ public:
+ explicit BLD_block_list_provider(
+ QWidget* parent,
+ std::function>()>
+ cb)
+ : QAbstractListModel{parent}, available_block_lists{std::move(cb)} {}
+ BLD_block_list_provider(const BLD_block_list_provider&) = delete;
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const final;
+
+ QVariant data(const QModelIndex& index,
+ int role = Qt::DisplayRole) const final;
+};
+class BLD_block_provider : public QAbstractListModel {
+ Q_OBJECT
+ private:
+ const std::function
+ available_block_list;
+ const std::function current_lang;
+
+ public:
+ explicit BLD_block_provider(
+ QWidget* parent,
+ std::function&& cb,
+ std::function&& lang_cb)
+ : QAbstractListModel{parent},
+ available_block_list{std::move(cb)},
+ current_lang{std::move(lang_cb)} {}
+
+ std::vector available_blocks()
+ const noexcept;
+ int rowCount(const QModelIndex& parent = QModelIndex()) const;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+};
+
+class BLD_block_info_provider : public QAbstractTableModel {
+ Q_OBJECT
+ public:
+ const std::function selected_block;
+
+ public:
+ explicit BLD_block_info_provider(
+ QWidget* parent,
+ std::function&& cb)
+ : QAbstractTableModel{parent}, selected_block{std::move(cb)} {}
+
+ int rowCount(const QModelIndex& qmi) const final;
+
+ int columnCount(const QModelIndex& qmi) const final;
+
+ static QString key_name(int index) noexcept;
+ static QVariant value_of_attribute(const SlopeCraft::mc_block_interface& blk,
+ int index) noexcept;
+
+ QVariant data(const QModelIndex& qmi, int role) const noexcept final;
+};
+
+class SCWind;
+class BlockListDialog : public QDialog {
+ Q_OBJECT
+ private:
+ std::unique_ptr ui;
+ std::unique_ptr block_list_provider{nullptr};
+ std::unique_ptr block_provider{nullptr};
+ std::unique_ptr block_info_provider{nullptr};
+
+ BlockListManager* const block_list_manager;
+
+ void update_info(const SlopeCraft::mc_block_interface*) noexcept;
+
+ public:
+ explicit BlockListDialog(SCWind* parent, BlockListManager* blm);
+ ~BlockListDialog();
+
+ private slots:
+ void on_pb_add_block_list_clicked() noexcept;
+ void on_pb_remove_block_list_clicked() noexcept;
+};
+
+#endif // SLOPECRAFT_BLOCKLISTDIALOG_H
diff --git a/SlopeCraft/BlockListDialog.ui b/SlopeCraft/BlockListDialog.ui
new file mode 100644
index 00000000..31aa5107
--- /dev/null
+++ b/SlopeCraft/BlockListDialog.ui
@@ -0,0 +1,167 @@
+
+
+ BlockListDialog
+
+
+
+ 0
+ 0
+ 820
+ 481
+
+
+
+ 方块列表
+
+
+ false
+
+
+ false
+
+
+
-
+
+
+ 0
+
+
-
+
+
+ 添加方块列表
+
+
+
+ -
+
+
+ 删除方块列表
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 确定
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+ QFrame::Shape::StyledPanel
+
+
+
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+
+ -
+
+
+ true
+
+
+ 方块 id
+
+
+
+ -
+
+
+ true
+
+
+ 1.12 id
+
+
+
+ -
+
+
+ true
+
+
+ 中文名
+
+
+
+ -
+
+
+ true
+
+
+ 英文名
+
+
+
+ -
+
+
+ true
+
+
+ false
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+
diff --git a/SlopeCraft/CMakeLists.txt b/SlopeCraft/CMakeLists.txt
new file mode 100644
index 00000000..ab7a15c6
--- /dev/null
+++ b/SlopeCraft/CMakeLists.txt
@@ -0,0 +1,139 @@
+cmake_minimum_required(VERSION 3.20)
+project(SlopeCraft_NewGUI VERSION ${SlopeCraft_version} LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+find_package(Qt6 COMPONENTS Widgets LinguistTools Network REQUIRED)
+find_package(magic_enum REQUIRED)
+find_package(tl-expected REQUIRED)
+
+set(SlopeCraft_rc_files)
+
+if (${WIN32})
+ configure_file(others/SlopeCraft.rc.in ${CMAKE_CURRENT_BINARY_DIR}/SlopeCraft.rc)
+ set(SlopeCraft_rc_files ${CMAKE_CURRENT_BINARY_DIR}/SlopeCraft.rc)
+endif ()
+
+set(SlopeCraft_headers
+ SCWind.h
+ cvt_task.h
+ PoolModel.h
+ AdaptiveListView.h
+ PreviewWind.h
+ ExportTableModel.h
+ AiCvterParameterDialog.h
+ CopyableTableView.h
+ TransparentStrategyWind.h
+ CompressEffectViewer.h
+ BlockListDialog.h
+)
+
+set(SlopeCraft_sources
+ SCWind.cpp
+ SCWind_slots.cpp
+ cvt_task.cpp
+ PoolModel.cpp
+ AdaptiveListView.cpp
+ PreviewWind.cpp
+ ExportTableModel.cpp
+ AiCvterParameterDialog.cpp
+ CopyableTableView.cpp
+ TransparentStrategyWind.cpp
+ CompressEffectViewer.cpp
+ BlockListDialog.cpp
+
+ main.cpp
+ ${SlopeCraft_rc_files})
+
+set(SlopeCraft_uis
+ SCWind.ui
+ PreviewWind.ui
+ AiCvterParameterDialog.ui
+ TransparentStrategyWind.ui
+ CompressEffectViewer.ui
+ BlockListDialog.ui
+)
+
+set(SlopeCraft_ts_files
+ others/SlopeCraft_en_US.ts
+)
+
+set(SlopeCraft_project_files
+ ${SlopeCraft_headers}
+ ${SlopeCraft_sources}
+ ${SlopeCraft_uis}
+
+ # ${SlopeCraft_ts_files}
+)
+
+qt_add_executable(SlopeCraft
+ MANUAL_FINALIZATION
+ ${SlopeCraft_project_files})
+
+target_compile_features(SlopeCraft PRIVATE cxx_std_23)
+target_precompile_headers(SlopeCraft PRIVATE ${SlopeCraft_headers})
+
+target_link_libraries(SlopeCraft PRIVATE
+ Qt6::Core
+ Qt6::Widgets
+ Qt6::Network
+ magic_enum::magic_enum
+ tl::expected
+
+ SlopeCraftL
+ AdaptiveLabel
+ VersionDialog
+ BlockListManager
+ StatMemory
+ MemoryPolicyDialog
+)
+
+target_include_directories(SlopeCraft PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+
+set_target_properties(SlopeCraft PROPERTIES
+ VERSION ${PROJECT_VERSION}
+ MACOSX_BUNDLE_ICON_FILE SlopeCraft.icns
+ MACOSX_BUNDLE_GUI_IDENTIFIER "com.github.ToKiNoBug.SlopeCraft"
+ MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
+ MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
+ MACOSX_BUNDLE TRUE
+ WIN32_EXECUTABLE TRUE
+)
+
+file(GLOB SlopeCraft_qrc_images "${CMAKE_CURRENT_SOURCE_DIR}/others/images/*.png")
+message(STATUS "SlopeCraft_qrc_images = ${SlopeCraft_qrc_images}")
+qt_add_resources(SlopeCraft "SC_images"
+ PREFIX "/images/"
+ BASE ${CMAKE_CURRENT_SOURCE_DIR}/others/images
+ FILES ${SlopeCraft_qrc_images})
+
+qt_add_lupdate(SlopeCraft
+ TS_FILES ${SlopeCraft_ts_files}
+ SOURCES ${SlopeCraft_project_files}
+ OPTIONS ${SC_lupdate_flags}
+)
+
+qt_add_lrelease(SlopeCraft TS_FILES ${SlopeCraft_ts_files}
+ QM_FILES_OUTPUT_VARIABLE SC_qm_files)
+
+qt_add_resources(SlopeCraft "SC_translations"
+ PREFIX "/i18n"
+ BASE ${CMAKE_CURRENT_BINARY_DIR}
+ FILES ${SC_qm_files})
+
+qt_finalize_executable(SlopeCraft)
+
+if (${WIN32})
+ add_custom_target(SC_create_symlink_SC
+ COMMAND mklink SlopeCraftL.dll "..\\SlopeCraftL\\SlopeCraftL.dll"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ DEPENDS SlopeCraftL
+ COMMENT "Create symlink to SlopeCraftL.dll for SlopeCraft.exe")
+ add_dependencies(SC_create_all_symlinks SC_create_symlink_SC)
+endif ()
+
+include(install.cmake)
\ No newline at end of file
diff --git a/SlopeCraft/CompressEffectViewer.cpp b/SlopeCraft/CompressEffectViewer.cpp
new file mode 100644
index 00000000..f3aef59b
--- /dev/null
+++ b/SlopeCraft/CompressEffectViewer.cpp
@@ -0,0 +1,41 @@
+#include
+#include "CompressEffectViewer.h"
+#include "ui_CompressEffectViewer.h"
+
+#include "SCWind.h"
+#include
+#include
+
+CompressEffectViewer::CompressEffectViewer(
+ SCWind* parent, const SlopeCraft::converted_image& cvted,
+ const SlopeCraft::structure_3D& structure)
+ : QDialog{parent}, ui{new Ui::CompressEffectViewer} {
+ this->ui->setupUi(this);
+
+ const int rows = cvted.rows();
+ const int cols = cvted.cols();
+ QImage img{cols, rows, QImage::Format_ARGB32};
+ cvted.get_compressed_image(structure,
+ reinterpret_cast(img.scanLine(0)));
+
+ this->ui->lb_display->setPixmap(QPixmap::fromImage(img));
+}
+
+CompressEffectViewer::~CompressEffectViewer() {}
+
+void CompressEffectViewer::on_pb_save_image_clicked() noexcept {
+ static QString prev_dir;
+ const QString filename = QFileDialog::getSaveFileName(
+ this, tr("保存压缩后图片"), prev_dir, "*.png");
+ if (filename.isEmpty()) {
+ return;
+ }
+
+ prev_dir = QFileInfo{filename}.dir().path();
+
+ auto pixmap = this->ui->lb_display->pixmap();
+ if (!pixmap.save(filename)) {
+ QMessageBox::warning(this, tr("保存图像失败"),
+ tr("无法保存图像 %1").arg(filename));
+ }
+}
\ No newline at end of file
diff --git a/SlopeCraft/CompressEffectViewer.h b/SlopeCraft/CompressEffectViewer.h
new file mode 100644
index 00000000..f9b40453
--- /dev/null
+++ b/SlopeCraft/CompressEffectViewer.h
@@ -0,0 +1,30 @@
+#ifndef SLOPECRAFT_SLOPECRAFT_COMPRESSEFFECTVIEWER_H
+#define SLOPECRAFT_SLOPECRAFT_COMPRESSEFFECTVIEWER_H
+
+#include
+#include
+#include
+
+class SCWind;
+
+class CompressEffectViewer;
+
+namespace Ui {
+class CompressEffectViewer;
+}
+
+class CompressEffectViewer : public QDialog {
+ Q_OBJECT
+ private:
+ std::unique_ptr ui;
+
+ public:
+ explicit CompressEffectViewer(SCWind* parent,
+ const SlopeCraft::converted_image&,
+ const SlopeCraft::structure_3D&);
+ ~CompressEffectViewer();
+ private slots:
+ void on_pb_save_image_clicked() noexcept;
+};
+
+#endif // SLOPECRAFT_SLOPECRAFT_COMPRESSEFFECTVIEWER_H
\ No newline at end of file
diff --git a/SlopeCraft/CompressEffectViewer.ui b/SlopeCraft/CompressEffectViewer.ui
new file mode 100644
index 00000000..678f56c2
--- /dev/null
+++ b/SlopeCraft/CompressEffectViewer.ui
@@ -0,0 +1,60 @@
+
+
+ CompressEffectViewer
+
+
+
+ 0
+ 0
+ 555
+ 460
+
+
+
+
+ 0
+ 0
+
+
+
+ 预览压缩效果
+
+
+ -
+
+
+ 保存图像
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QFrame::StyledPanel
+
+
+
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
+ AdaptiveLabel
+ QLabel
+
+
+
+
+
+
diff --git a/SlopeCraft/CopyableTableView.cpp b/SlopeCraft/CopyableTableView.cpp
new file mode 100644
index 00000000..9067888d
--- /dev/null
+++ b/SlopeCraft/CopyableTableView.cpp
@@ -0,0 +1,68 @@
+#include "CopyableTableView.h"
+#include
+#include
+#include
+#include
+
+CopyableTableView::CopyableTableView(QWidget* parent) : QTableView(parent) {}
+
+CopyableTableView::~CopyableTableView() = default;
+
+bool qmi_sorter(const QModelIndex& a, const QModelIndex& b) noexcept {
+ if (a.row() != b.row()) {
+ return a.row() < b.row();
+ }
+
+ return a.column() < b.column();
+}
+
+bool CopyableTableView::event(QEvent* event) noexcept {
+ if (event->type() == QEvent::Type::KeyPress) {
+ QKeyEvent* const ke = dynamic_cast(event);
+ if (ke != nullptr) {
+ if (ke->matches(QKeySequence::StandardKey::Copy)) {
+ auto sel = this->selectedIndexes();
+
+ std::sort(sel.begin(), sel.end(), qmi_sorter);
+
+ QString text;
+ text.reserve(sel.size() * 64);
+
+ int prev_row = -1;
+
+ for (const auto& qmi : sel) {
+ if (!qmi.isValid()) {
+ continue;
+ }
+ const int r = qmi.row();
+ const bool is_row_changed = (prev_row != r);
+ const bool is_prev_row_valid = (prev_row >= 0);
+
+ prev_row = r;
+
+ if (is_row_changed) {
+ if (is_prev_row_valid) {
+ text.push_back('\n');
+ // another row
+ }
+ // the first element, do not append any char
+
+ } else {
+ text.push_back('\t'); // same row, but different col
+ }
+
+ text.append(qmi.data(Qt::ItemDataRole::DisplayRole).toString());
+ }
+
+ QApplication::clipboard()->setText(text);
+
+ event->accept();
+ emit this->copied();
+ return true;
+ // do copy here
+ }
+ }
+ }
+
+ return QTableView::event(event);
+}
\ No newline at end of file
diff --git a/SlopeCraft/CopyableTableView.h b/SlopeCraft/CopyableTableView.h
new file mode 100644
index 00000000..4b36e397
--- /dev/null
+++ b/SlopeCraft/CopyableTableView.h
@@ -0,0 +1,21 @@
+#ifndef SLOPECRAFT_SLOPECRAFT_COPYABLETABLEVIEW_H
+#define SLOPECRAFT_SLOPECRAFT_COPYABLETABLEVIEW_H
+
+#include
+#include
+
+class CopyableTableView : public QTableView {
+ Q_OBJECT
+ private:
+ public:
+ explicit CopyableTableView(QWidget* parent = nullptr);
+ ~CopyableTableView();
+
+ protected:
+ bool event(QEvent* event) noexcept override;
+
+ signals:
+ void copied();
+};
+
+#endif // SLOPECRAFT_SLOPECRAFT_COPYABLETABLEVIEW_H
\ No newline at end of file
diff --git a/SlopeCraft/ExportTableModel.cpp b/SlopeCraft/ExportTableModel.cpp
new file mode 100644
index 00000000..27795c7d
--- /dev/null
+++ b/SlopeCraft/ExportTableModel.cpp
@@ -0,0 +1,137 @@
+#include "ExportTableModel.h"
+#include "SCWind.h"
+
+ExportTableModel::ExportTableModel(SCWind* parent)
+ : QAbstractTableModel(parent), pool{parent->tasks} {}
+
+ExportTableModel::~ExportTableModel() {}
+
+SCWind* ExportTableModel::scwind() const noexcept {
+ return dynamic_cast(this->parent());
+}
+
+QSize map_size_of_images(QSize image_size) noexcept {
+ const int width = std::ceil(image_size.width() / 128.0);
+ const int height = std::ceil(image_size.height() / 128.0);
+ return QSize{width, height};
+}
+
+// map_range map_range_at_index(const task_pool_t& pool, int first_map_seq_num,
+// int asked_idx) noexcept {
+// assert(asked_idx >= 0 && asked_idx < (int)pool.size());
+//
+// int current_start_seq = first_map_seq_num;
+//
+// for (int idx = 0; idx < asked_idx; idx++) {
+// const auto current_map_size =
+// map_size_of_images(pool[idx].original_image.size());
+// const int current_map_num =
+// current_map_size.height() * current_map_size.width();
+//
+// current_start_seq += current_map_num;
+// }
+//
+// const auto cms = map_size_of_images(pool[asked_idx].original_image.size());
+// const int cmn = cms.height() * cms.width();
+//
+// return map_range{current_start_seq, current_start_seq + cmn - 1};
+// }
+
+QString map_data_filename(QString dir, int seq_number) noexcept {
+ return QStringLiteral("%1/map_%2.dat").arg(dir).arg(seq_number);
+}
+
+void ExportTableModel::refresh() noexcept {
+ // emit
+ emit this->layoutChanged();
+ emit this->dataChanged(this->index(0, 0), this->index(this->rowCount({}),
+ this->columnCount({})));
+}
+
+int ExportTableModel::rowCount(const QModelIndex&) const noexcept {
+ return this->pool.converted_count(this->scwind()->current_color_table(),
+ this->scwind()->current_convert_option());
+}
+
+int ExportTableModel::columnCount(const QModelIndex&) const noexcept {
+ return 6;
+}
+
+QVariant ExportTableModel::data(const QModelIndex& qmi,
+ int role) const noexcept {
+ if (!qmi.isValid()) {
+ return {};
+ }
+
+ const int r = qmi.row();
+ const int c = qmi.column();
+
+ if (r < 0 || r >= (int)this->rowCount({})) {
+ return {};
+ }
+
+ const auto& pair = this->pool.converted_task_at_index(
+ this->scwind()->current_color_table(),
+ this->scwind()->current_convert_option(), static_cast(r));
+ if (!pair) {
+ return {};
+ }
+ // const auto task_global_index = pair.value().first;
+ const auto& task = *pair.value().second;
+
+ if (role == Qt::ItemDataRole::DisplayRole) {
+ const QSize map_size = map_size_of_images(task.original_image.size());
+
+ const int map_count = map_size.height() * map_size.width();
+ const int beg_idx = this->scwind()->current_map_begin_seq_number();
+ const auto range = this->pool.map_range_of(beg_idx, r);
+
+ switch (c) {
+ case 0:
+ return task.filename;
+ case 1:
+ return QStringLiteral("%1 × %2")
+ .arg(task.original_image.height())
+ .arg(task.original_image.width());
+ case 2:
+ return QStringLiteral("%1 × %2 = %3")
+ .arg(map_size.height())
+ .arg(map_size.width())
+ .arg(map_count);
+ case 3:
+ return QStringLiteral("%1 ~ %2").arg(range.first).arg(range.last);
+ case 4:
+ return QStringLiteral("map_%1.dat").arg(range.first);
+ case 5:
+ return QStringLiteral("map_%1.dat").arg(range.last);
+ }
+ }
+
+ return {};
+}
+
+QVariant ExportTableModel::headerData(int section, Qt::Orientation orientation,
+ int role) const noexcept {
+ if (orientation != Qt::Orientation::Horizontal ||
+ role != Qt::ItemDataRole::DisplayRole ||
+ section > this->columnCount({})) {
+ return QAbstractTableModel::headerData(section, orientation, role);
+ }
+
+ switch (section) {
+ case 0:
+ return tr("原图文件名");
+ case 1:
+ return tr("图像大小");
+ case 2:
+ return tr("地图大小");
+ case 3:
+ return tr("地图序号范围");
+ case 4:
+ return tr("第一个地图文件名");
+ case 5:
+ return tr("最后一个地图文件名");
+ default:
+ return QAbstractTableModel::headerData(section, orientation, role);
+ }
+}
\ No newline at end of file
diff --git a/SlopeCraft/ExportTableModel.h b/SlopeCraft/ExportTableModel.h
new file mode 100644
index 00000000..7688c72f
--- /dev/null
+++ b/SlopeCraft/ExportTableModel.h
@@ -0,0 +1,36 @@
+#ifndef SLOPECRAFT_SLOPECRAFT_EXPORTTABLEMODEL_H
+#define SLOPECRAFT_SLOPECRAFT_EXPORTTABLEMODEL_H
+
+#include
+#include "cvt_task.h"
+
+class SCWind;
+
+class ExportTableModel : public QAbstractTableModel {
+ Q_OBJECT
+ private:
+ task_pool& pool;
+
+ public:
+ explicit ExportTableModel(SCWind* parent);
+ ~ExportTableModel();
+
+ int rowCount(const QModelIndex&) const noexcept override;
+ int columnCount(const QModelIndex&) const noexcept override;
+
+ QVariant data(const QModelIndex&, int role) const noexcept override;
+
+ SCWind* scwind() const noexcept;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const noexcept override;
+
+ public slots:
+ void refresh() noexcept;
+};
+
+QSize map_size_of_images(QSize image_size) noexcept;
+
+QString map_data_filename(QString dir, int seq_number) noexcept;
+
+#endif // SLOPECRAFT_SLOPECRAFT_EXPORTTABLEMODEL_H
\ No newline at end of file
diff --git a/SlopeCraft/PoolModel.cpp b/SlopeCraft/PoolModel.cpp
new file mode 100644
index 00000000..75f9cf27
--- /dev/null
+++ b/SlopeCraft/PoolModel.cpp
@@ -0,0 +1,352 @@
+#include "PoolModel.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+PoolModel::PoolModel(SCWind* scw)
+ : QAbstractListModel(scw), pool{scw->tasks}, scwind{scw} {
+ assert(scw != nullptr);
+}
+
+PoolModel::~PoolModel() {}
+
+const QPixmap& PoolModel::icon_empty() noexcept {
+ static QPixmap img{":/images/empty.png"};
+ return img;
+}
+
+const QPixmap& PoolModel::icon_converted() noexcept {
+ static QPixmap img{":/images/converted.png"};
+ return img;
+}
+
+QVariant PoolModel::data(const QModelIndex& idx, int role) const {
+ const auto& task = this->pool.at(idx.row());
+ if (role == Qt::ItemDataRole::DisplayRole) {
+ return task.filename;
+ }
+
+ if (role == Qt::ItemDataRole::DecorationRole) {
+ assert(this->_listview != nullptr);
+ if (this->_listview->viewMode() == QListView::ViewMode::ListMode) {
+ return QVariant{};
+ }
+ auto raw_image = QPixmap::fromImage(task.original_image);
+ auto img = raw_image.scaledToWidth(this->_listview->size().width());
+
+ if (!task.is_converted_with(this->scwind->current_color_table(),
+ this->scwind->current_convert_option())) {
+ this->draw_icon(img, icon_empty(), 0);
+ } else {
+ this->draw_icon(img, icon_converted(), 0);
+ }
+
+ return QIcon{img};
+ }
+
+ return QVariant{};
+}
+
+QPixmap scale_up_to_3232(const QPixmap& original_pixmap,
+ QSize min_size) noexcept {
+ const QSize old_size = original_pixmap.size();
+ const QSize new_size{std::min(old_size.width(), min_size.width()),
+ std::min(old_size.height(), min_size.height())};
+ if (old_size == new_size) {
+ return original_pixmap;
+ }
+ QImage new_img{new_size, QImage::Format_ARGB32};
+ memset(new_img.scanLine(0), 0, new_img.sizeInBytes());
+ {
+ QPainter p{&new_img};
+ p.drawPixmap(0, 0, original_pixmap);
+ p.end();
+ }
+
+ return QPixmap::fromImage(new_img);
+}
+
+void PoolModel::draw_icon(QPixmap& image, const QPixmap& icon, int index,
+ QWidget* ptr_to_report_error) noexcept {
+ assert(index >= 0);
+ if (icon.size() != QSize{32, 32}) [[unlikely]] {
+ QMessageBox::critical(ptr_to_report_error,
+ QObject::tr("绘制图标时发现错误"),
+ tr("被绘制的图标尺寸应当是 32*32,但实际上是%1*%"
+ "2。这属于 SlopeCraft "
+ "内部错误,请向开发者反馈。SlopeCraft 必须崩溃。")
+ .arg(icon.size().height())
+ .arg(icon.size().width()));
+ abort();
+ // return;
+ }
+ {
+ const QSize expected_min_size{(index + 1) * 32, 32};
+
+ if (image.height() < expected_min_size.height() ||
+ image.width() < expected_min_size.width()) [[unlikely]] {
+ image = scale_up_to_3232(image, expected_min_size);
+ }
+ }
+ QPainter painter{&image};
+
+ const QSize img_size = image.size();
+
+ const int x = img_size.width() - (index + 1) * 32;
+ const int y = img_size.height() - 32;
+ painter.drawPixmap(x, y, icon);
+ painter.end();
+}
+
+CvtPoolModel::CvtPoolModel(SCWind* scw) : PoolModel{scw} {}
+
+CvtPoolModel::~CvtPoolModel() {}
+
+Qt::DropActions CvtPoolModel::supportedDropActions() const {
+ return Qt::DropActions{Qt::DropAction::MoveAction,
+ Qt::DropAction::CopyAction};
+}
+
+Qt::ItemFlags CvtPoolModel::flags(const QModelIndex& index) const {
+ Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
+
+ if (index.isValid())
+ return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
+ else
+ return Qt::ItemIsDropEnabled | defaultFlags;
+}
+
+QStringList CvtPoolModel::mimeTypes() const {
+ return QStringList{"text/plain", "image/png"};
+}
+
+const char mime_data_type[] = "application/slopecraft_pool_index";
+
+QByteArray encode_indices(const std::vector& temp) noexcept {
+ QByteArray qba{reinterpret_cast(temp.data()),
+ qsizetype(temp.size() * sizeof(int))};
+ return qba;
+}
+
+std::vector decode_indices(const QByteArray& qbav) noexcept {
+ if (qbav.size() % sizeof(int) != 0) {
+ return {};
+ }
+
+ const int size = qbav.size() / sizeof(int);
+
+ const int* const data = reinterpret_cast(qbav.data());
+
+ std::vector ret{data, data + size};
+ return ret;
+}
+
+QMimeData* CvtPoolModel::mimeData(const QModelIndexList& indexes) const {
+ std::vector temp;
+ temp.reserve(indexes.size());
+
+ for (const auto& midx : indexes) {
+ temp.emplace_back(midx.row());
+ }
+
+ QMimeData* ret = new QMimeData;
+
+ ret->setData(mime_data_type, encode_indices(temp));
+
+ return ret;
+}
+
+bool CvtPoolModel::canDropMimeData(const QMimeData* data, Qt::DropAction, int,
+ int col, const QModelIndex& parent) const {
+ // if (parent.isValid()) {
+ // return false;
+ // }
+
+ // if (col > 0) {
+ // return false;
+ // }
+
+ if (data->hasFormat(mime_data_type)) {
+ const int bytes = data->data(mime_data_type).size();
+
+ if (bytes % sizeof(int) not_eq 0) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ // // disable moving multiple items, because the behavior is incorrect
+ // if (bytes / sizeof(int) == 1) {
+ // return true;
+ // }
+ //
+ // return false;
+}
+
+template
+void iterator_add(it_t& it, int n) noexcept {
+ assert(n >= 0);
+ for (int i = 0; i < n; i++) {
+ ++it;
+ }
+}
+
+template
+void map_indices(std::vector& pool, std::vector moved_indices,
+ int begin_idx) noexcept {
+ std::list temp_pool;
+ for (T& t : pool) {
+ temp_pool.emplace_back(std::move(t));
+ }
+
+ std::sort(moved_indices.begin(), moved_indices.end());
+ std::vector::iterator> src_it_vec;
+ src_it_vec.reserve(moved_indices.size());
+ {
+ int idx = 0;
+ auto it = temp_pool.begin();
+ for (int sidx : moved_indices) {
+ const int offset = sidx - idx;
+ assert(offset >= 0);
+ iterator_add(it, offset);
+
+ src_it_vec.emplace_back(it);
+ }
+ }
+
+ auto begin_it = temp_pool.begin();
+
+ iterator_add(begin_it, begin_idx);
+
+ for (auto srcit : src_it_vec) {
+ temp_pool.emplace(begin_it, std::move(*srcit));
+ }
+
+ for (auto srcit : src_it_vec) {
+ temp_pool.erase(srcit);
+ }
+
+ pool.clear();
+ for (auto& t : temp_pool) {
+ pool.emplace_back(std::move(t));
+ }
+}
+
+bool CvtPoolModel::dropMimeData(const QMimeData* data, Qt::DropAction action,
+ int row, int column,
+ const QModelIndex& parent) {
+ if (not this->canDropMimeData(data, action, row, column, parent)) {
+ return false;
+ }
+
+ if (action == Qt::IgnoreAction) {
+ return true;
+ }
+
+ int begin_row;
+
+ if (row != -1)
+ begin_row = row;
+ else if (parent.isValid())
+ begin_row = parent.row();
+ else
+ begin_row = this->rowCount(QModelIndex{});
+
+ {
+ auto src_indices = decode_indices(data->data(mime_data_type));
+
+ if (src_indices.size() <= 0) {
+ return true;
+ }
+ std::sort(src_indices.begin(), src_indices.end(), std::greater{});
+
+ // Move all moved tasks into moved_tasks
+ std::stack moved_tasks;
+ for (int src_idx : src_indices) {
+ cvt_task temp;
+ std::swap(temp, this->pool[src_idx]);
+ moved_tasks.emplace(std::move(temp));
+ this->pool.erase(this->pool.begin() + src_idx);
+ }
+ assert(moved_tasks.size() == src_indices.size());
+ const int insert_dest = begin_row - moved_tasks.size();
+ assert(insert_dest <= this->pool.size());
+ auto it_dest = this->pool.begin() + insert_dest;
+ while (not moved_tasks.empty()) {
+ cvt_task temp;
+ std::swap(temp, moved_tasks.top());
+ moved_tasks.pop();
+ it_dest = this->pool.insert(it_dest, std::move(temp));
+ }
+ }
+ this->refresh();
+
+ return true;
+}
+
+ExportPoolModel::ExportPoolModel(SCWind* scw) : PoolModel(scw) {}
+
+ExportPoolModel::~ExportPoolModel() {}
+
+int ExportPoolModel::rowCount(const QModelIndex& midx) const {
+ if (midx.isValid()) {
+ return 0;
+ }
+ return this->pool.converted_count(this->scwind->current_color_table(),
+ this->scwind->current_convert_option());
+}
+
+std::optional ExportPoolModel::export_index_to_global_index(
+ int eidx) const noexcept {
+ if (eidx < 0) {
+ return std::nullopt;
+ }
+ return this->pool.export_index_to_global_index(
+ this->scwind->current_color_table(),
+ this->scwind->current_convert_option(), eidx);
+}
+
+cvt_task* ExportPoolModel::export_idx_to_task_ptr(int eidx) const noexcept {
+ auto global_index = this->export_index_to_global_index(eidx);
+ if (!global_index) {
+ return nullptr;
+ }
+ return &this->pool[global_index.value()];
+}
+
+QVariant ExportPoolModel::data(const QModelIndex& midx, int role) const {
+ const auto taskp = this->export_idx_to_task_ptr(midx.row());
+ assert(taskp);
+ if (taskp == nullptr) {
+ return {};
+ }
+
+ auto& task = *taskp;
+
+ if (role == Qt::ItemDataRole::DisplayRole) {
+ return task.filename;
+ }
+
+ if (role == Qt::ItemDataRole::DecorationRole) {
+ assert(this->_listview != nullptr);
+ if (this->_listview->viewMode() == QListView::ViewMode::ListMode) {
+ return QVariant{};
+ }
+ auto raw_image = QPixmap::fromImage(task.original_image);
+ auto img = raw_image.scaledToWidth(this->_listview->size().width());
+ if (!task.is_built_with(this->scwind->current_color_table(),
+ this->scwind->current_convert_option(),
+ this->scwind->current_build_option())) {
+ this->draw_icon(img, icon_empty(), 0);
+ } else {
+ this->draw_icon(img, icon_converted(), 0);
+ }
+ return QIcon{img};
+ }
+
+ return QVariant{};
+}
\ No newline at end of file
diff --git a/SlopeCraft/PoolModel.h b/SlopeCraft/PoolModel.h
new file mode 100644
index 00000000..c9cb3366
--- /dev/null
+++ b/SlopeCraft/PoolModel.h
@@ -0,0 +1,103 @@
+#ifndef SLOPECRAFT_SLOPECRAFT_POOLMODEL_H
+#define SLOPECRAFT_SLOPECRAFT_POOLMODEL_H
+
+#include
+#include
+#include
+#include
+#include
+
+class SCWind;
+
+class PoolModel : public QAbstractListModel {
+ Q_OBJECT
+ protected:
+ task_pool& pool;
+ QListView* _listview{nullptr};
+ SCWind* const scwind;
+
+ static const QPixmap& icon_empty() noexcept;
+ static const QPixmap& icon_converted() noexcept;
+
+ public:
+ explicit PoolModel(SCWind* scw);
+ ~PoolModel();
+
+ int rowCount(const QModelIndex& midx) const override {
+ if (midx.isValid()) {
+ return 0;
+ }
+ return this->pool.size();
+ }
+
+ QModelIndex parent(const QModelIndex&) const override {
+ return QModelIndex{};
+ }
+
+ QVariant data(const QModelIndex& idx, int role) const override;
+
+ public slots:
+ void refresh() noexcept {
+ emit dataChanged(this->index(0, 0), this->index(this->rowCount({}), 0));
+ }
+
+ public:
+ task_pool& attached_pool() noexcept { return this->pool; }
+ const task_pool& attached_pool() const noexcept { return this->pool; }
+ // void set_pool(task_pool_t* _pool) noexcept { this->pool = _pool; }
+
+ QListView* attached_listview() noexcept { return this->_listview; }
+ const QListView* attached_listview() const noexcept {
+ return this->_listview;
+ }
+ void set_listview(QListView* lv) noexcept { this->_listview = lv; }
+
+ static void draw_icon(QPixmap& image, const QPixmap& icon, int index,
+ QWidget* ptr_to_report_error) noexcept;
+
+ void draw_icon(QPixmap& image, const QPixmap& icon,
+ int index) const noexcept {
+ draw_icon(image, icon, index, this->_listview);
+ }
+};
+
+class CvtPoolModel : public PoolModel {
+ Q_OBJECT
+ public:
+ explicit CvtPoolModel(SCWind* scw);
+ ~CvtPoolModel();
+
+ Qt::DropActions supportedDropActions() const override;
+
+ Qt::ItemFlags flags(const QModelIndex& index) const override;
+ QStringList mimeTypes() const override;
+
+ QMimeData* mimeData(const QModelIndexList& indexes) const override;
+
+ bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row,
+ int column, const QModelIndex& parent) const override;
+
+ bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row,
+ int column, const QModelIndex& parent) override;
+};
+
+class ExportPoolModel : public PoolModel {
+ Q_OBJECT
+ public:
+ explicit ExportPoolModel(SCWind* scw);
+ ~ExportPoolModel();
+
+ int rowCount(const QModelIndex& midx) const override;
+
+ // std::vector iteration_map() const noexcept {
+ // return ::iteration_map(*this->pool);
+ // }
+
+ std::optional export_index_to_global_index(int eidx) const noexcept;
+
+ cvt_task* export_idx_to_task_ptr(int eidx) const noexcept;
+
+ QVariant data(const QModelIndex& idx, int role) const override;
+};
+
+#endif // SLOPECRAFT_SLOPECRAFT_POOLMODEL_H
\ No newline at end of file
diff --git a/SlopeCraft/PreviewWind.cpp b/SlopeCraft/PreviewWind.cpp
new file mode 100644
index 00000000..243a97f9
--- /dev/null
+++ b/SlopeCraft/PreviewWind.cpp
@@ -0,0 +1,254 @@
+#include
+#include
+#include
+#include
+#include
+#include "PreviewWind.h"
+#include "ui_PreviewWind.h"
+#include "SCWind.h"
+#include "CopyableTableView.h"
+
+PreviewWind::PreviewWind(QWidget* parent)
+ : QDialog(parent), ui(new Ui::PreviewWind) {
+ this->ui->setupUi(this);
+
+ this->mmp = new MaterialModel(this);
+ this->mmp->set_mat_list_pointer(&this->mat_list);
+ // this->ui->tv_mat->setModel(this->mmp);
+
+ // connect(this->mmp,&QAbstractTableModel::dataChanged,this->ui->tv_mat,&QTableView::)
+
+ connect(this->ui->cb_show_in_stacks, &QCheckBox::toggled, this->mmp,
+ &MaterialModel::refresh);
+ connect(this->ui->cb_sort_option, &QComboBox::currentIndexChanged, this->mmp,
+ &MaterialModel::refresh);
+ this->ui->tv_mat->setModel(this->mmp);
+
+ auto on_copied = [this]() noexcept {
+ this->setWindowTitle(
+ tr("%1 -- 表格内容已复制到剪贴板").arg(tr("查看材料列表")));
+ };
+ connect(this->ui->tv_mat, &CopyableTableView::copied, on_copied);
+ connect(this->ui->pb_copy_to_clipboard, &QPushButton::clicked,
+ this->ui->tv_mat, &CopyableTableView::copied);
+}
+
+PreviewWind::~PreviewWind() {}
+
+void PreviewWind::set_size(std::span size) & noexcept {
+ QString size_str =
+ tr("大小:%1 × %2 × %3").arg(size[0]).arg(size[1]).arg(size[2]);
+
+ this->ui->lb_show_size->setText(size_str);
+
+ this->ui->lb_show_volume->setText(
+ tr("体积:%1").arg(size[0] * size[1] * size[2]));
+}
+
+void PreviewWind::set_total_count(size_t count) & noexcept {
+ this->ui->lb_block_count->setText(tr("方块总数:%1").arg(count));
+}
+
+void PreviewWind::setup_data(
+ const SlopeCraft::color_table& table,
+ const SlopeCraft::structure_3D& structure) noexcept {
+ std::array count_list;
+ count_list.fill(0);
+ table.stat_blocks(structure, count_list.data());
+ {
+ size_t total_blocks{0};
+ for (auto c : count_list) {
+ total_blocks += c;
+ }
+ this->set_total_count(total_blocks);
+ }
+
+ std::vector blkp_arr;
+ table.visit_blocks([&blkp_arr](const SlopeCraft::mc_block_interface* b) {
+ blkp_arr.emplace_back(b);
+ });
+
+ this->mat_list.reserve(blkp_arr.size());
+ for (size_t idx = 0; idx < blkp_arr.size(); idx++) {
+ if (count_list[idx] > 0) {
+ this->mat_list.emplace_back(
+ material_item{.blk = blkp_arr[idx], .count = count_list[idx]});
+ }
+ }
+
+ {
+ std::array sz{structure.shape_x(), structure.shape_y(),
+ structure.shape_z()};
+ this->set_size(sz);
+ }
+
+ // this->ui->tv_mat->setModel(this->mmp);
+ emit this->mmp->layoutChanged();
+ // this->mmp->refresh();
+ // this->ui->tv_mat->doItemsLayout();
+}
+
+bool PreviewWind::is_unit_stack() const noexcept {
+ return this->ui->cb_show_in_stacks->isChecked();
+}
+
+PreviewWind::sort_option PreviewWind::current_sort_option() const noexcept {
+ const int cidx = this->ui->cb_sort_option->currentIndex();
+ switch (cidx) {
+ case 1:
+ return sort_option::ascending;
+ case 2:
+ return sort_option::descending;
+ default:
+ return sort_option::no_sort;
+ }
+}
+
+void PreviewWind::on_pb_export_file_clicked() noexcept {
+ const QString out_file = QFileDialog::getSaveFileName(
+ this, tr("保存材料表"), this->export_mat_list_prev_dir, "*.csv");
+ if (out_file.isEmpty()) {
+ return;
+ }
+ this->export_mat_list_prev_dir = QFileInfo{out_file}.dir().path();
+
+ QFile ofile{out_file, this};
+ if (not ofile.open(QIODevice::OpenMode::enum_type::WriteOnly)) {
+ QMessageBox::warning(this, tr("保存材料表失败"),
+ tr("无法打开文件 \"%1\",详细信息:%2")
+ .arg(out_file, ofile.errorString()));
+ return;
+ }
+
+ ofile.write("\"Block name\",\"Block id\",\"Count\"\n");
+ const auto current_version =
+ dynamic_cast(this->parent())->selected_version();
+ for (const auto& mat : this->mat_list) {
+ QString line =
+ QStringLiteral("\"%1\",\"%2\",%3\n")
+ .arg(mat.blk->getNameEN(), mat.blk->idForVersion(current_version))
+ .arg(mat.count);
+ ofile.write(line.toLocal8Bit());
+ }
+ ofile.close();
+}
+
+MaterialModel::MaterialModel(PreviewWind* parent)
+ : QAbstractTableModel(parent), pwind(parent) {}
+
+MaterialModel::~MaterialModel() {}
+
+void MaterialModel::refresh() noexcept {
+ emit this->dataChanged(
+ this->index(0, 0),
+ this->index(this->rowCount() - 1, this->columnCount() - 1));
+}
+
+QString format_num(int num, int stack_size) noexcept {
+ assert(stack_size > 0);
+ const int sb_size = stack_size * 27;
+
+ const int sb_num = num / sb_size;
+ num -= sb_num * sb_size;
+
+ const int stack_num = num / stack_size;
+ num -= stack_num * stack_size;
+
+ const int left_num = num;
+
+ if (sb_num > 0) {
+ return MaterialModel::tr("%1 盒 + %2 组 + %3 个")
+ .arg(sb_num)
+ .arg(stack_num)
+ .arg(left_num);
+ }
+
+ if (stack_num > 0) {
+ return MaterialModel::tr("%1 组 + %2 个").arg(stack_num).arg(left_num);
+ }
+
+ return MaterialModel::tr("%1 个").arg(left_num);
+}
+
+QVariant MaterialModel::data(const QModelIndex& qmi, int role) const noexcept {
+ if (!qmi.isValid()) {
+ return {};
+ }
+
+ const int r = qmi.row();
+ const int c = qmi.column();
+
+ if (r >= (int)this->mat_list->size()) {
+ return {};
+ }
+ if (c >= 2) {
+ return {};
+ }
+
+ using it_t = std::vector::const_iterator;
+ std::vector its;
+ {
+ its.reserve(this->mat_list->size());
+
+ for (auto it = this->mat_list->begin(); it != this->mat_list->end(); ++it) {
+ its.emplace_back(it);
+ }
+
+ const auto sort_opt = this->pwind->current_sort_option();
+
+ if (sort_opt != PreviewWind::sort_option::no_sort) {
+ auto sort_fun = [sort_opt](it_t a, it_t b) -> bool {
+ if (a->count == b->count) {
+ return a->blk < b->blk;
+ }
+
+ if (sort_opt == PreviewWind::sort_option::ascending) {
+ return a->count < b->count;
+ }
+
+ return a->count > b->count;
+ };
+
+ std::sort(its.begin(), its.end(), sort_fun);
+ }
+ }
+
+ const auto& mat = *its[r];
+
+ if (role == Qt::ItemDataRole::DisplayRole) {
+ if (c == 0) {
+ const auto lang =
+ dynamic_cast(this->pwind->parent())->lang();
+ if (lang == ::SCL_language::Chinese) {
+ return QString::fromUtf8(mat.blk->getNameZH());
+ } else {
+ return QString::fromUtf8(mat.blk->getNameEN());
+ }
+ }
+
+ if (c == 1) {
+ if (!this->pwind->is_unit_stack()) {
+ return QString::number(mat.count);
+ }
+ const int stack_size = std::max(1, mat.blk->getStackSize());
+ return format_num(mat.count, stack_size);
+ }
+ }
+
+ if (role == Qt::ItemDataRole::DecorationRole) {
+ if (c == 0) {
+ QImage img{16, 16, QImage::Format_ARGB32};
+ mat.blk->getImage((uint32_t*)img.scanLine(0));
+ return QIcon{QPixmap::fromImage(img)};
+ }
+ }
+
+ return {};
+}
+
+int MaterialModel::columnCount(const QModelIndex&) const noexcept { return 2; }
+
+int MaterialModel::rowCount(const QModelIndex&) const noexcept {
+ // const int rows = this->mat_list->size();
+ return this->mat_list->size();
+}
\ No newline at end of file
diff --git a/SlopeCraft/PreviewWind.h b/SlopeCraft/PreviewWind.h
new file mode 100644
index 00000000..fd8b7941
--- /dev/null
+++ b/SlopeCraft/PreviewWind.h
@@ -0,0 +1,80 @@
+#ifndef SLOPECRAFT_SLOPECRAFT_PREVIEWWIND_H
+#define SLOPECRAFT_SLOPECRAFT_PREVIEWWIND_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class PreviewWind;
+class MaterialModel;
+
+namespace Ui {
+class PreviewWind;
+}
+
+struct material_item {
+ const SlopeCraft::mc_block_interface* blk{nullptr};
+ size_t count{0};
+};
+
+class PreviewWind : public QDialog {
+ Q_OBJECT
+ private:
+ std::unique_ptr ui;
+ std::vector mat_list;
+ MaterialModel* mmp{nullptr};
+
+ QString export_mat_list_prev_dir;
+
+ void set_size(std::span size) & noexcept;
+ void set_total_count(size_t count) & noexcept;
+
+ public:
+ explicit PreviewWind(QWidget* parent = nullptr);
+ ~PreviewWind();
+
+ const auto& material_list() const noexcept { return this->mat_list; }
+ void setup_data(const SlopeCraft::color_table&,
+ const SlopeCraft::structure_3D&) noexcept;
+
+ enum class sort_option { no_sort, ascending, descending };
+
+ bool is_unit_stack() const noexcept;
+ sort_option current_sort_option() const noexcept;
+
+ private slots:
+ void on_pb_export_file_clicked() noexcept;
+};
+
+class MaterialModel : public QAbstractTableModel {
+ Q_OBJECT
+ private:
+ const std::vector* mat_list{nullptr};
+ const PreviewWind* const pwind;
+
+ public:
+ explicit MaterialModel(PreviewWind* parent = nullptr);
+ ~MaterialModel();
+
+ auto mat_list_pointer() const noexcept { return this->mat_list; }
+
+ void set_mat_list_pointer(const std::vector* mlp) noexcept {
+ this->mat_list = mlp;
+ }
+
+ public slots:
+ void refresh() noexcept;
+
+ public:
+ QVariant data(const QModelIndex& qmi,
+ int role = Qt::DisplayRole) const noexcept override;
+ int columnCount(
+ const QModelIndex& parent = QModelIndex{}) const noexcept override;
+ int rowCount(
+ const QModelIndex& parent = QModelIndex{}) const noexcept override;
+};
+
+#endif // SLOPECRAFT_SLOPECRAFT_PREVIEWWIND_H
\ No newline at end of file
diff --git a/SlopeCraft/PreviewWind.ui b/SlopeCraft/PreviewWind.ui
new file mode 100644
index 00000000..ff1533cb
--- /dev/null
+++ b/SlopeCraft/PreviewWind.ui
@@ -0,0 +1,160 @@
+
+
+ PreviewWind
+
+
+
+ 0
+ 0
+ 604
+ 444
+
+
+
+ 查看材料列表
+
+
+ -
+
+
+
+ 0
+ 24
+
+
+
+
+
+
+ QFrame::Shape::StyledPanel
+
+
+
+
+
+
+ -
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+ 200
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
+ 2
+
+
-
+
+ 不排序
+
+
+ -
+
+ 升序
+
+
+ -
+
+ 降序
+
+
+
+
+ -
+
+
+
+
+
+ QFrame::Shape::StyledPanel
+
+
+
+
+
+
+ -
+
+
+
+
+
+ QFrame::Shape::StyledPanel
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 按组显示
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Orientation::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ 导出为文件
+
+
+
+ -
+
+
+ 复制到剪贴板
+
+
+
+
+
+
+
+ CopyableTableView
+ QTableView
+
+
+
+
+
+
diff --git a/SlopeCraft/SCWind.cpp b/SlopeCraft/SCWind.cpp
new file mode 100644
index 00000000..7a5c56fc
--- /dev/null
+++ b/SlopeCraft/SCWind.cpp
@@ -0,0 +1,1381 @@
+#include "SCWind.h"
+#include "ui_SCWind.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+const QString SCWind::update_url{
+ "https://api.github.com/repos/SlopeCraft/SlopeCraft/releases"};
+
+// #include "PoolWidget.h"
+SCWind::SCWind(QWidget *parent) : QMainWindow(parent), ui(new Ui::SCWind) {
+ this->ui->setupUi(this);
+
+ this->connect_slots();
+ {
+ // create translators
+ const char *const translator_filenames[] = {
+ ":/i18n/SlopeCraft_en_US.qm", ":/i18n/BlockListManager_en_US.qm",
+ ":/i18n/VersionDialog_en_US.qm", ":/i18n/MemoryPolicyDialog_en_US.qm"};
+ /*this->translators.reserve(sizeof(translator_filenames) /
+ sizeof(const char *));
+ */
+ for (const char *tf : translator_filenames) {
+ QTranslator *t = new QTranslator{this};
+ QString filename = QString::fromUtf8(tf);
+ const bool ok = t->load(filename);
+ if (!ok) {
+ QMessageBox::warning(this, "Failed to load translate file",
+ QStringLiteral("Failed to load %1").arg(filename));
+ }
+
+ this->translators.emplace_back(t);
+ }
+ }
+
+ // initialize blm
+ {
+ this->ui->blm->setup_basecolors();
+ this->ui->blm->set_version_callback(
+ [this]() { return this->selected_version(); });
+
+ QDir::setCurrent(QCoreApplication::applicationDirPath());
+ const QString blocks_dir_path = QStringLiteral("./Blocks");
+ const QDir blocks_dir{blocks_dir_path};
+ if (not blocks_dir.exists()) {
+ QMessageBox::critical(
+ this, tr("无法加载方块列表"),
+ tr("存储方块列表的文件夹 \"%1\" 不存在,或不是文件夹。")
+ .arg(blocks_dir_path) +
+ tr("SlopeCraft 必须退出。"));
+ exit(1);
+ }
+ if (not this->ui->blm->add_blocklist(
+ QStringLiteral("%1/%2").arg(blocks_dir_path, "FixedBlocks.zip"))) {
+ QMessageBox::critical(
+ this, tr("无法加载方块列表"),
+ tr("无法加载 FixedBlocks.zip ,SlopeCraft 缺乏最基础的方块列表。") +
+ tr("SlopeCraft 必须退出。"));
+ exit(1);
+ }
+ QString default_loaded[] = {"CustomBlocks.zip"};
+ QString fail_list;
+ int fail_counter = 0;
+ for (auto file : default_loaded) {
+ assert(file not_eq "FixedBlocks.zip");
+ const QString abs_name =
+ QStringLiteral("%1/%2").arg(blocks_dir_path, file);
+ if (not this->ui->blm->add_blocklist(abs_name)) {
+ fail_counter++;
+ fail_list.append(abs_name);
+ fail_list.append('\n');
+ }
+ }
+ this->ui->blm->finish_blocklist();
+ if (fail_counter > 0) {
+ QMessageBox::warning(
+ this, tr("部分方块列表加载失败"),
+ tr("以下 %1 "
+ "个方块列表文件无法被加载:\n%"
+ "2\n由于它们不是必需,你可以忽略此错误并继续使用。")
+ .arg(fail_counter)
+ .arg(fail_list));
+ }
+
+ for (auto btnp : this->version_buttons()) {
+ connect(btnp, &QRadioButton::toggled, this,
+ &SCWind::when_version_buttons_toggled);
+ }
+
+ for (auto btnp : this->type_buttons()) {
+ connect(btnp, &QRadioButton::toggled, this,
+ &SCWind::when_type_buttons_toggled);
+ }
+
+ connect(this->ui->blm, &BlockListManager::changed, this,
+ &SCWind::when_blocklist_changed);
+ }
+
+ // initialize cvt pool model
+ {
+ this->cvt_pool_model = new CvtPoolModel{this};
+ this->ui->lview_pool_cvt->setModel(this->cvt_pool_model);
+ this->cvt_pool_model->set_listview(this->ui->lview_pool_cvt);
+ connect(this->ui->lview_pool_cvt->selectionModel(),
+ &QItemSelectionModel::selectionChanged, this,
+ &SCWind::when_cvt_pool_selectionChanged);
+
+ this->export_pool_model = new ExportPoolModel{this};
+ this->ui->lview_pool_export->setModel(this->export_pool_model);
+ this->export_pool_model->set_listview(this->ui->lview_pool_export);
+ connect(this->ui->lview_pool_export->selectionModel(),
+ &QItemSelectionModel::selectionChanged, this,
+ &SCWind::when_export_pool_selectionChanged);
+
+ connect(this, &SCWind::image_changed, this->cvt_pool_model,
+ &CvtPoolModel::refresh);
+ connect(this, &SCWind::image_changed, this->export_pool_model,
+ &ExportPoolModel::refresh);
+ connect(this, &SCWind::image_changed, [this]() {
+ this->ui->lview_pool_cvt->doItemsLayout();
+ this->ui->lview_pool_export->doItemsLayout();
+ });
+
+ connect(this->ui->tw_cvt_image, &QTabWidget::currentChanged, this,
+ &SCWind::when_cvt_pool_selectionChanged);
+ }
+ {
+ this->export_table_model = new ExportTableModel{this};
+ this->ui->tview_export_fileonly->setModel(this->export_table_model);
+
+ connect(this, &SCWind::image_changed, this->export_table_model,
+ &ExportTableModel::refresh);
+ connect(this->ui->sb_file_start_idx, &QSpinBox::valueChanged,
+ this->export_table_model, &ExportTableModel::refresh);
+ connect(this, &SCWind::image_changed, this->export_table_model,
+ &ExportTableModel::refresh);
+
+ connect(this->ui->lview_pool_export->selectionModel(),
+ &QItemSelectionModel::selectionChanged, this,
+ &SCWind::when_data_file_command_changed);
+ connect(this->ui->sb_file_start_idx, &QSpinBox::valueChanged, this,
+ &SCWind::when_data_file_command_changed);
+ connect(this->ui->cb_mc_version_geq_1_20_5, &QCheckBox::clicked, this,
+ &SCWind::when_data_file_command_changed);
+ }
+
+ for (QRadioButton *rbp : this->export_type_buttons()) {
+ connect(rbp, &QRadioButton::clicked, this,
+ &SCWind::when_export_type_toggled);
+ }
+ for (QRadioButton *rbp : this->preset_buttons_no_custom()) {
+ connect(rbp, &QRadioButton::clicked, this, &SCWind::when_preset_clicked);
+ }
+ {
+ for (QRadioButton *rbp : this->algo_buttons()) {
+ connect(rbp, &QRadioButton::clicked, this,
+ &SCWind::when_algo_btn_clicked);
+ }
+ connect(this->ui->cb_algo_dither, &QCheckBox::clicked, this,
+ &SCWind::when_algo_btn_clicked);
+ }
+ // setup presets
+ {
+ try {
+ this->default_presets[0] =
+ load_preset("./Blocks/Presets/vanilla.sc_preset_json");
+ this->default_presets[1] =
+ load_preset("./Blocks/Presets/cheap.sc_preset_json");
+ this->default_presets[2] =
+ load_preset("./Blocks/Presets/elegant.sc_preset_json");
+ this->default_presets[3] =
+ load_preset("./Blocks/Presets/shiny.sc_preset_json");
+ } catch (std::exception &e) {
+ QMessageBox::critical(this, tr("加载默认预设失败"),
+ tr("一个或多个内置的预设不能被解析。SlopeCraft "
+ "可能已经损坏,请重新安装。\n具体报错信息:\n%1")
+ .arg(e.what()));
+ abort();
+ }
+ }
+ // initialize combobox for map facing
+ {
+ const std::array key1{tr("墙面"), tr("顶面"), tr("底面")};
+ const std::array key2{tr("北"), tr("南"), tr("东"), tr("西")};
+ for (auto facing : magic_enum::enum_values()) {
+ const int idx = static_cast(facing);
+ const QString text = tr("%1,向%2").arg(key1[idx / 4], key2[idx % 4]);
+ this->ui->cb_map_direction->addItem(text, QVariant::fromValue(facing));
+ }
+ this->ui->cb_map_direction->setCurrentIndex(0);
+ }
+
+ this->when_preset_clicked();
+
+ connect(this->ui->pb_manage_block_list, &QPushButton::clicked, this,
+ &SCWind::on_ac_blocklist_triggered);
+}
+
+SCWind::~SCWind() {
+ delete this->ui;
+ {
+ QDir cache_dir{this->cache_root_dir()};
+ if (cache_dir.exists()) {
+ cache_dir.removeRecursively();
+ }
+ }
+}
+
+QString SCWind::cache_root_dir() const noexcept {
+ const auto pid = QApplication::applicationPid();
+
+ const QString sys_cache_dir = QDir::tempPath();
+ const QString cache_dir =
+ QStringLiteral("%1/SlopeCraft/pid=%2").arg(sys_cache_dir).arg(pid);
+ return cache_dir;
+}
+
+SlopeCraft::color_table *SCWind::current_color_table() noexcept {
+ auto settings = colortable_settings{this->ui->blm->current_selection(),
+ this->selected_type()};
+ {
+ auto find = this->color_tables.find(settings);
+ if (find not_eq this->color_tables.end()) {
+ return find->second.get();
+ }
+ }
+ {
+ std::vector a;
+ std::vector b;
+
+ this->ui->blm->get_blocklist(a, b);
+ SlopeCraft::color_table_create_info ci;
+ ci.map_type = this->selected_type();
+ ci.mc_version = this->selected_version();
+ for (size_t i = 0; i < 64; i++) {
+ ci.blocks[i] = b[i];
+ ci.basecolor_allow_LUT[i] = a[i];
+ }
+ std::unique_ptr ptr{
+ SlopeCraft::SCL_create_color_table(ci)};
+ if (ptr == nullptr) {
+ // QMessageBox::warning(this, tr("设置方块列表失败"),
+ // tr("您设置的方块列表可能存在错误"));
+ return nullptr;
+ }
+
+ auto it = this->color_tables.emplace(settings, std::move(ptr));
+ return it.first->second.get();
+ }
+}
+
+SlopeCraft::ui_callbacks SCWind::ui_callbacks() const noexcept {
+ return SlopeCraft::ui_callbacks{
+ .wind = const_cast(this),
+ .cb_keep_awake = [](void *) { QApplication::processEvents(); },
+ .cb_report_error =
+ [](void *wind, SCL_errorFlag ef, const char *msg) {
+ reinterpret_cast(wind)->report_error(ef, msg);
+ },
+ .cb_report_working_status =
+ [](void *wind, SCL_workStatus ws) {
+ SCWind *self = reinterpret_cast(wind);
+ const QString status_str = SCWind::workStatus_to_string(ws);
+ QString wind_title;
+ if (status_str.isEmpty()) {
+ wind_title = SCWind::default_wind_title();
+ } else {
+ wind_title = QStringLiteral("%1 | %2")
+ .arg(SCWind::default_wind_title(), status_str);
+ }
+ self->setWindowTitle(wind_title);
+ },
+ };
+}
+
+SlopeCraft::progress_callbacks progress_callback(QProgressBar *bar) noexcept {
+ return SlopeCraft::progress_callbacks{
+ .widget = bar,
+ .cb_set_range =
+ [](void *widget, int min, int max, int val) {
+ if (widget == nullptr) {
+ return;
+ }
+ QProgressBar *bar = reinterpret_cast(widget);
+ bar->setMinimum(min);
+ bar->setMaximum(max);
+ bar->setValue(val);
+ },
+ .cb_add =
+ [](void *widget, int delta) {
+ if (widget == nullptr) {
+ return;
+ }
+ QProgressBar *bar = reinterpret_cast(widget);
+ bar->setValue(bar->value() + delta);
+ }};
+}
+
+void SCWind::when_cvt_pool_selectionChanged() noexcept {
+ const auto selected_idx = this->selected_cvt_task_idx();
+
+ this->refresh_current_cvt_display(selected_idx);
+}
+
+#define SC_SLOPECRAFT_PRIVATEMACRO_VERSION_BUTTON_LIST \
+ { \
+ this->ui->rb_ver12, this->ui->rb_ver13, this->ui->rb_ver14, \
+ this->ui->rb_ver15, this->ui->rb_ver16, this->ui->rb_ver17, \
+ this->ui->rb_ver18, this->ui->rb_ver19, this->ui->rb_ver20, \
+ this->ui->rb_ver21 \
+ }
+
+std::array SCWind::version_buttons() noexcept {
+ return SC_SLOPECRAFT_PRIVATEMACRO_VERSION_BUTTON_LIST;
+}
+
+std::array SCWind::version_buttons()
+ const noexcept {
+ return SC_SLOPECRAFT_PRIVATEMACRO_VERSION_BUTTON_LIST;
+}
+
+#define SC_SLOPECRAFT_PRIVATEMACRO_TYPE_BUTTON_LIST \
+ { this->ui->rb_type_3d, this->ui->rb_type_flat, this->ui->rb_type_fileonly }
+
+std::array SCWind::type_buttons() noexcept {
+ return SC_SLOPECRAFT_PRIVATEMACRO_TYPE_BUTTON_LIST;
+}
+
+std::array SCWind::type_buttons() const noexcept {
+ return SC_SLOPECRAFT_PRIVATEMACRO_TYPE_BUTTON_LIST;
+}
+
+SCL_gameVersion SCWind::selected_version() const noexcept {
+ auto btns = this->version_buttons();
+ for (size_t idx = 0; idx < btns.size(); idx++) {
+ if (btns[idx]->isChecked()) {
+ return SCL_gameVersion(idx + 12);
+ }
+ }
+ return SCL_gameVersion::ANCIENT;
+}
+
+SCL_mapTypes SCWind::selected_type() const noexcept {
+ if (this->ui->rb_type_3d->isChecked()) {
+ return SCL_mapTypes::Slope;
+ }
+
+ if (this->ui->rb_type_flat->isChecked()) {
+ return SCL_mapTypes::Flat;
+ }
+
+ if (this->ui->rb_type_fileonly->isChecked()) {
+ return SCL_mapTypes::FileOnly;
+ }
+ return SCL_mapTypes::Slope;
+ // return {};
+}
+
+std::vector SCWind::selected_indices() const noexcept {
+ std::vector ret;
+ auto sel = this->ui->lview_pool_cvt->selectionModel()->selectedIndexes();
+ ret.reserve(sel.size());
+ for (auto &midx : sel) {
+ ret.emplace_back(midx.row());
+ }
+ return ret;
+}
+
+std::optional SCWind::selected_cvt_task_idx() const noexcept {
+ auto sel = this->ui->lview_pool_cvt->selectionModel()->selectedIndexes();
+ if (sel.size() <= 0) {
+ return std::nullopt;
+ }
+ auto front = sel.front();
+ if (not front.isValid()) {
+ return std::nullopt;
+ }
+
+ const int idx = front.row();
+ if (idx < 0 or idx >= this->tasks.size()) {
+ // In some corner cases this is true
+ // Return nullopt so that we don't return a invalid index
+ return std::nullopt;
+ }
+ return idx;
+}
+
+std::vector SCWind::selected_export_task_list() const noexcept {
+ auto selected_eidx =
+ this->ui->lview_pool_export->selectionModel()->selectedIndexes();
+ std::vector ret;
+ ret.reserve(selected_eidx.size());
+ for (auto &midx : selected_eidx) {
+ ret.emplace_back(
+ this->export_pool_model->export_idx_to_task_ptr(midx.row()));
+ }
+ return ret;
+}
+cvt_task *SCWind::selected_export_task() const noexcept {
+ auto selected = this->selected_export_task_list();
+ if (selected.empty()) {
+ return nullptr;
+ }
+
+ return selected.front();
+}
+
+SCL_convertAlgo SCWind::selected_algo() const noexcept {
+ if (this->ui->rb_algo_RGB->isChecked()) {
+ return SCL_convertAlgo::RGB;
+ }
+ if (this->ui->rb_algo_RGB_plus->isChecked()) {
+ return SCL_convertAlgo::RGB_Better;
+ }
+ if (this->ui->rb_algo_Lab94->isChecked()) {
+ return SCL_convertAlgo::Lab94;
+ }
+ if (this->ui->rb_algo_Lab00->isChecked()) {
+ return SCL_convertAlgo::Lab00;
+ }
+ if (this->ui->rb_algo_XYZ->isChecked()) {
+ return SCL_convertAlgo::XYZ;
+ }
+ if (this->ui->rb_algo_GACvter->isChecked()) {
+ return SCL_convertAlgo::gaCvter;
+ }
+ return SCL_convertAlgo::RGB_Better;
+ // return {};
+}
+
+bool SCWind::is_dither_selected() const noexcept {
+ return this->ui->cb_algo_dither->isChecked();
+}
+
+bool SCWind::is_lossless_compression_selected() const noexcept {
+ return this->ui->cb_compress_lossless->isChecked();
+}
+bool SCWind::is_lossy_compression_selected() const noexcept {
+ return this->ui->cb_compress_lossy->isChecked();
+}
+int SCWind::current_max_height() const noexcept {
+ return this->ui->sb_max_height->value();
+}
+
+SCL_compressSettings SCWind::current_compress_method() const noexcept {
+ auto result = static_cast(SCL_compressSettings::noCompress);
+ if (this->is_lossless_compression_selected()) {
+ result = result bitor int(SCL_compressSettings::NaturalOnly);
+ }
+ if (this->is_lossy_compression_selected()) {
+ result = result bitor int(SCL_compressSettings::ForcedOnly);
+ }
+ return static_cast(result);
+}
+
+bool SCWind::is_glass_bridge_selected() const noexcept {
+ return this->ui->cb_glass_bridge->isChecked();
+}
+int SCWind::current_glass_brigde_interval() const noexcept {
+ return this->ui->sb_glass_bridge_interval->value();
+}
+SCL_glassBridgeSettings SCWind::current_glass_method() const noexcept {
+ if (this->is_glass_bridge_selected()) {
+ return SCL_glassBridgeSettings::withBridge;
+ }
+ return SCL_glassBridgeSettings::noBridge;
+}
+
+bool SCWind::is_fire_proof_selected() const noexcept {
+ return this->ui->cb_fireproof->isChecked();
+}
+bool SCWind::is_enderman_proof_selected() const noexcept {
+ return this->ui->cb_enderproof->isChecked();
+}
+bool SCWind::is_connect_mushroom_selected() const noexcept {
+ return this->ui->cb_connect_mushroom->isChecked();
+}
+
+SlopeCraft::build_options SCWind::current_build_option() const noexcept {
+ return SlopeCraft::build_options{
+ .max_allowed_height = (uint16_t)this->current_max_height(),
+ .bridge_interval = (uint16_t)this->current_glass_brigde_interval(),
+ .compress_method = this->current_compress_method(),
+ .glass_method = this->current_glass_method(),
+ .fire_proof = this->is_fire_proof_selected(),
+ .enderman_proof = this->is_enderman_proof_selected(),
+ .connect_mushrooms = this->is_connect_mushroom_selected(),
+ .ui = this->ui_callbacks(),
+ .main_progressbar = progress_callback(this->ui->pbar_export),
+ .sub_progressbar = {}};
+}
+
+SCWind::export_type SCWind::selected_export_type() const noexcept {
+ auto btns = this->export_type_buttons();
+ static_assert(btns.size() == 5);
+
+ constexpr std::array export_type_list{
+ export_type::litematica, export_type::vanilla_structure,
+ export_type::WE_schem, export_type::flat_diagram,
+ export_type::data_file,
+ };
+ for (int i = 0; i < 5; i++) {
+ if (btns[i]->isChecked()) {
+ return export_type_list[i];
+ }
+ }
+
+ return SCWind::export_type::litematica;
+ // return {};
+}
+
+void SCWind::when_version_buttons_toggled(bool checked) noexcept {
+ if (not checked) {
+ return;
+ }
+ this->ui->blm->when_version_updated();
+ this->when_blocklist_changed();
+
+ // When mc version is not 1.20, it must be greater or less than 1.20.5, this
+ // checkbox is useless and can be fixed
+ const bool fix_geq_btn =
+ (this->selected_version() not_eq SCL_gameVersion::MC20);
+ if (this->selected_version() not_eq SCL_gameVersion::MC20) {
+ this->ui->cb_mc_version_geq_1_20_5->setChecked(this->selected_version() >
+ SCL_gameVersion::MC20);
+ }
+ this->ui->cb_mc_version_geq_1_20_5->setDisabled(fix_geq_btn);
+}
+
+void SCWind::when_type_buttons_toggled(bool checked) noexcept {
+ if (not checked) {
+ return;
+ }
+ this->when_blocklist_changed();
+ this->update_button_states();
+ {
+ auto valid_buttons = this->valid_export_type_buttons(this->selected_type());
+ for (auto btnp : valid_buttons) {
+ if (btnp->isChecked()) {
+ return;
+ }
+ }
+ valid_buttons[0]->click();
+ }
+ // this->when_export_type_toggled();
+}
+
+void SCWind::when_blocklist_changed() noexcept {
+ this->set_colorset();
+ this->ui->rb_preset_custom->setChecked(true);
+ // this->ui->rb_preset_
+}
+
+void SCWind::set_colorset() noexcept {
+ auto color_table = this->current_color_table();
+ const int num_colors =
+ (color_table not_eq nullptr) ? color_table->colors().num_colors : 0;
+
+ this->ui->lb_avaliable_colors->setText(
+ tr("可用颜色数量:%1").arg(num_colors));
+}
+
+#define SC_SLOPECRAFT_PREIVATEMACRO_EXPORT_TYPE_BUTTONS \
+ { \
+ this->ui->rb_export_lite, this->ui->rb_export_nbt, this->ui->rb_export_WE, \
+ this->ui->rb_export_flat_diagram, this->ui->rb_export_fileonly \
+ }
+
+std::array SCWind::export_type_buttons() noexcept {
+ return SC_SLOPECRAFT_PREIVATEMACRO_EXPORT_TYPE_BUTTONS;
+}
+std::array SCWind::export_type_buttons()
+ const noexcept {
+ return SC_SLOPECRAFT_PREIVATEMACRO_EXPORT_TYPE_BUTTONS;
+}
+
+std::vector SCWind::valid_export_type_buttons(
+ SCL_mapTypes type) const noexcept {
+ switch (type) {
+ case SCL_mapTypes::Slope:
+ return {this->ui->rb_export_lite, this->ui->rb_export_nbt,
+ this->ui->rb_export_WE, this->ui->rb_export_fileonly};
+ case SCL_mapTypes::Flat:
+ return SC_SLOPECRAFT_PREIVATEMACRO_EXPORT_TYPE_BUTTONS;
+ case SCL_mapTypes::FileOnly:
+ return {this->ui->rb_export_fileonly};
+ }
+ return {};
+}
+
+std::array SCWind::preset_buttons_no_custom() noexcept {
+ return {this->ui->rb_preset_vanilla, this->ui->rb_preset_cheap,
+ this->ui->rb_preset_elegant, this->ui->rb_preset_shiny};
+}
+
+#define SC_SLOPECRAFT_PRIVATEMARCO_ALGO_BUTTONS \
+ { \
+ this->ui->rb_algo_RGB, this->ui->rb_algo_RGB_plus, \
+ this->ui->rb_algo_Lab94, this->ui->rb_algo_Lab00, \
+ this->ui->rb_algo_XYZ, this->ui->rb_algo_GACvter \
+ }
+
+std::array SCWind::algo_buttons() const noexcept {
+ return SC_SLOPECRAFT_PRIVATEMARCO_ALGO_BUTTONS;
+}
+
+std::array SCWind::algo_buttons() noexcept {
+ return SC_SLOPECRAFT_PRIVATEMARCO_ALGO_BUTTONS;
+}
+
+QProgressBar *SCWind::current_bar() noexcept {
+ const int cid = this->ui->tw_main->currentIndex();
+ switch (cid) {
+ case 1:
+ return this->ui->pbar_cvt;
+ case 2:
+ return this->ui->pbar_export;
+ default:
+ return nullptr;
+ }
+}
+
+void SCWind::update_button_states() noexcept {
+ {
+ const bool disable_3d = (this->selected_type() == SCL_mapTypes::FileOnly);
+ std::array rb_export_3d_types{this->ui->rb_export_lite,
+ this->ui->rb_export_nbt,
+ this->ui->rb_export_WE};
+ for (auto rbp : rb_export_3d_types) {
+ rbp->setDisabled(disable_3d);
+ }
+
+ std::array pb_export{
+ this->ui->pb_export_all, this->ui->pb_build3d,
+ this->ui->pb_preview_compress_effect, this->ui->pb_preview_materials};
+ for (QPushButton *pbp : pb_export) {
+ pbp->setDisabled(disable_3d);
+ }
+
+ if (disable_3d) {
+ this->ui->rb_export_fileonly->setChecked(true);
+ }
+ }
+ {
+ const bool enable_flatdiagram =
+ (this->selected_type() == SCL_mapTypes::Flat);
+
+ this->ui->rb_export_flat_diagram->setEnabled(enable_flatdiagram);
+ }
+}
+
+void SCWind::when_preset_clicked() noexcept {
+ int final_idx = -1;
+
+ for (int idx = 0; idx < (int)this->preset_buttons_no_custom().size(); idx++) {
+ if (this->preset_buttons_no_custom()[idx]->isChecked()) {
+ final_idx = idx;
+ break;
+ }
+ }
+
+ assert(final_idx >= 0);
+
+ if (!this->ui->blm->loadPreset(this->default_presets[final_idx])) {
+ QMessageBox::warning(this, tr("应用预设失败"), "");
+ return;
+ }
+
+ this->preset_buttons_no_custom()[final_idx]->setChecked(true);
+}
+
+void SCWind::when_export_type_toggled() noexcept {
+ const bool page_3d = !this->ui->rb_export_fileonly->isChecked();
+ if (page_3d) {
+ this->ui->sw_export->setCurrentIndex(0);
+ } else {
+ this->ui->sw_export->setCurrentIndex(1);
+ }
+
+ const auto btns = this->export_type_buttons();
+
+ for (int idx = 0; idx < 4; idx++) {
+ if (btns[idx]->isChecked()) {
+ this->ui->tw_export_options->setCurrentIndex(idx);
+ }
+ }
+
+ this->update_button_states();
+}
+
+SlopeCraft::convert_option SCWind::current_convert_option() noexcept {
+ return SlopeCraft::convert_option{
+ .caller_api_version = SC_VERSION_U64,
+ .algo = this->selected_algo(),
+ .dither = this->is_dither_selected(),
+ .ai_cvter_opt = this->GA_option,
+ .progress = progress_callback(this->ui->pbar_cvt),
+ .ui = this->ui_callbacks(),
+ };
+}
+
+std::unique_ptr
+SCWind::convert_image(int idx) noexcept {
+ assert(idx >= 0);
+ assert(idx < (int)this->tasks.size());
+ return this->convert_image(this->tasks[idx]);
+}
+
+std::unique_ptr
+SCWind::convert_image(const cvt_task &task) noexcept {
+ auto ctable = this->current_color_table();
+ if (ctable == nullptr) {
+ QMessageBox::critical(
+ this, tr("没有可用颜色"),
+ tr("没有勾选任何颜色,无法转化图像。请至少勾选3~16种颜色。"));
+ return nullptr;
+ }
+
+ const auto num_blocks = ctable->num_blocks();
+ if (num_blocks <= 3) {
+ const auto reply = QMessageBox::warning(
+ this, tr("勾选颜色太少"),
+ tr("仅仅勾选了%"
+ "1种颜色,颜色过少,转化效率可能非常差。您可以点Yes继续"
+ "转化,但非常建议请尽量多勾选一些颜色。")
+ .arg(num_blocks),
+ QMessageBox::StandardButtons{QMessageBox::StandardButton::Yes,
+ QMessageBox::StandardButton::No},
+ QMessageBox::StandardButton::No);
+ if (reply not_eq QMessageBox::StandardButton::Yes) {
+ return nullptr;
+ }
+ }
+
+ const QImage &raw = task.original_image;
+ {
+ SlopeCraft::const_image_reference img{
+ .data = (const uint32_t *)raw.scanLine(0),
+ .rows = static_cast(raw.height()),
+ .cols = static_cast(raw.width()),
+ };
+ auto cvted_img = ctable->convert_image(img, this->current_convert_option());
+
+ return std::unique_ptr{
+ cvted_img};
+ }
+}
+
+const SlopeCraft::converted_image &SCWind::convert_if_need(
+ cvt_task &task) noexcept {
+ const auto table = this->current_color_table();
+ const auto opt = this->current_convert_option();
+ if (not task.is_converted_with(table, opt)) {
+ auto cvted = this->convert_image(task);
+ assert(cvted);
+ task.set_converted(table, opt, std::move(cvted));
+ }
+
+ auto &cvted = task.converted_images[{table, opt}].converted_image;
+ assert(cvted != nullptr);
+ return *cvted;
+}
+
+std::unique_ptr SCWind::build_3D(
+ const SlopeCraft::converted_image &cvted) noexcept {
+ auto ctable = this->current_color_table();
+ const auto opt = this->current_build_option();
+ auto str = ctable->build(cvted, opt);
+ return std::unique_ptr{str};
+}
+
+std::tuple
+SCWind::convert_and_build_if_need(cvt_task &task) noexcept {
+ const auto table = this->current_color_table();
+ const auto &cvted = this->convert_if_need(task);
+
+ assert(task.is_converted_with(table, this->current_convert_option()));
+ auto &cvt_result =
+ task.converted_images.at({table, this->current_convert_option()});
+ const auto build_opt = this->current_build_option();
+
+ if (auto str_3D = cvt_result.load_build_cache(*table, build_opt,
+ this->cache_root_dir())) {
+ // The 3D structure is built, it exists in memory or can be loaded
+ return {cvted, *str_3D};
+ }
+ // Build 3D structure now
+ auto s = this->build_3D(cvted);
+ auto ptr = s.get();
+ assert(ptr != nullptr);
+ cvt_result.set_built(build_opt, std::move(s));
+ return {cvted, *ptr};
+}
+
+// void SCWind::kernel_make_cvt_cache() noexcept {
+// std::string err;
+// err.resize(4096);
+// SlopeCraft::string_deliver sd{err.data(), err.size()};
+//
+// if (!this->kernel->saveConvertCache(sd)) {
+// QString qerr = QString::fromUtf8(sd.data);
+// QMessageBox::warning(this, tr("缓存失败"),
+// tr("未能创建缓存文件,错误信息:\n%1").arg(qerr));
+// }
+// }
+
+// QImage SCWind::get_converted_image_from_kernel() const noexcept {
+// assert(this->kernel->queryStep() >= SCL_step::converted);
+//
+// const int rows = this->kernel->getImageRows();
+// const int cols = this->kernel->getImageCols();
+//
+// QImage img{cols, rows, QImage::Format_ARGB32};
+//
+// this->kernel->getConvertedImage(nullptr, nullptr, (uint32_t
+// *)img.scanLine(0),
+// false);
+//
+// return img;
+// }
+
+QImage get_converted_image(const SlopeCraft::converted_image &cvted) noexcept {
+ QImage img{
+ QSize{static_cast(cvted.cols()), static_cast(cvted.rows())},
+ QImage::Format::Format_ARGB32};
+ cvted.get_converted_image(reinterpret_cast(img.scanLine(0)));
+ return img;
+}
+
+void SCWind::refresh_current_cvt_display(
+ std::optional selected_idx) noexcept {
+ if (not selected_idx.has_value()) {
+ this->ui->lb_raw_image->setPixmap({});
+ this->ui->lb_cvted_image->setPixmap({});
+ this->ui->lb_map_shape->setText("");
+ return;
+ }
+
+ const int idx = selected_idx.value();
+
+ this->ui->lb_raw_image->setPixmap(
+ QPixmap::fromImage(this->tasks[idx].original_image));
+ {
+ auto shape = this->tasks[idx].original_image.size();
+ const int map_rows = std::ceil(shape.height() / 128.0f);
+ const int map_cols = std::ceil(shape.width() / 128.0f);
+ this->ui->lb_map_shape->setText(
+ tr("%1行,%2列").arg(map_rows).arg(map_cols));
+ }
+
+ auto &task = this->tasks[selected_idx.value()];
+
+ auto it = task.converted_images.find(
+ {this->current_color_table(), this->current_convert_option()});
+ if (it != task.converted_images.end() &&
+ it->second.converted_image != nullptr) {
+ this->ui->lb_cvted_image->setPixmap(
+ QPixmap::fromImage(get_converted_image(*it->second.converted_image)));
+ return;
+ }
+
+ this->ui->lb_cvted_image->setPixmap({});
+}
+
+// void SCWind::mark_all_task_unconverted() noexcept {
+// for (auto &task : this->tasks) {
+// task.converted_img = nullptr;
+// task.structure = nullptr;
+// }
+// }
+
+void SCWind::when_algo_btn_clicked() noexcept {
+ this->cvt_pool_model->refresh();
+ this->refresh_current_cvt_display(this->selected_cvt_task_idx());
+}
+
+void SCWind::export_current_cvted_image(int idx, QString filename) noexcept {
+ assert(idx >= 0);
+ assert(idx < (int)this->tasks.size());
+
+ auto &task = this->tasks[idx];
+ auto cvted = task.get_converted_image(this->current_color_table(),
+ this->current_convert_option());
+ if (cvted == nullptr) {
+ const auto ret = QMessageBox::warning(
+ this, tr("无法保存第%1个转化后图像").arg(idx + 1),
+ tr("该图像未被转化,或者转化之后修改了颜色表/转化算法。请重新转化它。"),
+ QMessageBox::StandardButtons{QMessageBox::StandardButton::Ok,
+ QMessageBox::StandardButton::Ignore});
+ if (ret == QMessageBox::StandardButton::Ok) {
+ auto cvted_uptr = this->convert_image(task);
+ if (cvted_uptr == nullptr) {
+ return;
+ }
+ task.set_converted(this->current_color_table(),
+ this->current_convert_option(), std::move(cvted_uptr));
+ cvted = task.get_converted_image(this->current_color_table(),
+ this->current_convert_option());
+ if (cvted == nullptr) {
+ return;
+ }
+ } else {
+ return;
+ }
+ }
+
+ auto img = get_converted_image(*cvted);
+ bool ok = img.save(filename);
+
+ if (!ok) {
+ QMessageBox::warning(
+ this, tr("保存图像失败"),
+ tr("保存%1时失败。可能是因为文件路径错误,或者图片格式不支持。")
+ .arg(filename));
+ return;
+ }
+}
+
+// void SCWind::kernel_build_3d() noexcept {
+// if (!this->kernel->build(this->current_build_option())) {
+// QMessageBox::warning(this, tr("构建三维结构失败"),
+// tr("构建三维结构时,出现错误。可能是因为尝试跳步。"));
+// return;
+// }
+// }
+
+void SCWind::refresh_current_build_display(cvt_task *taskp) noexcept {
+ this->ui->lb_show_3dsize->setText(tr("大小:"));
+ this->ui->lb_show_block_count->setText(tr("方块数量:"));
+ if (taskp == nullptr) {
+ return;
+ }
+
+ auto &task = *taskp;
+ const auto cvted_it = task.get_convert_result(this->current_color_table(),
+ this->current_convert_option());
+ if (cvted_it == task.converted_images.end()) {
+ return;
+ }
+ if (auto str_with_info = cvted_it->second.get_build_cache_with_info_noload(
+ *this->current_color_table(), this->current_build_option(),
+ this->cache_root_dir())) {
+ // const SlopeCraft::structure_3D *str_3D = str_with_info->handle.get();
+ const auto x = str_with_info->shape[0], y = str_with_info->shape[1],
+ z = str_with_info->shape[2];
+
+ const auto block_count = str_with_info->block_count;
+ this->ui->lb_show_3dsize->setText(
+ tr("大小: %1 × %2 × %3").arg(x).arg(y).arg(z));
+ this->ui->lb_show_block_count->setText(tr("方块数量:%1").arg(block_count));
+ }
+
+ // if (this->selected_export_type() == SCWind::export_type::data_file) {
+ // this->when_data_file_command_changed();
+ // }
+}
+
+tl::expected SCWind::get_command(
+ const SlopeCraft::converted_image &cvted, int begin_idx) const noexcept {
+ QString command;
+ SlopeCraft::ostream_wrapper os{
+ .handle = &command,
+ .callback_write_data =
+ [](const void *data, size_t len, void *handle) {
+ QString *buf = reinterpret_cast(handle);
+ QString temp =
+ QString::fromUtf8(reinterpret_cast(data),
+ static_cast(len));
+ buf->append(temp);
+ },
+ };
+ bool after_1_20_5;
+ if (this->selected_version() not_eq SCL_gameVersion::MC20) {
+ after_1_20_5 = true;
+ } else {
+ after_1_20_5 = this->ui->cb_mc_version_geq_1_20_5->isChecked();
+ }
+
+ SlopeCraft::map_data_file_give_command_options opt{};
+ opt.destination = &os;
+ opt.begin_index = begin_idx;
+ opt.after_1_12 = (this->selected_version() > SCL_gameVersion::MC12);
+ opt.after_1_20_5 = after_1_20_5;
+ const bool ok = cvted.get_map_command(opt);
+ if (!ok) {
+ return tl::make_unexpected(tr("生成命令失败:\n%1").arg(command));
+ }
+ return command;
+}
+
+void SCWind::when_export_pool_selectionChanged() noexcept {
+ this->refresh_current_build_display(this->selected_export_task());
+}
+
+QString extension_of_export_type(SCWind::export_type et) noexcept {
+ switch (et) {
+ case SCWind::export_type::litematica:
+ return "litematic";
+ case SCWind::export_type::vanilla_structure:
+ return "nbt";
+ case SCWind::export_type::WE_schem:
+ return "schem";
+ case SCWind::export_type::flat_diagram:
+ return "flat-diagram.png";
+ case SCWind::export_type::data_file:
+ return "dat";
+ }
+
+ return "Invalid_export_type";
+}
+
+std::optional SCWind::current_litematic_option(
+ QString &err) const noexcept {
+ err.clear();
+ static std::string litename;
+ static std::string region_name;
+
+ litename = this->ui->le_lite_name->text().toUtf8().data();
+ region_name = this->ui->le_lite_region_name->text().toUtf8().data();
+ if (litename.empty()) {
+ litename = "UnnamedLitematica";
+ }
+ if (region_name.empty()) {
+ region_name = "UnnamedRegion";
+ }
+
+ return SlopeCraft::litematic_options{
+ .caller_api_version = SC_VERSION_U64,
+ .litename_utf8 = litename.data(),
+ .region_name_utf8 = region_name.data(),
+ .ui = this->ui_callbacks(),
+ .progressbar = progress_callback(this->ui->pbar_export),
+ };
+}
+
+std::optional SCWind::current_nbt_option(
+ QString &err) const noexcept {
+ err.clear();
+
+ return SlopeCraft::vanilla_structure_options{
+ .is_air_structure_void = this->ui->cb_nbt_air_void->isChecked(),
+ .ui{},
+ .progressbar{},
+ };
+}
+
+std::optional SCWind::current_schem_option(
+ QString &err) const noexcept {
+ err.clear();
+
+ SlopeCraft::WE_schem_options ret;
+
+ {
+ const std::array le_offset{this->ui->le_WE_offset_X,
+ this->ui->le_WE_offset_Y,
+ this->ui->le_WE_offset_Z};
+
+ for (size_t idx = 0; idx < le_offset.size(); idx++) {
+ bool ok;
+
+ ret.offset[idx] = le_offset[idx]->text().toInt(&ok);
+ if (!ok) {
+ err = tr("WE 原理图参数有错:输入给 offset 的值\"%"
+ "1\"不是一个有效的坐标,应当输入一个整数。")
+ .arg(le_offset[idx]->text());
+ return std::nullopt;
+ }
+ }
+ }
+
+ {
+ const std::array le_weoffset{this->ui->le_WE_weoffset_X,
+ this->ui->le_WE_weoffset_Y,
+ this->ui->le_WE_weoffset_Z};
+
+ for (size_t idx = 0; idx < le_weoffset.size(); idx++) {
+ bool ok;
+
+ ret.we_offset[idx] = le_weoffset[idx]->text().toInt(&ok);
+ if (!ok) {
+ err = tr("WE 原理图参数有错:输入给 we offset 的值\"%"
+ "1\"不是一个有效的数字,应当输入一个整数。")
+ .arg(le_weoffset[idx]->text());
+ return std::nullopt;
+ }
+ }
+ }
+ static std::string region_name;
+ region_name = this->ui->le_WE_region_name->text().toUtf8().data();
+
+ static std::vector mod_charp;
+ {
+ static std::vector mod_names;
+
+ const auto mod_names_q =
+ this->ui->le_WE_mods->toPlainText().replace("\r\n", "\n").split('\n');
+
+ mod_names.resize(mod_names_q.size());
+ mod_charp.resize(mod_names_q.size());
+
+ for (int idx = 0; idx < mod_names_q.size(); idx++) {
+ mod_names[idx] = mod_names_q[idx].toUtf8().data();
+ mod_charp[idx] = mod_names[idx].c_str();
+ }
+ }
+
+ ret.num_required_mods = mod_charp.size();
+ ret.required_mods_name_utf8 = mod_charp.data();
+
+ ret.ui = this->ui_callbacks();
+ ret.progressbar = progress_callback(this->ui->pbar_export);
+
+ return ret;
+}
+
+std::optional
+SCWind::current_flatdiagram_option(QString &err) const noexcept {
+ err.clear();
+
+ int row_margin = this->ui->sb_flatdiagram_hmargin->value();
+ int col_margin = this->ui->sb_flatdiagram_vmargin->value();
+
+ if (row_margin <= 0 || col_margin <= 0) {
+ err =
+ tr("平面示意图的分割线间距无效:水平间距为 %1,垂直间距为 %2, "
+ "但间距必须为正数。");
+ return std::nullopt;
+ }
+
+ if (!this->ui->cb_flatdiagram_hline->isChecked()) {
+ row_margin = -1;
+ }
+
+ if (!this->ui->cb_flatdiagram_vline->isChecked()) {
+ col_margin = -1;
+ }
+
+ return SlopeCraft::flag_diagram_options{
+ .caller_api_version = SC_VERSION_U64,
+ .split_line_row_margin = row_margin,
+ .split_line_col_margin = col_margin,
+ .ui = this->ui_callbacks(),
+ .progressbar = progress_callback(this->ui->pbar_export),
+ };
+}
+
+int SCWind::current_map_begin_seq_number() const noexcept {
+ return this->ui->sb_file_start_idx->value();
+}
+
+void SCWind::report_error(::SCL_errorFlag flag, const char *msg) noexcept {
+ if (flag == SCL_errorFlag::NO_ERROR_OCCUR) {
+ return;
+ }
+ using sb = QMessageBox::StandardButton;
+ using sbs = QMessageBox::StandardButtons;
+
+ auto flag_name = magic_enum::enum_name(flag);
+
+ const QString errmsg = tr("错误类型:%1,错误码:%2。详细信息:\n%3")
+ .arg(flag_name.data())
+ .arg(int(flag))
+ .arg(msg);
+
+ const auto ret = QMessageBox::critical(
+ this, tr("SlopeCraft 出现错误"),
+ tr("%1\n\n点击 Ok 以忽略这个错误,点击 Close 将退出 SlopeCraft。")
+ .arg(errmsg),
+ sbs{sb::Ok, sb::Close});
+
+ if (ret == sb::Close) {
+ exit(0);
+ }
+
+ return;
+}
+
+void SCWind::set_lang(::SCL_language lang) noexcept {
+ this->language = lang;
+ for (auto trans : this->translators) {
+ if (this->language == ::SCL_language::Chinese) {
+ QApplication::removeTranslator(trans);
+ } else {
+ QApplication::installTranslator(trans);
+ }
+ }
+ this->ui->retranslateUi(this);
+
+ this->ui->blm->when_lang_updated(lang);
+}
+
+QString impl_default_title() noexcept {
+ return QStringLiteral("SlopeCraft %1").arg(SlopeCraft::SCL_getSCLVersion());
+}
+
+const QString &SCWind::default_wind_title() noexcept {
+ static const QString title = impl_default_title();
+ return title;
+}
+
+QString SCWind::workStatus_to_string(::SCL_workStatus status) noexcept {
+ switch (status) {
+ case SlopeCraft::workStatus::none:
+ break;
+ case SlopeCraft::workStatus::buidingHeighMap:
+ return tr("正在构建高度矩阵");
+ case SlopeCraft::workStatus::building3D:
+ return tr("正在构建三维结构");
+ case SlopeCraft::workStatus::collectingColors:
+ return tr("正在收集整张图片的颜色");
+ case SlopeCraft::workStatus::compressing:
+ return tr("正在压缩立体地图画");
+ case SlopeCraft::workStatus::constructingBridges:
+ return tr("正在为立体地图画搭桥");
+ case SlopeCraft::workStatus::converting:
+ return tr("正在匹配颜色");
+ case SlopeCraft::workStatus::dithering:
+ return tr("正在使用抖动仿色");
+ case SlopeCraft::workStatus::flippingToWall:
+ return tr("正在将平板地图画变为墙面地图画");
+ case SlopeCraft::workStatus::writing3D:
+ return tr("正在写入三维结构");
+ case SlopeCraft::workStatus::writingBlockPalette:
+ return tr("正在写入方块列表");
+ case SlopeCraft::workStatus::writingMapDataFiles:
+ return tr("正在写入地图数据文件");
+ case SlopeCraft::workStatus::writingMetaInfo:
+ return tr("正在写入基础信息");
+ }
+
+ return {};
+}
+
+std::tuple
+SCWind::load_selected_3D() noexcept {
+ auto taskp = this->selected_export_task();
+ if (taskp == nullptr) {
+ QMessageBox::warning(this, tr("未选择图像"),
+ tr("请在左侧任务池选择一个图像"));
+ return {nullptr, nullptr};
+ }
+ assert(taskp != nullptr);
+
+ cvt_task &task = *taskp;
+ const ptrdiff_t index = &task - this->tasks.data();
+ assert(index >= 0 && index < ptrdiff_t(this->tasks.size()));
+ if (!task.is_converted_with(this->current_color_table(),
+ this->current_convert_option())) {
+ QMessageBox::warning(this, tr("该图像尚未被转化"),
+ tr("必须先转化一个图像,然后再为它构建三维结构"));
+ return {nullptr, nullptr};
+ }
+ QString errtitle;
+ QString errmsg;
+ // try to load cache
+ auto [cvted_img, structure_3D] =
+ [this, &task, &errtitle,
+ &errmsg]() -> std::pair {
+ auto it = task.converted_images.find(convert_input{
+ this->current_color_table(), this->current_convert_option()});
+ if (it == task.converted_images.end()) {
+ errtitle = tr("该图像尚未被转化");
+ errmsg =
+ tr("可能是在转化完成之后又修改了转化算法,因此之前的转化无效。必须重"
+ "新转化该图像。");
+ return {nullptr, nullptr};
+ }
+ auto str_3D = it->second.load_build_cache(*this->current_color_table(),
+ this->current_build_option(),
+ this->cache_root_dir());
+
+ if (str_3D == nullptr) {
+ errtitle = tr("尚未构建三维结构");
+ errmsg = tr(
+ "在预览材料表之前,必须先构建三维结构。出现这个警告,可能是因为你"
+ "在构建三维结构之后,又修改了三维结构的选项,因此之前的结果无效。");
+ return {it->second.converted_image.get(), nullptr};
+ }
+ return {it->second.converted_image.get(), str_3D};
+ }();
+
+ if (!errtitle.isEmpty()) {
+ QMessageBox::warning(this, errtitle, errmsg);
+ return {nullptr, nullptr};
+ }
+
+ return {cvted_img, structure_3D};
+}
+
+bool SCWind::should_auto_cache(bool suppress_warnings) noexcept {
+ bool result = false;
+ const auto self_used = get_self_memory_info();
+ QString error_template =
+ tr("这不是严重的问题,你可以直接忽略这个警告,或者把它反馈给开发者,"
+ "不影响正常使用。只是 Slopecraft "
+ "可能占用更多的内存。\n详细信息:\n%1");
+
+ if (not self_used) {
+ if (not suppress_warnings) {
+ QMessageBox::warning(
+ this, tr("获取本进程的内存占用失败"),
+ error_template.arg(QString::fromLocal8Bit(self_used.error().c_str())),
+ QMessageBox::StandardButtons{QMessageBox::StandardButton::Ok});
+ }
+ } else {
+ result = this->mem_policy.should_cache(self_used.value()) or result;
+ }
+
+ const auto system_info = get_system_memory_info();
+ if (not system_info) {
+ if (not suppress_warnings) {
+ QMessageBox::warning(
+ this, tr("获取操作系统内存占用失败"),
+ error_template.arg(QString::fromLocal8Bit(self_used.error().c_str())),
+ QMessageBox::StandardButtons{QMessageBox::StandardButton::Ok});
+ }
+ } else {
+ result = this->mem_policy.should_cache(system_info.value()) or result;
+ }
+ return result;
+}
+
+SCWind::auto_cache_report SCWind::auto_cache_3D(
+ [[maybe_unused]] bool cache_all) noexcept {
+ // const auto colortable = this->current_color_table();
+ // const auto build_opt = this->current_build_option();
+ auto_cache_report report{
+ .structures_cached = 0,
+ .memory_saved = 0,
+ };
+ const auto self_mem_before = get_self_memory_info();
+ const QString cache_root = this->cache_root_dir();
+
+ auto go_through = [this, cache_root, cache_all]() -> size_t {
+ size_t cached = 0;
+ for (auto &task : this->tasks) {
+ for (auto &[cvt_input, cvted] : task.converted_images) {
+ // if we don't need to cache, return.
+ if ((not cache_all) and (not this->should_auto_cache(true))) {
+ return cached;
+ }
+
+ auto report = cvted.cache_all_structures(
+ *cvt_input.table, *cvted.converted_image, cache_root);
+ cached += report.cache_num;
+ }
+ }
+ return cached;
+ };
+
+ report.structures_cached = go_through();
+ const auto self_mem_current = get_self_memory_info();
+ if (self_mem_before and self_mem_current) {
+ report.memory_saved =
+ self_mem_before.value().used - self_mem_current.value().used;
+ }
+
+ return report;
+}
+
+SlopeCraft::assembled_maps_options SCWind::current_assembled_maps_option()
+ const noexcept {
+ SlopeCraft::assembled_maps_options option;
+ option.mc_version = this->selected_version();
+ option.frame_variant = this->ui->cb_glowing_item_frame->isChecked()
+ ? SCL_item_frame_variant::glowing
+ : SCL_item_frame_variant::common;
+ option.after_1_20_5 = this->ui->cb_mc_version_geq_1_20_5->isChecked();
+ option.fixed_frame = this->ui->cb_fixed_frame->isChecked();
+ option.invisible_frame = this->ui->cb_invisible_frame->isChecked();
+ option.map_facing =
+ this->ui->cb_map_direction->currentData().value();
+
+ return option;
+}
\ No newline at end of file
diff --git a/SlopeCraft/SCWind.h b/SlopeCraft/SCWind.h
new file mode 100644
index 00000000..a2be7a01
--- /dev/null
+++ b/SlopeCraft/SCWind.h
@@ -0,0 +1,323 @@
+#ifndef SLOPECRAFT_SLOPECRAFT_SCWIND_H
+#define SLOPECRAFT_SLOPECRAFT_SCWIND_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "cvt_task.h"
+#include "PoolModel.h"
+#include "ExportTableModel.h"
+#include "MemoryPolicyDialog.h"
+
+class SCWind;
+
+namespace Ui {
+class SCWind;
+}
+
+struct colortable_settings {
+ colortable_settings() = delete;
+ colortable_settings(const selection& s, SCL_mapTypes t)
+ : selection_{s}, map_type{t} {}
+
+ colortable_settings(selection&& s, SCL_mapTypes t)
+ : selection_{std::move(s)}, map_type{t} {}
+
+ selection selection_;
+ SCL_mapTypes map_type;
+
+ [[nodiscard]] bool operator==(const colortable_settings& b) const noexcept {
+ if (this->selection_ not_eq b.selection_) {
+ return false;
+ }
+ if (this->map_type not_eq b.map_type) {
+ return false;
+ }
+ return true;
+ }
+};
+
+template <>
+struct std::hash {
+ uint64_t operator()(const colortable_settings& s) const noexcept {
+ return std::hash{}(s.selection_) xor
+ static_cast(s.map_type);
+ }
+};
+
+SlopeCraft::progress_callbacks progress_callback(QProgressBar* bar) noexcept;
+
+SlopeCraft::const_image_reference wrap_image(const QImage& img) noexcept;
+
+[[nodiscard]] QImage get_converted_image(
+ const SlopeCraft::converted_image&) noexcept;
+
+class SCWind : public QMainWindow {
+ Q_OBJECT
+ private:
+ void connect_slots() noexcept;
+
+ public:
+ explicit SCWind(QWidget* parent = nullptr);
+ ~SCWind();
+
+ inline static QNetworkAccessManager& network_manager() noexcept {
+ static QNetworkAccessManager manager;
+ return manager;
+ }
+
+ static const QString& default_wind_title() noexcept;
+
+ static QString workStatus_to_string(::SCL_workStatus) noexcept;
+
+ const static QString update_url;
+
+ enum class export_type {
+ litematica,
+ vanilla_structure,
+ WE_schem,
+ flat_diagram,
+ data_file
+ };
+
+ private slots:
+ void on_pb_add_image_clicked() noexcept;
+ void on_pb_remove_image_clicked() noexcept;
+ void on_pb_replace_image_clicked() noexcept;
+
+ void on_cb_lv_cvt_icon_mode_clicked() noexcept;
+ void on_cb_compress_lossy_toggled(bool checked) noexcept;
+
+ void on_pb_load_preset_clicked() noexcept;
+ void on_pb_save_preset_clicked() noexcept;
+
+ void on_pb_prefer_concrete_clicked() noexcept;
+ void on_pb_prefer_wool_clicked() noexcept;
+ void on_pb_prefer_glass_clicked() noexcept;
+ void on_pb_prefer_planks_clicked() noexcept;
+ void on_pb_prefer_logs_clicked() noexcept;
+ void on_pb_prefer_slabs_clicked() noexcept;
+ void on_pb_prefer_carpets_clicked() noexcept;
+ void on_pb_prefer_pressure_plates_clicked() noexcept;
+
+ void on_pb_select_all_clicked() noexcept;
+ void on_pb_deselect_all_clicked() noexcept;
+ void on_pb_invselect_clicked() noexcept;
+
+ void when_cvt_pool_selectionChanged() noexcept;
+ void when_export_pool_selectionChanged() noexcept;
+ void when_version_buttons_toggled(bool) noexcept;
+ void when_type_buttons_toggled(bool) noexcept;
+ void when_blocklist_changed() noexcept;
+ void when_preset_clicked() noexcept;
+ void when_export_type_toggled() noexcept;
+ void when_algo_btn_clicked() noexcept;
+
+ void on_pb_cvt_current_clicked() noexcept;
+ void on_pb_cvt_all_clicked() noexcept;
+ void on_pb_save_converted_clicked() noexcept;
+
+ void on_pb_build3d_clicked() noexcept;
+ void on_pb_preview_materials_clicked() noexcept;
+ void on_pb_preview_compress_effect_clicked() noexcept;
+ void on_pb_export_all_clicked() noexcept;
+ // exports for data file
+ void on_pb_export_file_clicked() noexcept;
+ void on_pb_export_data_command_clicked() noexcept;
+ void on_pb_export_data_vanilla_structure_clicked() noexcept;
+ void when_data_file_command_changed() noexcept;
+
+ private slots:
+ void on_ac_GAcvter_options_triggered() noexcept;
+
+ void on_ac_cache_dir_open_triggered() noexcept;
+ void on_ac_clear_cache_triggered() noexcept;
+
+ void on_ac_about_triggered() noexcept;
+
+ void on_ac_get_current_colorlist_triggered() noexcept;
+ void on_ac_test_blocklist_triggered() noexcept;
+
+ void on_ac_cache_all_3d_triggered() noexcept;
+
+ void on_ac_memory_policy_triggered() noexcept;
+
+ void on_ac_blocklist_triggered() noexcept;
+
+ private:
+ Ui::SCWind* ui;
+
+ std::unordered_map<
+ colortable_settings,
+ std::unique_ptr>
+ color_tables;
+ // SlopeCraft::Kernel* kernel;
+
+ CvtPoolModel* cvt_pool_model{nullptr};
+ ExportPoolModel* export_pool_model{nullptr};
+ ExportTableModel* export_table_model{nullptr};
+
+ std::array default_presets;
+
+ SCL_language language{SCL_language::Chinese};
+ std::vector translators;
+
+ QString prev_load_image_dir{""};
+
+ memory_policy mem_policy{};
+ // QString fileonly_export_dir{""};
+
+ public:
+ SlopeCraft::GA_converter_option GA_option{};
+ task_pool tasks;
+
+ QString cache_root_dir() const noexcept;
+ SlopeCraft::color_table* current_color_table() noexcept;
+ SlopeCraft::convert_option current_convert_option() noexcept;
+
+ SlopeCraft::ui_callbacks ui_callbacks() const noexcept;
+
+ std::array version_buttons() noexcept;
+ std::array version_buttons() const noexcept;
+
+ std::array type_buttons() noexcept;
+ std::array type_buttons() const noexcept;
+
+ std::array export_type_buttons() noexcept;
+ std::array export_type_buttons() const noexcept;
+ std::vector valid_export_type_buttons(
+ SCL_mapTypes type) const noexcept;
+
+ std::array preset_buttons_no_custom() noexcept;
+
+ std::array algo_buttons() const noexcept;
+ std::array algo_buttons() noexcept;
+
+ QProgressBar* current_bar() noexcept;
+
+ SCL_gameVersion selected_version() const noexcept;
+
+ SCL_mapTypes selected_type() const noexcept;
+
+ std::vector selected_indices() const noexcept;
+ std::optional selected_cvt_task_idx() const noexcept;
+
+ std::vector selected_export_task_list() const noexcept;
+ cvt_task* selected_export_task() const noexcept;
+
+ SCL_convertAlgo selected_algo() const noexcept;
+ bool is_dither_selected() const noexcept;
+
+ bool is_lossless_compression_selected() const noexcept;
+ bool is_lossy_compression_selected() const noexcept;
+ int current_max_height() const noexcept;
+ SCL_compressSettings current_compress_method() const noexcept;
+
+ bool is_glass_bridge_selected() const noexcept;
+ int current_glass_brigde_interval() const noexcept;
+ SCL_glassBridgeSettings current_glass_method() const noexcept;
+
+ bool is_fire_proof_selected() const noexcept;
+ bool is_enderman_proof_selected() const noexcept;
+ bool is_connect_mushroom_selected() const noexcept;
+
+ SlopeCraft::build_options current_build_option() const noexcept;
+
+ export_type selected_export_type() const noexcept;
+
+ std::optional current_litematic_option(
+ QString& err) const noexcept;
+ std::optional current_nbt_option(
+ QString& err) const noexcept;
+ std::optional current_schem_option(
+ QString& err) const noexcept;
+ std::optional current_flatdiagram_option(
+ QString& err) const noexcept;
+
+ int current_map_begin_seq_number() const noexcept;
+
+ SlopeCraft::assembled_maps_options current_assembled_maps_option()
+ const noexcept;
+
+ inline auto lang() const noexcept { return this->language; }
+ void set_lang(::SCL_language lang) noexcept;
+
+ private:
+ // kernel related functions
+ void set_colorset() noexcept;
+
+ void update_button_states() noexcept;
+
+ [[nodiscard]] std::unique_ptr
+ convert_image(int idx) noexcept;
+
+ [[nodiscard]] std::unique_ptr
+ convert_image(const cvt_task&) noexcept;
+
+ [[nodiscard]] const SlopeCraft::converted_image& convert_if_need(
+ cvt_task&) noexcept;
+
+ [[nodiscard]] std::tuple
+ convert_and_build_if_need(cvt_task&) noexcept;
+
+ [[nodiscard]] std::unique_ptr