diff options
| author | Sebastiano Tronto <sebastiano@tronto.net> | 2025-04-17 16:27:38 +0200 |
|---|---|---|
| committer | Sebastiano Tronto <sebastiano@tronto.net> | 2025-04-17 16:27:38 +0200 |
| commit | f91e5c4c83a071ab720417d490fd44a8f4678bc1 (patch) | |
| tree | 9d38fad279c015bea0bf256184b6936171310aa7 | |
| parent | c182f3f16e56e9533060ffa06b25391e239a28ee (diff) | |
| download | nissy-core-f91e5c4c83a071ab720417d490fd44a8f4678bc1.tar.gz nissy-core-f91e5c4c83a071ab720417d490fd44a8f4678bc1.zip | |
Improvements to QT UI
| -rw-r--r-- | qt/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | qt/Main.qml (renamed from qt/NissyMain.qml) | 135 | ||||
| -rw-r--r-- | qt/adapter.cpp | 33 | ||||
| -rw-r--r-- | qt/adapter.h | 7 | ||||
| -rw-r--r-- | qt/main.cpp | 2 |
5 files changed, 137 insertions, 42 deletions
diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt index 8ba768c..42c6d52 100644 --- a/qt/CMakeLists.txt +++ b/qt/CMakeLists.txt | |||
| @@ -19,7 +19,7 @@ qt_add_executable(appnissyqt | |||
| 19 | qt_add_qml_module(appnissyqt | 19 | qt_add_qml_module(appnissyqt |
| 20 | URI nissyqt | 20 | URI nissyqt |
| 21 | VERSION 1.0 | 21 | VERSION 1.0 |
| 22 | QML_FILES NissyMain.qml | 22 | QML_FILES Main.qml |
| 23 | ) | 23 | ) |
| 24 | 24 | ||
| 25 | target_link_libraries(appnissyqt | 25 | target_link_libraries(appnissyqt |
diff --git a/qt/NissyMain.qml b/qt/Main.qml index 24aff43..a6e034a 100644 --- a/qt/NissyMain.qml +++ b/qt/Main.qml | |||
| @@ -43,9 +43,11 @@ Window { | |||
| 43 | id: mainArea | 43 | id: mainArea |
| 44 | 44 | ||
| 45 | property alias scramble: scrambleRow.scramble | 45 | property alias scramble: scrambleRow.scramble |
| 46 | property alias solver: solverCfg.solver | ||
| 46 | property alias minmoves: solverCfg.minmoves | 47 | property alias minmoves: solverCfg.minmoves |
| 47 | property alias maxmoves: solverCfg.maxmoves | 48 | property alias maxmoves: solverCfg.maxmoves |
| 48 | property alias maxsolutions: solverCfg.maxsolutions | 49 | property alias maxsolutions: solverCfg.maxsolutions |
| 50 | property alias optimal: solverCfg.optimal | ||
| 49 | property alias sols: sols.text | 51 | property alias sols: sols.text |
| 50 | property alias solsHeader: solsHeader.text | 52 | property alias solsHeader: solsHeader.text |
| 51 | 53 | ||
| @@ -59,7 +61,7 @@ Window { | |||
| 59 | spacing: 10 | 61 | spacing: 10 |
| 60 | 62 | ||
| 61 | SplitView.minimumHeight: 180 | 63 | SplitView.minimumHeight: 180 |
| 62 | SplitView.preferredHeight: 300 | 64 | SplitView.preferredHeight: 500 |
| 63 | 65 | ||
| 64 | component Separator: Rectangle { | 66 | component Separator: Rectangle { |
| 65 | height: 1 | 67 | height: 1 |
| @@ -67,6 +69,50 @@ Window { | |||
| 67 | color: "black" | 69 | color: "black" |
| 68 | } | 70 | } |
| 69 | 71 | ||
| 72 | component OptionalValue: RowLayout { | ||
| 73 | property alias currentValue: valueRect.value | ||
| 74 | property alias from: spinBox.from | ||
| 75 | property alias to: spinBox.to | ||
| 76 | property alias defaultValue: spinBox.value | ||
| 77 | property alias defaultEnabled: sw.checked | ||
| 78 | property alias label: sw.text | ||
| 79 | property int defaultSavedValue: 1 | ||
| 80 | property int savedValue: defaultSavedValue | ||
| 81 | |||
| 82 | Switch { | ||
| 83 | id: sw | ||
| 84 | |||
| 85 | checked: true | ||
| 86 | |||
| 87 | onToggled: () => { | ||
| 88 | if (checked) { | ||
| 89 | currentValue = savedValue | ||
| 90 | } else { | ||
| 91 | savedValue = currentValue | ||
| 92 | currentValue = spinBox.to | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | Rectangle { | ||
| 98 | id: valueRect | ||
| 99 | |||
| 100 | property alias enabled: sw.checked | ||
| 101 | property alias value: spinBox.value | ||
| 102 | |||
| 103 | width: 65 | ||
| 104 | height: 20 | ||
| 105 | |||
| 106 | SpinBox { | ||
| 107 | id: spinBox | ||
| 108 | |||
| 109 | width: parent.width | ||
| 110 | editable: true | ||
| 111 | enabled: parent.enabled | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 70 | ColumnLayout { | 116 | ColumnLayout { |
| 71 | id: scrambleRow | 117 | id: scrambleRow |
| 72 | 118 | ||
| @@ -114,9 +160,36 @@ Window { | |||
| 114 | 160 | ||
| 115 | ColumnLayout { | 161 | ColumnLayout { |
| 116 | id: solverCfg | 162 | id: solverCfg |
| 163 | |||
| 117 | property alias minmoves: minMaxRow.min | 164 | property alias minmoves: minMaxRow.min |
| 118 | property alias maxmoves: minMaxRow.max | 165 | property alias maxmoves: minMaxRow.max |
| 119 | property alias maxsolutions: maxOptimal.maxsolutions | 166 | property alias maxsolutions: maxSols.currentValue |
| 167 | property alias optimal: optimal.currentValue | ||
| 168 | property alias solver: solverRow.solver | ||
| 169 | |||
| 170 | RowLayout { | ||
| 171 | id: solverRow | ||
| 172 | |||
| 173 | property alias solver: comboBox.currentValue | ||
| 174 | |||
| 175 | Label { text: "Solver" } | ||
| 176 | ComboBox { | ||
| 177 | id: comboBox | ||
| 178 | |||
| 179 | currentIndex: 3 | ||
| 180 | textRole: "text" | ||
| 181 | valueRole: "name" | ||
| 182 | implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted | ||
| 183 | |||
| 184 | model: ListModel { | ||
| 185 | ListElement { text: "h48 h=0, k=4 (59 Mb)"; name: "h48h0k4" } | ||
| 186 | ListElement { text: "h48 h=1, k=2 (115 Mb)"; name: "h48h1k2" } | ||
| 187 | ListElement { text: "h48 h=2, k=2 (171 Mb)"; name: "h48h2k2" } | ||
| 188 | ListElement { text: "h48 h=3, k=2 (283 Mb)"; name: "h48h3k2" } | ||
| 189 | ListElement { text: "h48 h=7, k=2 (3.6 Gb)"; name: "h48h7k2" } | ||
| 190 | } | ||
| 191 | } | ||
| 192 | } | ||
| 120 | 193 | ||
| 121 | RowLayout { | 194 | RowLayout { |
| 122 | id: minMaxRow | 195 | id: minMaxRow |
| @@ -148,40 +221,38 @@ Window { | |||
| 148 | } | 221 | } |
| 149 | } | 222 | } |
| 150 | 223 | ||
| 151 | RowLayout { | 224 | OptionalValue { |
| 152 | id: maxOptimal | 225 | id: optimal |
| 153 | |||
| 154 | property alias maxsolutions: maxSolsRect.maxsolutions | ||
| 155 | |||
| 156 | Rectangle { | ||
| 157 | width: 220 | ||
| 158 | height: 20 | ||
| 159 | Label { text: "Maximum number of solutions:" } | ||
| 160 | } | ||
| 161 | Rectangle { | ||
| 162 | id: maxSolsRect | ||
| 163 | |||
| 164 | property int maxsolutions: parseInt(textField.text) | ||
| 165 | |||
| 166 | width: 35 | ||
| 167 | height: 20 | ||
| 168 | 226 | ||
| 169 | TextField { | 227 | label: "Above optimal by at most" |
| 170 | id: textField | 228 | from: 0 |
| 229 | to: 20 | ||
| 230 | defaultValue: 20 | ||
| 231 | defaultEnabled: false | ||
| 232 | defaultSavedValue: 0 | ||
| 233 | } | ||
| 171 | 234 | ||
| 172 | width: parent.width | 235 | OptionalValue { |
| 173 | text: "1" | 236 | id: maxSols |
| 174 | validator: IntValidator{ bottom: 1; top: 999; } | ||
| 175 | 237 | ||
| 176 | onAccepted: submitScramble() | 238 | label: "Limit number of solutions to" |
| 177 | } | 239 | from: 1 |
| 178 | } | 240 | to: 999 |
| 241 | defaultValue: 1 | ||
| 242 | defaultEnabled: true | ||
| 243 | defaultSavedValue: 1 | ||
| 179 | } | 244 | } |
| 180 | } | 245 | } |
| 181 | 246 | ||
| 182 | Separator {} | 247 | Separator {} |
| 183 | 248 | ||
| 184 | Label { id: solsHeader } | 249 | StackLayout { |
| 250 | Layout.maximumHeight: 30 | ||
| 251 | currentIndex: mainArea.solutionsLoading ? 0 : 1 | ||
| 252 | |||
| 253 | BusyIndicator { running: mainArea.solutionsLoading } | ||
| 254 | Label { id: solsHeader } | ||
| 255 | } | ||
| 185 | 256 | ||
| 186 | ScrollView { | 257 | ScrollView { |
| 187 | Layout.fillHeight: true | 258 | Layout.fillHeight: true |
| @@ -229,16 +300,16 @@ Window { | |||
| 229 | 300 | ||
| 230 | function submitScramble() { | 301 | function submitScramble() { |
| 231 | mainArea.solutionsLoading = true; | 302 | mainArea.solutionsLoading = true; |
| 232 | mainArea.solsHeader = "Loading solutions..." | 303 | mainArea.solsHeader = "" |
| 233 | mainArea.sols = "" | 304 | mainArea.sols = "" |
| 234 | logView.text = "" | 305 | logView.text = "" |
| 235 | NissyAdapter.requestSolve( | 306 | NissyAdapter.requestSolve( |
| 236 | mainArea.scramble, | 307 | mainArea.scramble, |
| 237 | "h48h3k2", | 308 | mainArea.solver, |
| 238 | mainArea.minmoves, | 309 | mainArea.minmoves, |
| 239 | mainArea.maxmoves, | 310 | mainArea.maxmoves, |
| 240 | mainArea.maxsolutions, | 311 | mainArea.maxsolutions, |
| 241 | -1 | 312 | mainArea.optimal |
| 242 | ) | 313 | ) |
| 243 | } | 314 | } |
| 244 | 315 | ||
| @@ -251,7 +322,7 @@ Window { | |||
| 251 | } | 322 | } |
| 252 | function onSolverError(msg) { | 323 | function onSolverError(msg) { |
| 253 | mainArea.solutionsLoading = false | 324 | mainArea.solutionsLoading = false |
| 254 | mainArea.solusHeader = msg | 325 | mainArea.solsHeader = msg |
| 255 | mainArea.sols = "" | 326 | mainArea.sols = "" |
| 256 | } | 327 | } |
| 257 | function onAppendLog(msg) { | 328 | function onAppendLog(msg) { |
diff --git a/qt/adapter.cpp b/qt/adapter.cpp index 3711fa4..cb690a4 100644 --- a/qt/adapter.cpp +++ b/qt/adapter.cpp | |||
| @@ -9,20 +9,25 @@ | |||
| 9 | 9 | ||
| 10 | void logWrapper(const char *str, void *data) | 10 | void logWrapper(const char *str, void *data) |
| 11 | { | 11 | { |
| 12 | auto f = *reinterpret_cast<std::function<void(const char*)>*>(data); | 12 | auto f = *reinterpret_cast<std::function<void(std::string)>*>(data); |
| 13 | f(str); | 13 | f(std::string{str}); |
| 14 | } | 14 | } |
| 15 | 15 | ||
| 16 | NissyAdapter::NissyAdapter() | 16 | NissyAdapter::NissyAdapter() |
| 17 | { | 17 | { |
| 18 | // TODO: this list must be kept in sync with UI code, it is a bit ugly | ||
| 18 | std::vector<std::string> solverNames { | 19 | std::vector<std::string> solverNames { |
| 19 | "h48h3k2" | 20 | "h48h0k4", |
| 21 | "h48h1k2", | ||
| 22 | "h48h2k2", | ||
| 23 | "h48h3k2", | ||
| 24 | "h48h7k2", | ||
| 20 | }; | 25 | }; |
| 21 | 26 | ||
| 22 | for (auto s : solverNames) | 27 | for (auto s : solverNames) |
| 23 | initSolver(s); | 28 | initSolver(s); |
| 24 | 29 | ||
| 25 | writeLog = [&](const char *str) { | 30 | writeLog = [&](std::string str) { |
| 26 | emit appendLog(QString::fromStdString(str)); | 31 | emit appendLog(QString::fromStdString(str)); |
| 27 | }; | 32 | }; |
| 28 | 33 | ||
| @@ -45,6 +50,8 @@ bool NissyAdapter::loadSolverData(nissy::solver& solver) { | |||
| 45 | 50 | ||
| 46 | std::filesystem::path filePath("./tables/" + solver.id); | 51 | std::filesystem::path filePath("./tables/" + solver.id); |
| 47 | if (!std::filesystem::exists(filePath)) { | 52 | if (!std::filesystem::exists(filePath)) { |
| 53 | logLine("Data file for solver " + solver.name + " not found, " | ||
| 54 | "generating it..."); | ||
| 48 | auto err = solver.generate_data(); | 55 | auto err = solver.generate_data(); |
| 49 | if (!err.ok()) { | 56 | if (!err.ok()) { |
| 50 | emit solverError(QString("Error generating data!")); | 57 | emit solverError(QString("Error generating data!")); |
| @@ -55,16 +62,23 @@ bool NissyAdapter::loadSolverData(nissy::solver& solver) { | |||
| 55 | ofs.write(reinterpret_cast<char *>(solver.data.data()), | 62 | ofs.write(reinterpret_cast<char *>(solver.data.data()), |
| 56 | solver.size); | 63 | solver.size); |
| 57 | ofs.close(); | 64 | ofs.close(); |
| 65 | logLine("Data generated succesfully"); | ||
| 58 | } else { | 66 | } else { |
| 67 | logLine("Reading data for solver " + solver.name + | ||
| 68 | " from file"); | ||
| 59 | std::ifstream ifs(filePath, std::ios::binary); | 69 | std::ifstream ifs(filePath, std::ios::binary); |
| 60 | solver.read_data(ifs); | 70 | solver.read_data(ifs); |
| 61 | ifs.close(); | 71 | ifs.close(); |
| 72 | logLine("Data loaded"); | ||
| 62 | } | 73 | } |
| 63 | 74 | ||
| 75 | logLine("Checking data integrity " | ||
| 76 | "(this is done only once per solver per session)..."); | ||
| 64 | if (!solver.check_data().ok()) { | 77 | if (!solver.check_data().ok()) { |
| 65 | emit solverError(QString("Error reading data!")); | 78 | emit solverError(QString("Error reading data!")); |
| 66 | return false; | 79 | return false; |
| 67 | } | 80 | } |
| 81 | logLine("Data checked"); | ||
| 68 | 82 | ||
| 69 | return true; | 83 | return true; |
| 70 | } | 84 | } |
| @@ -102,7 +116,7 @@ Q_INVOKABLE void NissyAdapter::requestSolve( | |||
| 102 | } | 116 | } |
| 103 | 117 | ||
| 104 | SolveOptions opts{c, ss, (unsigned)minmoves, (unsigned)maxmoves, | 118 | SolveOptions opts{c, ss, (unsigned)minmoves, (unsigned)maxmoves, |
| 105 | (unsigned)maxsolutions, optimal}; | 119 | (unsigned)maxsolutions, (unsigned)optimal}; |
| 106 | auto _ = QtConcurrent::run(&NissyAdapter::startSolve, this, opts); | 120 | auto _ = QtConcurrent::run(&NissyAdapter::startSolve, this, opts); |
| 107 | return; | 121 | return; |
| 108 | } | 122 | } |
| @@ -132,9 +146,16 @@ void NissyAdapter::startSolve(SolveOptions opts) | |||
| 132 | std::stringstream ss; | 146 | std::stringstream ss; |
| 133 | for (auto s : sols) { | 147 | for (auto s : sols) { |
| 134 | auto n = nissy::count_moves(s).value; | 148 | auto n = nissy::count_moves(s).value; |
| 135 | ss << s << "(" << n << ")" << std::endl; // TODO: remove last newline | 149 | ss << s << " (" << n << ")" << std::endl; // TODO: remove last newline |
| 136 | } | 150 | } |
| 137 | emit solutionsReady(QString::fromStdString(hs.str()), | 151 | emit solutionsReady(QString::fromStdString(hs.str()), |
| 138 | QString::fromStdString(ss.str())); | 152 | QString::fromStdString(ss.str())); |
| 139 | } | 153 | } |
| 140 | } | 154 | } |
| 155 | |||
| 156 | void NissyAdapter::logLine(std::string str) | ||
| 157 | { | ||
| 158 | std::stringstream ss; | ||
| 159 | ss << str << std::endl; | ||
| 160 | writeLog(ss.str()); | ||
| 161 | } | ||
diff --git a/qt/adapter.h b/qt/adapter.h index 856cdad..e961459 100644 --- a/qt/adapter.h +++ b/qt/adapter.h | |||
| @@ -14,7 +14,7 @@ struct SolveOptions { | |||
| 14 | unsigned minmoves; | 14 | unsigned minmoves; |
| 15 | unsigned maxmoves; | 15 | unsigned maxmoves; |
| 16 | unsigned maxsolutions; | 16 | unsigned maxsolutions; |
| 17 | int optimal; | 17 | unsigned optimal; |
| 18 | }; | 18 | }; |
| 19 | 19 | ||
| 20 | class NissyAdapter : public QObject { | 20 | class NissyAdapter : public QObject { |
| @@ -23,6 +23,8 @@ class NissyAdapter : public QObject { | |||
| 23 | QML_ELEMENT | 23 | QML_ELEMENT |
| 24 | 24 | ||
| 25 | public: | 25 | public: |
| 26 | static constexpr int maxSolutionsHardLimit = 9999; | ||
| 27 | |||
| 26 | NissyAdapter(); | 28 | NissyAdapter(); |
| 27 | 29 | ||
| 28 | Q_INVOKABLE bool isValidScramble(QString); | 30 | Q_INVOKABLE bool isValidScramble(QString); |
| @@ -42,11 +44,12 @@ signals: | |||
| 42 | 44 | ||
| 43 | private: | 45 | private: |
| 44 | std::vector<nissy::solver> solvers; | 46 | std::vector<nissy::solver> solvers; |
| 45 | std::function<void(const char*)> writeLog; | 47 | std::function<void(std::string)> writeLog; |
| 46 | 48 | ||
| 47 | void initSolver(const std::string&); | 49 | void initSolver(const std::string&); |
| 48 | void startSolve(SolveOptions); | 50 | void startSolve(SolveOptions); |
| 49 | bool loadSolverData(nissy::solver&); | 51 | bool loadSolverData(nissy::solver&); |
| 52 | void logLine(std::string); | ||
| 50 | }; | 53 | }; |
| 51 | 54 | ||
| 52 | #endif | 55 | #endif |
diff --git a/qt/main.cpp b/qt/main.cpp index 658d448..a012642 100644 --- a/qt/main.cpp +++ b/qt/main.cpp | |||
| @@ -10,7 +10,7 @@ int main(int argc, char *argv[]) | |||
| 10 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, | 10 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, |
| 11 | &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); | 11 | &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); |
| 12 | 12 | ||
| 13 | engine.loadFromModule("nissyqt", "NissyMain"); | 13 | engine.loadFromModule("nissyqt", "Main"); |
| 14 | 14 | ||
| 15 | return app.exec(); | 15 | return app.exec(); |
| 16 | } | 16 | } |
