This commit is contained in:
2023-02-02 23:01:14 +01:00
parent a62ee9f811
commit 8d9694b125
14 changed files with 1905 additions and 41 deletions

View File

@@ -14,6 +14,7 @@
#include "Settings.h"
#include "AsuroTool.h"
#include "WindowsShell.h"
#include "Timer.h"
#include <array>
#include <functional>
@@ -22,8 +23,10 @@
#include <format>
#include <ctime>
#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;
@@ -34,6 +37,8 @@ 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)
@@ -89,6 +94,10 @@ void init(DrawData& drawData, ApplicationData& appData)
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));
@@ -118,14 +127,23 @@ 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.;
// 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
@@ -306,6 +324,162 @@ void drawDayLineButton(ApplicationData& appData, ImDrawList* drawList, float lin
}
}
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(35.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 && timerData.timerHasNotified)
{
formatTime(timerData.timerStartTimestamp + std::chrono::seconds((int)appData.settings.timerDuration), timeStr, sizeof(timeStr));
timerText = std::format("Ended 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))
@@ -351,16 +525,19 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
std::vector<time_t>& taskDates = appData.settings.tasks[taskName];
bool taskDone = std::any_of(taskDates.begin(), taskDates.end(), selectedDayMatcher);
bool highlightButton = std::all_of(taskDates.begin(), taskDates.end(), [&](time_t t) {
double timeDiffSeconds = difftime(today, getDayStartOf(t));
return timeDiffSeconds >= appData.checklistHighlightDurationDays * 24 * 60 * 60 + 1;
});
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)
@@ -372,11 +549,21 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
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);
}
ImGui::SameLine();
}
if (ImGui::BeginPopup(SETTINGS_POPUP_NAME))
@@ -393,6 +580,77 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
return size;
}
void startBaseStationProc(const char* args)
{
// 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
DWORD creationFlags = 0;
#ifdef _DEBUG
creationFlags = CREATE_NEW_CONSOLE;
#endif
CreateProcessA("lighthouse-v2-manager.exe", const_cast<LPSTR>(args), NULL, NULL, FALSE, creationFlags, 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());
}
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());
}
}
if (ImGui::BeginPopup(SETTINGS_POPUP_NAME))
{
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<AudioDevice>& deviceList, const char* title)
{
if (ImGui::Begin(title, 0, ImGuiWindowFlags_NoResize))