diff --git a/AsuroTool/ApplicationData.h b/AsuroTool/ApplicationData.h index 2123cc9..3bc7737 100644 --- a/AsuroTool/ApplicationData.h +++ b/AsuroTool/ApplicationData.h @@ -83,6 +83,7 @@ public: time_t hoverTargetDay = 0; TimerData timerData{}; std::mutex timerMutex{}; + bool lighthouseProcActive = false; //ApplicationData(const ApplicationData&) = delete; //ApplicationData& operator=(const ApplicationData&) = delete; diff --git a/AsuroTool/AsuroTool.cpp b/AsuroTool/AsuroTool.cpp index fffd825..d38ae91 100644 --- a/AsuroTool/AsuroTool.cpp +++ b/AsuroTool/AsuroTool.cpp @@ -94,6 +94,9 @@ void init(DrawData& drawData, ApplicationData& appData) iconFontPath.append("\\remixicon.ttf"); io.Fonts->AddFontFromFileTTF(iconFontPath.c_str(), 14.0f, &icons_config, icons_ranges); + // style + loadUiStyle(); + // Start timer thread std::thread timerThread(updateTimer, std::ref(appData)); timerThread.detach(); @@ -108,6 +111,8 @@ void init(DrawData& drawData, ApplicationData& appData) void draw(DrawData& drawData, ApplicationData& appData) { + const float panelGap = 0.f; + justDocked = false; appData.hoverTargetType = HoverTargetType::HOVER_TARGET_NONE; @@ -127,7 +132,7 @@ void draw(DrawData& drawData, ApplicationData& appData) 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.; + customYCursor += panelGap; // Timer ImGui::SetNextWindowPos(ImVec2(0, customYCursor)); @@ -138,13 +143,13 @@ void draw(DrawData& drawData, ApplicationData& appData) ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2.f, customYCursor)); ImGui::SetNextWindowSize(ImVec2(viewportSize.x / 2.f, 0)); customYCursor += baseStationWindow(appData).y; - customYCursor += 5.; + customYCursor += panelGap; // 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.; + customYCursor += panelGap; // Recording devices ImGui::SetNextWindowPos(ImVec2(0, customYCursor)); @@ -179,6 +184,12 @@ void cleanup(DrawData& drawData, ApplicationData& appData) cleanupShell(drawData); } +float getButtonWidth(const char* label) +{ + ImVec2 labelSize = ImGui::CalcTextSize(label); + return labelSize.x + ImGui::GetStyle().FramePadding.x * 2.f; +} + ImVec2 menuBar(DrawData& drawData, ApplicationData& appData) { ImVec2 size{}; @@ -219,7 +230,7 @@ ImVec2 menuBar(DrawData& drawData, ApplicationData& appData) { ImVec2 availableSpace = ImGui::GetContentRegionAvail(); ImVec2 cursorPos = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(cursorPos.x + availableSpace.x - 47.); + ImGui::SetCursorPosX(cursorPos.x + availableSpace.x - getButtonWidth(ICON_SUBTRACT_FILL) * 2.f); if (ImGui::SmallButton(ICON_SUBTRACT_FILL)) { @@ -270,7 +281,7 @@ bool windowHeaderButton(const char* id) ImVec2 windowMax = ImGui::GetWindowContentRegionMax(); ImGui::PopClipRect(); - ImGui::SetCursorPos({ windowMax.x - 20.f, tempPos.y - 28.f }); + ImGui::SetCursorPos({ windowMax.x - getButtonWidth(ICON_CLOSE_FILL), tempPos.y - 30.f}); bool result = ImGui::SmallButton(id); ImGui::PushClipRect(tempRect.Min, tempRect.Max, false); @@ -441,11 +452,11 @@ ImVec2 timerWindow(DrawData& drawData, ApplicationData& appData) { if (timerData.timerHasNotified) { - formatTime(timerData.timerStartTimestamp + std::chrono::seconds((int)appData.settings.timerDuration), timeStr, sizeof(timeStr)); - timerText = std::format("Ended at {}", timeStr); - } - else - { + 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); } @@ -531,12 +542,18 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title) std::vector& taskDates = appData.settings.tasks[taskName]; bool taskDone = std::any_of(taskDates.begin(), taskDates.end(), selectedDayMatcher); + bool highlightButton = false; + std::string hoverText = ""; + + if (taskDates.size() > 0) + { + time_t mostRecentDoneDate = *std::max_element(taskDates.begin(), taskDates.end()); + double timeDiffSeconds = difftime(today, getDayStartOf(mostRecentDoneDate)); + int timeDiffDays = timeDiffSeconds / (60 * 60 * 24); + highlightButton = timeDiffDays > appData.checklistHighlightDurationDays; + hoverText = std::format("{}", timeDiffDays); + } - 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 }); @@ -545,7 +562,8 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title) } ImVec2 beforeCheckbox = ImGui::GetCursorScreenPos(); - if (ImGui::Checkbox(taskName.c_str(), &taskDone)) + std::string checkboxText = std::format("{}##taskcheck{}", taskName.c_str(), taskName.c_str()); + if (ImGui::Checkbox(checkboxText.c_str(), &taskDone)) { if (taskDone) { @@ -563,11 +581,10 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title) ImGui::PopStyleColor(3); ImVec2 afterCheckbox = ImGui::GetCursorScreenPos(); - std::string dayText = std::format("{}", timeDiffDays); - float textWidth = ImGui::CalcTextSize(dayText.c_str()).x; + float textWidth = ImGui::CalcTextSize(hoverText.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::Text(hoverText.c_str()); ImGui::PopStyleColor(); ImGui::SetCursorScreenPos(afterCheckbox); } @@ -575,9 +592,37 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title) if (ImGui::BeginPopup(SETTINGS_POPUP_NAME)) { + ImGui::Text("Task Highlight Duration"); + ImGui::SameLine(); + ImGui::PushItemWidth(75.f); - ImGui::DragInt("Task highlight duration", &appData.checklistHighlightDurationDays, .05f, 1, 7, "%d days"); + ImGui::DragInt("##taskduration", &appData.checklistHighlightDurationDays, .05f, 1, 7, "%d days"); ImGui::PopItemWidth(); + + ImGui::Text("Tasks"); + auto taskIterator = appData.settings.taskNames.begin(); + while (taskIterator != appData.settings.taskNames.end()) + { + size_t taskIndex = taskIterator - appData.settings.taskNames.begin(); + std::string& taskName = *taskIterator; + taskName.reserve(128); + ImGui::InputText(std::format("##taskinput{}", taskIndex).c_str(), taskName.data(), taskName.capacity()); + ImGui::SameLine(); + if (ImGui::SmallButton(std::format("{}##deltask{}", ICON_CLOSE_FILL, taskIndex).c_str())) + { + taskIterator = appData.settings.taskNames.erase(taskIterator); + } + else + { + taskIterator++; + } + } + + if (ImGui::Button(ICON_ADD_FILL)) + { + appData.settings.taskNames.push_back(""); + } + ImGui::EndPopup(); } } @@ -587,8 +632,21 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title) return size; } -void startBaseStationProc(const char* args, DWORD flags = 0) +void startBaseStationProc(ApplicationData& appData, const char* args, DWORD flags = 0) { + if (appData.lighthouseProcActive) return; + appData.lighthouseProcActive = true; + + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + HANDLE exeOutWrite; + HANDLE exeOutRead; + CreatePipe(&exeOutRead, &exeOutWrite, &saAttr, 0); + SetHandleInformation(exeOutRead, HANDLE_FLAG_INHERIT, 0); + // additional information STARTUPINFOA si; PROCESS_INFORMATION pi; @@ -596,14 +654,33 @@ void startBaseStationProc(const char* args, DWORD flags = 0) // set the size of the structures ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); + si.hStdOutput = exeOutWrite; + si.dwFlags = STARTF_USESTDHANDLES; ZeroMemory(&pi, sizeof(pi)); // start the program up - CreateProcessA("lighthouse-v2-manager.exe", const_cast(args), NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi); + CreateProcessA("lighthouse-v2-manager.exe", const_cast(args), NULL, NULL, true, flags, NULL, NULL, &si, &pi); + WaitForSingleObject(pi.hProcess, INFINITE); + + for (;;) + { + constexpr size_t BUFFER_SIZE = 4096; + char readBuffer[BUFFER_SIZE]{}; + DWORD readCount = 0; + bool readSuccess = ReadFile(exeOutRead, readBuffer, BUFFER_SIZE, &readCount, NULL); + if (!readSuccess || readCount == 0) break; + + std::cout << readBuffer << std::endl; + } + // Close process and thread handles. CloseHandle(pi.hProcess); CloseHandle(pi.hThread); + CloseHandle(exeOutWrite); + CloseHandle(exeOutRead); + + appData.lighthouseProcActive = false; } ImVec2 baseStationWindow(ApplicationData& appData) @@ -623,7 +700,7 @@ ImVec2 baseStationWindow(ApplicationData& appData) params.append(" "); params.append(mac.c_str()); } - startBaseStationProc(params.c_str(), appData.settings.baseStationShowConsole ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); + startBaseStationProc(appData, params.c_str(), appData.settings.baseStationShowConsole ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); } ImGui::SameLine(); if (ImGui::Button("Shutdown")) @@ -634,7 +711,7 @@ ImVec2 baseStationWindow(ApplicationData& appData) params.append(" "); params.append(mac.c_str()); } - startBaseStationProc(params.c_str(), appData.settings.baseStationShowConsole ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); + startBaseStationProc(appData, params.c_str(), appData.settings.baseStationShowConsole ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); } } @@ -643,7 +720,7 @@ ImVec2 baseStationWindow(ApplicationData& appData) ImGui::Checkbox("Show Console", &appData.settings.baseStationShowConsole); if (ImGui::Button("Search")) { - startBaseStationProc("discover"); + startBaseStationProc(appData, "discover"); // TODO: parse stuff (annoying) } ImGui::Text(std::format("Known: {}", appData.settings.baseStationMacAdresses.size()).c_str()); @@ -664,11 +741,12 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector& dev ImGui::OpenPopup(SETTINGS_POPUP_NAME); } - if (ImGui::BeginTable("DeviceTable", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders | ImGuiTableFlags_NoSavedSettings)) + if (ImGui::BeginTable("DeviceTable", 4, 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::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, getButtonWidth(ICON_MUSIC_2_FILL)); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, getButtonWidth(ICON_PHONE_FILL)); ImGui::TableHeadersRow(); for (auto& dev : deviceList) @@ -756,22 +834,31 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector& dev } // Defaults + const float circleSize = 5.f; + ImGui::TableNextColumn(); if (dev.state == DEVICE_STATE_ACTIVE) { if (dev.isDefaultConsole) { - drawCircle(5, IM_COL32(50, 50, 222, 255)); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + getButtonWidth(ICON_MUSIC_2_FILL) / 2.f - circleSize); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 4.f); + drawCircle(circleSize, 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(); + ImGui::TableNextColumn(); + if (dev.state == DEVICE_STATE_ACTIVE) + { if (dev.isDefaultCommunication) { - drawCircle(5, IM_COL32(222, 50, 50, 255)); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + getButtonWidth(ICON_PHONE_FILL) / 2.f - circleSize); + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 4.f); + drawCircle(circleSize, IM_COL32(222, 50, 50, 255)); } if (customButton("bn_c_", deviceIdUtf8.c_str(), ICON_PHONE_FILL, !dev.isDefaultCommunication)) { diff --git a/AsuroTool/Settings.cpp b/AsuroTool/Settings.cpp index 02a29a6..0908598 100644 --- a/AsuroTool/Settings.cpp +++ b/AsuroTool/Settings.cpp @@ -172,3 +172,68 @@ void setAutostart(bool newValue) if (isError(hr, "Failed to delete autostart key: ")) return; } } + +void loadUiStyle() +{ + ImGuiStyle& style = ImGui::GetStyle(); + style.WindowPadding = { 6.f, 8.f }; + style.FramePadding = { 8.f, 4.f }; + style.ItemSpacing = { 4.f, 4.f }; + style.WindowRounding = 4.f; + style.GrabRounding = 2.f; + + ImVec4* colors = style.Colors; + colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.07f, 0.09f, 0.99f); + colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.04f, 0.07f, 1.00f); + colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.50f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.02f, 0.01f, 0.01f, 0.54f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.98f, 0.36f, 0.36f, 0.40f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.60f, 0.41f, 0.41f, 0.67f); + colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.02f, 0.02f, 0.02f, 0.51f); + colors[ImGuiCol_MenuBarBg] = ImVec4(0.06f, 0.05f, 0.06f, 0.99f); + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f); + colors[ImGuiCol_CheckMark] = ImVec4(0.98f, 0.61f, 0.26f, 1.00f); + colors[ImGuiCol_SliderGrab] = ImVec4(0.88f, 0.38f, 0.24f, 1.00f); + colors[ImGuiCol_SliderGrabActive] = ImVec4(0.98f, 0.41f, 0.26f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.04f, 0.01f, 0.03f, 0.73f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.14f, 0.23f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.47f, 0.23f, 0.39f, 1.00f); + colors[ImGuiCol_Header] = ImVec4(0.46f, 0.19f, 0.12f, 0.67f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.69f, 0.29f, 0.19f, 0.80f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.98f, 0.41f, 0.26f, 1.00f); + colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.45f, 0.43f, 0.50f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.75f, 0.24f, 0.10f, 0.78f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.75f, 0.24f, 0.10f, 1.00f); + colors[ImGuiCol_ResizeGrip] = ImVec4(0.98f, 0.41f, 0.26f, 0.20f); + colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.98f, 0.41f, 0.26f, 0.67f); + colors[ImGuiCol_ResizeGripActive] = ImVec4(0.98f, 0.41f, 0.26f, 0.95f); + colors[ImGuiCol_Tab] = ImVec4(0.09f, 0.02f, 0.07f, 0.86f); + colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.15f, 0.24f, 1.00f); + colors[ImGuiCol_TabActive] = ImVec4(0.37f, 0.14f, 0.29f, 1.00f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.15f, 0.09f, 0.07f, 0.97f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.42f, 0.20f, 0.14f, 1.00f); + colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); + colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); + colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f); + colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f); + colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f); + colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); + colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); + colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); + colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); + colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); + colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); +} \ No newline at end of file diff --git a/AsuroTool/Settings.h b/AsuroTool/Settings.h index b865290..c1039cf 100644 --- a/AsuroTool/Settings.h +++ b/AsuroTool/Settings.h @@ -17,3 +17,5 @@ void settingsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTex void applySettings(DrawData& drawData, ApplicationData& appData); void updateDocked(DrawData& drawData, ApplicationData& appData); void setAutostart(bool newValue); + +void loadUiStyle(); \ No newline at end of file