diff options
| author | Sebastiano Tronto <sebastiano@tronto.net> | 2025-04-16 09:56:55 +0200 |
|---|---|---|
| committer | Sebastiano Tronto <sebastiano@tronto.net> | 2025-04-16 09:56:55 +0200 |
| commit | 9589072687726dd51776b0e952625ae1996f4721 (patch) | |
| tree | 2dbc63de0b3e5805b5dca2919d05b4356c1194a4 | |
| parent | dc5e8188a7e29909cbdf661e9b2a80700fe0491d (diff) | |
| download | nissy-core-9589072687726dd51776b0e952625ae1996f4721.tar.gz nissy-core-9589072687726dd51776b0e952625ae1996f4721.zip | |
Added rudimentary QT UI
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Makefile | 10 | ||||
| -rw-r--r-- | qt/CMakeLists.txt | 27 | ||||
| -rw-r--r-- | qt/Makefile | 15 | ||||
| -rw-r--r-- | qt/NissyMain.qml | 261 | ||||
| -rw-r--r-- | qt/adapter.cpp | 140 | ||||
| -rw-r--r-- | qt/adapter.h | 52 | ||||
| -rw-r--r-- | qt/main.cpp | 16 |
8 files changed, 521 insertions, 3 deletions
| @@ -9,11 +9,12 @@ utils/.DS_Store | |||
| 9 | perf.data | 9 | perf.data |
| 10 | perf.data.old | 10 | perf.data.old |
| 11 | run | 11 | run |
| 12 | nissyqt | ||
| 13 | qt/build | ||
| 12 | debugrun | 14 | debugrun |
| 13 | shell/lasttest.out | 15 | shell/lasttest.out |
| 14 | shell/lasttest.err | 16 | shell/lasttest.err |
| 15 | tables/* | 17 | tables/* |
| 16 | tables-old*/* | ||
| 17 | test/*/runtest | 18 | test/*/runtest |
| 18 | test/.DS_Store | 19 | test/.DS_Store |
| 19 | test/run | 20 | test/run |
| @@ -18,7 +18,7 @@ debugnissy.o: | |||
| 18 | ${CC} ${MACROS} ${DBGFLAGS} -c -o debugnissy.o src/nissy.c | 18 | ${CC} ${MACROS} ${DBGFLAGS} -c -o debugnissy.o src/nissy.c |
| 19 | 19 | ||
| 20 | clean: | 20 | clean: |
| 21 | rm -rf *.out *.o *.s *.so run debugrun | 21 | rm -rf *.out *.o *.s *.so run debugrun nissyqt qt/build |
| 22 | 22 | ||
| 23 | test: debugnissy.o | 23 | test: debugnissy.o |
| 24 | CC="${CC} ${MACROS} ${DBGFLAGS}" OBJ=debugnissy.o ./test/test.sh | 24 | CC="${CC} ${MACROS} ${DBGFLAGS}" OBJ=debugnissy.o ./test/test.sh |
| @@ -46,4 +46,10 @@ python: nissy.o | |||
| 46 | ${CC} ${CFLAGS} -shared ${PYTHON3_INCLUDES} -o nissy_python_module.so \ | 46 | ${CC} ${CFLAGS} -shared ${PYTHON3_INCLUDES} -o nissy_python_module.so \ |
| 47 | nissy.o python/nissy_module.c | 47 | nissy.o python/nissy_module.c |
| 48 | 48 | ||
| 49 | .PHONY: all clean test tool debugtool shell debugshell shelltest python | 49 | qt: nissy.o |
| 50 | mkdir -p qt/build | ||
| 51 | cd qt && cmake . -B build | ||
| 52 | cd qt/build && make | ||
| 53 | cp qt/build/appnissyqt ./nissyqt | ||
| 54 | |||
| 55 | .PHONY: all clean test tool debugtool shell debugshell shelltest python qt | ||
diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt new file mode 100644 index 0000000..8ba768c --- /dev/null +++ b/qt/CMakeLists.txt | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | cmake_minimum_required(VERSION 3.16) | ||
| 2 | |||
| 3 | project(nissyqt VERSION 0.1 LANGUAGES CXX) | ||
| 4 | |||
| 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
| 6 | set(CMAKE_CXX_STANDARD 20) | ||
| 7 | |||
| 8 | find_package(Qt6 REQUIRED COMPONENTS Quick Concurrent) | ||
| 9 | |||
| 10 | qt_standard_project_setup(REQUIRES 6.5) | ||
| 11 | |||
| 12 | qt_add_executable(appnissyqt | ||
| 13 | main.cpp | ||
| 14 | adapter.cpp adapter.h | ||
| 15 | ../cpp/nissy.h ../cpp/nissy.cpp | ||
| 16 | ../nissy.o | ||
| 17 | ) | ||
| 18 | |||
| 19 | qt_add_qml_module(appnissyqt | ||
| 20 | URI nissyqt | ||
| 21 | VERSION 1.0 | ||
| 22 | QML_FILES NissyMain.qml | ||
| 23 | ) | ||
| 24 | |||
| 25 | target_link_libraries(appnissyqt | ||
| 26 | PRIVATE Qt6::Quick | ||
| 27 | ) | ||
diff --git a/qt/Makefile b/qt/Makefile new file mode 100644 index 0000000..5b13026 --- /dev/null +++ b/qt/Makefile | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | all: nissyqt | ||
| 2 | |||
| 3 | nissyqt: | ||
| 4 | mkdir -p build | ||
| 5 | cmake . -B build | ||
| 6 | cd build && make | ||
| 7 | cp build/appnissyqt ./run | ||
| 8 | |||
| 9 | run: | ||
| 10 | QT_LOGGING_RULES="*.debug=true; qt.*.debug=false" ./run | ||
| 11 | |||
| 12 | clean: | ||
| 13 | rm -rf run build | ||
| 14 | |||
| 15 | .PHONY: all nissyqt run clean | ||
diff --git a/qt/NissyMain.qml b/qt/NissyMain.qml new file mode 100644 index 0000000..24aff43 --- /dev/null +++ b/qt/NissyMain.qml | |||
| @@ -0,0 +1,261 @@ | |||
| 1 | import QtQuick | ||
| 2 | import QtQuick.Controls | ||
| 3 | import QtQuick.Layouts | ||
| 4 | |||
| 5 | Window { | ||
| 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 minmoves: solverCfg.minmoves | ||
| 47 | property alias maxmoves: solverCfg.maxmoves | ||
| 48 | property alias maxsolutions: solverCfg.maxsolutions | ||
| 49 | property alias sols: sols.text | ||
| 50 | property alias solsHeader: solsHeader.text | ||
| 51 | |||
| 52 | property bool solutionsLoading: false | ||
| 53 | |||
| 54 | anchors.top: parent.top | ||
| 55 | anchors.left: parent.left | ||
| 56 | anchors.right: parent.right | ||
| 57 | anchors.bottom: logView.top | ||
| 58 | anchors.margins: 6 | ||
| 59 | spacing: 10 | ||
| 60 | |||
| 61 | SplitView.minimumHeight: 180 | ||
| 62 | SplitView.preferredHeight: 300 | ||
| 63 | |||
| 64 | component Separator: Rectangle { | ||
| 65 | height: 1 | ||
| 66 | Layout.fillWidth: true | ||
| 67 | color: "black" | ||
| 68 | } | ||
| 69 | |||
| 70 | ColumnLayout { | ||
| 71 | id: scrambleRow | ||
| 72 | |||
| 73 | property alias scramble: scrambleRowLayout.scramble | ||
| 74 | |||
| 75 | RowLayout { | ||
| 76 | id: scrambleRowLayout | ||
| 77 | |||
| 78 | property alias scramble: scrambleEditor.text | ||
| 79 | |||
| 80 | spacing: 6 | ||
| 81 | |||
| 82 | TextField { | ||
| 83 | id: scrambleEditor | ||
| 84 | |||
| 85 | placeholderText: "Enter scramble here" | ||
| 86 | Layout.fillWidth: true | ||
| 87 | padding: 4 | ||
| 88 | |||
| 89 | readonly property bool empty: text.trim().length == 0 | ||
| 90 | readonly property bool valid: NissyAdapter.isValidScramble(text) | ||
| 91 | |||
| 92 | onAccepted: if (!empty && valid) submitScramble() | ||
| 93 | } | ||
| 94 | |||
| 95 | Button { | ||
| 96 | id: solveButton | ||
| 97 | |||
| 98 | enabled: !scrambleEditor.empty && scrambleEditor.valid && | ||
| 99 | !mainArea.solutionsLoading | ||
| 100 | text: "Solve!" | ||
| 101 | |||
| 102 | onPressed: submitScramble() | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | Label { | ||
| 107 | id: invalidScrambleWarning | ||
| 108 | text: scrambleEditor.empty || scrambleEditor.valid ? | ||
| 109 | "" : "Invalid Scramble" | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | Separator {} | ||
| 114 | |||
| 115 | ColumnLayout { | ||
| 116 | id: solverCfg | ||
| 117 | property alias minmoves: minMaxRow.min | ||
| 118 | property alias maxmoves: minMaxRow.max | ||
| 119 | property alias maxsolutions: maxOptimal.maxsolutions | ||
| 120 | |||
| 121 | RowLayout { | ||
| 122 | id: minMaxRow | ||
| 123 | |||
| 124 | property alias min: slider.min | ||
| 125 | property alias max: slider.max | ||
| 126 | |||
| 127 | Rectangle { | ||
| 128 | width: 100 | ||
| 129 | height: 20 | ||
| 130 | Label { text: "Min moves: " + slider.min } | ||
| 131 | } | ||
| 132 | RangeSlider { | ||
| 133 | id: slider | ||
| 134 | from: 0 | ||
| 135 | to: 20 | ||
| 136 | first.value: from | ||
| 137 | second.value: to | ||
| 138 | stepSize: 1 | ||
| 139 | snapMode: RangeSlider.SnapAlways | ||
| 140 | |||
| 141 | readonly property int min: Math.round(first.value) | ||
| 142 | readonly property int max: Math.round(second.value) | ||
| 143 | } | ||
| 144 | Rectangle { | ||
| 145 | width: 100 | ||
| 146 | height: 20 | ||
| 147 | Label { text: "Max moves: " + slider.max } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | RowLayout { | ||
| 152 | id: maxOptimal | ||
| 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 | |||
| 169 | TextField { | ||
| 170 | id: textField | ||
| 171 | |||
| 172 | width: parent.width | ||
| 173 | text: "1" | ||
| 174 | validator: IntValidator{ bottom: 1; top: 999; } | ||
| 175 | |||
| 176 | onAccepted: submitScramble() | ||
| 177 | } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | Separator {} | ||
| 183 | |||
| 184 | Label { id: solsHeader } | ||
| 185 | |||
| 186 | ScrollView { | ||
| 187 | Layout.fillHeight: true | ||
| 188 | Layout.fillWidth: true | ||
| 189 | Layout.bottomMargin: 10 | ||
| 190 | ScrollBar.vertical: MyScrollBar {} | ||
| 191 | |||
| 192 | TextEdit { | ||
| 193 | id: sols | ||
| 194 | readOnly: true | ||
| 195 | font.family: "Monospace" | ||
| 196 | } | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | ScrollView { | ||
| 201 | id: logView | ||
| 202 | |||
| 203 | property alias text: logText.text | ||
| 204 | |||
| 205 | anchors.left: parent.left | ||
| 206 | anchors.right: parent.right | ||
| 207 | anchors.bottom: parent.bottom | ||
| 208 | anchors.margins: 6 | ||
| 209 | |||
| 210 | SplitView.preferredHeight: 300 | ||
| 211 | |||
| 212 | background: Rectangle { | ||
| 213 | color: "#404040" | ||
| 214 | radius: 4 | ||
| 215 | } | ||
| 216 | |||
| 217 | ScrollBar.vertical: MyScrollBar { | ||
| 218 | id: scrollBar | ||
| 219 | position: 1.0 - size | ||
| 220 | } | ||
| 221 | Label { | ||
| 222 | id: logText | ||
| 223 | |||
| 224 | font.family: "Monospace" | ||
| 225 | color: "white" | ||
| 226 | } | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | function submitScramble() { | ||
| 231 | mainArea.solutionsLoading = true; | ||
| 232 | mainArea.solsHeader = "Loading solutions..." | ||
| 233 | mainArea.sols = "" | ||
| 234 | logView.text = "" | ||
| 235 | NissyAdapter.requestSolve( | ||
| 236 | mainArea.scramble, | ||
| 237 | "h48h3k2", | ||
| 238 | mainArea.minmoves, | ||
| 239 | mainArea.maxmoves, | ||
| 240 | mainArea.maxsolutions, | ||
| 241 | -1 | ||
| 242 | ) | ||
| 243 | } | ||
| 244 | |||
| 245 | Connections { | ||
| 246 | target: NissyAdapter | ||
| 247 | function onSolutionsReady(header, sols) { | ||
| 248 | mainArea.solutionsLoading = false | ||
| 249 | mainArea.solsHeader = header | ||
| 250 | mainArea.sols = sols | ||
| 251 | } | ||
| 252 | function onSolverError(msg) { | ||
| 253 | mainArea.solutionsLoading = false | ||
| 254 | mainArea.solusHeader = msg | ||
| 255 | mainArea.sols = "" | ||
| 256 | } | ||
| 257 | function onAppendLog(msg) { | ||
| 258 | logView.text += msg | ||
| 259 | } | ||
| 260 | } | ||
| 261 | } | ||
diff --git a/qt/adapter.cpp b/qt/adapter.cpp new file mode 100644 index 0000000..3711fa4 --- /dev/null +++ b/qt/adapter.cpp | |||
| @@ -0,0 +1,140 @@ | |||
| 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 | |||
| 10 | void logWrapper(const char *str, void *data) | ||
| 11 | { | ||
| 12 | auto f = *reinterpret_cast<std::function<void(const char*)>*>(data); | ||
| 13 | f(str); | ||
| 14 | } | ||
| 15 | |||
| 16 | NissyAdapter::NissyAdapter() | ||
| 17 | { | ||
| 18 | std::vector<std::string> solverNames { | ||
| 19 | "h48h3k2" | ||
| 20 | }; | ||
| 21 | |||
| 22 | for (auto s : solverNames) | ||
| 23 | initSolver(s); | ||
| 24 | |||
| 25 | writeLog = [&](const char *str) { | ||
| 26 | emit appendLog(QString::fromStdString(str)); | ||
| 27 | }; | ||
| 28 | |||
| 29 | nissy::set_logger(&logWrapper, &writeLog); | ||
| 30 | } | ||
| 31 | |||
| 32 | void NissyAdapter::initSolver(const std::string& s) { | ||
| 33 | auto se = nissy::solver::get(s); | ||
| 34 | if (std::holds_alternative<nissy::error>(se)) { | ||
| 35 | qDebug("Error loading solver!"); | ||
| 36 | return; | ||
| 37 | } | ||
| 38 | auto ss = std::get<nissy::solver>(se); | ||
| 39 | solvers.push_back(ss); | ||
| 40 | } | ||
| 41 | |||
| 42 | bool NissyAdapter::loadSolverData(nissy::solver& solver) { | ||
| 43 | if (solver.data_checked) | ||
| 44 | return true; | ||
| 45 | |||
| 46 | std::filesystem::path filePath("./tables/" + solver.id); | ||
| 47 | if (!std::filesystem::exists(filePath)) { | ||
| 48 | auto err = solver.generate_data(); | ||
| 49 | if (!err.ok()) { | ||
| 50 | emit solverError(QString("Error generating data!")); | ||
| 51 | return false; | ||
| 52 | } | ||
| 53 | std::filesystem::create_directory("./tables/"); | ||
| 54 | std::ofstream ofs(filePath, std::ios::binary); | ||
| 55 | ofs.write(reinterpret_cast<char *>(solver.data.data()), | ||
| 56 | solver.size); | ||
| 57 | ofs.close(); | ||
| 58 | } else { | ||
| 59 | std::ifstream ifs(filePath, std::ios::binary); | ||
| 60 | solver.read_data(ifs); | ||
| 61 | ifs.close(); | ||
| 62 | } | ||
| 63 | |||
| 64 | if (!solver.check_data().ok()) { | ||
| 65 | emit solverError(QString("Error reading data!")); | ||
| 66 | return false; | ||
| 67 | } | ||
| 68 | |||
| 69 | return true; | ||
| 70 | } | ||
| 71 | |||
| 72 | Q_INVOKABLE bool NissyAdapter::isValidScramble(QString qscr) | ||
| 73 | { | ||
| 74 | nissy::cube c; | ||
| 75 | return c.move(qscr.toStdString()).ok(); | ||
| 76 | } | ||
| 77 | |||
| 78 | Q_INVOKABLE void NissyAdapter::requestSolve( | ||
| 79 | QString scramble, | ||
| 80 | QString solver, | ||
| 81 | int minmoves, | ||
| 82 | int maxmoves, | ||
| 83 | int maxsolutions, | ||
| 84 | int optimal | ||
| 85 | ) | ||
| 86 | { | ||
| 87 | nissy::cube c; | ||
| 88 | if (!c.move(scramble.toStdString()).ok()) { | ||
| 89 | emit solverError(QString("Unexpected error: invalid scramble")); | ||
| 90 | return; | ||
| 91 | } | ||
| 92 | |||
| 93 | nissy::solver *ss = nullptr; | ||
| 94 | for (auto& s : solvers) | ||
| 95 | if (s.name == solver) | ||
| 96 | ss = &s; | ||
| 97 | if (ss == nullptr) { | ||
| 98 | std::string msg = "Error: solver '" + solver.toStdString() | ||
| 99 | + "' not available"; | ||
| 100 | emit solverError(QString::fromStdString(msg)); | ||
| 101 | return; | ||
| 102 | } | ||
| 103 | |||
| 104 | SolveOptions opts{c, ss, (unsigned)minmoves, (unsigned)maxmoves, | ||
| 105 | (unsigned)maxsolutions, optimal}; | ||
| 106 | auto _ = QtConcurrent::run(&NissyAdapter::startSolve, this, opts); | ||
| 107 | return; | ||
| 108 | } | ||
| 109 | |||
| 110 | void NissyAdapter::startSolve(SolveOptions opts) | ||
| 111 | { | ||
| 112 | loadSolverData(*opts.solver); | ||
| 113 | |||
| 114 | auto result = opts.solver->solve(opts.cube, nissy::nissflag::NORMAL, | ||
| 115 | opts.minmoves, opts.maxmoves, opts.maxsolutions, opts.optimal, 8); | ||
| 116 | |||
| 117 | if (!result.err.ok()) { | ||
| 118 | std::string msg = "Error computing solutions: " + | ||
| 119 | std::to_string(result.err.value); | ||
| 120 | emit solverError(QString::fromStdString(msg)); | ||
| 121 | return; | ||
| 122 | } | ||
| 123 | |||
| 124 | auto& sols = result.solutions; | ||
| 125 | if (sols.size() == 0) { | ||
| 126 | emit solutionsReady("No solution found", ""); | ||
| 127 | } else { | ||
| 128 | std::stringstream hs; | ||
| 129 | hs << "Found " << sols.size() << " solution" | ||
| 130 | << (sols.size() > 1 ? "s:" : ":"); | ||
| 131 | |||
| 132 | std::stringstream ss; | ||
| 133 | for (auto s : sols) { | ||
| 134 | auto n = nissy::count_moves(s).value; | ||
| 135 | ss << s << "(" << n << ")" << std::endl; // TODO: remove last newline | ||
| 136 | } | ||
| 137 | emit solutionsReady(QString::fromStdString(hs.str()), | ||
| 138 | QString::fromStdString(ss.str())); | ||
| 139 | } | ||
| 140 | } | ||
diff --git a/qt/adapter.h b/qt/adapter.h new file mode 100644 index 0000000..856cdad --- /dev/null +++ b/qt/adapter.h | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | #ifndef ADAPTER_H | ||
| 2 | #define ADAPTER_H | ||
| 3 | |||
| 4 | #include "../cpp/nissy.h" | ||
| 5 | |||
| 6 | #include <map> | ||
| 7 | #include <string> | ||
| 8 | #include <QObject> | ||
| 9 | #include <QtQmlIntegration> | ||
| 10 | |||
| 11 | struct SolveOptions { | ||
| 12 | nissy::cube cube; | ||
| 13 | nissy::solver *solver; | ||
| 14 | unsigned minmoves; | ||
| 15 | unsigned maxmoves; | ||
| 16 | unsigned maxsolutions; | ||
| 17 | int optimal; | ||
| 18 | }; | ||
| 19 | |||
| 20 | class NissyAdapter : public QObject { | ||
| 21 | Q_OBJECT | ||
| 22 | QML_SINGLETON | ||
| 23 | QML_ELEMENT | ||
| 24 | |||
| 25 | public: | ||
| 26 | NissyAdapter(); | ||
| 27 | |||
| 28 | Q_INVOKABLE bool isValidScramble(QString); | ||
| 29 | Q_INVOKABLE void requestSolve( | ||
| 30 | QString scramble, | ||
| 31 | QString solver, | ||
| 32 | int minmoves, | ||
| 33 | int maxmoves, | ||
| 34 | int maxsolutions, | ||
| 35 | int optimal | ||
| 36 | ); | ||
| 37 | |||
| 38 | signals: | ||
| 39 | void solutionsReady(QString, QString); | ||
| 40 | void solverError(QString); | ||
| 41 | void appendLog(QString); | ||
| 42 | |||
| 43 | private: | ||
| 44 | std::vector<nissy::solver> solvers; | ||
| 45 | std::function<void(const char*)> writeLog; | ||
| 46 | |||
| 47 | void initSolver(const std::string&); | ||
| 48 | void startSolve(SolveOptions); | ||
| 49 | bool loadSolverData(nissy::solver&); | ||
| 50 | }; | ||
| 51 | |||
| 52 | #endif | ||
diff --git a/qt/main.cpp b/qt/main.cpp new file mode 100644 index 0000000..658d448 --- /dev/null +++ b/qt/main.cpp | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | #include "adapter.h" | ||
| 2 | |||
| 3 | #include <QGuiApplication> | ||
| 4 | #include <QQmlApplicationEngine> | ||
| 5 | |||
| 6 | int 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", "NissyMain"); | ||
| 14 | |||
| 15 | return app.exec(); | ||
| 16 | } | ||
