/
reds
/
OpenDAW
Обзор
Документация
Войти
/
reds
/
OpenDAW
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/main_gui.cpp
652 строки
23 KB
OpenDAW Developer
feat: Add Complete GUI Debug Infrastructure
21 фев 2026, 07:47
21 фев 2026, 07:47
fa78579
Код
Авторство
О чём код?
// OpenDAW v0.6.1 - GUI с поддержкой плагинов и Debug Mode #include <juce_core/juce_core.h> #include <juce_gui_basics/juce_gui_basics.h> #include <opendaw/core/AudioEngine.h> #include <opendaw/plugin/PluginHost.h> #include <iostream> #include <fstream> #include <chrono> // ============================================================================ // GUI Debug Logger // ============================================================================ class GUIDebugLogger { public: static GUIDebugLogger& getInstance() { static GUIDebugLogger instance; return instance; } void log(const juce::String& component, const juce::String& message) { auto now = std::chrono::system_clock::now(); auto time = std::chrono::system_clock::to_time_t(now); juce::String logLine = juce::String(std::ctime(&time)).trim() + " | " + component + " | " + message; logs_.add(logLine); std::cout << "[GUI Debug] " << logLine << std::endl; // Save to file if (logFile_.exists()) { logFile_.appendText(logLine + "\n"); } } void setLogFile(const juce::File& file) { logFile_ = file; logFile_.deleteFile(); logFile_.create(); } const juce::StringArray& getLogs() const { return logs_; } void printComponentTree(juce::Component* comp, int indent = 0) { if (!comp) return; juce::String indentStr; for (int i = 0; i < indent * 2; ++i) indentStr += " "; juce::String info = indentStr + comp->getName() + " (" + juce::String(comp->getWidth()) + "x" + juce::String(comp->getHeight()) + ", visible=" + juce::String(comp->isVisible() ? "true" : "false") + ", enabled=" + juce::String(comp->isEnabled() ? "true" : "false") + ")"; log("ComponentTree", info); for (auto* child : comp->getChildren()) { printComponentTree(child, indent + 1); } } private: GUIDebugLogger() { auto logPath = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory) .getChildFile("OpenDAW") .getChildFile("gui_debug.log"); setLogFile(logPath); } juce::StringArray logs_; juce::File logFile_; }; #define GUI_DEBUG_LOG(comp, msg) GUIDebugLogger::getInstance().log(comp, msg) #define GUI_DEBUG_TREE(root) GUIDebugLogger::getInstance().printComponentTree(root) using namespace opendaw; // ============================================================================ // Debug LookAndFeel - Цветные рамки для кнопок // ============================================================================ class DebugLookAndFeel : public juce::LookAndFeel_V4 { public: DebugLookAndFeel() { setColour(juce::ResizableWindow::backgroundColourId, juce::Colour(0xFF1E1E1E)); } void drawButtonBackground(juce::Graphics& g, juce::Button& button, const juce::Colour& backgroundColour, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override { // Стандартная отрисовка LookAndFeel_V4::drawButtonBackground(g, button, backgroundColour, shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown); // Красная рамка вокруг кнопки g.setColour(juce::Colours::red); g.drawRect(button.getLocalBounds(), 2); } }; // ============================================================================ // Plugin Browser Component // ============================================================================ class PluginBrowser : public juce::Component, public juce::ListBoxModel { public: PluginBrowser() { GUI_DEBUG_LOG("PluginBrowser", "Creating PluginBrowser component"); addAndMakeVisible(browserList); GUI_DEBUG_LOG("PluginBrowser", "Added browserList"); addAndMakeVisible(searchBox); GUI_DEBUG_LOG("PluginBrowser", "Added searchBox"); addAndMakeVisible(refreshButton); GUI_DEBUG_LOG("PluginBrowser", "Added refreshButton"); searchBox.setText("Search...", juce::dontSendNotification); searchBox.onTextChange = [this]() { filterPlugins(); }; refreshButton.setButtonText("🔄"); refreshButton.onClick = [this]() { GUI_DEBUG_LOG("PluginBrowser", "Refresh button clicked"); refreshPlugins(); }; browserList.setModel(this); GUI_DEBUG_LOG("PluginBrowser", "Set list model"); // Scan for plugins on startup refreshPlugins(); GUI_DEBUG_LOG("PluginBrowser", "PluginBrowser creation complete"); } void refreshPlugins() { GUI_DEBUG_LOG("PluginBrowser", "Refreshing plugins..."); pluginHost.scanAllPlugins(); filteredPlugins = pluginHost.getDiscoveredPlugins(); GUI_DEBUG_LOG("PluginBrowser", "Found " + juce::String(filteredPlugins.size()) + " plugins"); filterPlugins(); } void filterPlugins() { auto searchText = searchBox.getText().toLowerCase(); filteredPlugins.clear(); for (const auto& plugin : pluginHost.getDiscoveredPlugins()) { juce::String pluginName(plugin.name.c_str()); juce::String pluginVendor(plugin.vendor.c_str()); if (searchText.isEmpty() || pluginName.toLowerCase().contains(searchText) || pluginVendor.toLowerCase().contains(searchText)) { filteredPlugins.push_back(plugin); } } browserList.updateContent(); if (onPluginSelected) { onPluginSelected(nullptr); } } int getNumRows() override { return static_cast<int>(filteredPlugins.size()); } void paintListBoxRow(int, juce::Graphics& g, int width, int height, bool rowIsSelected) { if (rowIsSelected) { g.setColour(juce::Colour(0xFF4A90D9)); g.fillRect(0, 0, width, height); } } void paintListBoxItem(int, juce::Graphics&, int, int, bool) override { // Custom painting handled by refreshComponentForRow } void selectedRowsChanged() { auto selected = browserList.getSelectedRow(); if (selected >= 0 && selected < filteredPlugins.size()) { if (onPluginSelected) { onPluginSelected(&filteredPlugins[selected]); } } else if (onPluginSelected) { onPluginSelected(nullptr); } } juce::Component* refreshComponentForRow(int rowIndex, bool, juce::Component* existing) override { if (rowIndex >= filteredPlugins.size()) return nullptr; auto* label = existing ? dynamic_cast<juce::Label*>(existing) : nullptr; if (!label) { label = new juce::Label(); label->setFont(juce::Font(14.0f)); label->setColour(juce::Label::textColourId, juce::Colours::white); } const auto& plugin = filteredPlugins[rowIndex]; juce::String icon = plugin.isInstrument ? "🎹" : "🎛️"; label->setText(icon + " " + plugin.name + " (" + plugin.vendor + ")", juce::dontSendNotification); return label; } void paint(juce::Graphics& g) override { g.fillAll(juce::Colour(0xFF2A2A2A)); } void resized() override { auto bounds = getLocalBounds(); searchBox.setBounds(bounds.removeFromTop(30).reduced(5)); refreshButton.setBounds(bounds.removeFromTop(30).removeFromRight(50).reduced(5)); browserList.setBounds(bounds); } std::function<void(const PluginInfo*)> onPluginSelected; private: juce::ListBox browserList; juce::TextEditor searchBox; juce::TextButton refreshButton; PluginHost pluginHost; std::vector<PluginInfo> filteredPlugins; }; // ============================================================================ // Track Component with Plugin Slots // ============================================================================ class PluginSlot : public juce::Component { public: PluginSlot(int slotIndex) : index_(slotIndex) { addAndMakeVisible(pluginName); addAndMakeVisible(bypassButton); addAndMakeVisible(editButton); pluginName.setText("Empty", juce::dontSendNotification); pluginName.setJustificationType(juce::Justification::centredLeft); pluginName.setFont(juce::Font(12.0f)); bypassButton.setButtonText("B"); bypassButton.setClickingTogglesState(true); bypassButton.setColour(juce::TextButton::buttonOnColourId, juce::Colours::red); editButton.setButtonText("⚙️"); editButton.onClick = [this]() { if (onEditClick && plugin_) { onEditClick(plugin_); } }; } void setPlugin(Plugin* plugin) { plugin_ = plugin; if (plugin) { pluginName.setText(plugin->getInfo().name, juce::dontSendNotification); } else { pluginName.setText("Empty", juce::dontSendNotification); } } void paint(juce::Graphics& g) override { g.fillAll(plugin_ ? juce::Colour(0xFF3A3A4A) : juce::Colour(0xFF1E1E1E)); } void resized() override { auto bounds = getLocalBounds().reduced(2); bypassButton.setBounds(bounds.removeFromLeft(30)); editButton.setBounds(bounds.removeFromRight(30)); pluginName.setBounds(bounds); } std::function<void(Plugin*)> onEditClick; private: int index_; Plugin* plugin_ = nullptr; juce::Label pluginName; juce::TextButton bypassButton, editButton; }; class TrackComponent : public juce::Component { public: TrackComponent(const juce::String& name, int trackIndex) { addAndMakeVisible(trackName); addAndMakeVisible(volumeFader); addAndMakeVisible(panKnob); addAndMakeVisible(muteButton); addAndMakeVisible(soloButton); addAndMakeVisible(pluginSlotsContainer); trackName.setText(name, juce::dontSendNotification); trackName.setJustificationType(juce::Justification::centred); trackName.setFont(juce::Font(14.0f)); volumeFader.setSliderStyle(juce::Slider::LinearVertical); volumeFader.setRange(0, 100, 1); volumeFader.setValue(75); volumeFader.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); panKnob.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); panKnob.setRange(-100, 100, 1); panKnob.setValue(0); panKnob.setTextBoxStyle(juce::Slider::NoTextBox, false, 0, 0); muteButton.setButtonText("M"); muteButton.setClickingTogglesState(true); muteButton.setColour(juce::TextButton::buttonOnColourId, juce::Colours::red); soloButton.setButtonText("S"); soloButton.setClickingTogglesState(true); soloButton.setColour(juce::TextButton::buttonOnColourId, juce::Colours::yellow); // Add 4 plugin slots per track for (int i = 0; i < 4; ++i) { auto slot = std::make_unique<PluginSlot>(i); slot->onEditClick = [this](Plugin* plugin) { if (onPluginEditRequested) { onPluginEditRequested(plugin); } }; pluginSlots.push_back(slot.get()); pluginSlotsContainer.addAndMakeVisible(slot.get()); pluginSlots_.push_back(std::move(slot)); } } void paint(juce::Graphics& g) override { g.fillAll(juce::Colour(0xFF1E1E1E)); } void resized() override { auto bounds = getLocalBounds(); // Track name at top trackName.setBounds(bounds.removeFromTop(25)); // Plugin slots auto pluginArea = bounds.removeFromTop(120); int slotWidth = pluginArea.getWidth() / 4; for (int i = 0; i < 4; ++i) { if (pluginSlots[i]) { pluginSlots[i]->setBounds(i * slotWidth, 0, slotWidth - 2, 120); } } // Controls at bottom auto controlsArea = bounds; muteButton.setBounds(controlsArea.removeFromLeft(40).reduced(5)); soloButton.setBounds(controlsArea.removeFromLeft(40).reduced(5)); volumeFader.setBounds(controlsArea.removeFromLeft(50).reduced(5)); panKnob.setBounds(controlsArea.removeFromLeft(50).reduced(5)); } std::function<void(Plugin*)> onPluginEditRequested; private: juce::Label trackName; juce::Slider volumeFader, panKnob; juce::TextButton muteButton, soloButton; juce::Component pluginSlotsContainer; std::vector<PluginSlot*> pluginSlots; std::vector<std::unique_ptr<PluginSlot>> pluginSlots_; }; // ============================================================================ // Plugin Editor Window // ============================================================================ class PluginEditorWindow : public juce::DocumentWindow { public: PluginEditorWindow(Plugin* plugin) : DocumentWindow(plugin->getInfo().name, juce::Colour(0xFF2A2A2A), DocumentWindow::allButtons) { plugin_ = plugin; // Create parameter controls auto* content = new juce::Component(); content->setSize(400, 300); int numParams = plugin->getNumParameters(); int y = 10; for (int i = 0; i < numParams && i < 10; ++i) { auto param = plugin->getParameterInfo(i); auto* label = new juce::Label(param.name, param.name); label->setBounds(10, y, 150, 25); label->setFont(juce::Font(12.0f)); label->setColour(juce::Label::textColourId, juce::Colours::white); content->addAndMakeVisible(label); auto* slider = new juce::Slider(param.name); slider->setBounds(170, y, 220, 25); slider->setSliderStyle(juce::Slider::LinearHorizontal); slider->setRange(param.minValue, param.maxValue, 0.01f); slider->setValue(plugin->getParameterValue(i)); slider->onValueChange = [this, i, slider]() { if (plugin_) { plugin_->setParameterValue(i, slider->getValue()); } }; content->addAndMakeVisible(slider); parameterSliders.push_back(slider); y += 30; } setContentOwned(content, true); setUsingNativeTitleBar(true); centreWithSize(400, y + 50); setVisible(true); } void closeButtonPressed() override { setVisible(false); } private: Plugin* plugin_; std::vector<juce::Slider*> parameterSliders; }; // ============================================================================ // Main Window // ============================================================================ class MainWindow : public juce::DocumentWindow { public: MainWindow() : DocumentWindow("OpenDAW v0.6.1", juce::Colour(0xFF1E1E1E), DocumentWindow::allButtons) { GUI_DEBUG_LOG("MainWindow", "Creating MainWindow"); GUI_DEBUG_LOG("MainWindow", "Before LookAndFeel"); try { // Установить Debug LookAndFeel debugLF = std::make_unique<DebugLookAndFeel>(); juce::LookAndFeel::setDefaultLookAndFeel(debugLF.get()); GUI_DEBUG_LOG("MainWindow", "Debug LookAndFeel установлен"); } catch (const std::exception& e) { GUI_DEBUG_LOG("MainWindow", "LookAndFeel Exception: " + juce::String(e.what())); } GUI_DEBUG_LOG("MainWindow", "After LookAndFeel"); try { // Initialize Audio Engine audioEngine = std::make_unique<AudioEngine>(); GUI_DEBUG_LOG("MainWindow", "Created AudioEngine"); if (audioEngine->initialize()) { audioEngine->start(); GUI_DEBUG_LOG("MainWindow", "AudioEngine started"); } else { GUI_DEBUG_LOG("MainWindow", "AudioEngine failed to initialize"); } } catch (const std::exception& e) { GUI_DEBUG_LOG("MainWindow", "Exception: " + juce::String(e.what())); } GUI_DEBUG_LOG("MainWindow", "After AudioEngine"); // Plugin Browser (left panel) addAndMakeVisible(pluginBrowser); GUI_DEBUG_LOG("MainWindow", "Added PluginBrowser"); pluginBrowser.onPluginSelected = [this](const PluginInfo* plugin) { selectedPlugin_ = plugin; if (plugin) { GUI_DEBUG_LOG("MainWindow", "Selected plugin: " + juce::String(plugin->name.c_str())); } }; // Track list (center panel) addAndMakeVisible(trackListContainer); GUI_DEBUG_LOG("MainWindow", "Added trackListContainer"); // Add some default tracks for (int i = 0; i < 4; ++i) { auto track = std::make_unique<TrackComponent>("Track " + juce::String(i + 1), i); GUI_DEBUG_LOG("MainWindow", "Created TrackComponent " + juce::String(i)); track->onPluginEditRequested = [this](Plugin* plugin) { if (plugin) { GUI_DEBUG_LOG("MainWindow", "Opening plugin editor"); auto editor = std::make_unique<PluginEditorWindow>(plugin); pluginEditors.push_back(std::move(editor)); } }; tracks.push_back(track.get()); trackListContainer.addAndMakeVisible(track.get()); tracks_.push_back(std::move(track)); } GUI_DEBUG_LOG("MainWindow", "Added 4 tracks"); // Status bar addAndMakeVisible(statusLabel); statusLabel.setText("OpenDAW v0.6.1 - Plugin Support Ready", juce::dontSendNotification); statusLabel.setJustificationType(juce::Justification::centred); statusLabel.setFont(juce::Font(12.0f)); GUI_DEBUG_LOG("MainWindow", "Added status bar"); setContentOwned(new juce::Component(), true); setUsingNativeTitleBar(true); setSize(1200, 800); centreWithSize(1200, 800); setVisible(true); setResizable(true, true); GUI_DEBUG_LOG("MainWindow", "MainWindow creation complete"); GUI_DEBUG_LOG("MainWindow", "Window size: " + juce::String(getWidth()) + "x" + juce::String(getHeight())); // Принудительный resize после показа resized(); GUI_DEBUG_LOG("MainWindow", "Called resized()"); // Print component tree after delay juce::Timer::callAfterDelay(2000, [this]() { GUI_DEBUG_LOG("MainWindow", "=== Component Tree ==="); GUI_DEBUG_LOG("MainWindow", "Final window size: " + juce::String(getWidth()) + "x" + juce::String(getHeight())); GUI_DEBUG_TREE(this); takeScreenshot(); }); } void takeScreenshot() { GUI_DEBUG_LOG("MainWindow", "Taking screenshot..."); auto bounds = getBounds(); juce::Image screenshot(juce::Image::ARGB, bounds.getWidth(), bounds.getHeight(), true); juce::Graphics g(screenshot); paintEntireComponent(g, true); auto screenshotDir = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory) .getChildFile("OpenDAW") .getChildFile("screenshots"); screenshotDir.createDirectory(); auto timestamp = juce::Time::getCurrentTime().formatted("%Y-%m-%d_%H-%M-%S"); auto screenshotFile = screenshotDir.getChildFile("main_window_" + timestamp + ".png"); juce::PNGImageFormat pngFormat; std::unique_ptr<juce::FileOutputStream> stream(new juce::FileOutputStream(screenshotFile)); if (stream->openedOk()) { pngFormat.writeImageToStream(screenshot, *stream); GUI_DEBUG_LOG("MainWindow", "Screenshot saved: " + screenshotFile.getFullPathName()); } else { GUI_DEBUG_LOG("MainWindow", "Failed to save screenshot"); } } ~MainWindow() { if (audioEngine) { audioEngine->stop(); audioEngine->shutdown(); } } void closeButtonPressed() override { juce::JUCEApplication::quit(); } void resized() override { auto bounds = getContentComponent()->getBounds(); // Left panel: Plugin Browser (300px) pluginBrowser.setBounds(bounds.removeFromLeft(300)); // Right panel: Track list trackListContainer.setBounds(bounds); // Status bar at bottom auto trackBounds = trackListContainer.getBounds(); statusLabel.setBounds(trackBounds.removeFromBottom(25)); // Layout tracks int trackHeight = 150; int y = 0; for (auto& track : tracks) { track->setBounds(0, y, trackListContainer.getWidth(), trackHeight); y += trackHeight; } } private: std::unique_ptr<AudioEngine> audioEngine; std::unique_ptr<DebugLookAndFeel> debugLF; PluginBrowser pluginBrowser; juce::Component trackListContainer; juce::Label statusLabel; std::vector<TrackComponent*> tracks; std::vector<std::unique_ptr<TrackComponent>> tracks_; std::vector<std::unique_ptr<PluginEditorWindow>> pluginEditors; const PluginInfo* selectedPlugin_ = nullptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow) }; // ============================================================================ // Application // ============================================================================ class OpenDAWApplication : public juce::JUCEApplication { public: void initialise(const juce::String&) override { mainWindow = std::make_unique<MainWindow>(); mainWindow->setName("OpenDAW v0.6.1"); mainWindow->setVisible(true); } void shutdown() override { mainWindow = nullptr; } void systemRequestedQuit() override { quit(); } void anotherInstanceStarted(const juce::String&) override { if (mainWindow) { mainWindow->toFront(true); } } const juce::String getApplicationName() override { return "OpenDAW"; } const juce::String getApplicationVersion() override { return "0.6.1"; } private: std::unique_ptr<MainWindow> mainWindow; }; START_JUCE_APPLICATION(OpenDAWApplication)