371 lines
10 KiB
C++
371 lines
10 KiB
C++
|
#include "ImGuiDatePicker.h"
|
|||
|
#include "imgui/imgui_internal.h"
|
|||
|
#include <cstdint>
|
|||
|
#include <chrono>
|
|||
|
#include <vector>
|
|||
|
#include <unordered_map>
|
|||
|
|
|||
|
|
|||
|
#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<std::string> 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<std::string> 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<int>(std::floor((13 * (month + 1)) / 5.0))
|
|||
|
// + year
|
|||
|
// + static_cast<int>(std::floor(year / 4.0))
|
|||
|
// - static_cast<int>(std::floor(year / 100.0))
|
|||
|
// + static_cast<int>(std::floor(year / 400.0))) % 7;
|
|||
|
//return static_cast<int>(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<int>(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<int> CalendarWeek(int week, int startDay, int daysInMonth)
|
|||
|
{
|
|||
|
std::vector<int> 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<std::string>& 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);
|
|||
|
}
|
|||
|
}
|