diff --git a/src/ui/TranslationUI.cpp b/src/ui/TranslationUI.cpp index 05f5c7f..ada5da1 100644 --- a/src/ui/TranslationUI.cpp +++ b/src/ui/TranslationUI.cpp @@ -1,250 +1,250 @@ -#include // MUST be FIRST! Provides OpenGL functions -#define GLFW_INCLUDE_NONE // Tell GLFW not to include OpenGL headers (GLAD does it) -#include "TranslationUI.h" -#include -#include -#include -#include -#include - -namespace secondvoice { - -TranslationUI::TranslationUI(int width, int height) - : width_(width) - , height_(height) { -} - -TranslationUI::~TranslationUI() { - if (window_) { - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - glfwDestroyWindow(window_); - glfwTerminate(); - } -} - -bool TranslationUI::initialize() { - // Initialize GLFW - std::cout << "[UI] Initializing GLFW..." << std::endl; - glfwSetErrorCallback([](int error, const char* description) { - std::cerr << "[GLFW Error " << error << "] " << description << std::endl; - }); - - if (!glfwInit()) { - std::cerr << "[UI] Failed to initialize GLFW" << std::endl; - return false; - } - std::cout << "[UI] GLFW initialized successfully" << std::endl; - - // FORCE high-performance GPU (NVIDIA/AMD dedicated) - std::cout << "[UI] Requesting high-performance GPU..." << std::endl; - - // OpenGL 3.3 - core profile - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - - // Create window - std::cout << "[UI] Creating GLFW window (" << width_ << "x" << height_ << ")..." << std::endl; - window_ = glfwCreateWindow(width_, height_, "SecondVoice - Live Translation", nullptr, nullptr); - if (!window_) { - std::cerr << "[UI] Failed to create GLFW window" << std::endl; - glfwTerminate(); - return false; - } - std::cout << "[UI] GLFW window created successfully" << std::endl; - - glfwMakeContextCurrent(window_); - glfwSwapInterval(1); // Enable vsync - - // Initialize GLAD - MUST happen after glfwMakeContextCurrent! - std::cout << "[UI] Initializing GLAD OpenGL loader..." << std::endl; - if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { - std::cerr << "[UI] Failed to initialize GLAD!" << std::endl; - glfwDestroyWindow(window_); - glfwTerminate(); - return false; - } - std::cout << "[UI] GLAD initialized successfully" << std::endl; - - // Query OpenGL context to see which GPU we got - const GLubyte* vendor = glGetString(GL_VENDOR); - const GLubyte* renderer = glGetString(GL_RENDERER); - const GLubyte* version = glGetString(GL_VERSION); - - std::cout << "[UI] ========================================" << std::endl; - std::cout << "[UI] OpenGL Context Info:" << std::endl; - std::cout << "[UI] Vendor: " << (vendor ? (const char*)vendor : "Unknown") << std::endl; - std::cout << "[UI] Renderer: " << (renderer ? (const char*)renderer : "Unknown") << std::endl; - std::cout << "[UI] Version: " << (version ? (const char*)version : "Unknown") << std::endl; - std::cout << "[UI] ========================================" << std::endl; - - // Initialize ImGui - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - ImGui::StyleColorsDark(); - - ImGui_ImplGlfw_InitForOpenGL(window_, true); - - // TEST: Compile a simple shader to see REAL error messages - std::cout << "[UI] Testing shader compilation..." << std::endl; - const char* vertexShaderSource = R"( -#version 130 -in vec2 Position; -in vec2 UV; -in vec4 Color; -varying vec2 Frag_UV; -varying vec4 Frag_Color; -void main() { - Frag_UV = UV; - Frag_Color = Color; - gl_Position = vec4(Position, 0, 1); -} -)"; - - GLuint testShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(testShader, 1, &vertexShaderSource, NULL); - glCompileShader(testShader); - - GLint success; - glGetShaderiv(testShader, GL_COMPILE_STATUS, &success); - if (!success) { - char infoLog[1024]; - glGetShaderInfoLog(testShader, 1024, NULL, infoLog); - std::cerr << "[UI] ==================== SHADER COMPILATION ERROR ====================" << std::endl; - std::cerr << infoLog << std::endl; - std::cerr << "[UI] ====================================================================" << std::endl; - } else { - std::cout << "[UI] Test shader compiled successfully!" << std::endl; - } - glDeleteShader(testShader); - - // DEBUG: Check if GLAD loaded glCreateShader - std::cout << "[UI] DEBUG: glCreateShader function pointer = " << (void*)glCreateShader << std::endl; - if (glCreateShader == nullptr) { - std::cerr << "[UI] ERROR: glCreateShader is NULL!" << std::endl; - return false; - } - - // Let ImGui auto-detect the GLSL version - ImGui_ImplOpenGL3_Init(nullptr); - - // CRITICAL: Release the OpenGL context from this thread - // The UI rendering will happen in a separate thread which will call makeContextCurrent() - std::cout << "[UI] Releasing OpenGL context from initialization thread" << std::endl; - glfwMakeContextCurrent(nullptr); - - return true; -} - -void TranslationUI::makeContextCurrent() { - if (window_) { - std::cout << "[UI] Making OpenGL context current in thread: " << std::this_thread::get_id() << std::endl; - glfwMakeContextCurrent(window_); - } -} - -void TranslationUI::render() { - glfwPollEvents(); - - // Start ImGui frame - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - // Main window (full viewport) - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); - ImGui::Begin("SecondVoice", nullptr, - ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoCollapse); - - renderTranslations(); - renderControls(); - renderStatus(); - - ImGui::End(); - - // Rendering - ImGui::Render(); - int display_w, display_h; - glfwGetFramebufferSize(window_, &display_w, &display_h); - glViewport(0, 0, display_w, display_h); - glClearColor(0.1f, 0.1f, 0.1f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(window_); -} - -bool TranslationUI::shouldClose() const { - return glfwWindowShouldClose(window_); -} - -void TranslationUI::addTranslation(const std::string& chinese, const std::string& french) { - messages_.push_back({chinese, french}); -} - -void TranslationUI::renderTranslations() { - ImGui::Text("SecondVoice - Live Translation"); - ImGui::Separator(); - - ImGui::BeginChild("Translations", ImVec2(0, -120), true); - - for (const auto& msg : messages_) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.8f, 1.0f, 1.0f)); - ImGui::TextWrapped("中文: %s", msg.chinese.c_str()); - ImGui::PopStyleColor(); - - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 1.0f, 0.5f, 1.0f)); - ImGui::TextWrapped("FR: %s", msg.french.c_str()); - ImGui::PopStyleColor(); - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - } - - if (auto_scroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { - ImGui::SetScrollHereY(1.0f); - } - - ImGui::EndChild(); -} - -void TranslationUI::renderControls() { - ImGui::Spacing(); - - // Center the stop button - float button_width = 200.0f; - float window_width = ImGui::GetWindowWidth(); - ImGui::SetCursorPosX((window_width - button_width) * 0.5f); - - if (ImGui::Button("STOP RECORDING", ImVec2(button_width, 40))) { - stop_requested_ = true; - } - - ImGui::Spacing(); -} - -void TranslationUI::renderStatus() { - ImGui::Separator(); - - // Format duration as MM:SS - int minutes = recording_duration_ / 60; - int seconds = recording_duration_ % 60; - ImGui::Text("Recording... Duration: %02d:%02d", minutes, seconds); - - if (!processing_status_.empty()) { - ImGui::SameLine(); - ImGui::Text(" | Status: %s", processing_status_.c_str()); - } -} - -} // namespace secondvoice +#include // MUST be FIRST! Provides OpenGL functions +#define GLFW_INCLUDE_NONE // Tell GLFW not to include OpenGL headers (GLAD does it) +#include "TranslationUI.h" +#include +#include +#include +#include +#include + +namespace secondvoice { + +TranslationUI::TranslationUI(int width, int height) + : width_(width) + , height_(height) { +} + +TranslationUI::~TranslationUI() { + if (window_) { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + glfwDestroyWindow(window_); + glfwTerminate(); + } +} + +bool TranslationUI::initialize() { + // Initialize GLFW + std::cout << "[UI] Initializing GLFW..." << std::endl; + glfwSetErrorCallback([](int error, const char* description) { + std::cerr << "[GLFW Error " << error << "] " << description << std::endl; + }); + + if (!glfwInit()) { + std::cerr << "[UI] Failed to initialize GLFW" << std::endl; + return false; + } + std::cout << "[UI] GLFW initialized successfully" << std::endl; + + // FORCE high-performance GPU (NVIDIA/AMD dedicated) + std::cout << "[UI] Requesting high-performance GPU..." << std::endl; + + // OpenGL 3.3 - core profile + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + // Create window + std::cout << "[UI] Creating GLFW window (" << width_ << "x" << height_ << ")..." << std::endl; + window_ = glfwCreateWindow(width_, height_, "SecondVoice - Live Translation", nullptr, nullptr); + if (!window_) { + std::cerr << "[UI] Failed to create GLFW window" << std::endl; + glfwTerminate(); + return false; + } + std::cout << "[UI] GLFW window created successfully" << std::endl; + + glfwMakeContextCurrent(window_); + glfwSwapInterval(1); // Enable vsync + + // Initialize GLAD - MUST happen after glfwMakeContextCurrent! + std::cout << "[UI] Initializing GLAD OpenGL loader..." << std::endl; + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + std::cerr << "[UI] Failed to initialize GLAD!" << std::endl; + glfwDestroyWindow(window_); + glfwTerminate(); + return false; + } + std::cout << "[UI] GLAD initialized successfully" << std::endl; + + // Query OpenGL context to see which GPU we got + const GLubyte* vendor = glGetString(GL_VENDOR); + const GLubyte* renderer = glGetString(GL_RENDERER); + const GLubyte* version = glGetString(GL_VERSION); + + std::cout << "[UI] ========================================" << std::endl; + std::cout << "[UI] OpenGL Context Info:" << std::endl; + std::cout << "[UI] Vendor: " << (vendor ? (const char*)vendor : "Unknown") << std::endl; + std::cout << "[UI] Renderer: " << (renderer ? (const char*)renderer : "Unknown") << std::endl; + std::cout << "[UI] Version: " << (version ? (const char*)version : "Unknown") << std::endl; + std::cout << "[UI] ========================================" << std::endl; + + // Initialize ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + + ImGui::StyleColorsDark(); + + ImGui_ImplGlfw_InitForOpenGL(window_, true); + + // TEST: Compile a simple shader to see REAL error messages + std::cout << "[UI] Testing shader compilation..." << std::endl; + const char* vertexShaderSource = R"( +#version 130 +in vec2 Position; +in vec2 UV; +in vec4 Color; +varying vec2 Frag_UV; +varying vec4 Frag_Color; +void main() { + Frag_UV = UV; + Frag_Color = Color; + gl_Position = vec4(Position, 0, 1); +} +)"; + + GLuint testShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(testShader, 1, &vertexShaderSource, NULL); + glCompileShader(testShader); + + GLint success; + glGetShaderiv(testShader, GL_COMPILE_STATUS, &success); + if (!success) { + char infoLog[1024]; + glGetShaderInfoLog(testShader, 1024, NULL, infoLog); + std::cerr << "[UI] ==================== SHADER COMPILATION ERROR ====================" << std::endl; + std::cerr << infoLog << std::endl; + std::cerr << "[UI] ====================================================================" << std::endl; + } else { + std::cout << "[UI] Test shader compiled successfully!" << std::endl; + } + glDeleteShader(testShader); + + // DEBUG: Check if GLAD loaded glCreateShader + std::cout << "[UI] DEBUG: glCreateShader function pointer = " << (void*)glCreateShader << std::endl; + if (glCreateShader == nullptr) { + std::cerr << "[UI] ERROR: glCreateShader is NULL!" << std::endl; + return false; + } + + // Let ImGui auto-detect the GLSL version + ImGui_ImplOpenGL3_Init(nullptr); + + // CRITICAL: Release the OpenGL context from this thread + // The UI rendering will happen in a separate thread which will call makeContextCurrent() + std::cout << "[UI] Releasing OpenGL context from initialization thread" << std::endl; + glfwMakeContextCurrent(nullptr); + + return true; +} + +void TranslationUI::makeContextCurrent() { + if (window_) { + std::cout << "[UI] Making OpenGL context current in thread: " << std::this_thread::get_id() << std::endl; + glfwMakeContextCurrent(window_); + } +} + +void TranslationUI::render() { + glfwPollEvents(); + + // Start ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Main window (full viewport) + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); + ImGui::Begin("SecondVoice", nullptr, + ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoCollapse); + + renderTranslations(); + renderControls(); + renderStatus(); + + ImGui::End(); + + // Rendering + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window_, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(window_); +} + +bool TranslationUI::shouldClose() const { + return glfwWindowShouldClose(window_); +} + +void TranslationUI::addTranslation(const std::string& chinese, const std::string& french) { + messages_.push_back({chinese, french}); +} + +void TranslationUI::renderTranslations() { + ImGui::Text("SecondVoice - Live Translation"); + ImGui::Separator(); + + ImGui::BeginChild("Translations", ImVec2(0, -120), true); + + for (const auto& msg : messages_) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.8f, 1.0f, 1.0f)); + ImGui::TextWrapped("中文: %s", msg.chinese.c_str()); + ImGui::PopStyleColor(); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 1.0f, 0.5f, 1.0f)); + ImGui::TextWrapped("FR: %s", msg.french.c_str()); + ImGui::PopStyleColor(); + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + } + + if (auto_scroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { + ImGui::SetScrollHereY(1.0f); + } + + ImGui::EndChild(); +} + +void TranslationUI::renderControls() { + ImGui::Spacing(); + + // Center the stop button + float button_width = 200.0f; + float window_width = ImGui::GetWindowWidth(); + ImGui::SetCursorPosX((window_width - button_width) * 0.5f); + + if (ImGui::Button("STOP RECORDING", ImVec2(button_width, 40))) { + stop_requested_ = true; + } + + ImGui::Spacing(); +} + +void TranslationUI::renderStatus() { + ImGui::Separator(); + + // Format duration as MM:SS + int minutes = recording_duration_ / 60; + int seconds = recording_duration_ % 60; + ImGui::Text("Recording... Duration: %02d:%02d", minutes, seconds); + + if (!processing_status_.empty()) { + ImGui::SameLine(); + ImGui::Text(" | Status: %s", processing_status_.c_str()); + } +} + +} // namespace secondvoice