#include"main_window.hpp" #include<QIcon> #include<QLayout> #include<QApplication> #include<QMessageBox> #include<QFile> #include<QTextStream> #include<QInputDialog> #include<QFileInfo> #include"settings.hpp" #include"audio-window.hpp" #include <random> #include <algorithm> #include<QProcess> #include<QTextStream> void MainWindow::initControls() { process = new QProcess(this); connect(process, &QProcess::readyReadStandardOutput, this, [=]() { QString output = process->readAllStandardOutput(); output.replace("\n", "<br>"); this->Write(output); }); connect(process, &QProcess::readyReadStandardError, this, [=]() { QString errorOutput = process->readAllStandardError(); if(!errorOutput.contains("GStreamer")) { errorOutput.replace("\n", "<br>"); this->Write("<b style='color:red;'>Error:</b> " + errorOutput); } }); connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus) { QString text; QTextStream stream(&text); stream << "acmx2: Exited with Code: " << exitCode; Log(text + "<br>"); play_stop->setEnabled(false); }); camera_index = 0; camera_res = QSize(1280, 720); screen_res = QSize(0, 0); setGeometry(150, 150, 1280, 720); setWindowTitle("ACMX2 - Interface"); QMenuBar *menuBarPtr = menuBar(); fileMenu = menuBarPtr->addMenu(tr("File")); cameraMenu = menuBarPtr->addMenu(tr("Session")); playbackMenu = menuBarPtr->addMenu(tr("Playback")); runMenu = menuBarPtr->addMenu(tr("Run")); listMenu = menuBarPtr->addMenu(tr("List")); helpMenu = menuBarPtr->addMenu(tr("Help")); fileMenu_prop = new QAction(tr("Properties"), this); fileMenu->addAction(fileMenu_prop); connect(fileMenu_prop, &QAction::triggered, this, &MainWindow::fileOpenProp); fileMenu->addSeparator(); fileMenu_exit = new QAction(tr("Exit"), this); connect(fileMenu_exit, &QAction::triggered, this, &MainWindow::fileExit); fileMenu->addAction(fileMenu_exit); cameraSet = new QAction(tr("Session Properties"), this); connect(cameraSet, &QAction::triggered, this, &MainWindow::cameraSettings); cameraMenu->addAction(cameraSet); audioSet = new QAction(tr("Audio Settings"), this); connect(audioSet,&QAction::triggered, this, &MainWindow::menuAudioSettings); cameraMenu->addAction(audioSet); runMenu_select = new QAction(tr("Run Selected"), this); runMenu_select->setShortcut(QKeySequence("F5")); connect(runMenu_select, &QAction::triggered, this, &MainWindow::runSelected); runMenu->addAction(runMenu_select); runMenu->addSeparator(); runMenu_all = new QAction(tr("Run All"), this); runMenu_all->setShortcut(QKeySequence("Ctrl+E")); connect(runMenu_all, &QAction::triggered, this, &MainWindow::runAll); runMenu->addAction(runMenu_all); play_repeat = new QAction(tr("Repeat"), this); play_repeat->setCheckable(true); play_repeat->setChecked(false); playbackMenu->addAction(play_repeat); play_stop = new QAction(tr("Stop"), this); play_stop->setEnabled(false); connect(play_stop, &QAction::triggered, this, [=]() { process->terminate(); }); playbackMenu->addAction(play_stop); playbackMenu->addSeparator(); shaderPassAction = new QAction(tr("Multi-Pass Shader Settings..."), this); connect(shaderPassAction, &QAction::triggered, this, &MainWindow::menuShaderPassSettings); playbackMenu->addAction(shaderPassAction); playbackMenu->addSeparator(); buildCacheAction = new QAction(tr("Rebuild Shader Cache"), this); connect(buildCacheAction, &QAction::triggered, this, &MainWindow::menuBuildShaderCache); playbackMenu->addAction(buildCacheAction); runFromCacheAction = new QAction(tr("Run from Cache"), this); runFromCacheAction->setCheckable(true); runFromCacheAction->setChecked(true); connect(runFromCacheAction, &QAction::toggled, this, [this](bool checked) { use_shader_cache = checked; if (checked) { Log("Shader cache enabled - will use cached shaders if available"); } else { Log("Shader cache disabled - shaders will be recompiled each run"); } }); playbackMenu->addAction(runFromCacheAction); recompileShadersAction = new QAction(tr("Recompile All Shaders"), this); connect(recompileShadersAction, &QAction::triggered, this, &MainWindow::menuRecompileShaders); playbackMenu->addAction(recompileShadersAction); listMenu_new = new QAction(tr("New Shader Library"), this); connect(listMenu_new, &QAction::triggered, this, &MainWindow::newList); listMenu->addAction(listMenu_new); listMenu_shader = new QAction(tr("New Shader GLSL File"), this); connect(listMenu_shader, &QAction::triggered, this, &MainWindow::newShader); listMenu->addAction(listMenu_shader); listMenu->addSeparator(); listMenu_remove = new QAction(tr("Remove Shader"), this); connect(listMenu_remove, &QAction::triggered, this, &MainWindow::menuRemove); listMenu->addAction(listMenu_remove); listMenu->addSeparator(); listMenu_up = new QAction(tr("Shift Shader Up"), this); connect(listMenu_up, &QAction::triggered, this, &MainWindow::menuUp); listMenu->addAction(listMenu_up); listMenu_down = new QAction(tr("Shift Shader Down"), this); connect(listMenu_down, &QAction::triggered, this, &MainWindow::menuDown); listMenu->addAction(listMenu_down); listMenu_shuffle = new QAction(tr("Shuffle Shaders"), this); connect(listMenu_shuffle, &QAction::triggered, this, &MainWindow::menuShuffle); listMenu->addAction(listMenu_shuffle); listMenu_sort = new QAction(tr("Sort Shaders"), this); connect(listMenu_sort, &QAction::triggered, this, &MainWindow::menuSort); listMenu->addAction(listMenu_sort); listMenu->addSeparator(); listMenu_search = new QAction(tr("Search Shaders"), this); listMenu_search->setShortcut(QKeySequence("Ctrl+F")); connect(listMenu_search, &QAction::triggered, this, &MainWindow::menuSearch); listMenu->addAction(listMenu_search); listMenu_findNext = new QAction(tr("Find Next"), this); listMenu_findNext->setShortcut(QKeySequence("F3")); connect(listMenu_findNext, &QAction::triggered, this, &MainWindow::menuFindNext); listMenu->addAction(listMenu_findNext); helpMenu_about = new QAction("About", this); connect(helpMenu_about, &QAction::triggered, this, [=](){ QMessageBox box(this); box.setWindowTitle("About ACMX2"); box.setWindowIcon(QIcon(":/win-icon.png")); QString info; QTextStream stream(&info); stream << "ACMX2 " << VERSION_INFO << "\n(C) 2025 " << VERSION_AUTHOR << " Software\nhttps://lostsidedead.biz\nThis software is dedicated to all that have experienced mental illness.\n"; box.setText(info); QPixmap bigIcon(":/win-icon.png"); if (!bigIcon.isNull()) { QPixmap resizedIcon = bigIcon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); box.setIconPixmap(resizedIcon); } Log(info); box.exec(); }); helpMenu->addAction(helpMenu_about); model = new ReadOnlyStringListModel(this); model->setStringList(items); list_view = new QListView(this); list_view->setStyleSheet("QListView { background-color: black; color: white; font-size: 24px; font-family: 'Courier New', Courier, monospace; }"); list_view->setModel(model); bottomTextBox = new QTextEdit(this); bottomTextBox->setHtml("<b style='color:red;'>ACMX2</b> - Interface: Loaded."); bottomTextBox->setStyleSheet("QTextEdit { background-color: black; color: lime; font-size: 24px; font-family: 'Courier New', Courier, monospace;; }"); bottomTextBox->setReadOnly(true); connect(list_view, &QListView::doubleClicked, this, &MainWindow::listClicked); QWidget *centralWidget = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(centralWidget); layout->addWidget(list_view, 3); layout->addWidget(bottomTextBox, 1); centralWidget->setLayout(layout); setCentralWidget(centralWidget); QSettings appSettings("LostSideDead"); QString path = appSettings.value("shaders", "").toString(); #ifdef _WIN32 executable_path = appSettings.value("exePath", "acmx2.exe").toString(); #else executable_path = appSettings.value("exePath", "acmx2").toString(); #endif prefix_path = appSettings.value("prefix_path", ".").toString(); if(!path.isEmpty()) { shader_path = path; loadShaders(path); } } void MainWindow::newList() { LibraryWindow library(this); if(library.exec() == QDialog::Accepted) { shader_path = library.getShaderPath(); loadShaders(shader_path); QSettings appSettings("LostSideDead"); appSettings.setValue("shaders", shader_path); } } void MainWindow::menuSearch() { bool ok; QString searchText = QInputDialog::getText(this, tr("Search Shaders"), tr("Enter shader name to search:"), QLineEdit::Normal, lastSearchText, &ok); if (!ok || searchText.isEmpty()) { return; } lastSearchText = searchText; lastFoundIndex = -1; // Reset for new search QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model()); if (!model) { QMessageBox::warning(this, "Error", "The model is not a QStringListModel."); return; } QStringList items = model->stringList(); int foundIndex = -1; for (int i = 0; i < items.size(); ++i) { if (items[i].compare(searchText, Qt::CaseInsensitive) == 0) { foundIndex = i; break; } } if (foundIndex == -1) { for (int i = 0; i < items.size(); ++i) { if (items[i].contains(searchText, Qt::CaseInsensitive)) { foundIndex = i; break; } } } if (foundIndex != -1) { lastFoundIndex = foundIndex; QModelIndex matchIndex = model->index(foundIndex, 0); list_view->setCurrentIndex(matchIndex); list_view->selectionModel()->select(matchIndex, QItemSelectionModel::ClearAndSelect); list_view->scrollTo(matchIndex, QAbstractItemView::PositionAtCenter); Log("Found shader: " + items[foundIndex] + " at index " + QString::number(foundIndex)); } else { QMessageBox::information(this, tr("Not Found"), tr("Shader \"") + searchText + tr("\" not found in the list.")); Log("Shader not found: " + searchText); } } void MainWindow::menuFindNext() { if (lastSearchText.isEmpty()) { QMessageBox::information(this, tr("No Search"), tr("Please perform a search first (Ctrl+F).")); return; } QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model()); if (!model) { QMessageBox::warning(this, "Error", "The model is not a QStringListModel."); return; } QStringList items = model->stringList(); if (items.isEmpty()) { return; } int foundIndex = -1; int startIndex = (lastFoundIndex + 1) % items.size(); for (int i = startIndex; i < items.size(); ++i) { if (items[i].contains(lastSearchText, Qt::CaseInsensitive)) { foundIndex = i; break; } } if (foundIndex == -1 && startIndex > 0) { for (int i = 0; i < startIndex; ++i) { if (items[i].contains(lastSearchText, Qt::CaseInsensitive)) { foundIndex = i; break; } } } if (foundIndex != -1) { lastFoundIndex = foundIndex; QModelIndex matchIndex = model->index(foundIndex, 0); list_view->setCurrentIndex(matchIndex); list_view->selectionModel()->select(matchIndex, QItemSelectionModel::ClearAndSelect); list_view->scrollTo(matchIndex, QAbstractItemView::PositionAtCenter); Log("Found next: " + items[foundIndex] + " at index " + QString::number(foundIndex)); } else { QMessageBox::information(this, tr("No More Results"), tr("No more matches for \"") + lastSearchText + tr("\".")); Log("No more matches for: " + lastSearchText); } } void MainWindow::newShader() { ShaderDialog new_shader(this); new_shader.setShaderPath(shader_path); if(new_shader.exec() == QDialog::Accepted) { loadShaders(shader_path); QSettings appSettings("LostSideDead"); appSettings.setValue("shaders", shader_path); } } void MainWindow::menuRemove() { QModelIndex currentIndex = list_view->selectionModel()->currentIndex(); if (!currentIndex.isValid()) { return; } model->removeRow(currentIndex.row()); updateIndex(); } void MainWindow::updateIndex() { QFile file(shader_path + "/index.txt"); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream out(&file); QStringListModel *stringModel = qobject_cast<QStringListModel *>(list_view->model()); if (!stringModel) { return; } QStringList writtenItems; int rowCount = stringModel->rowCount(); for (int row = 0; row < rowCount; ++row) { QModelIndex index = stringModel->index(row, 0); QVariant data = stringModel->data(index, Qt::DisplayRole); QString shaderName = data.toString().trimmed(); if (shaderName.isEmpty() || writtenItems.contains(shaderName, Qt::CaseInsensitive)) { continue; } QString fullPath = shader_path + "/" + shaderName; QFileInfo fileInfo(fullPath); if (fileInfo.exists() && fileInfo.isFile()) { out << shaderName << "\n"; writtenItems.append(shaderName); } else { Log("Warning: File no longer exists, removing from list: " + shaderName); } } file.close(); if (writtenItems.size() != rowCount) { items = writtenItems; stringModel->setStringList(items); Log("Updated shader list, removed " + QString::number(rowCount - writtenItems.size()) + " non-existent files"); } } } void MainWindow::menuUp() { QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model()); if (!model) { QMessageBox::warning(list_view, "Error", "The model is not a QStringListModel."); return; } QModelIndex currentIndex = list_view->selectionModel()->currentIndex(); if (!currentIndex.isValid()) { return; } int currentRow = currentIndex.row(); if (currentRow == 0) { return; } QStringList items = model->stringList(); items.swapItemsAt(currentRow, currentRow - 1); model->setStringList(items); QModelIndex newIndex = model->index(currentRow - 1); list_view->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select); updateIndex(); } void MainWindow::menuDown() { QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model()); if (!model) { return; } QModelIndex currentIndex = list_view->selectionModel()->currentIndex(); if (!currentIndex.isValid()) { return; } int currentRow = currentIndex.row(); int rowCount = model->rowCount(); if (currentRow >= rowCount - 1) { return; } QStringList items = model->stringList(); items.swapItemsAt(currentRow, currentRow + 1); model->setStringList(items); QModelIndex newIndex = model->index(currentRow + 1); list_view->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select); updateIndex(); } QString MainWindow::readFileContents(const QString &filePath) { QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { Log("Failed to open file: " + filePath); return QString(); } QTextStream in(&file); QString contents = in.readAll(); file.close(); return contents; } void MainWindow::listClicked(const QModelIndex &i) { if (!i.isValid()) return; QString itemText = i.data(Qt::DisplayRole).toString(); open_files.append(new TextEditor(this)); QString filePath = shader_path + "/" + itemText; open_files.back()->setText(readFileContents(filePath)); open_files.back()->setFileName(filePath); open_files.back()->setIndex(&open_files, open_files.size()-1); open_files.back()->show(); } void MainWindow::Log(const QString &message) { bottomTextBox->append(message); QTextCursor cursor = bottomTextBox->textCursor(); cursor.movePosition(QTextCursor::End); bottomTextBox->setTextCursor(cursor); } void MainWindow::Write(const QString &message) { QTextCursor cursor = bottomTextBox->textCursor(); cursor.movePosition(QTextCursor::End); cursor.insertHtml(message); bottomTextBox->setTextCursor(cursor); } void MainWindow::fileOpenProp() { PropWindow propWindow(this); if (propWindow.exec() == QDialog::Accepted) { QString exePath = propWindow.exePathLineEdit->text(); QString shaderDir = propWindow.shaderDirLineEdit->text(); QString prefix = propWindow.screenshotDirLineEdit->text(); if(exePath.length()==0) { QMessageBox::information(this, "No Path", "Requires Executable path"); return; } if(shaderDir.length()==0) { QMessageBox::information(this, "Shader Path", "Requires Shader Path"); return; } QSettings appSettings("LostSideDead"); appSettings.setValue("exePath", exePath); appSettings.setValue("prefix_path", prefix); appSettings.setValue("shaders", shaderDir); executable_path = exePath; prefix_path = prefix; shader_path = shaderDir; Log("Executable Path: " + exePath); Log("Prefix Path: " + prefix); Log("Shader Directory: " + shaderDir); if(loadShaders(shaderDir)) { Log("Successfully loaded shaders from new directory<br>"); } else { Log("Warning: Could not load shaders from new directory<br>"); } } else { Log("Canceled"); } } bool MainWindow::loadShaders(const QString &path) { QFile file(path+"/index.txt"); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, "Could not open index file", "Failed to open file:" + file.errorString()); return false; } items.clear(); QStringList uniqueItems; QTextStream in(&file); while (!in.atEnd()) { QString line = in.readLine().trimmed(); if (line.isEmpty()) { continue; } QString fullPath = path + "/" + line; QFileInfo fileInfo(fullPath); if (!fileInfo.exists() || !fileInfo.isFile()) { Log("Skipping non-existent file: " + line); continue; } if (!uniqueItems.contains(line, Qt::CaseInsensitive)) { uniqueItems.append(line); Log("Added shader: " + line); } else { Log("Skipping duplicate shader: " + line); } } file.close(); items = uniqueItems; model->setStringList(items); if (!items.isEmpty()) { QModelIndex firstIndex = model->index(0, 0); list_view->setCurrentIndex(firstIndex); list_view->selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); } Log("Loaded " + QString::number(items.size()) + " unique shader files"); menuSort(); return true; } void MainWindow::fileExit() { QApplication::quit(); } void MainWindow::menuAudioSettings() { AudioSettings audio_set(this); if(audio_set.exec() == QDialog::Accepted) { audio_enabled = audio_set.isAudioReactivityEnabled(); audio_channels = audio_set.getNumberOfChannels(); audio_sense = audio_set.getSensitivity(); audio_passthrough = audio_set.isAudioPassThroughEnabled(); audio_input = audio_set.getInputDeviceIndex(); audio_output = audio_set.getOutputDeviceIndex(); Log("Audio Settings Saved"); } } void MainWindow::menuShaderPassSettings() { if(shader_path.isEmpty()) { QMessageBox::information(this, "Load Shaders First", "Please load a shader library before configuring multi-pass shaders."); return; } if(items.isEmpty()) { if(!loadShaders(shader_path)) { QMessageBox::information(this, "Load Shaders First", "Please load a shader library before configuring multi-pass shaders."); return; } } ShaderPassDialog passDialog(items, this); passDialog.setEnabled(shader_pass_enabled); if(!shader_pass_names.isEmpty()) { passDialog.setSelectedShaderNames(shader_pass_names); } if(passDialog.exec() == QDialog::Accepted) { shader_pass_enabled = passDialog.isShaderPassEnabled(); shader_pass_names = passDialog.getSelectedShaderNames(); if(shader_pass_enabled) { Log("Multi-Pass Shader Settings Saved: " + QString::number(shader_pass_names.size()) + " passes"); } else { Log("Multi-Pass Shader Disabled"); } } } void MainWindow::cameraSettings() { SettingsWindow settingsWindow(this); if(settingsWindow.exec() == QDialog::Accepted) { full_screen_value = settingsWindow.isFullscreen(); if (settingsWindow.isUsingInputVideoFile()) { QString videoFile = settingsWindow.getInputVideoFile(); QSize screenResolution = settingsWindow.getSelectedScreenResolution(); screen_res = screenResolution; video_file = videoFile; graphics_file = ""; cache_enabled = settingsWindow.isTextureCacheEnabled(); cache_delay = settingsWindow.getCacheDelay(); copy_audio = settingsWindow.isCopyAudioEnabled(); } else if (settingsWindow.isUsingGraphicsFile()) { QString graphicsFile = settingsWindow.getGraphicsFile(); QSize screenResolution = settingsWindow.getSelectedScreenResolution(); screen_res = screenResolution; graphics_file = graphicsFile; video_file = ""; output_fps = settingsWindow.getCameraFPS(); cache_enabled = false; cache_delay = 1; copy_audio = false; } else { int cameraIndex = settingsWindow.getSelectedCameraIndex(); QSize cameraResolution = settingsWindow.getSelectedCameraResolution(); QSize screenResolution = settingsWindow.getSelectedScreenResolution(); screen_res = screenResolution; camera_index = cameraIndex; video_file = ""; graphics_file = ""; camera_res = cameraResolution; output_fps = settingsWindow.getCameraFPS(); cache_enabled = false; cache_delay = 1; } if(settingsWindow.isSavingToOutputVideoFile()) { output_file = settingsWindow.getOutputVideoFile(); output_kbps = settingsWindow.getSaveFileKbps(); } else { output_file = ""; output_kbps = 23; } } enable_3d = settingsWindow.is3dEnabled(); model_file = settingsWindow.getModelFile(); } void MainWindow::runSelected() { if(shader_path.length()==0) { QMessageBox::information(this, "Select Shaders", "Select Shader Path"); return; } QItemSelectionModel *selectionModel = list_view->selectionModel(); if (!selectionModel->hasSelection()) { Log("<b>No item selected.</b>"); return; } QModelIndex selectedIndex = selectionModel->currentIndex(); QString data = selectedIndex.data(Qt::DisplayRole).toString(); QStringList arguments; QString dirPath = QCoreApplication::applicationDirPath(); #ifdef BUILD_BUNDLE executable_path = dirPath + "/../Helpers/acmx2"; #endif QString shader_file = shader_path + "/" + data; arguments << "--path" << dirPath << "--fragment" << shader_file; QString res; QTextStream stream(&res); stream << camera_res.width() << "x" << camera_res.height(); QString scr_res; QTextStream stream_r(&scr_res); stream_r << screen_res.width() << "x" << screen_res.height(); if(full_screen_value) arguments << "--fullscreen"; if(!graphics_file.isEmpty()) { arguments << "--graphic" << graphics_file; if(screen_res.width() != 0) arguments << "--resolution" << scr_res; arguments << "--fps" << QString::number(output_fps); } else if(video_file.isEmpty()) { arguments << "--camera-res" << res; if(screen_res.width() != 0) arguments << "--resolution" << scr_res; arguments << "--device" << QString::number(camera_index); arguments << "--fps" << QString::number(output_fps); } else { arguments << "--input" << video_file; if(screen_res.width() != 0) arguments << "--resolution" << scr_res; if(play_repeat->isChecked()) arguments << "--repeat"; if(cache_enabled) { arguments << "--texture-cache"; arguments << "--cache-delay" << QString::number(cache_delay); } if(copy_audio) arguments << "--copy-audio"; } arguments << "--prefix" << prefix_path; if(!output_file.isEmpty()) { arguments << "--output" << output_file; arguments << "--bitrate" << QString::number(output_kbps); } if(audio_enabled) { arguments << "--enable-audio"; arguments << "--channels" << QString::number(audio_channels); arguments << "--sense" << QString::number(audio_sense); if(audio_passthrough) arguments << "--pass-through"; if(audio_input == -1) arguments << "--audio-input" << "default"; else arguments << "--audio-input" << QString::number(audio_input); if(audio_output == -1) arguments << "--audio-output" << "default"; else arguments << "--audio-output" << QString::number(audio_output); } if(enable_3d) { arguments << "--enable-3d"; arguments << "--model" << model_file; } if(shader_pass_enabled && !shader_pass_names.isEmpty()) { QString passIndices = getShaderPassIndicesFromNames(); if(!passIndices.isEmpty()) { arguments << "--shader-pass" << passIndices; } } if(use_shader_cache) { arguments << "--shader-cache"; } Log("shell: acmx2 " + concatList(arguments) + "<br>"); process->start(executable_path, arguments); if(!process->waitForStarted()) { Log("<b style='color:red;'>Failed to start the program.</b>"); QMessageBox::critical(this, "Error", "Failed to start the program."); } else { play_stop->setEnabled(true); } } void MainWindow::runAll() { if(shader_path.length()==0) { QMessageBox::information(this, "Select Shaders", "Select Shader Path"); return; } int index = 0; QItemSelectionModel *selectionModel = list_view->selectionModel(); if (!selectionModel->hasSelection()) { index = 0; Log("No selection, defaulting to index 0"); } else { QModelIndex selectedIndex = selectionModel->currentIndex(); index = selectedIndex.row(); QString selectedData = selectedIndex.data(Qt::DisplayRole).toString(); Log("Selected shader: " + selectedData + " at index: " + QString::number(index)); } QStringList arguments; QString dirPath = QCoreApplication::applicationDirPath(); #ifdef BUILD_BUNDLE executable_path = dirPath + "/../Helpers/acmx2"; #endif QString shader_file = shader_path; arguments << "--path" << dirPath << "--shaders" << shader_file; QString res; QTextStream stream(&res); stream << camera_res.width() << "x" << camera_res.height(); QString scr_res; QTextStream stream_r(&scr_res); stream_r << screen_res.width() << "x" << screen_res.height(); if(full_screen_value) arguments << "--fullscreen"; if(!graphics_file.isEmpty()) { arguments << "--graphic" << graphics_file; if(screen_res.width() != 0) arguments << "--resolution" << scr_res; arguments << "--fps" << QString::number(output_fps); } else if(video_file.isEmpty()) { arguments << "--camera-res" << res; if(screen_res.width() != 0) arguments << "--resolution" << scr_res; arguments << "--device" << QString::number(camera_index); arguments << "--fps" << QString::number(output_fps); } else { arguments << "--input" << video_file; if(screen_res.width() != 0) arguments << "--resolution" << scr_res; if(play_repeat->isChecked()) arguments << "--repeat"; if(cache_enabled) { arguments << "--texture-cache"; arguments << "--cache-delay" << QString::number(cache_delay); } if(copy_audio) arguments << "--copy-audio"; } arguments << "--prefix" << prefix_path; if(!output_file.isEmpty()) { arguments << "--output" << output_file; arguments << "--bitrate" << QString::number(output_kbps); } arguments << "--shader" << QString::number(index); if(audio_enabled) { arguments << "--enable-audio"; arguments << "--channels" << QString::number(audio_channels); arguments << "--sense" << QString::number(audio_sense); if(audio_passthrough) arguments << "--pass-through"; if(audio_input == -1) arguments << "--audio-input" << "default"; else arguments << "--audio-input" << QString::number(audio_input); if(audio_output == -1) arguments << "--audio-output" << "default"; else arguments << "--audio-output" << QString::number(audio_output); } if(enable_3d) { arguments << "--enable-3d"; arguments << "--model" << model_file; } if(shader_pass_enabled && !shader_pass_names.isEmpty()) { QString passIndices = getShaderPassIndicesFromNames(); if(!passIndices.isEmpty()) { arguments << "--shader-pass" << passIndices; } } if(use_shader_cache) { arguments << "--shader-cache"; } Log("shell: acmx2 " + concatList(arguments) + "<br>"); process->start(executable_path, arguments); if(!process->waitForStarted()) { Log("<b style='color:red;'>Failed to start the program.</b>"); QMessageBox::critical(this, "Error", "Failed to start the program."); } else { play_stop->setEnabled(true); } } QString MainWindow::concatList(const QStringList lst) { QString text; QTextStream stream(&text); for(auto &i : lst) { stream << i << " "; } return text; } QString MainWindow::getShaderPassIndicesFromNames() { QStringList indices; for (const QString &name : shader_pass_names) { int idx = items.indexOf(name); if (idx >= 0) { indices.append(QString::number(idx)); } } return indices.join(","); } void MainWindow::menuShuffle() { QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model()); if (!model) { QMessageBox::warning(this, "Error", "The model is not a QStringListModel."); return; } QStringList items = model->stringList(); if (items.isEmpty()) { return; } std::random_device rd; std::mt19937 g(rd()); std::shuffle(items.begin(), items.end(), g); model->setStringList(items); updateIndex(); Log("Shaders shuffled"); } void MainWindow::menuSort() { QStringListModel *model = qobject_cast<QStringListModel *>(list_view->model()); if (!model) { QMessageBox::warning(this, "Error", "The model is not a QStringListModel."); return; } QStringList items = model->stringList(); if (items.isEmpty()) { return; } items.sort(Qt::CaseInsensitive); model->setStringList(items); updateIndex(); Log("Shaders sorted alphabetically"); } void MainWindow::menuBuildShaderCache() { QString build_path = shader_path; if (build_path.isEmpty()) { QSettings appSettings("LostSideDead"); build_path = appSettings.value("shaders", "").toString(); } if (build_path.isEmpty()) { QMessageBox::warning(this, "Error", "No shader library loaded. Please set a shader directory in Properties or load a shader library first."); return; } if (process->state() == QProcess::Running) { QMessageBox::warning(this, "Error", "A process is already running. Please wait for it to finish."); return; } QString dirPath = QCoreApplication::applicationDirPath(); #ifdef BUILD_BUNDLE QString assets_path = dirPath + "/../Helpers"; #else QString assets_path = dirPath; #endif QStringList args; args << "--build" << build_path; args << "-p" << assets_path; if (enable_3d) { args << "--enable-3d"; } Log("Building shader cache for: " + build_path); Log("Command: " + executable_path + " " + args.join(" ")); play_stop->setEnabled(true); process->start(executable_path, args); if (!process->waitForStarted()) { Log("<b style='color:red;'>Error:</b> Failed to start shader cache build process"); play_stop->setEnabled(false); } } void MainWindow::menuRunFromCache() { } void MainWindow::menuRecompileShaders() { QString recompile_path = shader_path; if (recompile_path.isEmpty()) { QSettings appSettings("LostSideDead"); recompile_path = appSettings.value("shaders", "").toString(); } if (recompile_path.isEmpty()) { QMessageBox::warning(this, "Error", "No shader library loaded. Please set a shader directory in Properties or load a shader library first."); return; } QString cacheFile = recompile_path + "/.shader_cache"; QFile cache(cacheFile); if (cache.exists()) { if (cache.remove()) { Log("Deleted shader cache: " + cacheFile); } else { Log("<b style='color:red;'>Warning:</b> Could not delete cache file: " + cacheFile); } } else { Log("No existing shader cache found"); } QMessageBox::StandardButton reply = QMessageBox::question( this, "Rebuild Cache?", "Shader cache has been cleared. Would you like to rebuild the cache now?", QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { QString old_path = shader_path; shader_path = recompile_path; menuBuildShaderCache(); shader_path = old_path; } else { Log("Shaders will be recompiled on next run"); } }