timer
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user