#include "ImGuiDatePicker.h" #include "imgui/imgui_internal.h" #include #include #include #include #define GET_DAY(timePoint) int(timePoint.tm_mday) #define GET_MONTH_UNSCALED(timePoint) timePoint.tm_mon #define GET_MONTH(timePoint) int(GET_MONTH_UNSCALED(timePoint) + 1) #define GET_YEAR(timePoint) int(timePoint.tm_year + 1900) #define SET_DAY(timePoint, day) timePoint.tm_mday = day #define SET_MONTH(timePoint, month) timePoint.tm_mon = month - 1 #define SET_YEAR(timePoint, year) timePoint.tm_year = year - 1900 namespace ImGui { static const std::vector MONTHS = { (const char*)u8"1月", (const char*)u8"2月", (const char*)u8"3月", (const char*)u8"4月", (const char*)u8"5月", (const char*)u8"6月", (const char*)u8"7月", (const char*)u8"8月", (const char*)u8"9月", (const char*)u8"10月", (const char*)u8"11月", (const char*)u8"12月" }; static const std::vector DAYS = { (const char*)u8"一", (const char*)u8"二", (const char*)u8"三", (const char*)u8"四", (const char*)u8"五", (const char*)u8"六", (const char*)u8"日" }; // Implements Zeller's Congruence to determine the day of week [1, 7](Mon-Sun) from the given parameters inline static int DayOfWeek(int dayOfMonth, int month, int year) noexcept { if (month < 3) { --year; month += 12; } int K = year % 100; int J = year / 100; return ((dayOfMonth + 13 * (month + 1) / 5 + K + K / 4 + J / 4 + 5 * J) % 7 + 5) % 7 + 1; //if ((month == 1) || (month == 2)) //{ // month += 12; // year -= 1; //} //int h = (dayOfMonth // + static_cast(std::floor((13 * (month + 1)) / 5.0)) // + year // + static_cast(std::floor(year / 4.0)) // - static_cast(std::floor(year / 100.0)) // + static_cast(std::floor(year / 400.0))) % 7; //return static_cast(std::floor(((h + 5) % 7) + 1)); } constexpr static bool IsLeapYear(int year) noexcept { return ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)); } inline static int NumDaysInMonth(int month, int year) { if (month < 1 || month > 12) return 0; if (month == 2 && IsLeapYear(year)) return 29; static constexpr int monthDayMap[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; return monthDayMap[month - 1]; } // Returns the number of calendar weeks spanned by month in the specified year inline static int NumWeeksInMonth(int month, int year) { int days = NumDaysInMonth(month, year); int firstDay = DayOfWeek(1, month, year); return static_cast(std::ceil((days + firstDay - 1) / 7.0)); } // Returns a vector containing dates as they would appear on the calendar for a given week. Populates 0 if there is no day. inline static std::vector CalendarWeek(int week, int startDay, int daysInMonth) { std::vector res(7, 0); int startOfWeek = 7 * (week - 1) + 2 - startDay; if (startOfWeek >= 1) res[0] = startOfWeek; for (int i = 1; i < 7; ++i) { int day = startOfWeek + i; if ((day >= 1) && (day <= daysInMonth)) res[i] = day; } return res; } constexpr static tm EncodeTimePoint(int dayOfMonth, int month, int year) noexcept { tm res{ }; res.tm_isdst = -1; SET_DAY(res, dayOfMonth); SET_MONTH(res, month); SET_YEAR(res, year); return res; } inline static std::string TimePointToLongString(const tm& timePoint) noexcept { std::string day = std::to_string(GET_DAY(timePoint)); std::string month = MONTHS[GET_MONTH_UNSCALED(timePoint)]; std::string year = std::to_string(GET_YEAR(timePoint)); return std::string(year + (const char*)u8"年 " + month + " " + day + (const char*)u8"日"); } inline static tm Today() noexcept { std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); std::time_t currentTime = std::chrono::system_clock::to_time_t(now); tm res; gmtime_s(&res, ¤tTime); return res; } inline static tm PreviousMonth(const tm& timePoint) noexcept { int month = GET_MONTH(timePoint); int year = GET_YEAR(timePoint); if (month == 1) { int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(12, --year)); return EncodeTimePoint(newDay, 12, year); } int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(--month, year)); return EncodeTimePoint(newDay, month, year); } inline static tm NextMonth(const tm& timePoint) noexcept { int month = GET_MONTH(timePoint); int year = GET_YEAR(timePoint); if (month == 12) { int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(1, ++year)); return EncodeTimePoint(newDay, 1, year); } int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(++month, year)); return EncodeTimePoint(newDay, month, year); } constexpr static bool IsMinDate(const tm& timePoint) noexcept { return (GET_MONTH(timePoint) == 1) && (GET_YEAR(timePoint) == IMGUI_DATEPICKER_YEAR_MIN); } constexpr static bool IsMaxDate(const tm& timePoint) noexcept { return (GET_MONTH(timePoint) == 12) && (GET_YEAR(timePoint) == IMGUI_DATEPICKER_YEAR_MAX); } static bool ComboBox(const std::string& label, const std::vector& items, int& v, ImFont* altFont) { bool res = false; ImGui::PushFont(altFont); if (ImGui::BeginCombo(label.c_str(), items[v].c_str())) { for (int i = 0; i < items.size(); ++i) { bool selected = (items[v] == items[i]); if (ImGui::Selectable(items[i].c_str(), &selected)) { v = i; res = true; } if (selected) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } ImGui::PopFont(); return res; } bool DatePickerEx(const std::string& label, tm& v, ImFont* altFont, bool clampToBorder, float itemSpacing) { bool res = false; ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; bool hiddenLabel = label.substr(0, 2) == "##"; std::string myLabel = (hiddenLabel) ? label.substr(2) : label; if (!hiddenLabel) { Text(label.c_str()); SameLine((itemSpacing == 0.0f) ? 0.0f : GetCursorPos().x + itemSpacing); } if (clampToBorder) SetNextItemWidth(GetContentRegionAvail().x); const ImVec2 windowSize = ImVec2(274.5f, 301.5f); SetNextWindowSize(windowSize); if (BeginCombo(std::string("##" + myLabel).c_str(), TimePointToLongString(v).c_str())) { int monthIdx = GET_MONTH_UNSCALED(v); int year = GET_YEAR(v); PushItemWidth((GetContentRegionAvail().x * 0.5f)); if (ComboBox("##CmbMonth_" + myLabel, MONTHS, monthIdx, altFont)) { SET_MONTH(v, monthIdx + 1); res = true; } PopItemWidth(); SameLine(); PushItemWidth(GetContentRegionAvail().x); if (InputInt(std::string("##IntYear_" + myLabel).c_str(), &year)) { SET_YEAR(v, std::min(std::max(IMGUI_DATEPICKER_YEAR_MIN, year), IMGUI_DATEPICKER_YEAR_MAX)); res = true; } PopItemWidth(); const float contentWidth = GetContentRegionAvail().x; const float arrowSize = GetFrameHeight(); const float arrowButtonWidth = arrowSize * 2.0f + GetStyle().ItemSpacing.x; const float bulletSize = arrowSize - 5.0f; const float bulletButtonWidth = bulletSize + GetStyle().ItemSpacing.x; const float combinedWidth = arrowButtonWidth + bulletButtonWidth; const float offset = (contentWidth - combinedWidth) * 0.5f; SetCursorPosX(GetCursorPosX() + offset); PushStyleVar(ImGuiStyleVar_FrameRounding, 20.0f); //PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); //PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); BeginDisabled(IsMinDate(v)); if (ArrowButtonEx(std::string("##ArrowLeft_" + myLabel).c_str(), ImGuiDir_Left, ImVec2(arrowSize, arrowSize))) { v = PreviousMonth(v); res = true; } EndDisabled(); //PopStyleColor(2); SameLine(); //PushStyleColor(ImGuiCol_Button, GetStyleColorVec4(ImGuiCol_Text)); SetCursorPosY(GetCursorPosY() + 2.0f); if (ButtonEx(std::string("##ArrowMid_" + myLabel).c_str(), ImVec2(bulletSize, bulletSize))) { v = Today(); res = true; CloseCurrentPopup(); } //PopStyleColor(); SameLine(); //PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); //PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); BeginDisabled(IsMaxDate(v)); if (ArrowButtonEx(std::string("##ArrowRight_" + myLabel).c_str(), ImGuiDir_Right, ImVec2(arrowSize, arrowSize))) { v = NextMonth(v); res = true; } EndDisabled(); //PopStyleColor(2); PopStyleVar(); constexpr ImGuiTableFlags TABLE_FLAGS = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY; if (BeginTable(std::string("##Table_" + myLabel).c_str(), 7, TABLE_FLAGS, GetContentRegionAvail())) { for (const auto& day : DAYS) TableSetupColumn(day.c_str(), ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderWidth, 30.0f); //PushStyleColor(ImGuiCol_HeaderHovered, GetStyleColorVec4(ImGuiCol_TableHeaderBg)); //PushStyleColor(ImGuiCol_HeaderActive, GetStyleColorVec4(ImGuiCol_TableHeaderBg)); PushFont(altFont); TableHeadersRow(); //PopStyleColor(); PopFont(); TableNextRow(); TableSetColumnIndex(0); int month = monthIdx + 1; int firstDayOfMonth = DayOfWeek(1, month, year); int numDaysInMonth = NumDaysInMonth(month, year); int numWeeksInMonth = NumWeeksInMonth(month, year); for (int i = 1; i <= numWeeksInMonth; ++i) { for (const auto& day : CalendarWeek(i, firstDayOfMonth, numDaysInMonth)) { if (day != 0) { PushStyleVar(ImGuiStyleVar_FrameRounding, 20.0f); const bool selected = day == GET_DAY(v); if (!selected) { //PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); //PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); } if (Button(std::to_string(day).c_str(), ImVec2(GetContentRegionAvail().x, GetTextLineHeightWithSpacing() + 5.0f))) { v = EncodeTimePoint(day, month, year); res = true; CloseCurrentPopup(); } //if (!selected) // PopStyleColor(2); PopStyleVar(); } if (day != numDaysInMonth) TableNextColumn(); } } EndTable(); } EndCombo(); } return res; } bool DatePicker(const std::string& label, tm& v, bool clampToBorder, float itemSpacing) { return DatePickerEx(label, v, nullptr, clampToBorder, itemSpacing); } }