big time refactor + button styles

This commit is contained in:
2023-03-26 22:23:49 +02:00
parent a7fb909174
commit 6b29c5e0b8
6 changed files with 223 additions and 116 deletions

View File

@@ -21,8 +21,10 @@
#include <iostream>
#include <sstream>
#include <format>
#include <ctime>
using std::chrono::year_month_day;
#undef min
#undef max
#define MAX_FONT_PATH_LENGTH 2048
#define SETTINGS_POPUP_NAME "settings_popup"
@@ -33,7 +35,7 @@ DrawData* gDrawData;
ApplicationData* gAppData;
bool justDocked = false;
bool isHidden = false;
time_t selectedDay = 0;
year_month_day selectedDay = {};
int main()
{
@@ -56,36 +58,6 @@ int main()
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 dropShadow(float size, ImColor color)
{
ImDrawList* windowDrawList = ImGui::GetWindowDrawList();
ImVec2 lastMin = ImGui::GetItemRectMin();
ImVec2 lastMax = ImGui::GetItemRectMax();
windowDrawList->AddQuadFilled(
{ lastMin.x, lastMax.y },
{ lastMax.x, lastMax.y },
{ lastMax.x + size, lastMax.y + size },
{ lastMin.x + size, lastMax.y + size },
color);
windowDrawList->AddQuadFilled(
{ lastMax.x, lastMin.y },
{ lastMax.x + size, lastMin.y + size },
{ lastMax.x + size, lastMax.y + size },
{ lastMax.x, lastMax.y },
color);
}
void init(DrawData& drawData, ApplicationData& appData)
{
std::wstring appPath;
@@ -122,7 +94,7 @@ void init(DrawData& drawData, ApplicationData& appData)
timerThread.detach();
// Time
selectedDay = getDayStartOf(std::time(nullptr));
selectedDay = std::chrono::floor<std::chrono::days>(std::chrono::system_clock::now());
initShell(drawData);
initSettings(drawData, appData);
@@ -156,14 +128,14 @@ void draw(DrawData& drawData, ApplicationData& appData)
// Timer
ImGui::SetNextWindowPos(ImVec2(0, customYCursor));
ImGui::SetNextWindowSize(ImVec2(viewportSize.x / 2.f, 0));
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, 0));
ImGui::SetNextWindowSize(ImVec2(viewportSize.x / 2.f - 1.f, 0));
float baseStationWindowHeight = baseStationWindow(appData).y;
customYCursor += max(baseStationWindowHeight, timerWindowHeight);
customYCursor += std::fmaxf(baseStationWindowHeight, timerWindowHeight);
customYCursor += panelGap;
// Playback Devices
@@ -187,14 +159,12 @@ void draw(DrawData& drawData, ApplicationData& appData)
glfwSetWindowPos(drawData.window, monitorX + monitorW - drawData.window_size.x, monitorY + monitorH - drawData.window_size.y);
}
// Tooltip
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);
snprintf(timeStr, _countof(timeStr), "%02u.%02u", unsigned int{ appData.hoverTargetDay.day() }, unsigned int{ appData.hoverTargetDay.month() });
ImGui::Text(timeStr);
ImGui::EndTooltip();
}
@@ -290,7 +260,7 @@ bool customButton(const char* id_start, const char* id_end, const char* title, b
if (visible)
{
ImGui::PushID(buttonId.c_str());
result = ImGui::SmallButton(title);
result = dropButton(title, {}, 2.f, true);
ImGui::PopID();
}
else
@@ -308,8 +278,9 @@ bool windowHeaderButton(const char* id)
ImVec2 windowMax = ImGui::GetWindowContentRegionMax();
ImGui::PopClipRect();
ImGui::SetCursorPos({ windowMax.x - getButtonWidth(ICON_CLOSE_FILL), tempPos.y - 30.f});
bool result = ImGui::SmallButton(id);
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);
@@ -317,26 +288,29 @@ bool windowHeaderButton(const char* id)
return result;
}
void drawChecklistDayLines(ApplicationData& appData, ImDrawList* drawList, float lineHeight, time_t day)
void drawChecklistDayLines(ApplicationData& appData, ImDrawList* drawList, float lineHeight, year_month_day 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<std::string, std::vector<time_t>> t) { return std::any_of(t.second.begin(), t.second.end(), [&](time_t tt) { return tt == day; }); });
for (int i = 0; i < count; i++)
for (std::string& taskName : appData.settings.taskNames)
{
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);
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, time_t day, bool drawRect = true)
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 };
@@ -362,11 +336,11 @@ void drawDayLineButton(ApplicationData& appData, ImDrawList* drawList, float lin
}
}
void SetTimerDuration(ApplicationData& appData, int timerEndHours, int timerEndMinutes, tm& timeInfo)
void setTimerDuration(ApplicationData& appData, int timerEndHours, int timerEndMinutes, tm& timeInfo)
{
timeInfo.tm_hour = timerEndHours;
timeInfo.tm_min = timerEndMinutes;
std::chrono::time_point<std::chrono::system_clock> newEndTime = std::chrono::system_clock::from_time_t(mktime(&timeInfo));
TimePoint newEndTime = std::chrono::system_clock::from_time_t(mktime(&timeInfo));
if (newEndTime < std::chrono::system_clock::now())
{
@@ -443,7 +417,7 @@ ImVec2 timerWindow(DrawData& drawData, ApplicationData& appData)
}
// Play/Reset
if (ImGui::Button(timerData.isTimerActive ? ICON_RESTART_LINE : ICON_PLAY_FILL))
if (dropButton(timerData.isTimerActive ? ICON_RESTART_LINE : ICON_PLAY_FILL))
{
if (!timerData.isTimerActive)
{
@@ -461,34 +435,31 @@ ImVec2 timerWindow(DrawData& drawData, ApplicationData& appData)
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.f);
if (ImGui::Button(ICON_SUBTRACT_FILL))
if (dropButton(ICON_SUBTRACT_FILL))
{
appData.settings.timerDuration -= 60.f;
if (appData.settings.timerDuration < 0.) appData.settings.timerDuration = 0.f;
}
dropShadow(3.f, { 1.f, 1.f, 1.f, .3f });
ImGui::SameLine();
const char* durationFormatStr = "%.0fm";
char durationCalcBuffer[32];
snprintf(durationCalcBuffer, 32, durationFormatStr, timerDisplayMinutes);
ImGui::PushItemWidth(ImGui::CalcTextSize(durationCalcBuffer).x + ImGui::GetStyle().FramePadding.x * 2.f);
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 (ImGui::Button(ICON_ADD_FILL))
if (dropButton(ICON_ADD_FILL))
{
appData.settings.timerDuration += 60.f;
}
ImGui::SameLine();
std::chrono::time_point<std::chrono::system_clock> endTime;
TimePoint endTime;
const char* endTimeText;
if (timerData.isTimerActive)
@@ -510,39 +481,44 @@ ImVec2 timerWindow(DrawData& drawData, ApplicationData& appData)
endTimeText = "Ends";
}
const float timeDragWidth = 32.f;
float requiredWidth = ImGui::CalcTextSize(endTimeText).x + timeDragWidth * 2.f + ImGui::GetStyle().ItemSpacing.x * 4.f;
float spaceWidth = ImGui::GetContentRegionAvail().x - requiredWidth;
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + spaceWidth);
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::PushItemWidth(timeDragWidth);
if (ImGui::DragInt("##timerEndHours", &timerEndHours, .1f, 0, 23, "%d", ImGuiInputTextFlags_CharsDecimal))
ImGui::SetNextItemWidth(hourDragWidth);
if (ImGui::DragInt("##timerEndHours", &timerEndHours, .1f, 0, 23, hourInputFormat, ImGuiInputTextFlags_CharsDecimal))
{
SetTimerDuration(appData, timerEndHours, timerEndMinutes, timeInfo);
setTimerDuration(appData, timerEndHours, timerEndMinutes, timeInfo);
}
dropShadow();
ImGui::SameLine();
ImGui::Text(":");
ImGui::SameLine();
if (ImGui::DragInt("##timerEndMinutes", &timerEndMinutes, .2f, 0, 59, "%02d", ImGuiInputTextFlags_CharsDecimal))
ImGui::SetNextItemWidth(minuteDragWidth);
if (ImGui::DragInt("##timerEndMinutes", &timerEndMinutes, .2f, 0, 59, minuteInputFormat, ImGuiInputTextFlags_CharsDecimal))
{
SetTimerDuration(appData, timerEndHours, timerEndMinutes, timeInfo);
setTimerDuration(appData, timerEndHours, timerEndMinutes, timeInfo);
}
ImGui::PopItemWidth();
dropShadow();
}
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();
@@ -572,13 +548,15 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
ImGui::OpenPopup(SETTINGS_POPUP_NAME);
}
time_t today = getDayStartOf(std::time(nullptr));
// Previous days
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);
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);
@@ -586,37 +564,39 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
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 = [&](time_t t) { return getDayStartOf(t) == selectedDay; };
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<time_t>{} });
appData.settings.tasks.insert({ taskName, std::vector<year_month_day>{} });
}
std::vector<time_t>& taskDates = appData.settings.tasks[taskName];
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)
{
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);
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)
@@ -628,7 +608,7 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
ImVec2 beforeCheckbox = ImGui::GetCursorScreenPos();
std::string checkboxText = std::format("{}##taskcheck{}", taskName.c_str(), taskName.c_str());
if (ImGui::Checkbox(checkboxText.c_str(), &taskDone))
if (checkboxWithDropShadow(checkboxText.c_str(), &taskDone))
{
if (taskDone)
{
@@ -655,6 +635,7 @@ ImVec2 checklistWindow(ApplicationData& appData, const char* title)
}
}
// Popup
if (ImGui::BeginPopup(SETTINGS_POPUP_NAME))
{
ImGui::Text("Task Highlight Duration");
@@ -757,7 +738,7 @@ ImVec2 baseStationWindow(ApplicationData& appData)
ImGui::OpenPopup(SETTINGS_POPUP_NAME);
}
if (ImGui::Button("Wake"))
if (dropButton("Wake"))
{
std::string params{ " on" };
for (std::string& mac : appData.settings.baseStationMacAdresses)
@@ -768,7 +749,7 @@ ImVec2 baseStationWindow(ApplicationData& appData)
startBaseStationProc(appData, params.c_str(), appData.settings.baseStationShowConsole ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW);
}
ImGui::SameLine();
if (ImGui::Button("Shutdown"))
if (dropButton("Shutdown"))
{
std::string params{ " off" };
for (std::string& mac : appData.settings.baseStationMacAdresses)
@@ -833,30 +814,34 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& dev
ImGui::TableNextRow();
ImGui::TableNextColumn();
float yPosTemp = ImGui::GetCursorPosY();
const float yPosOffset = 4.f;
// Device Name
float yPosTemp = ImGui::GetCursorPosY();
ImGui::SetCursorPosY(yPosTemp + 4.f);
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::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::SetCursorPosY(yPosTemp + yPosOffset);
bool isDeviceMuted = isMuted(dev.volumeInterface);
if (ImGui::Button(isDeviceMuted ? ICON_VOLUME_MUTE_FILL : ICON_VOLUME_UP_FILL))
if (dropButton(isDeviceMuted ? ICON_VOLUME_MUTE_FILL : ICON_VOLUME_UP_FILL, {}, 2.f, true))
{
setMuted(dev.volumeInterface, !isDeviceMuted);
}
ImGui::PopStyleColor();
ImGui::PopID();
ImGui::SameLine(0, 2);
ImGui::SameLine();
ImGui::SetCursorPosY(startCursorY);
// Meter
static std::array<float, 2> meterValues{};
@@ -910,7 +895,7 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& dev
if (dev.isDefaultConsole)
{
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + getButtonWidth(ICON_MUSIC_2_FILL) / 2.f - circleSize);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 4.f);
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))
@@ -925,7 +910,7 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector<AudioDevice>& dev
if (dev.isDefaultCommunication)
{
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + getButtonWidth(ICON_PHONE_FILL) / 2.f - circleSize);
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 4.f);
ImGui::SetCursorPosY(yPosTemp + yPosOffset);
drawCircle(circleSize, IM_COL32(222, 50, 50, 255));
}
if (customButton("bn_c_", deviceIdUtf8.c_str(), ICON_PHONE_FILL, !dev.isDefaultCommunication))