Files
HellionChat/HellionChat/Util/DatePicker.cs
T
JonKazama-Hellion 699d4ede1d chore: housekeeping — linter & formatter setup
Add .prettierrc.json, .markdownlint.json, .yamllint.yaml, .gitattributes
Run CSharpier, Prettier and markdownlint across the entire codebase.
No logic changes — formatting, using order and line endings only.
2026-05-10 13:01:00 +02:00

372 lines
12 KiB
C#

using System.Globalization;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Utility;
using HellionChat.Resources;
namespace HellionChat.Util;
// From https://github.com/Flix01/imgui/blob/imgui_with_addons/addons/imguidatechooser/imguidatechooser.cpp
public static class DateWidget
{
private const int HeightInItems = 1 + 1 + 1 + 4;
private static readonly DateTime Sample = DateTime.UnixEpoch;
private static readonly Vector4 Transparent = new(1, 1, 1, 0);
private static readonly string[] DayNames =
[
Language.DateWidget_Day_Sun,
Language.DateWidget_Day_Mon,
Language.DateWidget_Day_Tue,
Language.DateWidget_Day_Wed,
Language.DateWidget_Day_Thu,
Language.DateWidget_Day_Fri,
Language.DateWidget_Day_Sat,
];
private static readonly string[] MonthNames =
[
Language.DateWidget_Month_January,
Language.DateWidget_Month_February,
Language.DateWidget_Month_March,
Language.DateWidget_Month_April,
Language.DateWidget_Month_May,
Language.DateWidget_Month_June,
Language.DateWidget_Month_July,
Language.DateWidget_Month_August,
Language.DateWidget_Month_September,
Language.DateWidget_Month_October,
Language.DateWidget_Month_November,
Language.DateWidget_Month_December,
];
private static readonly int[] NumDaysPerMonth =
[
31,
28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31,
];
private static float LongestMonthWidth;
private static readonly float[] MonthWidths = new float[12];
private static uint LastOpenComboId;
public static bool Validate(DateTime minimal, ref DateTime currentMin, ref DateTime currentMax)
{
var needsRefresh = false;
if (minimal > currentMin)
{
currentMin = minimal;
Plugin.Notification.AddNotification(
new Notification
{
Content = Language.DateWidget_InvalidDate.Format(minimal.ToShortDateString()),
Type = NotificationType.Warning,
Minimized = false,
}
);
needsRefresh = true;
}
else if (currentMin > currentMax)
{
currentMax = currentMin;
needsRefresh = true;
}
return needsRefresh;
}
public static void DatePickerWithInput(
string label,
int id,
ref string dateString,
ref DateTime date,
string format,
bool sameLine = false,
bool closeWhenMouseLeavesIt = true
)
{
if (sameLine)
ImGui.SameLine();
ImGui.SetNextItemWidth(
ImGui.CalcTextSize(Sample.ToString(format)).X + ImGui.GetStyle().ItemInnerSpacing.X * 2
);
if (
ImGui.InputTextWithHint(
$"##{label}Input",
format.ToUpper(),
ref dateString,
32,
ImGuiInputTextFlags.CallbackCompletion
)
)
{
if (
DateTime.TryParseExact(
dateString,
format,
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var tmp
)
)
date = tmp;
}
ImGui.SameLine(0, 3.0f * ImGuiHelpers.GlobalScale);
ImGuiUtil.IconButton(FontAwesomeIcon.Calendar, id.ToString());
if (ImGui.IsItemHovered())
ImGui.SetTooltip(Language.DatePicker_Tooltip);
if (DatePicker(label, ref date, closeWhenMouseLeavesIt))
dateString = date.ToString(format);
}
private static bool DatePicker(
string label,
ref DateTime dateOut,
bool closeWhenMouseLeavesIt,
string leftArrow = "",
string rightArrow = ""
)
{
using var mono = ImRaii.PushFont(UiBuilder.MonoFont);
if (LongestMonthWidth == 0.0f)
{
for (var i = 0; i < 12; i++)
{
var mw = ImGui.CalcTextSize(MonthNames[i]).X;
MonthWidths[i] = mw;
LongestMonthWidth = Math.Max(LongestMonthWidth, mw);
}
}
var id = ImGui.GetID(label);
var style = ImGui.GetStyle();
var arrowLeft = leftArrow.Length > 0 ? leftArrow : "<";
var arrowRight = rightArrow.Length > 0 ? rightArrow : ">";
var arrowLeftWidth = ImGui.CalcTextSize(arrowLeft).X;
var arrowRightWidth = ImGui.CalcTextSize(arrowRight).X;
var labelSize = ImGui.CalcTextSize(label, true, 0);
var widthRequiredByCalendar =
(2.0f * arrowLeftWidth)
+ (2.0f * arrowRightWidth)
+ LongestMonthWidth
+ ImGui.CalcTextSize("9999").X
+ (120.0f * ImGuiHelpers.GlobalScale);
var popupHeight =
((labelSize.Y + (2 * style.ItemSpacing.Y)) * HeightInItems)
+ (style.FramePadding.Y * 3);
var valueChanged = false;
ImGui.SetNextWindowSize(new Vector2(widthRequiredByCalendar, widthRequiredByCalendar));
ImGui.SetNextWindowSizeConstraints(
new Vector2(widthRequiredByCalendar, popupHeight + 40),
new Vector2(widthRequiredByCalendar, popupHeight + 40)
);
using var popupItem = ImRaii.ContextPopupItem(label, ImGuiPopupFlags.None);
if (!popupItem.Success)
return valueChanged;
if (ImGui.GetIO().MouseClicked[1])
{
// reset date when user right-clicks the date chooser header when the dialog is open
dateOut = DateTime.Now;
}
else if (LastOpenComboId != id)
{
LastOpenComboId = id;
if (dateOut.Year == 1)
dateOut = DateTime.Now;
}
using var windowPadding = ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, style.FramePadding);
using var buttonColor = ImRaii.PushColor(ImGuiCol.Button, Transparent);
ImGui.Spacing();
var yearString = $"{dateOut.Year}";
var yearPartWidth = arrowLeftWidth + arrowRightWidth + ImGui.CalcTextSize(yearString).X;
var oldWindowRounding = style.WindowRounding;
style.WindowRounding = 0;
using (ImRaii.PushId(1234))
{
if (ImGui.SmallButton(arrowLeft))
{
valueChanged = true;
dateOut = dateOut.AddMonths(-1);
}
ImGui.SameLine();
var color = ImGui.GetColorU32(style.Colors[(int)ImGuiCol.Text]);
var monthWidth = MonthWidths[dateOut.Month - 1];
var pos = ImGui.GetCursorScreenPos();
pos = pos with { X = pos.X + ((LongestMonthWidth - monthWidth) * 0.5f) };
ImGui.GetForegroundDrawList().AddText(pos, color, MonthNames[dateOut.Month - 1]);
ImGui.SameLine(0, LongestMonthWidth + style.ItemSpacing.X * 2);
if (ImGui.SmallButton(arrowRight))
{
valueChanged = true;
dateOut = dateOut.AddMonths(1);
}
}
ImGui.SameLine(
ImGui.GetWindowWidth()
- yearPartWidth
- style.WindowPadding.X
- style.ItemSpacing.X * 4.0f
);
using (ImRaii.PushId(1235))
{
if (ImGui.SmallButton(arrowLeft))
{
valueChanged = true;
dateOut = dateOut.AddYears(-1);
}
ImGui.SameLine();
ImGui.Text($"{dateOut.Year}");
ImGui.SameLine();
if (ImGui.SmallButton(arrowRight))
{
valueChanged = true;
dateOut = dateOut.AddYears(1);
}
}
ImGui.Spacing();
// This could be calculated only when needed (but I guess it's fast in any case...)
var maxDayOfCurMonth = NumDaysPerMonth[dateOut.Month - 1];
if (maxDayOfCurMonth == 28)
{
var year = dateOut.Year;
var bis = ((year % 4) == 0) && ((year % 100) != 0 || (year % 400) == 0);
if (bis)
maxDayOfCurMonth = 29;
}
using var buttonHovered = ImRaii.PushColor(
ImGuiCol.ButtonHovered,
ImGuiColors.DalamudOrange
);
using var buttonActive = ImRaii.PushColor(ImGuiCol.ButtonActive, ImGuiColors.DalamudYellow);
ImGui.Separator();
// Display items
var dayClicked = false;
var dayOfWeek = (int)new DateTime(dateOut.Year, dateOut.Month, 1).DayOfWeek;
for (var dw = 0; dw < 7; dw++)
{
using (ImRaii.Group())
{
using var textColor = ImRaii.PushColor(
ImGuiCol.Text,
CalculateTextColor(),
dw == 0
);
ImGui.Text($"{(dw == 0 ? "" : " ")}{DayNames[dw]}");
if (dw == 0)
ImGui.Separator();
else
ImGui.Spacing();
// Use dayOfWeek for spacing
var curDay = dw - dayOfWeek;
for (var row = 0; row < 7; row++)
{
var cday = curDay + (7 * row);
if (cday >= 0 && cday < maxDayOfCurMonth)
{
using var rowId = ImRaii.PushId(row * 10 + dw);
if (ImGui.SmallButton(string.Format(cday < 9 ? " {0}" : "{0}", cday + 1)))
{
ImGui.SetItemDefaultFocus();
dayClicked = true;
valueChanged = true;
dateOut = new DateTime(dateOut.Year, dateOut.Month, cday + 1);
}
}
else
{
ImGui.TextUnformatted(" ");
}
}
if (dw == 0)
ImGui.Separator();
}
if (dw != 6)
ImGui.SameLine(ImGui.GetWindowWidth() - (6 - dw) * (ImGui.GetWindowWidth() / 7.0f));
}
style.WindowRounding = oldWindowRounding;
var mustCloseCombo = dayClicked;
if (closeWhenMouseLeavesIt && !mustCloseCombo)
{
var distance = ImGui.GetFontSize() * 1.75f; //1.3334f; //24;
var pos = ImGui.GetWindowPos();
pos.X -= distance;
pos.Y -= distance;
var size = ImGui.GetWindowSize();
size.X += 2.0f * distance;
size.Y += 2.0f * distance;
var mousePos = ImGui.GetIO().MousePos;
if (
mousePos.X < pos.X
|| mousePos.Y < pos.Y
|| mousePos.X > pos.X + size.X
|| mousePos.Y > pos.Y + size.Y
)
mustCloseCombo = true;
}
// ImGui issue #273849, children keep popups from closing automatically
if (mustCloseCombo)
ImGui.CloseCurrentPopup();
return valueChanged;
}
private static Vector4 CalculateTextColor()
{
var textColor = ImGuiColors.DalamudGrey;
var l = (textColor.X + textColor.Y + textColor.Z) * 0.33334f;
return new Vector4(l * 2.0f > 1 ? 1 : l * 2.0f, l * .5f, l * .5f, textColor.W);
}
}