diff --git a/AsuroTool/ApplicationData.h b/AsuroTool/ApplicationData.h index 3bc7737..99e4b65 100644 --- a/AsuroTool/ApplicationData.h +++ b/AsuroTool/ApplicationData.h @@ -51,7 +51,7 @@ public: bool showDisabledDevices = false; bool showChecklistExtras = false; std::vector taskNames = {}; - std::unordered_map> tasks = {}; + std::unordered_map> tasks = {}; std::vector baseStationMacAdresses = {}; bool baseStationShowConsole; float timerDuration = 5.f * 60.f; @@ -80,7 +80,7 @@ public: int checklistHighlightDurationDays = 3; std::shared_ptr audioData = std::make_shared(); HoverTargetType hoverTargetType = HoverTargetType::HOVER_TARGET_NONE; - time_t hoverTargetDay = 0; + std::chrono::year_month_day hoverTargetDay = {}; TimerData timerData{}; std::mutex timerMutex{}; bool lighthouseProcActive = false; diff --git a/AsuroTool/AsuroTool.cpp b/AsuroTool/AsuroTool.cpp index 8e9c1ed..f5a44ef 100644 --- a/AsuroTool/AsuroTool.cpp +++ b/AsuroTool/AsuroTool.cpp @@ -21,8 +21,10 @@ #include #include #include -#include +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::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> 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 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 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(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::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{} }); + appData.settings.tasks.insert({ taskName, std::vector{} }); } - std::vector& taskDates = appData.settings.tasks[taskName]; + 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); + year_month_day mostRecentDoneDate = *std::max_element(taskDates.begin(), taskDates.end()); + std::chrono::days timeDiffDays = std::chrono::round(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& 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 meterValues{}; @@ -910,7 +895,7 @@ ImVec2 audioDeviceWindow(ApplicationData& appData, std::vector& 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& 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)) diff --git a/AsuroTool/AsuroTool.h b/AsuroTool/AsuroTool.h index 146cb31..a616880 100644 --- a/AsuroTool/AsuroTool.h +++ b/AsuroTool/AsuroTool.h @@ -4,11 +4,14 @@ #include "ApplicationData.h" #include +#include #define DAY_LINE_WIDTH 3.f #define DAY_LINE_OFFSET 5.f #define DAY_OUTLINE_SIZE 3.f +typedef std::chrono::time_point TimePoint; + void init(DrawData& drawData, ApplicationData& customData); void draw(DrawData& drawData, ApplicationData& customData); void cleanup(DrawData& drawData, ApplicationData& appData); diff --git a/AsuroTool/Settings.cpp b/AsuroTool/Settings.cpp index 5d66ede..26cc65f 100644 --- a/AsuroTool/Settings.cpp +++ b/AsuroTool/Settings.cpp @@ -56,16 +56,19 @@ void settingsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, void* en std::string task{}; task.resize(MAX_TASK_NAME_LENGTH); - time_t dayTimestamp; - if (sscanf_s(line, "task=%lld %s", &dayTimestamp, &task[0], MAX_TASK_NAME_LENGTH)) + int year; + unsigned int month; + unsigned int day; + if (sscanf_s(line, "task=%d-%u-%u %s", &year, &month, &day, &task[0], MAX_TASK_NAME_LENGTH)) { + std::chrono::year_month_day date = std::chrono::year_month_day{ std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day} }; if (settings->tasks.contains(task)) { - settings->tasks[task].push_back(dayTimestamp); + settings->tasks[task].push_back(date); } else { - settings->tasks.insert({ task, std::vector{ dayTimestamp } }); + settings->tasks.insert({ task, std::vector{ date } }); } } @@ -117,7 +120,7 @@ void settingsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTex { for (auto& date : task.second) { - outBuf->appendf("task=%lld %s\n", date, task.first.c_str()); + outBuf->appendf("task=%d-%u-%u %s\n", int{ date.year() }, unsigned int{ date.month() }, unsigned int{ date.day() }, task.first.c_str()); } } @@ -176,11 +179,14 @@ void setAutostart(bool newValue) void loadUiStyle() { ImGuiStyle& style = ImGui::GetStyle(); - style.WindowPadding = { 6.f, 8.f }; + style.WindowPadding = { 6.f, 12.f }; + style.WindowBorderSize = 0.f; + style.FramePadding = { 8.f, 4.f }; - style.ItemSpacing = { 4.f, 4.f }; - style.WindowRounding = 4.f; + + style.ItemSpacing = { 6.f, 4.f }; style.GrabRounding = 2.f; + style.PopupBorderSize = 4.f; ImVec4* colors = style.Colors; colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); @@ -188,7 +194,7 @@ void loadUiStyle() 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_Border] = ImVec4(0.14f, 0.10f, 0.13f, 0.99f); 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); @@ -219,7 +225,7 @@ void loadUiStyle() 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_TabUnfocused] = ImVec4(0.33f, 0.21f, 0.28f, 1.00f); 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); diff --git a/ImguiBase/ImguiBase.cpp b/ImguiBase/ImguiBase.cpp index 8604f5d..c5b2f1f 100644 --- a/ImguiBase/ImguiBase.cpp +++ b/ImguiBase/ImguiBase.cpp @@ -1,6 +1,9 @@ #include "ImguiBase.h" +// hacky inlcude for custom button alignment +#include "imgui_internal.h" + #include "imgui_impl_glfw.h" #include "imgui_impl_vulkan.h" @@ -568,4 +571,88 @@ ImVec2 getWindowSize(GLFWwindow* window) int window_height; glfwGetWindowSize(window, &window_width, &window_height); return ImVec2(window_width, window_height); -} \ No newline at end of file +} + +void dropShadow() +{ + dropShadow(DEFAULT_DROP_SHADOW_SIZE, ImGui::GetStyle().Colors[DEFAULT_DROP_SHADOW_COLOR_ID]); +} + +void dropShadow(const float shadowSize, const ImColor& color) +{ + ImVec2 lastMin = ImGui::GetItemRectMin(); + ImVec2 lastMax = ImGui::GetItemRectMax(); + renderDropShadow(shadowSize, lastMin, lastMax, color); +} + +void renderDropShadow(const float shadowSize, const ImVec2& min, const ImVec2& max, const ImColor& color) +{ + ImDrawList* windowDrawList = ImGui::GetWindowDrawList(); + windowDrawList->AddQuadFilled( + { min.x, max.y }, + { max.x, max.y }, + { max.x + shadowSize, max.y + shadowSize }, + { min.x + shadowSize, max.y + shadowSize }, + color); + windowDrawList->AddQuadFilled( + { max.x, min.y }, + { max.x + shadowSize, min.y + shadowSize }, + { max.x + shadowSize, max.y + shadowSize }, + { max.x, max.y }, + color); +} + +bool dropButton(const char* label, const ImVec2& size, float shadowSize, bool smallButton) +{ + ImGuiStyle style = ImGui::GetStyle(); + ImVec2 backupPadding = style.FramePadding; + if (smallButton) style.FramePadding.y = 0.f; + + const ImVec2 labelSize = ImGui::CalcTextSize(label, NULL, true); + ImVec2 buttonMin = ImGui::GetCursorScreenPos(); + + ImVec2 buttonSize = size; + if (buttonSize.x <= 0.f) buttonSize.x = labelSize.x + style.FramePadding.x * 2.0f; + if (buttonSize.y <= 0.f) buttonSize.y = labelSize.y + style.FramePadding.y * 2.0f; + + float baseLineOffset = ImGui::GetCurrentWindowRead()->DC.CurrLineTextBaseOffset; + if (smallButton && style.FramePadding.y < baseLineOffset) buttonMin.y += baseLineOffset - style.FramePadding.y; + + bool result = ImGui::InvisibleButton(label, buttonSize); + bool held = ImGui::IsItemActive(); + bool hovered = ImGui::IsItemHovered(); + const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + + float maxOffset = std::fmaxf(shadowSize - 1.f, 0.f); + ImVec2 offset = held ? ImVec2{ maxOffset, maxOffset } : ImVec2{ 0.f, 0.f }; + ImVec2 rectMin = buttonMin + offset; + ImVec2 rectMax = buttonMin + buttonSize + offset; + + ImGui::RenderFrame(rectMin, rectMax, col, true, style.FrameRounding); + ImGui::RenderTextClipped(rectMin, rectMax, label, NULL, &labelSize, style.ButtonTextAlign); + renderDropShadow(held ? 1.f : shadowSize, rectMin, rectMax, ImGui::GetStyle().Colors[DEFAULT_DROP_SHADOW_COLOR_ID]); + + style.FramePadding = backupPadding; + return result; +} + +bool checkboxWithDropShadow(const char* label, bool* v, const float shadowSize) +{ + ImGuiStyle& style = ImGui::GetStyle(); + float oldSpacingX = style.ItemInnerSpacing.x; + style.ItemInnerSpacing.x += shadowSize; + bool result = ImGui::Checkbox(label, v); + style.ItemInnerSpacing.x = oldSpacingX; + + ImVec2 lastMin = ImGui::GetItemRectMin(); + renderDropShadow(shadowSize, lastMin, lastMin + ImVec2{ ImGui::GetFrameHeight(), ImGui::GetFrameHeight() }, ImGui::GetStyle().Colors[DEFAULT_DROP_SHADOW_COLOR_ID]); + + return result; +} + +ImVec2 operator +(const ImVec2& a, const ImVec2& b) { return ImVec2{ a.x + b.x, a.y + b.y }; } +ImVec2 operator +(const ImVec2& a, const float s) { return ImVec2{ a.x + s, a.y + s }; } +ImVec2 operator -(const ImVec2& a, const ImVec2& b) { return ImVec2{ a.x - b.x, a.y - b.y }; } +ImVec2 operator -(const ImVec2& a, const float s) { return ImVec2{ a.x - s, a.y - s }; } +ImVec2 operator *(const ImVec2& a, float s) { return ImVec2{ a.x * s, a.y * s }; } +ImVec2 operator /(const ImVec2& a, float s) { return ImVec2{ a.x / s, a.y / s }; } diff --git a/ImguiBase/ImguiBase.h b/ImguiBase/ImguiBase.h index 42d2e17..bce5492 100644 --- a/ImguiBase/ImguiBase.h +++ b/ImguiBase/ImguiBase.h @@ -57,3 +57,29 @@ public: int startImgui(ImGuiCallbacks& callbacks, const char* title, int windowWidth, int windowHeight); ImVec2 getWindowSize(GLFWwindow* window); + +constexpr float DEFAULT_DROP_SHADOW_SIZE = 3.f; +constexpr ImGuiCol DEFAULT_DROP_SHADOW_COLOR_ID = ImGuiCol_TabUnfocused; + +void dropShadow(); +void dropShadow(const float shadowSize, const ImColor& color); +void renderDropShadow(const float shadowSize, const ImVec2& min, const ImVec2& max, const ImColor& color); + +bool dropButton(const char* label, const ImVec2& size = ImVec2(0, 0), float shadowSize = DEFAULT_DROP_SHADOW_SIZE, bool smallButton = false); +bool checkboxWithDropShadow(const char* label, bool* v, const float shadowSize = DEFAULT_DROP_SHADOW_SIZE); + +template +float calcInputWidth(const char* formatStr, T value) +{ + constexpr size_t MAX_BUFFER_SIZE = 256; + char formatStrBuffer[MAX_BUFFER_SIZE]; + snprintf(formatStrBuffer, MAX_BUFFER_SIZE, formatStr, value); + return ImGui::CalcTextSize(formatStrBuffer).x + ImGui::GetStyle().FramePadding.x * 2.f; +} + +ImVec2 operator +(const ImVec2& a, const ImVec2& b); +ImVec2 operator +(const ImVec2& a, const float s); +ImVec2 operator -(const ImVec2& a, const ImVec2& b); +ImVec2 operator -(const ImVec2& a, const float s); +ImVec2 operator *(const ImVec2& a, float s); +ImVec2 operator /(const ImVec2& a, float s);