954 lines
29 KiB
C++
954 lines
29 KiB
C++
//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 <array>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <format>
|
|
using std::chrono::year_month_day;
|
|
|
|
#undef min
|
|
#undef max
|
|
|
|
#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;
|
|
year_month_day selectedDay = {};
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
// style
|
|
loadUiStyle();
|
|
|
|
// Start timer thread
|
|
std::thread timerThread(updateTimer, std::ref(appData));
|
|
timerThread.detach();
|
|
|
|
// Time
|
|
selectedDay = std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now());
|
|
|
|
initShell(drawData);
|
|
initSettings(drawData, appData);
|
|
initAudio(appData);
|
|
}
|
|
|
|
void draw(DrawData& drawData, ApplicationData& appData)
|
|
{
|
|
const float panelGap = 0.f;
|
|
|
|
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 += panelGap;
|
|
|
|
// Timer
|
|
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
|
|
ImGui::SetNextWindowSize(ImVec2(viewportSize.x / 2.f - 1.f, 0));
|
|
float timerWindowHeight = timerWindow(drawData, appData).y;
|
|
|
|
// Base Stations
|
|
ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2.f, customYCursor));
|
|
ImGui::SetNextWindowSize(ImVec2(viewportSize.x / 2.f - 1.f, 0));
|
|
float baseStationWindowHeight = baseStationWindow(appData).y;
|
|
customYCursor += std::fmaxf(baseStationWindowHeight, timerWindowHeight);
|
|
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 += panelGap;
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Tooltip
|
|
if (appData.hoverTargetType == HoverTargetType::HOVER_TARGET_CHECKLIST_DAY)
|
|
{
|
|
ImGui::BeginTooltip();
|
|
ImGui::Text(std::format("{:%a, %d.%m}", std::chrono::sys_days{ appData.hoverTargetDay }).c_str());
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
|
|
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{};
|
|
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 (ImGui::BeginMenu("Style"))
|
|
{
|
|
ImGui::ShowStyleEditor();
|
|
ImGui::EndMenu();
|
|
}
|
|
|
|
if (appData.settings.docked)
|
|
{
|
|
ImVec2 availableSpace = ImGui::GetContentRegionAvail();
|
|
ImVec2 cursorPos = ImGui::GetCursorPos();
|
|
ImGui::SetCursorPosX(cursorPos.x + availableSpace.x - getButtonWidth(ICON_SUBTRACT_FILL) * 2.f);
|
|
|
|
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 = dropButton(title, {}, 2.f, true);
|
|
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();
|
|
|
|
ImGuiStyle style = ImGui::GetStyle();
|
|
ImGui::SetCursorPos({ windowMax.x - getButtonWidth(ICON_CLOSE_FILL), tempPos.y - style.WindowPadding.y - 22.f });
|
|
bool result = dropButton(id, ImVec2{}, 1.f, true);
|
|
|
|
ImGui::PushClipRect(tempRect.Min, tempRect.Max, false);
|
|
ImGui::SetCursorPos(tempPos);
|
|
|
|
return result;
|
|
}
|
|
|
|
void drawChecklistDayLines(ApplicationData& appData, ImDrawList* drawList, float lineHeight, year_month_day day)
|
|
{
|
|
for (std::string& taskName : appData.settings.taskNames)
|
|
{
|
|
ImVec2 cursorPos = ImGui::GetCursorScreenPos();
|
|
ImColor color;
|
|
auto& tasks = appData.settings.tasks[taskName];
|
|
|
|
if (std::any_of(tasks.begin(), tasks.end(), [&](year_month_day taskDay) { return taskDay == day; }))
|
|
{
|
|
color = ImColor{ .4f, .9f, .3f };
|
|
}
|
|
else
|
|
{
|
|
color = ImColor{ .1f, .3f, .05f };
|
|
}
|
|
|
|
drawList->AddLine({ cursorPos.x, cursorPos.y }, { cursorPos.x, cursorPos.y + lineHeight }, color, DAY_LINE_WIDTH);
|
|
ImGui::SetCursorScreenPos({ cursorPos.x + DAY_LINE_OFFSET, cursorPos.y });
|
|
}
|
|
}
|
|
|
|
void drawDayLineButton(ApplicationData& appData, ImDrawList* drawList, float lineHeight, year_month_day 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 setTimerDuration(ApplicationData& appData, int timerEndHours, int timerEndMinutes, tm& timeInfo)
|
|
{
|
|
timeInfo.tm_hour = timerEndHours;
|
|
timeInfo.tm_min = timerEndMinutes;
|
|
TimePoint newEndTime = std::chrono::system_clock::from_time_t(mktime(&timeInfo));
|
|
|
|
if (newEndTime < std::chrono::system_clock::now())
|
|
{
|
|
newEndTime += std::chrono::days(1);
|
|
}
|
|
else if (newEndTime > std::chrono::system_clock::now() + std::chrono::days(1))
|
|
{
|
|
newEndTime -= std::chrono::days(1);
|
|
}
|
|
|
|
appData.settings.timerDuration = std::chrono::duration_cast<std::chrono::seconds>(newEndTime - std::chrono::system_clock::now()).count();
|
|
}
|
|
|
|
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))
|
|
{
|
|
dropShadowWindowTitle();
|
|
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 (dropButton(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;
|
|
WinToast::instance()->clear();
|
|
}
|
|
}
|
|
|
|
// Timer controls
|
|
ImGui::SameLine();
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f);
|
|
|
|
if (dropButton(ICON_SUBTRACT_FILL))
|
|
{
|
|
appData.settings.timerDuration -= 60.f;
|
|
if (appData.settings.timerDuration < 0.) appData.settings.timerDuration = 0.f;
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
const char* durationFormatStr = "%.0fm";
|
|
ImGui::PushItemWidth(calcInputWidth(durationFormatStr, timerDisplayMinutes));
|
|
if (ImGui::DragFloat("##timer", &timerDisplayMinutes, .1f, 0.f, 1000.f, durationFormatStr))
|
|
{
|
|
appData.settings.timerDuration = timerDisplayMinutes * 60.f;
|
|
}
|
|
dropShadow();
|
|
ImGui::PopItemWidth();
|
|
|
|
ImGui::SameLine();
|
|
if (dropButton(ICON_ADD_FILL))
|
|
{
|
|
appData.settings.timerDuration += 60.f;
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
TimePoint endTime;
|
|
const char* endTimeText;
|
|
|
|
if (timerData.isTimerActive)
|
|
{
|
|
if (timerData.timerHasNotified)
|
|
{
|
|
endTime = timerData.timerStartTimestamp + std::chrono::seconds((int)appData.settings.timerDuration);
|
|
endTimeText = "Ended";
|
|
}
|
|
else
|
|
{
|
|
endTime = appData.timerData.timerStartTimestamp + std::chrono::seconds((int)appData.settings.timerDuration);
|
|
endTimeText = "Ends";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
endTime = std::chrono::system_clock::now() + std::chrono::seconds((int)appData.settings.timerDuration);
|
|
endTimeText = "Ends";
|
|
}
|
|
|
|
const time_t tTime = std::chrono::system_clock::to_time_t(endTime);
|
|
tm timeInfo;
|
|
localtime_s(&timeInfo, &tTime);
|
|
int timerEndHours = timeInfo.tm_hour;
|
|
int timerEndMinutes = timeInfo.tm_min;
|
|
|
|
const char* hourInputFormat = "%d";
|
|
float hourDragWidth = calcInputWidth(hourInputFormat, timerEndHours);
|
|
|
|
const char* minuteInputFormat = "%02d";
|
|
float minuteDragWidth = calcInputWidth(minuteInputFormat, timerEndMinutes);
|
|
|
|
ImGuiStyle style = ImGui::GetStyle();
|
|
float requiredWidth = ImGui::CalcTextSize(endTimeText).x + style.ItemSpacing.x + hourDragWidth + style.ItemSpacing.x + minuteDragWidth + 2.f;
|
|
float spaceWidth = ImGui::GetContentRegionAvail().x - requiredWidth;
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + spaceWidth);
|
|
|
|
ImGui::Text("%s", endTimeText);
|
|
ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(hourDragWidth);
|
|
if (ImGui::DragInt("##timerEndHours", &timerEndHours, .1f, 0, 23, hourInputFormat, ImGuiInputTextFlags_CharsDecimal))
|
|
{
|
|
setTimerDuration(appData, timerEndHours, timerEndMinutes, timeInfo);
|
|
}
|
|
dropShadow();
|
|
|
|
ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(minuteDragWidth);
|
|
if (ImGui::DragInt("##timerEndMinutes", &timerEndMinutes, .2f, 0, 59, minuteInputFormat, ImGuiInputTextFlags_CharsDecimal))
|
|
{
|
|
setTimerDuration(appData, timerEndHours, timerEndMinutes, timeInfo);
|
|
}
|
|
dropShadow();
|
|
}
|
|
|
|
if (ImGui::BeginPopup(SETTINGS_POPUP_NAME))
|
|
{
|
|
// Loop
|
|
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))
|
|
{
|
|
dropShadowWindowTitle();
|
|
if (windowHeaderButton(ICON_SETTINGS_FILL))
|
|
{
|
|
ImGui::OpenPopup(SETTINGS_POPUP_NAME);
|
|
}
|
|
|
|
// Previous days
|
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
|
float lineHeight = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2;
|
|
|
|
for (int pastDay = 3; pastDay >= 1; pastDay--)
|
|
{
|
|
TimePoint now = std::chrono::system_clock::now();
|
|
TimePoint selectedDay = now - std::chrono::days{ pastDay };
|
|
year_month_day date = std::chrono::floor<std::chrono::days>(selectedDay);
|
|
|
|
drawDayLineButton(appData, drawList, lineHeight, date);
|
|
drawChecklistDayLines(appData, drawList, lineHeight, date);
|
|
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f);
|
|
}
|
|
|
|
// Current day
|
|
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);
|
|
|
|
year_month_day today = std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now());
|
|
drawDayLineButton(appData, drawList, lineHeight, today, false);
|
|
drawChecklistDayLines(appData, drawList, lineHeight, today);
|
|
|
|
// Checkboxes
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 10.f);
|
|
|
|
auto selectedDayMatcher = [&](year_month_day taskDay) { return taskDay == selectedDay; };
|
|
|
|
for (std::string& taskName : appData.settings.taskNames)
|
|
{
|
|
if (!appData.settings.tasks.contains(taskName))
|
|
{
|
|
appData.settings.tasks.insert({ taskName, std::vector<year_month_day>{} });
|
|
}
|
|
|
|
std::vector<year_month_day>& 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)
|
|
{
|
|
year_month_day mostRecentDoneDate = *std::max_element(taskDates.begin(), taskDates.end());
|
|
std::chrono::days timeDiffDays = std::chrono::round<std::chrono::days>(std::chrono::sys_days{ today } - std::chrono::sys_days{ mostRecentDoneDate });
|
|
highlightButton = timeDiffDays > std::chrono::days{ appData.checklistHighlightDurationDays };
|
|
hoverText = std::format("{}", timeDiffDays.count());
|
|
}
|
|
|
|
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();
|
|
std::string checkboxText = std::format("{}##taskcheck{}", taskName.c_str(), taskName.c_str());
|
|
if (checkboxWithDropShadow(checkboxText.c_str(), &taskDone))
|
|
{
|
|
if (taskDone)
|
|
{
|
|
taskDates.push_back(selectedDay);
|
|
}
|
|
else
|
|
{
|
|
std::erase_if(taskDates, selectedDayMatcher);
|
|
}
|
|
}
|
|
ImGui::SameLine(0.f, 10.f);
|
|
|
|
if (highlightButton)
|
|
{
|
|
ImGui::PopStyleColor(3);
|
|
|
|
ImVec2 afterCheckbox = ImGui::GetCursorScreenPos();
|
|
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(hoverText.c_str());
|
|
ImGui::PopStyleColor();
|
|
ImGui::SetCursorScreenPos(afterCheckbox);
|
|
}
|
|
}
|
|
|
|
// Popup
|
|
if (ImGui::BeginPopup(SETTINGS_POPUP_NAME))
|
|
{
|
|
ImGui::Text("Task Highlight Duration");
|
|
ImGui::SameLine();
|
|
|
|
ImGui::PushItemWidth(75.f);
|
|
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();
|
|
}
|
|
}
|
|
|
|
ImVec2 size = ImGui::GetWindowSize();
|
|
ImGui::End();
|
|
return size;
|
|
}
|
|
|
|
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;
|
|
|
|
// 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<LPSTR>(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)
|
|
{
|
|
if (ImGui::Begin(std::format("{} Base Stations", ICON_ALARM_WARNING_FILL).c_str(), 0, ImGuiWindowFlags_NoResize))
|
|
{
|
|
dropShadowWindowTitle();
|
|
if (windowHeaderButton(ICON_SETTINGS_FILL))
|
|
{
|
|
ImGui::OpenPopup(SETTINGS_POPUP_NAME);
|
|
}
|
|
|
|
if (dropButton("Wake"))
|
|
{
|
|
std::string params{ " on" };
|
|
for (std::string& mac : appData.settings.baseStationMacAdresses)
|
|
{
|
|
params.append(" ");
|
|
params.append(mac.c_str());
|
|
}
|
|
startBaseStationProc(appData, params.c_str(), appData.settings.baseStationShowConsole ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
|
|
}
|
|
ImGui::SameLine();
|
|
if (dropButton("Shutdown"))
|
|
{
|
|
std::string params{ " off" };
|
|
for (std::string& mac : appData.settings.baseStationMacAdresses)
|
|
{
|
|
params.append(" ");
|
|
params.append(mac.c_str());
|
|
}
|
|
startBaseStationProc(appData, 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(appData, "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<AudioDevice>& deviceList, const char* title)
|
|
{
|
|
if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize))
|
|
{
|
|
dropShadowWindowTitle();
|
|
if (windowHeaderButton(ICON_SETTINGS_FILL))
|
|
{
|
|
ImGui::OpenPopup(SETTINGS_POPUP_NAME);
|
|
}
|
|
|
|
if (ImGui::BeginTable("DeviceTable", 4, ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_NoSavedSettings))
|
|
{
|
|
ImGui::TableSetupColumn("Devices", ImGuiTableColumnFlags_WidthStretch, 3.);
|
|
ImGui::TableSetupColumn("Volume", ImGuiTableColumnFlags_WidthStretch, 1.);
|
|
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, getButtonWidth(ICON_MUSIC_2_FILL));
|
|
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, getButtonWidth(ICON_PHONE_FILL));
|
|
|
|
dropShadowTableHeadersRow();
|
|
|
|
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.));
|
|
}
|
|
|
|
ImGui::TableNextRow();
|
|
ImGui::TableNextColumn();
|
|
float yPosTemp = ImGui::GetCursorPosY();
|
|
const float yPosOffset = 4.f;
|
|
|
|
// Device Name
|
|
ImGui::SetCursorPosY(yPosTemp + yPosOffset);
|
|
ImGui::Text(dev.name.c_str());
|
|
ImGui::SetCursorPosY(yPosTemp);
|
|
|
|
// Volume
|
|
ImGui::TableNextColumn();
|
|
|
|
if (dev.state == DEVICE_STATE_ACTIVE)
|
|
{
|
|
float startCursorY = ImGui::GetCursorPosY();
|
|
|
|
// Mute button
|
|
ImGui::PushID(std::string("bn_mute_").append(deviceIdUtf8).c_str());
|
|
ImGui::SetCursorPosY(yPosTemp + yPosOffset);
|
|
|
|
bool isDeviceMuted = isMuted(dev.volumeInterface);
|
|
if (dropButton(isDeviceMuted ? ICON_VOLUME_MUTE_FILL : ICON_VOLUME_UP_FILL, {}, 2.f, true))
|
|
{
|
|
setMuted(dev.volumeInterface, !isDeviceMuted);
|
|
}
|
|
|
|
ImGui::PopID();
|
|
ImGui::SameLine();
|
|
ImGui::SetCursorPosY(startCursorY);
|
|
|
|
// Meter
|
|
static std::array<float, 2> 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
|
|
const float circleSize = 5.f;
|
|
|
|
ImGui::TableNextColumn();
|
|
if (dev.state == DEVICE_STATE_ACTIVE)
|
|
{
|
|
if (dev.isDefaultConsole)
|
|
{
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + getButtonWidth(ICON_MUSIC_2_FILL) / 2.f - circleSize);
|
|
ImGui::SetCursorPosY(yPosTemp + yPosOffset);
|
|
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::TableNextColumn();
|
|
if (dev.state == DEVICE_STATE_ACTIVE)
|
|
{
|
|
if (dev.isDefaultCommunication)
|
|
{
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + getButtonWidth(ICON_PHONE_FILL) / 2.f - circleSize);
|
|
ImGui::SetCursorPosY(yPosTemp + yPosOffset);
|
|
drawCircle(circleSize, 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);
|
|
}
|