aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastiano Tronto <sebastiano@tronto.net>2025-04-25 16:18:26 +0200
committerSebastiano Tronto <sebastiano@tronto.net>2025-04-25 16:19:02 +0200
commit97b5778172572504c8dfbe173b18281e786270c0 (patch)
treee775099577e9ef61465717ae453d71d8ff44559f
downloadqt-experiments-97b5778172572504c8dfbe173b18281e786270c0.tar.gz
qt-experiments-97b5778172572504c8dfbe173b18281e786270c0.zip
Initial commit
-rw-r--r--.gitignore8
-rw-r--r--.gitmodules3
-rw-r--r--README.md9
m---------nissy-core0
-rw-r--r--qt-quick/CMakeLists.txt26
-rw-r--r--qt-quick/Main.qml332
-rw-r--r--qt-quick/Makefile30
-rw-r--r--qt-quick/README.md28
-rw-r--r--qt-quick/adapter.cpp163
-rw-r--r--qt-quick/adapter.h55
-rw-r--r--qt-quick/main.cpp16
-rw-r--r--qt-widgets/README.md8
-rw-r--r--qt-widgets/adapter.cpp46
-rw-r--r--qt-widgets/adapter.h35
-rw-r--r--qt-widgets/main.cpp11
-rw-r--r--qt-widgets/nissyqt.pro18
-rw-r--r--qt-widgets/nissywindow.cpp35
-rw-r--r--qt-widgets/nissywindow.h36
-rw-r--r--qt-widgets/nissywindow.ui33
-rw-r--r--qt-widgets/solvercfgwidget.cpp90
-rw-r--r--qt-widgets/solvercfgwidget.h43
-rw-r--r--qt-widgets/solvercfgwidget.ui81
22 files changed, 1106 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..efd56c2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
1qt-quick/build
2qt-quick/nissy
3qt-quick/run
4qt-widgets/Makefile
5qt-widgets/build
6qt-widgets/generated_files
7qt-widgets/nissyqt
8qt-widgets/.qmake*
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c6ec593
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
1[submodule "nissy-core"]
2 path = nissy-core
3 url = https://git.tronto.net/nissy-core
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d6a1fbc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
1# Experiments with QT framework and nissy
2
3This repository contains some experiments with [QT](https://www.qt.io)
4in an attempt to make a usable UI for [nissy](https://nissy.tronto.net).
5
6These experiments are not fully-featured and I may not develop further in
7this direction.
8
9If you want to try them out, the most usable one is in the `qt-quick` folder.
diff --git a/nissy-core b/nissy-core
new file mode 160000
Subproject 24c2fd2ffa582ea83172184b1da2816555340a0
diff --git a/qt-quick/CMakeLists.txt b/qt-quick/CMakeLists.txt
new file mode 100644
index 0000000..8c7874e
--- /dev/null
+++ b/qt-quick/CMakeLists.txt
@@ -0,0 +1,26 @@
1cmake_minimum_required(VERSION 3.16)
2
3project(nissyqt VERSION 0.1 LANGUAGES CXX)
4
5set(CMAKE_CXX_STANDARD_REQUIRED ON)
6set(CMAKE_CXX_STANDARD 20)
7
8find_package(Qt6 REQUIRED COMPONENTS Quick Concurrent)
9
10qt_standard_project_setup(REQUIRES 6.5)
11
12qt_add_executable(appnissyqt
13 main.cpp
14 adapter.cpp adapter.h
15 build/nissy.h build/nissy.cpp build/nissy.o
16)
17
18qt_add_qml_module(appnissyqt
19 URI nissyqt
20 VERSION 1.0
21 QML_FILES Main.qml
22)
23
24target_link_libraries(appnissyqt
25 PRIVATE Qt6::Quick
26)
diff --git a/qt-quick/Main.qml b/qt-quick/Main.qml
new file mode 100644
index 0000000..a6e034a
--- /dev/null
+++ b/qt-quick/Main.qml
@@ -0,0 +1,332 @@
1import QtQuick
2import QtQuick.Controls
3import QtQuick.Layouts
4
5Window {
6 id: mainWindow
7
8 width: 800
9 height: 600
10 visible: true
11 title: "Nissy 3.0 - Preview"
12
13 SplitView {
14 id: splitView
15 anchors.fill: parent
16 orientation: Qt.Vertical
17
18 handle: Rectangle {
19 implicitHeight: 3
20 color: SplitHandle.hovered ? "black" : "#AAAAAA"
21 }
22
23 component MyScrollBar: ScrollBar {
24 orientation: Qt.Vertical
25 size: parent.height
26 policy: ScrollBar.AlwaysOn
27 anchors.right: parent.right
28 anchors.top: parent.top
29 anchors.bottom: parent.bottom
30 contentItem: Rectangle {
31 implicitWidth: 4
32 radius: implicitWidth/2
33 color: "black"
34 }
35 background: Rectangle {
36 implicitWidth: 4
37 radius: implicitWidth/2
38 color: "#AAAAAA"
39 }
40 }
41
42 ColumnLayout {
43 id: mainArea
44
45 property alias scramble: scrambleRow.scramble
46 property alias solver: solverCfg.solver
47 property alias minmoves: solverCfg.minmoves
48 property alias maxmoves: solverCfg.maxmoves
49 property alias maxsolutions: solverCfg.maxsolutions
50 property alias optimal: solverCfg.optimal
51 property alias sols: sols.text
52 property alias solsHeader: solsHeader.text
53
54 property bool solutionsLoading: false
55
56 anchors.top: parent.top
57 anchors.left: parent.left
58 anchors.right: parent.right
59 anchors.bottom: logView.top
60 anchors.margins: 6
61 spacing: 10
62
63 SplitView.minimumHeight: 180
64 SplitView.preferredHeight: 500
65
66 component Separator: Rectangle {
67 height: 1
68 Layout.fillWidth: true
69 color: "black"
70 }
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
116 ColumnLayout {
117 id: scrambleRow
118
119 property alias scramble: scrambleRowLayout.scramble
120
121 RowLayout {
122 id: scrambleRowLayout
123
124 property alias scramble: scrambleEditor.text
125
126 spacing: 6
127
128 TextField {
129 id: scrambleEditor
130
131 placeholderText: "Enter scramble here"
132 Layout.fillWidth: true
133 padding: 4
134
135 readonly property bool empty: text.trim().length == 0
136 readonly property bool valid: NissyAdapter.isValidScramble(text)
137
138 onAccepted: if (!empty && valid) submitScramble()
139 }
140
141 Button {
142 id: solveButton
143
144 enabled: !scrambleEditor.empty && scrambleEditor.valid &&
145 !mainArea.solutionsLoading
146 text: "Solve!"
147
148 onPressed: submitScramble()
149 }
150 }
151
152 Label {
153 id: invalidScrambleWarning
154 text: scrambleEditor.empty || scrambleEditor.valid ?
155 "" : "Invalid Scramble"
156 }
157 }
158
159 Separator {}
160
161 ColumnLayout {
162 id: solverCfg
163
164 property alias minmoves: minMaxRow.min
165 property alias maxmoves: minMaxRow.max
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 }
193
194 RowLayout {
195 id: minMaxRow
196
197 property alias min: slider.min
198 property alias max: slider.max
199
200 Rectangle {
201 width: 100
202 height: 20
203 Label { text: "Min moves: " + slider.min }
204 }
205 RangeSlider {
206 id: slider
207 from: 0
208 to: 20
209 first.value: from
210 second.value: to
211 stepSize: 1
212 snapMode: RangeSlider.SnapAlways
213
214 readonly property int min: Math.round(first.value)
215 readonly property int max: Math.round(second.value)
216 }
217 Rectangle {
218 width: 100
219 height: 20
220 Label { text: "Max moves: " + slider.max }
221 }
222 }
223
224 OptionalValue {
225 id: optimal
226
227 label: "Above optimal by at most"
228 from: 0
229 to: 20
230 defaultValue: 20
231 defaultEnabled: false
232 defaultSavedValue: 0
233 }
234
235 OptionalValue {
236 id: maxSols
237
238 label: "Limit number of solutions to"
239 from: 1
240 to: 999
241 defaultValue: 1
242 defaultEnabled: true
243 defaultSavedValue: 1
244 }
245 }
246
247 Separator {}
248
249 StackLayout {
250 Layout.maximumHeight: 30
251 currentIndex: mainArea.solutionsLoading ? 0 : 1
252
253 BusyIndicator { running: mainArea.solutionsLoading }
254 Label { id: solsHeader }
255 }
256
257 ScrollView {
258 Layout.fillHeight: true
259 Layout.fillWidth: true
260 Layout.bottomMargin: 10
261 ScrollBar.vertical: MyScrollBar {}
262
263 TextEdit {
264 id: sols
265 readOnly: true
266 font.family: "Monospace"
267 }
268 }
269 }
270
271 ScrollView {
272 id: logView
273
274 property alias text: logText.text
275
276 anchors.left: parent.left
277 anchors.right: parent.right
278 anchors.bottom: parent.bottom
279 anchors.margins: 6
280
281 SplitView.preferredHeight: 300
282
283 background: Rectangle {
284 color: "#404040"
285 radius: 4
286 }
287
288 ScrollBar.vertical: MyScrollBar {
289 id: scrollBar
290 position: 1.0 - size
291 }
292 Label {
293 id: logText
294
295 font.family: "Monospace"
296 color: "white"
297 }
298 }
299 }
300
301 function submitScramble() {
302 mainArea.solutionsLoading = true;
303 mainArea.solsHeader = ""
304 mainArea.sols = ""
305 logView.text = ""
306 NissyAdapter.requestSolve(
307 mainArea.scramble,
308 mainArea.solver,
309 mainArea.minmoves,
310 mainArea.maxmoves,
311 mainArea.maxsolutions,
312 mainArea.optimal
313 )
314 }
315
316 Connections {
317 target: NissyAdapter
318 function onSolutionsReady(header, sols) {
319 mainArea.solutionsLoading = false
320 mainArea.solsHeader = header
321 mainArea.sols = sols
322 }
323 function onSolverError(msg) {
324 mainArea.solutionsLoading = false
325 mainArea.solsHeader = msg
326 mainArea.sols = ""
327 }
328 function onAppendLog(msg) {
329 logView.text += msg
330 }
331 }
332}
diff --git a/qt-quick/Makefile b/qt-quick/Makefile
new file mode 100644
index 0000000..3132ab2
--- /dev/null
+++ b/qt-quick/Makefile
@@ -0,0 +1,30 @@
1all: nissyqt
2
3build:
4 mkdir -p build
5
6build/nissy.h:
7 cp ../nissy-core/cpp/nissy.h build/
8
9build/nissy.cpp:
10 cp ../nissy-core/cpp/nissy.cpp build/
11
12../nissy-core/config.mk:
13 cd ../nissy-core && ./configure.sh
14
15build/nissy.o: build ../nissy-core/config.mk
16 cd ../nissy-core && make nissy.o
17 cp ../nissy-core/nissy.o build/
18
19nissyqt: build/nissy.o build/nissy.h build/nissy.cpp
20 cmake . -B build
21 cd build && make
22 cp build/appnissyqt ./run
23
24run:
25 QT_LOGGING_RULES="*.debug=true; qt.*.debug=false" ./run
26
27clean:
28 rm -rf run build
29
30.PHONY: all nissyqt run clean
diff --git a/qt-quick/README.md b/qt-quick/README.md
new file mode 100644
index 0000000..54c4136
--- /dev/null
+++ b/qt-quick/README.md
@@ -0,0 +1,28 @@
1# QT-quick implementation of a nissy UI
2
3This is a UI for nissy written using
4[QT Quick](https://doc.qt.io/qt-6/qtquick-index.html).
5
6It has been tested only on Linux with QT 6.9.
7Building requires CMake.
8
9To build this project, run
10
11```
12make
13```
14
15To run it you can use
16
17```
18./run
19``
20
21or
22
23```
24make run
25```
26
27The latter command is going to build the project (if has not already
28been built) and run it with some debug options enabled.
diff --git a/qt-quick/adapter.cpp b/qt-quick/adapter.cpp
new file mode 100644
index 0000000..9594c56
--- /dev/null
+++ b/qt-quick/adapter.cpp
@@ -0,0 +1,163 @@
1#include "adapter.h"
2
3#include <fstream>
4#include <sstream>
5#include <string>
6#include <vector>
7#include <QDebug>
8#include <QtConcurrent/QtConcurrent>
9
10const std::string tablesdir = "../nissy-core/tables/";
11
12void logWrapper(const char *str, void *data)
13{
14 auto f = *reinterpret_cast<std::function<void(std::string)>*>(data);
15 f(std::string{str});
16}
17
18NissyAdapter::NissyAdapter()
19{
20 // TODO: this list must be kept in sync with UI code, it is a bit ugly
21 std::vector<std::string> solverNames {
22 "h48h0k4",
23 "h48h1k2",
24 "h48h2k2",
25 "h48h3k2",
26 "h48h7k2",
27 };
28
29 for (auto s : solverNames)
30 initSolver(s);
31
32 writeLog = [&](std::string str) {
33 emit appendLog(QString::fromStdString(str));
34 };
35
36 nissy::set_logger(&logWrapper, &writeLog);
37}
38
39void NissyAdapter::initSolver(const std::string& s) {
40 auto se = nissy::solver::get(s);
41 if (std::holds_alternative<nissy::error>(se)) {
42 qDebug("Error loading solver!");
43 return;
44 }
45 auto ss = std::get<nissy::solver>(se);
46 solvers.push_back(ss);
47}
48
49bool NissyAdapter::loadSolverData(nissy::solver& solver) {
50 if (solver.data_checked)
51 return true;
52
53 std::filesystem::path filePath(tablesdir + solver.id);
54 if (!std::filesystem::exists(filePath)) {
55 logLine("Data file for solver " + solver.name + " not found, "
56 "generating it...");
57 auto err = solver.generate_data();
58 if (!err.ok()) {
59 emit solverError(QString("Error generating data!"));
60 return false;
61 }
62 std::filesystem::create_directory(tablesdir);
63 std::ofstream ofs(filePath, std::ios::binary);
64 ofs.write(reinterpret_cast<char *>(solver.data.data()),
65 solver.size);
66 ofs.close();
67 logLine("Data generated succesfully");
68 } else {
69 logLine("Reading data for solver " + solver.name +
70 " from file");
71 std::ifstream ifs(filePath, std::ios::binary);
72 solver.read_data(ifs);
73 ifs.close();
74 logLine("Data loaded");
75 }
76
77 logLine("Checking data integrity "
78 "(this is done only once per solver per session)...");
79 if (!solver.check_data().ok()) {
80 emit solverError(QString("Error reading data!"));
81 return false;
82 }
83 logLine("Data checked");
84
85 return true;
86}
87
88Q_INVOKABLE bool NissyAdapter::isValidScramble(QString qscr)
89{
90 nissy::cube c;
91 return c.move(qscr.toStdString()).ok();
92}
93
94Q_INVOKABLE void NissyAdapter::requestSolve(
95 QString scramble,
96 QString solver,
97 int minmoves,
98 int maxmoves,
99 int maxsolutions,
100 int optimal
101)
102{
103 nissy::cube c;
104 if (!c.move(scramble.toStdString()).ok()) {
105 emit solverError(QString("Unexpected error: invalid scramble"));
106 return;
107 }
108
109 nissy::solver *ss = nullptr;
110 for (auto& s : solvers)
111 if (s.name == solver)
112 ss = &s;
113 if (ss == nullptr) {
114 std::string msg = "Error: solver '" + solver.toStdString()
115 + "' not available";
116 emit solverError(QString::fromStdString(msg));
117 return;
118 }
119
120 SolveOptions opts{c, ss, (unsigned)minmoves, (unsigned)maxmoves,
121 (unsigned)maxsolutions, (unsigned)optimal};
122 auto _ = QtConcurrent::run(&NissyAdapter::startSolve, this, opts);
123 return;
124}
125
126void NissyAdapter::startSolve(SolveOptions opts)
127{
128 loadSolverData(*opts.solver);
129
130 auto result = opts.solver->solve(opts.cube, nissy::nissflag::NORMAL,
131 opts.minmoves, opts.maxmoves, opts.maxsolutions, opts.optimal, 8);
132
133 if (!result.err.ok()) {
134 std::string msg = "Error computing solutions: " +
135 std::to_string(result.err.value);
136 emit solverError(QString::fromStdString(msg));
137 return;
138 }
139
140 auto& sols = result.solutions;
141 if (sols.size() == 0) {
142 emit solutionsReady("No solution found", "");
143 } else {
144 std::stringstream hs;
145 hs << "Found " << sols.size() << " solution"
146 << (sols.size() > 1 ? "s:" : ":");
147
148 std::stringstream ss;
149 for (auto s : sols) {
150 auto n = nissy::count_moves(s).value;
151 ss << s << " (" << n << ")" << std::endl; // TODO: remove last newline
152 }
153 emit solutionsReady(QString::fromStdString(hs.str()),
154 QString::fromStdString(ss.str()));
155 }
156}
157
158void NissyAdapter::logLine(std::string str)
159{
160 std::stringstream ss;
161 ss << str << std::endl;
162 writeLog(ss.str());
163}
diff --git a/qt-quick/adapter.h b/qt-quick/adapter.h
new file mode 100644
index 0000000..bde618f
--- /dev/null
+++ b/qt-quick/adapter.h
@@ -0,0 +1,55 @@
1#ifndef ADAPTER_H
2#define ADAPTER_H
3
4#include "../nissy-core/cpp/nissy.h"
5
6#include <map>
7#include <string>
8#include <QObject>
9#include <QtQmlIntegration>
10
11struct SolveOptions {
12 nissy::cube cube;
13 nissy::solver *solver;
14 unsigned minmoves;
15 unsigned maxmoves;
16 unsigned maxsolutions;
17 unsigned optimal;
18};
19
20class NissyAdapter : public QObject {
21 Q_OBJECT
22 QML_SINGLETON
23 QML_ELEMENT
24
25public:
26 static constexpr int maxSolutionsHardLimit = 9999;
27
28 NissyAdapter();
29
30 Q_INVOKABLE bool isValidScramble(QString);
31 Q_INVOKABLE void requestSolve(
32 QString scramble,
33 QString solver,
34 int minmoves,
35 int maxmoves,
36 int maxsolutions,
37 int optimal
38 );
39
40signals:
41 void solutionsReady(QString, QString);
42 void solverError(QString);
43 void appendLog(QString);
44
45private:
46 std::vector<nissy::solver> solvers;
47 std::function<void(std::string)> writeLog;
48
49 void initSolver(const std::string&);
50 void startSolve(SolveOptions);
51 bool loadSolverData(nissy::solver&);
52 void logLine(std::string);
53};
54
55#endif
diff --git a/qt-quick/main.cpp b/qt-quick/main.cpp
new file mode 100644
index 0000000..a012642
--- /dev/null
+++ b/qt-quick/main.cpp
@@ -0,0 +1,16 @@
1#include "adapter.h"
2
3#include <QGuiApplication>
4#include <QQmlApplicationEngine>
5
6int main(int argc, char *argv[])
7{
8 QGuiApplication app(argc, argv);
9 QQmlApplicationEngine engine;
10 QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
11 &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
12
13 engine.loadFromModule("nissyqt", "Main");
14
15 return app.exec();
16}
diff --git a/qt-widgets/README.md b/qt-widgets/README.md
new file mode 100644
index 0000000..f19c5b4
--- /dev/null
+++ b/qt-widgets/README.md
@@ -0,0 +1,8 @@
1# QT-widgets implementation of a nissy UI
2
3This is a UI for nissy written using
4[QT Widgets](https://doc.qt.io/qt-6/qtwidgets-index.html)
5
6You can't actually build or run this as is, because it relies on
7an older version of the C++ bindings for nissy. I'll fix it
8at some point, maybe.
diff --git a/qt-widgets/adapter.cpp b/qt-widgets/adapter.cpp
new file mode 100644
index 0000000..c15181c
--- /dev/null
+++ b/qt-widgets/adapter.cpp
@@ -0,0 +1,46 @@
1#include "adapter.h"
2
3#include <array>
4#include <filesystem>
5#include <fstream>
6#include <sstream>
7#include <QDebug>
8
9NissyAdapter::NissyAdapter()
10{
11 auto sid = nissy::solverinfo(defaultOptimalSolver);
12 auto [sz, dataid] = std::get<std::pair<size_t, std::string>>(sid);
13 const std::string path = "../nissy-core/tables/" + dataid;
14
15 std::filesystem::path filePath(path);
16 if (std::filesystem::file_size(filePath) != static_cast<uintmax_t>(sz))
17 qDebug("Error in file size!"); // TODO: better handle error, gentable
18
19 optimalSolverData.value.resize(sz);
20 std::ifstream ifs(path, std::ios::binary);
21 ifs.read(reinterpret_cast<char *>(optimalSolverData.value.data()), sz);
22 ifs.close();
23}
24
25NissyAdapter::~NissyAdapter() {}
26
27void NissyAdapter::solve(SolverConfiguration cfg)
28{
29 auto se = nissy::solve(cfg.cube, defaultOptimalSolver,
30 nissy::nissflag::NORMAL, cfg.minmoves, cfg.maxmoves,
31 cfg.maxsolutions, cfg.optimal, cfg.threads, optimalSolverData);
32 if (std::holds_alternative<nissy::error_t>(se)) {
33 auto code = std::get<nissy::error_t>(se).value;
34 emit solveDone(QString("Error " + code));
35 return;
36 }
37
38 auto [sols, stats] = std::get<nissy::solve_result_t>(se);
39 if (sols.size() == 0) {
40 emit solveDone(QString("No solution found"));
41 } else {
42 auto solstr = std::accumulate(
43 sols.begin(), sols.end(), std::string{});
44 emit solveDone(QString::fromStdString(solstr));
45 }
46}
diff --git a/qt-widgets/adapter.h b/qt-widgets/adapter.h
new file mode 100644
index 0000000..9fc35fa
--- /dev/null
+++ b/qt-widgets/adapter.h
@@ -0,0 +1,35 @@
1#ifndef ADAPTER_H
2#define ADAPTER_H
3
4#include "../nissy-core/cpp/nissy.h"
5
6#include <string>
7#include <vector>
8#include <QObject>
9
10struct SolverConfiguration {
11 nissy::cube cube{nissy::cube::SOLVED};
12 unsigned minmoves{0};
13 unsigned maxmoves{20};
14 unsigned maxsolutions{1};
15 int optimal{-1};
16 int threads{8};
17};
18
19class NissyAdapter : public QObject {
20 Q_OBJECT
21
22public:
23 NissyAdapter();
24 ~NissyAdapter();
25 void solve(SolverConfiguration);
26
27signals:
28 void solveDone(QString);
29
30private:
31 static constexpr nissy::solver defaultOptimalSolver{"h48h3k2"};
32 nissy::solver_data_t optimalSolverData;
33};
34
35#endif
diff --git a/qt-widgets/main.cpp b/qt-widgets/main.cpp
new file mode 100644
index 0000000..5addb96
--- /dev/null
+++ b/qt-widgets/main.cpp
@@ -0,0 +1,11 @@
1#include "nissywindow.h"
2
3#include <QApplication>
4
5int main(int argc, char *argv[])
6{
7 QApplication a(argc, argv);
8 NissyWindow w;
9 w.show();
10 return a.exec();
11}
diff --git a/qt-widgets/nissyqt.pro b/qt-widgets/nissyqt.pro
new file mode 100644
index 0000000..43f198c
--- /dev/null
+++ b/qt-widgets/nissyqt.pro
@@ -0,0 +1,18 @@
1# Source files
2SOURCES = main.cpp adapter.cpp nissywindow.cpp solvercfgwidget.cpp
3HEADERS = adapter.h nissywindow.h solvercfgwidget.h
4FORMS = nissywindow.ui solvercfgwidget.ui
5
6# Add nissy backend headers and code
7SOURCES += ../nissy-core/cpp/nissy.cpp
8HEADERS += ../nissy-core/cpp/nissy.h
9
10# Compiler configuration
11CONFIG += qt debug c++20
12LIBS += ../nissy-core/nissy.o
13QT += widgets concurrent
14
15# Destination folders for generated files
16MOC_DIR = generated_files
17UI_DIR = generated_files
18OBJECTS_DIR = build
diff --git a/qt-widgets/nissywindow.cpp b/qt-widgets/nissywindow.cpp
new file mode 100644
index 0000000..98cfc86
--- /dev/null
+++ b/qt-widgets/nissywindow.cpp
@@ -0,0 +1,35 @@
1#include "nissywindow.h"
2#include "./ui_nissywindow.h"
3#include "nissy/cpp/nissy.h"
4
5#include <QtConcurrent>
6
7NissyWindow::NissyWindow(QWidget *parent)
8 : QMainWindow(parent), ui(new Ui::NissyWindow)
9{
10 ui->setupUi(this);
11
12 QObject::connect(ui->solverCfgWidget,
13 SIGNAL(solveRequest(const SolverConfiguration&)), this,
14 SLOT(startSolve(const SolverConfiguration&)));
15 QObject::connect(&adapter, SIGNAL(solveDone(QString)), this,
16 SLOT(showSolutions(QString)));
17}
18
19NissyWindow::~NissyWindow()
20{
21 delete ui;
22}
23
24void NissyWindow::startSolve(const SolverConfiguration& config)
25{
26 ui->solverCfgWidget->lockSubmit();
27 ui->solutionsLabel->setText("Loading solutions...");
28 auto _ = QtConcurrent::run(&NissyAdapter::solve, &adapter, config);
29}
30
31void NissyWindow::showSolutions(QString solutions) {
32 QString header = QString("Solution(s) found:\n");
33 ui->solutionsLabel->setText(header + solutions);
34 ui->solverCfgWidget->unlockSubmit();
35}
diff --git a/qt-widgets/nissywindow.h b/qt-widgets/nissywindow.h
new file mode 100644
index 0000000..c2e7b44
--- /dev/null
+++ b/qt-widgets/nissywindow.h
@@ -0,0 +1,36 @@
1#ifndef NISSYWINDOW_H
2#define NISSYWINDOW_H
3
4#include "adapter.h"
5#include "solvercfgwidget.h"
6
7#include <QMainWindow>
8#include <QLabel>
9#include <QLineEdit>
10#include <QPushButton>
11#include <QString>
12
13QT_BEGIN_NAMESPACE
14namespace Ui {
15class NissyWindow;
16}
17QT_END_NAMESPACE
18
19class NissyWindow : public QMainWindow
20{
21 Q_OBJECT
22
23public:
24 NissyWindow(QWidget *parent = nullptr);
25 ~NissyWindow();
26
27private slots:
28 void showSolutions(QString);
29 void startSolve(const SolverConfiguration&);
30
31private:
32 Ui::NissyWindow *ui;
33 NissyAdapter adapter;
34};
35
36#endif
diff --git a/qt-widgets/nissywindow.ui b/qt-widgets/nissywindow.ui
new file mode 100644
index 0000000..6b11229
--- /dev/null
+++ b/qt-widgets/nissywindow.ui
@@ -0,0 +1,33 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3<class>NissyWindow</class>
4<widget class="QMainWindow" name="nissyWindow">
5
6<property name="geometry">
7 <rect>
8 <x>0</x>
9 <y>0</y>
10 <width>800</width>
11 <height>600</height>
12 </rect>
13</property>
14<property name="windowTitle">
15 <string>Nissy 3.0 - preview</string>
16</property>
17
18<widget class="QWidget" name="centralWidget">
19
20<layout class="QVBoxLayout" name="verticalLayout">
21 <item>
22 <widget class="SolverCfgWidget" name="solverCfgWidget" />
23 </item>
24
25 <item>
26 <widget class="QLabel" name="solutionsLabel" />
27 </item>
28</layout>
29
30</widget>
31
32</widget>
33</ui>
diff --git a/qt-widgets/solvercfgwidget.cpp b/qt-widgets/solvercfgwidget.cpp
new file mode 100644
index 0000000..8027fe5
--- /dev/null
+++ b/qt-widgets/solvercfgwidget.cpp
@@ -0,0 +1,90 @@
1#include "solvercfgwidget.h"
2#include "./ui_solvercfgwidget.h"
3
4enum class ScrambleState { empty, valid, invalid };
5
6ScrambleState getScrambleState(const std::string&);
7
8SolverCfgWidget::SolverCfgWidget(QWidget *parent)
9 : QWidget(parent), ui(new Ui::SolverCfgWidget)
10{
11 ui->setupUi(this);
12 onScrambleChanged(ui->scrambleEditor->text());
13
14 nmovesValidator = new QIntValidator(0, 20, this);
15 ui->minMovesEditor->setValidator(nmovesValidator);
16 ui->maxMovesEditor->setValidator(nmovesValidator);
17
18 QObject::connect(ui->scrambleEditor,
19 SIGNAL(textChanged(const QString&)), this,
20 SLOT(onScrambleChanged(const QString&)));
21 QObject::connect(ui->scrambleEditor, SIGNAL(returnPressed()),
22 this, SLOT(onScrambleSubmitted()));
23 QObject::connect(ui->solveButton, SIGNAL(clicked()),
24 this, SLOT(onScrambleSubmitted()));
25}
26
27SolverCfgWidget::~SolverCfgWidget()
28{
29 delete nmovesValidator;
30 delete ui;
31}
32
33void SolverCfgWidget::lockSubmit()
34{
35 submitLocked = true;
36 onScrambleChanged(ui->scrambleEditor->text());
37}
38
39void SolverCfgWidget::unlockSubmit()
40{
41 submitLocked = false;
42 onScrambleChanged(ui->scrambleEditor->text());
43}
44
45void SolverCfgWidget::onScrambleChanged(const QString& text)
46{
47 auto scrambleState = getScrambleState(text.toStdString());
48 switch (scrambleState) {
49 case ScrambleState::empty:
50 ui->solveButton->setEnabled(false);
51 ui->scrambleEditor->setStyleSheet("");
52 break;
53 case ScrambleState::valid:
54 ui->solveButton->setEnabled(!submitLocked);
55 ui->scrambleEditor->setStyleSheet("");
56 break;
57 case ScrambleState::invalid:
58 ui->solveButton->setEnabled(false);
59 ui->scrambleEditor->setStyleSheet("border : 2px solid red");
60 break;
61 }
62}
63
64void SolverCfgWidget::onScrambleSubmitted()
65{
66 std::string scramble = ui->scrambleEditor->text().toStdString();
67 auto state = getScrambleState(scramble);
68 if (state != ScrambleState::valid)
69 return;
70
71 SolverConfiguration sc {
72 .cube = std::get<nissy::cube_t>(nissy::applymoves(
73 nissy::cube::SOLVED, nissy::moves_t{scramble})),
74 .minmoves = ui->minMovesEditor->text().toUInt(),
75 .maxmoves = ui->maxMovesEditor->text().toUInt(),
76 };
77 emit solveRequest(sc);
78}
79
80ScrambleState getScrambleState(const std::string& s)
81{
82 if (std::all_of(s.begin(), s.end(),
83 [](std::string::value_type c){ return std::isspace(c); }))
84 return ScrambleState::empty;
85
86 auto c = nissy::applymoves(nissy::cube::SOLVED, nissy::moves_t{s});
87 return std::holds_alternative<nissy::error_t>(c) ?
88 ScrambleState::invalid : ScrambleState::valid;
89}
90
diff --git a/qt-widgets/solvercfgwidget.h b/qt-widgets/solvercfgwidget.h
new file mode 100644
index 0000000..b6494c8
--- /dev/null
+++ b/qt-widgets/solvercfgwidget.h
@@ -0,0 +1,43 @@
1#ifndef SOLVERCFGWIDGET_H
2#define SOLVERCFGWIDGET_H
3
4#include "adapter.h"
5
6#include <QWidget>
7#include <QLineEdit>
8#include <QPushButton>
9#include <QString>
10#include <QIntValidator>
11
12QT_BEGIN_NAMESPACE
13namespace Ui {
14class SolverCfgWidget;
15}
16QT_END_NAMESPACE
17
18class SolverCfgWidget : public QWidget
19{
20 Q_OBJECT
21
22public:
23 SolverCfgWidget(QWidget *parent);
24 ~SolverCfgWidget();
25
26 void lockSubmit();
27 void unlockSubmit();
28
29signals:
30 void solveRequest(const SolverConfiguration&);
31
32private slots:
33 void onScrambleChanged(const QString&);
34 void onScrambleSubmitted();
35
36private:
37 bool submitLocked;
38 QIntValidator *nmovesValidator;
39
40 Ui::SolverCfgWidget *ui;
41};
42
43#endif
diff --git a/qt-widgets/solvercfgwidget.ui b/qt-widgets/solvercfgwidget.ui
new file mode 100644
index 0000000..19ff082
--- /dev/null
+++ b/qt-widgets/solvercfgwidget.ui
@@ -0,0 +1,81 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3<class>SolverCfgWidget</class>
4<widget class="SolverCfgWidget" name="solverCfgWidget">
5
6<layout class="QVBoxLayout">
7
8<item>
9<layout class="QHBoxLayout">
10 <item>
11 <widget class="QLineEdit" name="scrambleEditor">
12 <property name="placeholderText">
13 <string>Enter your scramble here...</string>
14 </property>
15 </widget>
16 </item>
17
18 <item>
19 <widget class="QPushButton" name="solveButton">
20 <property name="text">
21 <string>Solve!</string>
22 </property>
23 </widget>
24 </item>
25</layout>
26</item>
27
28<item>
29 <layout class="QHBoxLayout">
30 <item>
31 <widget class="QLabel">
32 <property name="text">
33 <string>Min moves</string>
34 </property>
35 </widget>
36 </item>
37
38 <item>
39 <widget class="QLineEdit" name="minMovesEditor">
40 <property name="maximumSize">
41 <size>
42 <width>25</width>
43 <height>20</height>
44 </size>
45 </property>
46
47 <property name="text">
48 <string>0</string>
49 </property>
50 </widget>
51 </item>
52
53 <item>
54 <widget class="QLabel">
55 <property name="text">
56 <string>Max moves</string>
57 </property>
58 </widget>
59 </item>>
60
61 <item>
62 <widget class="QLineEdit" name="maxMovesEditor">
63 <property name="maximumSize">
64 <size>
65 <width>25</width>
66 <height>20</height>
67 </size>
68 </property>
69
70 <property name="text">
71 <string>20</string>
72 </property>
73 </widget>
74 </item>
75 </layout>
76</item>
77</layout>
78
79
80</widget>
81</ui>

Generated with cgit - Back to sebastiano.tronto.net