├── ImGuiDatePicker.cpp ├── ImGuiDatePicker.hpp ├── Images ├── AltFontDemoImage.png ├── DemoGif.gif └── DemoImage.png ├── LICENSE └── README.md /ImGuiDatePicker.cpp: -------------------------------------------------------------------------------- 1 | #include "ImGuiDatePicker.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | #define GET_DAY(timePoint) int(timePoint.tm_mday) 10 | #define GET_MONTH_UNSCALED(timePoint) timePoint.tm_mon 11 | #define GET_MONTH(timePoint) int(GET_MONTH_UNSCALED(timePoint) + 1) 12 | #define GET_YEAR(timePoint) int(timePoint.tm_year + 1900) 13 | 14 | #define SET_DAY(timePoint, day) timePoint.tm_mday = day 15 | #define SET_MONTH(timePoint, month) timePoint.tm_mon = month - 1 16 | #define SET_YEAR(timePoint, year) timePoint.tm_year = year - 1900 17 | 18 | namespace ImGui 19 | { 20 | static const std::vector MONTHS = 21 | { 22 | "January", 23 | "February", 24 | "March", 25 | "April", 26 | "May", 27 | "June", 28 | "July", 29 | "August", 30 | "September", 31 | "October", 32 | "November", 33 | "December" 34 | }; 35 | 36 | static const std::vector DAYS = 37 | { 38 | "Mo", 39 | "Tu", 40 | "We", 41 | "Th", 42 | "Fr", 43 | "Sa", 44 | "Su" 45 | }; 46 | 47 | // Implements Zeller's Congruence to determine the day of week [1, 7](Mon-Sun) from the given parameters 48 | inline static int DayOfWeek(int dayOfMonth, int month, int year) noexcept 49 | { 50 | if ((month == 1) || (month == 2)) 51 | { 52 | month += 12; 53 | year -= 1; 54 | } 55 | 56 | int h = (dayOfMonth 57 | + static_cast(std::floor((13 * (month + 1)) / 5.0)) 58 | + year 59 | + static_cast(std::floor(year / 4.0)) 60 | - static_cast(std::floor(year / 100.0)) 61 | + static_cast(std::floor(year / 400.0))) % 7; 62 | 63 | return static_cast(std::floor(((h + 5) % 7) + 1)); 64 | } 65 | 66 | constexpr static bool IsLeapYear(int year) noexcept 67 | { 68 | if ((year % 400) == 0) 69 | return true; 70 | 71 | if ((year % 4 == 0) && ((year % 100) != 0)) 72 | return true; 73 | 74 | return false; 75 | } 76 | 77 | inline static int NumDaysInMonth(int month, int year) 78 | { 79 | if (month == 2) 80 | return IsLeapYear(year) ? 29 : 28; 81 | 82 | // Month index paired to the number of days in that month excluding February 83 | static const std::unordered_map monthDayMap = 84 | { 85 | { 1, 31 }, 86 | { 3, 31 }, 87 | { 4, 30 }, 88 | { 5, 31 }, 89 | { 6, 30 }, 90 | { 7, 31 }, 91 | { 8, 31 }, 92 | { 9, 30 }, 93 | { 10, 31 }, 94 | { 11, 30 }, 95 | { 12, 31 } 96 | }; 97 | 98 | return monthDayMap.at(month); 99 | } 100 | 101 | // Returns the number of calendar weeks spanned by month in the specified year 102 | inline static int NumWeeksInMonth(int month, int year) 103 | { 104 | int days = NumDaysInMonth(month, year); 105 | int firstDay = DayOfWeek(1, month, year); 106 | 107 | return static_cast(std::ceil((days + firstDay - 1) / 7.0)); 108 | } 109 | 110 | // Returns a vector containing dates as they would appear on the calendar for a given week. Populates 0 if there is no day. 111 | inline static std::vector CalendarWeek(int week, int startDay, int daysInMonth) 112 | { 113 | std::vector res(7, 0); 114 | int startOfWeek = 7 * (week - 1) + 2 - startDay; 115 | 116 | if (startOfWeek >= 1) 117 | res[0] = startOfWeek; 118 | 119 | for (int i = 1; i < 7; ++i) 120 | { 121 | int day = startOfWeek + i; 122 | if ((day >= 1) && (day <= daysInMonth)) 123 | res[i] = day; 124 | } 125 | 126 | return res; 127 | } 128 | 129 | constexpr static tm EncodeTimePoint(int dayOfMonth, int month, int year) noexcept 130 | { 131 | tm res{ }; 132 | res.tm_isdst = -1; 133 | SET_DAY(res, dayOfMonth); 134 | SET_MONTH(res, month); 135 | SET_YEAR(res, year); 136 | 137 | return res; 138 | } 139 | 140 | inline static std::string TimePointToLongString(const tm& timePoint) noexcept 141 | { 142 | std::string day = std::to_string(GET_DAY(timePoint)); 143 | std::string month = MONTHS[GET_MONTH_UNSCALED(timePoint)]; 144 | std::string year = std::to_string(GET_YEAR(timePoint)); 145 | 146 | return std::string(day + " " + month + " " + year); 147 | } 148 | 149 | inline static tm Today() noexcept 150 | { 151 | std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); 152 | std::time_t currentTime = std::chrono::system_clock::to_time_t(now); 153 | 154 | tm res; 155 | gmtime_s(&res, ¤tTime); 156 | 157 | return res; 158 | } 159 | 160 | inline static tm PreviousMonth(const tm& timePoint) noexcept 161 | { 162 | int month = GET_MONTH(timePoint); 163 | int year = GET_YEAR(timePoint); 164 | 165 | if (month == 1) 166 | { 167 | int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(12, --year)); 168 | return EncodeTimePoint(newDay, 12, year); 169 | } 170 | 171 | int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(--month, year)); 172 | return EncodeTimePoint(newDay, month, year); 173 | } 174 | 175 | inline static tm NextMonth(const tm& timePoint) noexcept 176 | { 177 | int month = GET_MONTH(timePoint); 178 | int year = GET_YEAR(timePoint); 179 | 180 | if (month == 12) 181 | { 182 | int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(1, ++year)); 183 | return EncodeTimePoint(newDay, 1, year); 184 | } 185 | 186 | int newDay = std::min(GET_DAY(timePoint), NumDaysInMonth(++month, year)); 187 | return EncodeTimePoint(newDay, month, year); 188 | } 189 | 190 | constexpr static bool IsMinDate(const tm& timePoint) noexcept 191 | { 192 | return (GET_MONTH(timePoint) == 1) && (GET_YEAR(timePoint) == IMGUI_DATEPICKER_YEAR_MIN); 193 | } 194 | 195 | constexpr static bool IsMaxDate(const tm& timePoint) noexcept 196 | { 197 | return (GET_MONTH(timePoint) == 12) && (GET_YEAR(timePoint) == IMGUI_DATEPICKER_YEAR_MAX); 198 | } 199 | 200 | static bool ComboBox(const std::string& label, const std::vector& items, int& v, ImFont* altFont) 201 | { 202 | bool res = false; 203 | 204 | ImGui::PushFont(altFont); 205 | if (ImGui::BeginCombo(label.c_str(), items[v].c_str())) 206 | { 207 | for (int i = 0; i < items.size(); ++i) 208 | { 209 | bool selected = (items[v] == items[i]); 210 | if (ImGui::Selectable(items[i].c_str(), &selected)) 211 | { 212 | v = i; 213 | res = true; 214 | } 215 | 216 | if (selected) 217 | ImGui::SetItemDefaultFocus(); 218 | } 219 | 220 | ImGui::EndCombo(); 221 | } 222 | 223 | ImGui::PopFont(); 224 | return res; 225 | } 226 | 227 | bool DatePickerEx(const std::string& label, tm& v, ImFont* altFont, bool clampToBorder, float itemSpacing) 228 | { 229 | bool res = false; 230 | 231 | ImGuiWindow* window = GetCurrentWindow(); 232 | if (window->SkipItems) 233 | return false; 234 | 235 | bool hiddenLabel = label.substr(0, 2) == "##"; 236 | std::string myLabel = (hiddenLabel) ? label.substr(2) : label; 237 | 238 | if (!hiddenLabel) 239 | { 240 | Text("%s", label.c_str()); 241 | SameLine((itemSpacing == 0.0f) ? 0.0f : GetCursorPos().x + itemSpacing); 242 | } 243 | 244 | if (clampToBorder) 245 | SetNextItemWidth(GetContentRegionAvail().x); 246 | 247 | const ImVec2 windowSize = ImVec2(274.5f, 301.5f); 248 | SetNextWindowSize(windowSize); 249 | 250 | if (BeginCombo(std::string("##" + myLabel).c_str(), TimePointToLongString(v).c_str())) 251 | { 252 | int monthIdx = GET_MONTH_UNSCALED(v); 253 | int year = GET_YEAR(v); 254 | 255 | PushItemWidth((GetContentRegionAvail().x * 0.5f)); 256 | 257 | if (ComboBox("##CmbMonth_" + myLabel, MONTHS, monthIdx, altFont)) 258 | { 259 | SET_MONTH(v, monthIdx + 1); 260 | res = true; 261 | } 262 | 263 | PopItemWidth(); 264 | SameLine(); 265 | PushItemWidth(GetContentRegionAvail().x); 266 | 267 | if (InputInt(std::string("##IntYear_" + myLabel).c_str(), &year)) 268 | { 269 | SET_YEAR(v, std::min(std::max(IMGUI_DATEPICKER_YEAR_MIN, year), IMGUI_DATEPICKER_YEAR_MAX)); 270 | res = true; 271 | } 272 | 273 | PopItemWidth(); 274 | 275 | const float contentWidth = GetContentRegionAvail().x; 276 | const float arrowSize = GetFrameHeight(); 277 | const float arrowButtonWidth = arrowSize * 2.0f + GetStyle().ItemSpacing.x; 278 | const float bulletSize = arrowSize - 5.0f; 279 | const float bulletButtonWidth = bulletSize + GetStyle().ItemSpacing.x; 280 | const float combinedWidth = arrowButtonWidth + bulletButtonWidth; 281 | const float offset = (contentWidth - combinedWidth) * 0.5f; 282 | 283 | SetCursorPosX(GetCursorPosX() + offset); 284 | PushStyleVar(ImGuiStyleVar_FrameRounding, 20.0f); 285 | PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); 286 | PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); 287 | BeginDisabled(IsMinDate(v)); 288 | 289 | if (ArrowButtonEx(std::string("##ArrowLeft_" + myLabel).c_str(), ImGuiDir_Left, ImVec2(arrowSize, arrowSize))) 290 | { 291 | v = PreviousMonth(v); 292 | res = true; 293 | } 294 | 295 | EndDisabled(); 296 | PopStyleColor(2); 297 | SameLine(); 298 | PushStyleColor(ImGuiCol_Button, GetStyleColorVec4(ImGuiCol_Text)); 299 | SetCursorPosY(GetCursorPosY() + 2.0f); 300 | 301 | if (ButtonEx(std::string("##ArrowMid_" + myLabel).c_str(), ImVec2(bulletSize, bulletSize))) 302 | { 303 | v = Today(); 304 | res = true; 305 | CloseCurrentPopup(); 306 | } 307 | 308 | PopStyleColor(); 309 | SameLine(); 310 | PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); 311 | PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); 312 | BeginDisabled(IsMaxDate(v)); 313 | 314 | if (ArrowButtonEx(std::string("##ArrowRight_" + myLabel).c_str(), ImGuiDir_Right, ImVec2(arrowSize, arrowSize))) 315 | { 316 | v = NextMonth(v); 317 | res = true; 318 | } 319 | 320 | EndDisabled(); 321 | PopStyleColor(2); 322 | PopStyleVar(); 323 | 324 | constexpr ImGuiTableFlags TABLE_FLAGS = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingFixedFit | 325 | ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY; 326 | 327 | if (BeginTable(std::string("##Table_" + myLabel).c_str(), 7, TABLE_FLAGS, GetContentRegionAvail())) 328 | { 329 | for (const auto& day : DAYS) 330 | TableSetupColumn(day.c_str(), ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoHeaderWidth, 30.0f); 331 | 332 | PushStyleColor(ImGuiCol_HeaderHovered, GetStyleColorVec4(ImGuiCol_TableHeaderBg)); 333 | PushStyleColor(ImGuiCol_HeaderActive, GetStyleColorVec4(ImGuiCol_TableHeaderBg)); 334 | PushFont(altFont); 335 | TableHeadersRow(); 336 | PopStyleColor(2); 337 | PopFont(); 338 | 339 | TableNextRow(); 340 | TableSetColumnIndex(0); 341 | 342 | int month = monthIdx + 1; 343 | int firstDayOfMonth = DayOfWeek(1, month, year); 344 | int numDaysInMonth = NumDaysInMonth(month, year); 345 | int numWeeksInMonth = NumWeeksInMonth(month, year); 346 | 347 | for (int i = 1; i <= numWeeksInMonth; ++i) 348 | { 349 | for (const auto& day : CalendarWeek(i, firstDayOfMonth, numDaysInMonth)) 350 | { 351 | if (day != 0) 352 | { 353 | PushStyleVar(ImGuiStyleVar_FrameRounding, 20.0f); 354 | 355 | const bool selected = day == GET_DAY(v); 356 | if (!selected) 357 | { 358 | PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); 359 | PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); 360 | } 361 | 362 | if (Button(std::to_string(day).c_str(), ImVec2(GetContentRegionAvail().x, GetTextLineHeightWithSpacing() + 5.0f))) 363 | { 364 | v = EncodeTimePoint(day, month, year); 365 | res = true; 366 | CloseCurrentPopup(); 367 | } 368 | 369 | if (!selected) 370 | PopStyleColor(2); 371 | 372 | PopStyleVar(); 373 | } 374 | 375 | if (day != numDaysInMonth) 376 | TableNextColumn(); 377 | } 378 | } 379 | 380 | EndTable(); 381 | } 382 | 383 | EndCombo(); 384 | } 385 | 386 | return res; 387 | } 388 | 389 | bool DatePicker(const std::string& label, tm& v, bool clampToBorder, float itemSpacing) 390 | { 391 | return DatePickerEx(label, v, nullptr, clampToBorder, itemSpacing); 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /ImGuiDatePicker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | 6 | #ifndef IMGUI_DATEPICKER_YEAR_MIN 7 | #define IMGUI_DATEPICKER_YEAR_MIN 1900 8 | #endif // !IMGUI_DATEPICKER_YEAR_MIN 9 | 10 | #ifndef IMGUI_DATEPICKER_YEAR_MAX 11 | #define IMGUI_DATEPICKER_YEAR_MAX 3000 12 | #endif // !IMGUI_DATEPICKER_YEAR_MAX 13 | 14 | namespace ImGui 15 | { 16 | IMGUI_API bool DatePickerEx(const std::string& label, tm& v, ImFont* altFont, bool clampToBorder = false, float itemSpacing = 130.0f); 17 | 18 | IMGUI_API bool DatePicker(const std::string& label, tm& v, bool clampToBorder = false, float itemSpacing = 130.0f); 19 | } 20 | -------------------------------------------------------------------------------- /Images/AltFontDemoImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DnA-IntRicate/ImGuiDatePicker/ca5e3a20f945c221e76e6bde3b09101acf589b5c/Images/AltFontDemoImage.png -------------------------------------------------------------------------------- /Images/DemoGif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DnA-IntRicate/ImGuiDatePicker/ca5e3a20f945c221e76e6bde3b09101acf589b5c/Images/DemoGif.gif -------------------------------------------------------------------------------- /Images/DemoImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DnA-IntRicate/ImGuiDatePicker/ca5e3a20f945c221e76e6bde3b09101acf589b5c/Images/DemoImage.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Adam Foflonker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImGuiDatePicker 2 | ImGuiDatePicker is a custom calendar-style date-picker widget for [Dear ImGui](https://github.com/ocornut/imgui) written in `C++20` that can easily be compiled alongside `ImGui` by adding the header and source files to your `ImGui` project. 3 | 4 | ![DemoGif](Images/DemoGif.gif) 5 | Font used: [Open Sans](https://fonts.google.com/specimen/Open+Sans) 6 | 7 | ImGuiDatePicker uses the `tm` struct for working with dates: 8 | ``` C++ 9 | struct tm 10 | { 11 | int tm_sec; // seconds after the minute - [0, 60] including leap second 12 | int tm_min; // minutes after the hour - [0, 59] 13 | int tm_hour; // hours since midnight - [0, 23] 14 | int tm_mday; // day of the month - [1, 31] 15 | int tm_mon; // months since January - [0, 11] 16 | int tm_year; // years since 1900 17 | int tm_wday; // days since Sunday - [0, 6] 18 | int tm_yday; // days since January 1 - [0, 365] 19 | int tm_isdst; // daylight savings time flag 20 | }; 21 | ``` 22 | ## Including 23 | `IMGUI_DATEPICKER_YEAR_MIN` and `IMGUI_DATEPICKER_YEAR_MAX` should be defined before including [ImGuiDatePicker.hpp](ImGuiDatePicker.hpp). If they are not defined, they will default to `1900` and `3000` respectively. 24 | ``` C++ 25 | // Define the lowest year that the picker can select. In this example, '1970' is the Unix epoch. 26 | #define IMGUI_DATEPICKER_YEAR_MIN 1970 27 | // Define the highest year that the picker can select. 28 | #define IMGUI_DATEPICKER_YEAR_MAX 3000 29 | #include 30 | ``` 31 | ## Usage 32 | ``` C++ 33 | // Get today's date and store it in a 'tm' struct named 't' 34 | std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); 35 | std::time_t currentTime = std::chrono::system_clock::to_time_t(now); 36 | tm t = *std::gmtime(¤tTime); 37 | 38 | // Use the picker 39 | if (ImGui::DatePicker("Date", t)) 40 | { 41 | // Perform some event whenever the date 't' is changed 42 | } 43 | ``` 44 | An `alt font` can also be supplied to the picker by calling `DatePickerEx`. This `alt font` will be used for the picker's days-of-the-week headers and for the month-picker combo box. 45 | ``` C++ 46 | ImFont* boldFont = GetBoldFontFromSomewhere(); 47 | tm t = GetTmFromSomewhere(); 48 | ImGui::DatePickerEx("Date", t, boldFont); 49 | ``` 50 | ![AltFontDemoImage](Images/AltFontDemoImage.png) 51 | 52 | ## License 53 | ImGuiDatePicker is licensed under the MIT License. See [LICENSE](LICENSE). 54 | 55 | ``` 56 | MIT License 57 | 58 | Copyright (c) 2024 Adam Foflonker 59 | 60 | Permission is hereby granted, free of charge, to any person obtaining a copy 61 | of this software and associated documentation files (the "Software"), to deal 62 | in the Software without restriction, including without limitation the rights 63 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 64 | copies of the Software, and to permit persons to whom the Software is 65 | furnished to do so, subject to the following conditions: 66 | 67 | The above copyright notice and this permission notice shall be included in all 68 | copies or substantial portions of the Software. 69 | 70 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 71 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 72 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 73 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 74 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 75 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 76 | SOFTWARE. 77 | ``` 78 | --------------------------------------------------------------------------------