//Disables console window #if !_DEBUG #pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") #endif // we need commctrl v6 for LoadIconMetric() // see https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/winui/shell/appshellintegration/NotificationIcon/NotificationIcon.cpp #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #pragma comment(lib, "comctl32.lib") #pragma comment(lib, "rpcrt4.lib") #include "Util.h" #include "AudioApi.h" #include "Settings.h" #include "AsuroTool.h" #include "WindowsShell.h" #include "Timer.h" #include #include #include #include #include #include #define MAX_FONT_PATH_LENGTH 2048 #define SETTINGS_POPUP_NAME "settings_popup" #define TIMER_WARNING_COLOR ImVec4(.7f, 0.2f, 0.2f, .5f) // Globals for use in callbacks DrawData* gDrawData; ApplicationData* gAppData; bool justDocked = false; bool isHidden = false; time_t selectedDay = 0; int main() { isError(CoInitializeEx(NULL, COINIT_MULTITHREADED), "Failed to initialize"); std::wstring appDir; getAppDir(appDir); if (_wchdir(appDir.c_str()) != 0) { std::cout << "Failed to set working dir." << std::endl; } ApplicationData applicationData{}; ImGuiCallbacks callbacks{}; callbacks.initFunc = std::bind(init, std::placeholders::_1, std::ref(applicationData)); callbacks.drawFunc = std::bind(draw, std::placeholders::_1, std::ref(applicationData)); callbacks.cleanupFunc = std::bind(cleanup, std::placeholders::_1, std::ref(applicationData)); startImgui(callbacks, "Audio Thingy", 700, 400); } time_t getDayStartOf(time_t time) { tm localTime; localtime_s(&localTime, &time); localTime.tm_hour = 0; localTime.tm_min = 0; localTime.tm_sec = 0; return mktime(&localTime); } void init(DrawData& drawData, ApplicationData& appData) { std::wstring appPath; getAppDir(appPath); char appPathStr[MAX_FONT_PATH_LENGTH]; HRESULT convResult = WideCharToMultiByte(CP_UTF8, NULL, &appPath[0], -1, appPathStr, MAX_FONT_PATH_LENGTH, NULL, nullptr); isError(convResult, "Failed to convert path: "); // sad :( gDrawData = &drawData; gAppData = &appData; // Load text font ImGuiIO& io = ImGui::GetIO(); std::string fontPath = std::string(appPathStr); fontPath.append("\\Montserrat-Regular.ttf"); io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 18.0f); // Load icon font static const ImWchar icons_ranges[] = { 0xEA01, 0xF2DF, 0 }; ImFontConfig icons_config; icons_config.MergeMode = true; icons_config.PixelSnapH = true; icons_config.GlyphOffset = { 0., 1. }; std::string iconFontPath = std::string(appPathStr); iconFontPath.append("\\remixicon.ttf"); io.Fonts->AddFontFromFileTTF(iconFontPath.c_str(), 14.0f, &icons_config, icons_ranges); // Start timer thread std::thread timerThread(updateTimer, std::ref(appData)); timerThread.detach(); // Time selectedDay = getDayStartOf(std::time(nullptr)); initShell(drawData); initSettings(drawData, appData); initAudio(appData); } void draw(DrawData& drawData, ApplicationData& appData) { justDocked = false; appData.hoverTargetType = HoverTargetType::HOVER_TARGET_NONE; if (isHidden) { glfwWaitEvents(); return; } float customYCursor = 0; ImVec2 viewportSize = ImGui::GetMainViewport()->Size; // Menu Bar customYCursor += menuBar(drawData, appData).y; // Checklist ImGui::SetNextWindowPos(ImVec2(0, customYCursor)); ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0)); customYCursor += checklistWindow(appData, std::format(" {} Checklist", ICON_CHECK_FILL).c_str()).y; customYCursor += 5.; // Timer ImGui::SetNextWindowPos(ImVec2(0, customYCursor)); ImGui::SetNextWindowSize(ImVec2(viewportSize.x / 2.f, 0)); timerWindow(drawData, appData); // Base Stations ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2.f, customYCursor)); ImGui::SetNextWindowSize(ImVec2(viewportSize.x / 2.f, 0)); customYCursor += baseStationWindow(appData).y; customYCursor += 5.; // Playback Devices ImGui::SetNextWindowPos(ImVec2(0, customYCursor)); ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0)); customYCursor += audioDeviceWindow(appData, appData.audioData->playbackDevices, std::format(" {} Playback", ICON_HEADPHONE_FILL).c_str()).y; customYCursor += 5.; // Recording devices ImGui::SetNextWindowPos(ImVec2(0, customYCursor)); ImGui::SetNextWindowSize(ImVec2(viewportSize.x, 0)); customYCursor += audioDeviceWindow(appData, appData.audioData->recordingDevices, std::format(" {} Recording", ICON_MIC_FILL).c_str()).y; // Resize viewport drawData.window_size.y = customYCursor; if (appData.settings.docked) { int monitorX, monitorY, monitorW, monitorH; glfwGetMonitorWorkarea(glfwGetPrimaryMonitor(), &monitorX, &monitorY, &monitorW, &monitorH); glfwSetWindowPos(drawData.window, monitorX + monitorW - drawData.window_size.x, monitorY + monitorH - drawData.window_size.y); } if (appData.hoverTargetType == HoverTargetType::HOVER_TARGET_CHECKLIST_DAY) { tm time_tm; localtime_s(&time_tm, &appData.hoverTargetDay); ImGui::BeginTooltip(); char timeStr[32]; strftime(timeStr, 32, "%d.%m", &time_tm); ImGui::Text(timeStr); ImGui::EndTooltip(); } } void cleanup(DrawData& drawData, ApplicationData& appData) { cleanupShell(drawData); } ImVec2 menuBar(DrawData& drawData, ApplicationData& appData) { ImVec2 size{}; bool closeMenu = false; if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Settings")) { if (ImGui::Checkbox("Docked", &appData.settings.docked)) { closeMenu = true; updateDocked(drawData, appData); } if (ImGui::Checkbox("Autostart", &appData.settings.autostart)) { setAutostart(appData.settings.autostart); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Debug")) { if (ImGui::Button("Manual Refresh")) { reloadDeviceLists(*appData.audioData); } ImGui::Text("setup: %.1f ms", drawData.frameTimeSetup); ImGui::Text("draw: %.1f ms", drawData.frameTimeDraw); ImGui::Text("render: %.1f ms", drawData.frameTimeRender); ImGui::Text("display: %.1f ms", drawData.frameTimeDisplay); ImGui::EndMenu(); } if (appData.settings.docked) { ImVec2 availableSpace = ImGui::GetContentRegionAvail(); ImVec2 cursorPos = ImGui::GetCursorPos(); ImGui::SetCursorPosX(cursorPos.x + availableSpace.x - 47.); if (ImGui::SmallButton(ICON_SUBTRACT_FILL)) { glfwIconifyWindow(drawData.window); } if (ImGui::SmallButton(ICON_CLOSE_FILL)) { glfwSetWindowShouldClose(drawData.window, GLFW_TRUE); } } size = ImGui::GetWindowSize(); ImGui::EndMainMenuBar(); } if (closeMenu) { ImGui::SetWindowFocus(); } return size; } bool customButton(const char* id_start, const char* id_end, const char* title, bool visible) { std::string buttonId(id_start); buttonId.append(id_end); bool result = false; if (visible) { ImGui::PushID(buttonId.c_str()); result = ImGui::SmallButton(title); ImGui::PopID(); } else { ImGui::InvisibleButton(buttonId.c_str(), ImGui::CalcTextSize(title)); } return result; } bool windowHeaderButton(const char* id) { ImRect tempRect = ImGui::GetCurrentWindow()->ClipRect; ImVec2 tempPos = ImGui::GetCursorPos(); ImVec2 windowMax = ImGui::GetWindowContentRegionMax(); ImGui::PopClipRect(); ImGui::SetCursorPos({ windowMax.x - 20.f, tempPos.y - 28.f }); bool result = ImGui::SmallButton(id); ImGui::PushClipRect(tempRect.Min, tempRect.Max, false); ImGui::SetCursorPos(tempPos); return result; } void drawChecklistDayLines(ApplicationData& appData, ImDrawList* drawList, float lineHeight, time_t day) { auto& tasks = appData.settings.tasks; size_t totalTasks = appData.settings.taskNames.size(); size_t count = std::count_if(tasks.begin(), tasks.end(), [&](std::pair> t) { return std::any_of(t.second.begin(), t.second.end(), [&](time_t tt) { return tt == day; }); }); for (int i = 0; i < count; i++) { ImVec2 cursorPos = ImGui::GetCursorScreenPos(); drawList->AddLine({ cursorPos.x, cursorPos.y }, { cursorPos.x, cursorPos.y + lineHeight }, ImColor(.4f, .9f, .3f), DAY_LINE_WIDTH); ImGui::SetCursorScreenPos({ cursorPos.x + DAY_LINE_OFFSET, cursorPos.y }); } for (int i = 0; i < max(0, totalTasks - count); i++) { ImVec2 cursorPos = ImGui::GetCursorScreenPos(); drawList->AddLine({ cursorPos.x, cursorPos.y }, { cursorPos.x, cursorPos.y + lineHeight }, ImColor(.1f, .3f, .05f), DAY_LINE_WIDTH); ImGui::SetCursorScreenPos({ cursorPos.x + DAY_LINE_OFFSET, cursorPos.y }); } } void drawDayLineButton(ApplicationData& appData, ImDrawList* drawList, float lineHeight, time_t day, bool drawRect = true) { ImVec2 pos = ImGui::GetCursorScreenPos(); ImVec2 startPos = { pos.x - DAY_OUTLINE_SIZE, pos.y - DAY_OUTLINE_SIZE }; size_t taskCount = appData.settings.taskNames.size(); ImVec2 size = { DAY_LINE_OFFSET * taskCount + DAY_OUTLINE_SIZE, lineHeight + DAY_OUTLINE_SIZE * 2.f }; ImVec2 endPos = { startPos.x + size.x, startPos.y + size.y }; ImGui::SetCursorScreenPos(startPos); if (selectedDay == day) { if (drawRect) drawList->AddRect(startPos, endPos, ImColor(1.f, 1.f, 1.f)); } else if (ImGui::InvisibleButton(std::format("daybn_{}", day).c_str(), size)) { selectedDay = day; } ImGui::SetCursorScreenPos(pos); if (ImGui::IsMouseHoveringRect(startPos, endPos, false)) { appData.hoverTargetType = HoverTargetType::HOVER_TARGET_CHECKLIST_DAY; appData.hoverTargetDay = day; } } void formatTime(std::chrono::system_clock::time_point time, char* buffer, size_t bufferSize) { const time_t tTime = std::chrono::system_clock::to_time_t(time); tm timeInfo; localtime_s(&timeInfo, &tTime); std::strftime(buffer, bufferSize, "%H:%M", &timeInfo); } ImVec2 timerWindow(DrawData& drawData, ApplicationData& appData) { appData.timerMutex.lock(); TimerData& timerData = appData.timerData; std::string title; if (timerData.isTimerActive) { if (timerData.timerHasNotified) { title = std::format("{} Timer done!", ICON_TIMER_FILL); } else { float elapsedMinutes = calcElapsedSeconds(timerData.timerStartTimestamp) / 60.f; title = std::format("{} Timer ({:.0f}/{:.0f} minutes)", ICON_TIMER_FILL, elapsedMinutes, appData.settings.timerDuration / 60.f); } } else { title = std::format("{} Timer", ICON_TIMER_FILL); } // Draw window if (ImGui::Begin(title.c_str(), 0, ImGuiWindowFlags_NoResize)) { if (windowHeaderButton(ICON_SETTINGS_FILL)) { ImGui::OpenPopup(SETTINGS_POPUP_NAME); } float timerDisplayMinutes = appData.settings.timerDuration / 60.f; if (timerData.isTimerActive) { float elapsedSeconds = calcElapsedSeconds(timerData.timerStartTimestamp); float timerPercentComplete = elapsedSeconds / appData.settings.timerDuration; float timerDisplayPercent = timerPercentComplete; timerDisplayMinutes = (appData.settings.timerDuration - elapsedSeconds) / 60.f; if (timerDisplayMinutes < 0.f) timerDisplayMinutes = 0.f; // Draw background rectangle ImVec2 padding = ImGui::GetStyle().WindowPadding; ImVec2 contentMin = ImGui::GetWindowContentRegionMin(); ImVec2 contentMax = ImGui::GetWindowContentRegionMax(); ImVec2 windowPos = ImGui::GetWindowPos(); ImVec2 rectMin = { contentMin.x + windowPos.x - padding.x, contentMin.y + windowPos.y - padding.y }; ImVec2 rectMax = { contentMax.x + windowPos.x + padding.x, contentMax.y + windowPos.y + padding.y }; if (timerDisplayMinutes > 0.f) { float contentWidth = rectMax.x - rectMin.x; rectMax = { rectMin.x + contentWidth * (1.f - timerDisplayPercent), rectMax.y }; } ImDrawList* drawList = ImGui::GetWindowDrawList(); ImRect tempRect = ImGui::GetCurrentWindow()->ClipRect; ImGui::PopClipRect(); drawList->AddRectFilled(rectMin, rectMax, ImColor{ timerDisplayMinutes > 0.f ? ImGui::GetStyleColorVec4(ImGuiCol_TabUnfocusedActive) : TIMER_WARNING_COLOR }); ImGui::PushClipRect(tempRect.Min, tempRect.Max, false); } // Play/Reset if (ImGui::Button(timerData.isTimerActive ? ICON_RESTART_LINE : ICON_PLAY_FILL)) { if (!timerData.isTimerActive) { timerData.isTimerActive = true; timerData.timerHasNotified = false; timerData.timerStartTimestamp = std::chrono::system_clock::now(); } else { timerData.isTimerActive = false; } } // Timer controls ImGui::SameLine(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f); if (ImGui::Button(ICON_SUBTRACT_FILL)) { appData.settings.timerDuration -= 60.f; if (appData.settings.timerDuration < 0.) appData.settings.timerDuration = 0.f; } ImGui::SameLine(); ImGui::PushItemWidth(45.f); if (ImGui::InputFloat("##timer", &timerDisplayMinutes, 0, 0, "%.0fm")) { appData.settings.timerDuration = timerDisplayMinutes * 60.f; } ImGui::PopItemWidth(); ImGui::SameLine(); if (ImGui::Button(ICON_ADD_FILL)) { appData.settings.timerDuration += 60.f; } ImGui::SameLine(); char timeStr[32]; std::string timerText; if (timerData.isTimerActive) { if (timerData.timerHasNotified) { formatTime(timerData.timerStartTimestamp + std::chrono::seconds((int)appData.settings.timerDuration), timeStr, sizeof(timeStr)); timerText = std::format("Ended at {}", timeStr); } else { formatTime(appData.timerData.timerStartTimestamp + std::chrono::seconds((int)appData.settings.timerDuration), timeStr, sizeof(timeStr)); timerText = std::format("Ends at {}", timeStr); } } else { formatTime(std::chrono::system_clock::now() + std::chrono::seconds((int)appData.settings.timerDuration), timeStr, sizeof(timeStr)); timerText = std::format("Ends at {}", timeStr); } float availWidth = ImGui::GetContentRegionMax().x - ImGui::GetCursorPosX(); float textWidth = ImGui::CalcTextSize(timerText.c_str()).x; ImGui::SetCursorPosX(ImGui::GetCursorPosX() + availWidth - textWidth); ImGui::Text(timerText.c_str()); } if (ImGui::BeginPopup(SETTINGS_POPUP_NAME)) { // Loop //float loopContentWidth = ImGui::GetFrameHeight() + 5.f + ImGui::CalcTextSize("Loop").x + 5.f + 30.f + 10.f; //ImGui::SetCursorPosX(ImGui::GetWindowWidth() - loopContentWidth); ImGui::Checkbox("Loop", &appData.settings.timerRepeating); ImGui::SameLine(); float loopTimerMinutes = appData.settings.timerRepeatDuration / 60.f; ImGui::PushItemWidth(35.f); if (ImGui::InputFloat("##timerloop", &loopTimerMinutes, 0, 0, "%.0fm")) { appData.settings.timerRepeatDuration = loopTimerMinutes * 60.f; } ImGui::PopItemWidth(); ImGui::EndPopup(); } appData.timerMutex.unlock(); ImVec2 size = ImGui::GetWindowSize(); ImGui::End(); return size; } ImVec2 checklistWindow(ApplicationData& appData, const char* title) { if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize)) { if (windowHeaderButton(ICON_SETTINGS_FILL)) { ImGui::OpenPopup(SETTINGS_POPUP_NAME); } time_t today = getDayStartOf(std::time(nullptr)); ImDrawList* drawList = ImGui::GetWindowDrawList(); float lineHeight = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2; for (int pastDay = 3; pastDay >= 1; pastDay--) { time_t date = today - (60 * 60 * 24 * pastDay); drawDayLineButton(appData, drawList, lineHeight, date); drawChecklistDayLines(appData, drawList, lineHeight, date); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f); } ImVec2 cursorPos = ImGui::GetCursorScreenPos(); drawList->AddLine({ cursorPos.x, cursorPos.y - 2.f }, { cursorPos.x, cursorPos.y + lineHeight + 2.f }, IM_COL32_WHITE, 1.f); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.f); drawDayLineButton(appData, drawList, lineHeight, today, false); drawChecklistDayLines(appData, drawList, lineHeight, today); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.f); auto selectedDayMatcher = [&](time_t t) { return getDayStartOf(t) == selectedDay; }; for (std::string& taskName : appData.settings.taskNames) { if (!appData.settings.tasks.contains(taskName)) { appData.settings.tasks.insert({ taskName, std::vector{} }); } std::vector& taskDates = appData.settings.tasks[taskName]; bool taskDone = std::any_of(taskDates.begin(), taskDates.end(), selectedDayMatcher); time_t mostRecentDoneDate = *std::max_element(taskDates.begin(), taskDates.end()); double timeDiffSeconds = difftime(today, getDayStartOf(mostRecentDoneDate)); int timeDiffDays = timeDiffSeconds / (60 * 60 * 24); bool highlightButton = timeDiffDays > appData.checklistHighlightDurationDays; if (highlightButton) { ImGui::PushStyleColor(ImGuiCol_FrameBg, { .5f, .2f, .15f, 0.54f }); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, { .6f, .3f, .2f, 0.40f }); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { .6f, .3f, .2f, 0.67f }); } ImVec2 beforeCheckbox = ImGui::GetCursorScreenPos(); if (ImGui::Checkbox(taskName.c_str(), &taskDone)) { if (taskDone) { taskDates.push_back(selectedDay); } else { std::erase_if(taskDates, selectedDayMatcher); } } ImGui::SameLine(); if (highlightButton) { ImGui::PopStyleColor(3); ImVec2 afterCheckbox = ImGui::GetCursorScreenPos(); std::string dayText = std::format("{}", timeDiffDays); float textWidth = ImGui::CalcTextSize(dayText.c_str()).x; ImGui::SetCursorScreenPos({ beforeCheckbox.x + ImGui::GetFrameHeight() / 2.f - textWidth / 2.f, beforeCheckbox.y }); ImGui::PushStyleColor(ImGuiCol_Text, { 1.f, 1.f, 1.f, 1.f }); ImGui::Text(dayText.c_str()); ImGui::PopStyleColor(); ImGui::SetCursorScreenPos(afterCheckbox); } } if (ImGui::BeginPopup(SETTINGS_POPUP_NAME)) { ImGui::PushItemWidth(75.f); ImGui::DragInt("Task highlight duration", &appData.checklistHighlightDurationDays, .05f, 1, 7, "%d days"); ImGui::PopItemWidth(); ImGui::EndPopup(); } } ImVec2 size = ImGui::GetWindowSize(); ImGui::End(); return size; } void startBaseStationProc(const char* args, DWORD flags = 0) { // additional information STARTUPINFOA si; PROCESS_INFORMATION pi; // set the size of the structures ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); // start the program up CreateProcessA("lighthouse-v2-manager.exe", const_cast(args), NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi); // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } ImVec2 baseStationWindow(ApplicationData& appData) { if (ImGui::Begin(std::format("{} Base Stations", ICON_ALARM_WARNING_FILL).c_str(), 0, ImGuiWindowFlags_NoResize)) { if (windowHeaderButton(ICON_SETTINGS_FILL)) { ImGui::OpenPopup(SETTINGS_POPUP_NAME); } if (ImGui::Button("Wake")) { std::string params{ " on" }; for (std::string& mac : appData.settings.baseStationMacAdresses) { params.append(" "); params.append(mac.c_str()); } startBaseStationProc(params.c_str(), appData.settings.baseStationShowConsole ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); } ImGui::SameLine(); if (ImGui::Button("Shutdown")) { std::string params{ " off" }; for (std::string& mac : appData.settings.baseStationMacAdresses) { params.append(" "); params.append(mac.c_str()); } startBaseStationProc(params.c_str(), appData.settings.baseStationShowConsole ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); } } if (ImGui::BeginPopup(SETTINGS_POPUP_NAME)) { ImGui::Checkbox("Show Console", &appData.settings.baseStationShowConsole); if (ImGui::Button("Search")) { startBaseStationProc("discover"); // TODO: parse stuff (annoying) } ImGui::Text(std::format("Known: {}", appData.settings.baseStationMacAdresses.size()).c_str()); ImGui::EndPopup(); } ImVec2 size = ImGui::GetWindowSize(); ImGui::End(); return size; } ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector& deviceList, const char* title) { if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize)) { if (windowHeaderButton(ICON_SETTINGS_FILL)) { ImGui::OpenPopup(SETTINGS_POPUP_NAME); } if (ImGui::BeginTable("DeviceTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_NoSavedSettings)) { ImGui::TableSetupColumn("Devices", ImGuiTableColumnFlags_WidthStretch, 3.); ImGui::TableSetupColumn("Volume", ImGuiTableColumnFlags_WidthStretch, 1.); ImGui::TableSetupColumn("Defaults", ImGuiTableColumnFlags_WidthFixed, 55.); ImGui::TableHeadersRow(); for (auto& dev : deviceList) { std::string deviceIdUtf8 = utf8Encode(dev.id); if (dev.state == DEVICE_STATE_ACTIVE) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1., 1., 1., 1.)); } else if (!appData.settings.showDisabledDevices) { continue; } else { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(.7, .7, .7, 1.)); } // Device Name ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text(dev.name.c_str()); // Volume ImGui::TableNextColumn(); if (dev.state == DEVICE_STATE_ACTIVE) { // Mute button ImGui::PushID(std::string("bn_mute_").append(deviceIdUtf8).c_str()); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); bool isDeviceMuted = isMuted(dev.volumeInterface); if (ImGui::Button(isDeviceMuted ? ICON_VOLUME_MUTE_FILL : ICON_VOLUME_UP_FILL)) { setMuted(dev.volumeInterface, !isDeviceMuted); } ImGui::PopStyleColor(); ImGui::PopID(); ImGui::SameLine(0, 2); // Meter static std::array meterValues{}; UINT channelCount = getMeterValues(dev.meterInterface, meterValues); auto drawList = ImGui::GetWindowDrawList(); ImVec2 windowPos = ImGui::GetWindowPos(); ImVec2 cursorPos = ImGui::GetCursorScreenPos(); ImVec2 space = ImGui::GetContentRegionAvail(); float lineY = cursorPos.y + ImGui::GetTextLineHeight() / 2. + 2.; const float linePaddingX = 3.; cursorPos.x += linePaddingX; // BG line drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX), lineY), IM_COL32(120, 120, 120, 255), 2.); // Channel lines if (channelCount == 1) { drawList->AddLine(ImVec2(cursorPos.x, lineY), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValues[0], lineY), IM_COL32(200, 200, 255, 255), 3.); } if (channelCount == 2) { drawList->AddLine(ImVec2(cursorPos.x, lineY - 2), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValues[0], lineY - 2), IM_COL32(200, 200, 255, 255), 3.); drawList->AddLine(ImVec2(cursorPos.x, lineY + 2), ImVec2(cursorPos.x + (space.x - 2. * linePaddingX) * meterValues[1], lineY + 2), IM_COL32(200, 200, 255, 255), 3.); } ImGui::SetNextItemWidth(space.x); ImGui::PushID(&dev.id); ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0)); if (isDeviceMuted) ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(.4, .4, .4, 1.)); float volume = getVolume(dev.volumeInterface); if (ImGui::SliderFloat("", &volume, 0., 1., "", ImGuiSliderFlags_NoRoundToFormat | ImGuiSliderFlags_AlwaysClamp)) { setVolume(dev.volumeInterface, volume); } if (isDeviceMuted) ImGui::PopStyleColor(); ImGui::PopStyleColor(); ImGui::PopID(); } // Defaults ImGui::TableNextColumn(); if (dev.state == DEVICE_STATE_ACTIVE) { if (dev.isDefaultConsole) { drawCircle(5, IM_COL32(50, 50, 222, 255)); } if (customButton("bn_d_", deviceIdUtf8.c_str(), ICON_MUSIC_2_FILL, !dev.isDefaultConsole)) { setDefaultAudioDevice(*appData.audioData, dev.id.c_str(), ERole::eConsole); } ImGui::SameLine(); if (dev.isDefaultCommunication) { drawCircle(5, IM_COL32(222, 50, 50, 255)); } if (customButton("bn_c_", deviceIdUtf8.c_str(), ICON_PHONE_FILL, !dev.isDefaultCommunication)) { setDefaultAudioDevice(*appData.audioData, dev.id.c_str(), ERole::eCommunications); } } ImGui::PopStyleColor(); } ImGui::EndTable(); } } if (ImGui::BeginPopup(SETTINGS_POPUP_NAME)) { ImGui::Checkbox("Show Disabled Devices", &appData.settings.showDisabledDevices); ImGui::EndPopup(); } ImVec2 size = ImGui::GetWindowSize(); ImGui::End(); return size; } void drawCircle(float radius, ImU32 color) { ImGui::Dummy(ImVec2(0, 0)); ImGui::SameLine(); auto drawList = ImGui::GetWindowDrawList(); ImVec2 cursorPos = ImGui::GetCursorPos(); ImVec2 windowPos = ImGui::GetWindowPos(); drawList->AddCircleFilled(ImVec2(cursorPos.x + windowPos.x, cursorPos.y + windowPos.y + ImGui::GetTextLineHeight() / 2.), radius, color); }