Implement DBViewer
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Version>1.25.2</Version>
|
||||
<Version>1.25.3</Version>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -372,6 +372,78 @@ internal class MessageStore : IDisposable
|
||||
cmd.Parameters.AddWithValue("$Id", id);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
internal long CountDateRange(DateTime after, DateTime before, ulong? receiver = null)
|
||||
{
|
||||
List<string> whereClauses = ["deleted = false"];
|
||||
if (receiver != null)
|
||||
whereClauses.Add("Receiver = $Receiver");
|
||||
|
||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||
whereClauses.Add("Code != 72");
|
||||
|
||||
var whereClause = "WHERE " + string.Join(" AND ", whereClauses);
|
||||
|
||||
var cmd = Connection.CreateCommand();
|
||||
// Select last N messages by date DESC, but reverse the order to get
|
||||
// them in ascending order.
|
||||
cmd.CommandText = @"
|
||||
SELECT COUNT(*)
|
||||
FROM messages
|
||||
" + whereClause;
|
||||
cmd.CommandTimeout = 120; // this could take a while on slow computers
|
||||
|
||||
if (receiver != null)
|
||||
cmd.Parameters.AddWithValue("$Receiver", receiver);
|
||||
|
||||
cmd.Parameters.AddWithValue("$After", ((DateTimeOffset) after).ToUnixTimeMilliseconds());
|
||||
cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset) before).ToUnixTimeMilliseconds());
|
||||
|
||||
return (long) cmd.ExecuteScalar()!;
|
||||
}
|
||||
|
||||
internal MessageEnumerator GetDateRange(DateTime after, DateTime before, ulong? receiver = null, int page = 0)
|
||||
{
|
||||
List<string> whereClauses = ["deleted = false"];
|
||||
if (receiver != null)
|
||||
whereClauses.Add("Receiver = $Receiver");
|
||||
|
||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||
whereClauses.Add("Code != 72");
|
||||
|
||||
var whereClause = "WHERE " + string.Join(" AND ", whereClauses);
|
||||
|
||||
var cmd = Connection.CreateCommand();
|
||||
// Select last N messages by date DESC, but reverse the order to get
|
||||
// them in ascending order.
|
||||
cmd.CommandText = @"
|
||||
SELECT
|
||||
Id,
|
||||
Receiver,
|
||||
ContentId,
|
||||
Date,
|
||||
Code,
|
||||
Sender,
|
||||
Content,
|
||||
SenderSource,
|
||||
ContentSource,
|
||||
SortCode,
|
||||
ExtraChatChannel
|
||||
FROM messages
|
||||
" + whereClause + @"
|
||||
LIMIT $Offset, 500;
|
||||
";
|
||||
cmd.CommandTimeout = 120; // this could take a while on slow computers
|
||||
|
||||
if (receiver != null)
|
||||
cmd.Parameters.AddWithValue("$Receiver", receiver);
|
||||
|
||||
cmd.Parameters.AddWithValue("$After", ((DateTimeOffset) after).ToUnixTimeMilliseconds());
|
||||
cmd.Parameters.AddWithValue("$Before", ((DateTimeOffset) before).ToUnixTimeMilliseconds());
|
||||
cmd.Parameters.AddWithValue("$Offset", 500 * page);
|
||||
|
||||
return new MessageEnumerator(cmd.ExecuteReader());
|
||||
}
|
||||
}
|
||||
|
||||
internal class MessageEnumerator(DbDataReader reader) : IEnumerable<Message>
|
||||
|
||||
@@ -45,6 +45,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
public readonly WindowSystem WindowSystem = new(PluginName);
|
||||
public SettingsWindow SettingsWindow { get; }
|
||||
public ChatLogWindow ChatLogWindow { get; }
|
||||
public DbViewer DbViewer { get; }
|
||||
public InputPreview InputPreview { get; }
|
||||
public CommandHelpWindow CommandHelpWindow { get; }
|
||||
public SeStringDebugger SeStringDebugger { get; }
|
||||
@@ -89,6 +90,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
|
||||
ChatLogWindow = new ChatLogWindow(this);
|
||||
SettingsWindow = new SettingsWindow(this);
|
||||
DbViewer = new DbViewer(this);
|
||||
InputPreview = new InputPreview(ChatLogWindow);
|
||||
CommandHelpWindow = new CommandHelpWindow(ChatLogWindow);
|
||||
SeStringDebugger = new SeStringDebugger(this);
|
||||
@@ -96,6 +98,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
|
||||
WindowSystem.AddWindow(ChatLogWindow);
|
||||
WindowSystem.AddWindow(SettingsWindow);
|
||||
WindowSystem.AddWindow(DbViewer);
|
||||
WindowSystem.AddWindow(InputPreview);
|
||||
WindowSystem.AddWindow(CommandHelpWindow);
|
||||
WindowSystem.AddWindow(SeStringDebugger);
|
||||
@@ -152,6 +155,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
|
||||
WindowSystem?.RemoveAllWindows();
|
||||
ChatLogWindow?.Dispose();
|
||||
DbViewer?.Dispose();
|
||||
InputPreview?.Dispose();
|
||||
SettingsWindow?.Dispose();
|
||||
DebuggerWindow?.Dispose();
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
public class DbViewer : Window
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
|
||||
private static readonly DateTime MinimalDate = new(2021, 1, 1);
|
||||
|
||||
private DateTime AfterDate;
|
||||
private DateTime BeforeDate;
|
||||
|
||||
private int CurrentPage = 1;
|
||||
private string SimpleSearchTerm = "";
|
||||
private bool OnlyCurrentCharacter = true;
|
||||
|
||||
private bool IsProcessing;
|
||||
private long ProcessingStart = Environment.TickCount64;
|
||||
private (DateTime Min, DateTime Max, int Page, bool Local) LastProcessed;
|
||||
|
||||
private string MinDateString = "";
|
||||
private string MaxDateString = "";
|
||||
|
||||
private readonly string DateFormat;
|
||||
private readonly string DateTimeFormat;
|
||||
|
||||
private long Count;
|
||||
private Message[] Messages = []; // Messages are only touched while processing is false
|
||||
private ConcurrentStack<Message> Filtered = []; // Is used every frame, so ConcurrentStack for safety
|
||||
|
||||
public DbViewer(Plugin plugin) : base("DBViewer###chat2-dbviewer")
|
||||
{
|
||||
Plugin = plugin;
|
||||
|
||||
DateFormat = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
|
||||
DateTimeFormat = CultureInfo.CurrentCulture.DateTimeFormat.FullDateTimePattern;
|
||||
|
||||
LastProcessed = (AfterDate, BeforeDate, CurrentPage, OnlyCurrentCharacter);
|
||||
DateReset();
|
||||
|
||||
SizeConstraints = new WindowSizeConstraints
|
||||
{
|
||||
MinimumSize = new Vector2(475, 600),
|
||||
MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
|
||||
};
|
||||
|
||||
RespectCloseHotkey = false;
|
||||
DisableWindowSounds = true;
|
||||
|
||||
Plugin.Commands.Register("/chat2Viewer").Execute += Toggle;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Plugin.Commands.Register("/chat2Viewer").Execute -= Toggle;
|
||||
}
|
||||
|
||||
private void Toggle(string _, string __) => Toggle();
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
var totalPages = (int)Math.Ceiling(Count / 500.0f);
|
||||
if (totalPages < 1)
|
||||
totalPages = 1;
|
||||
|
||||
if (CurrentPage > totalPages)
|
||||
CurrentPage = 1;
|
||||
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextColored(ImGuiColors.DalamudViolet, "FromTo:");
|
||||
ImGui.SameLine();
|
||||
|
||||
var spacing = 3.0f * ImGuiHelpers.GlobalScale;
|
||||
DateWidget.DatePickerWithInput("##FromDate", 1, ref MinDateString, ref AfterDate, DateFormat);
|
||||
DateWidget.DatePickerWithInput("##ToDate", 2, ref MaxDateString, ref BeforeDate, DateFormat, true);
|
||||
ImGui.SameLine(0, spacing);
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.Recycle))
|
||||
DateReset();
|
||||
ImGuiUtil.DrawArrows(ref CurrentPage, 1, totalPages, spacing);
|
||||
|
||||
var skipText = "Only current character";
|
||||
var textLength = ImGui.GetTextLineHeight() + ImGui.CalcTextSize(skipText).X + ImGui.GetStyle().ItemInnerSpacing.X + ImGui.GetStyle().FramePadding.X * 2;
|
||||
ImGui.SameLine(ImGui.GetContentRegionMax().X - textLength);
|
||||
ImGui.Checkbox(skipText, ref OnlyCurrentCharacter);
|
||||
|
||||
var width = 350 * ImGuiHelpers.GlobalScale;
|
||||
var loadingIndicator = IsProcessing && ProcessingStart < Environment.TickCount64;
|
||||
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted($"Page: {CurrentPage} / {totalPages} ({Count} Rows) {(loadingIndicator ? "[Loading...]" : "")}");
|
||||
ImGui.SameLine(ImGui.GetContentRegionMax().X - width);
|
||||
ImGui.SetNextItemWidth(width);
|
||||
if (ImGui.InputTextWithHint("##searchbar", "Simple Sender/Content Search", ref SimpleSearchTerm, 30))
|
||||
Filter();
|
||||
|
||||
if (DateWidget.Validate(MinimalDate, ref AfterDate, ref BeforeDate))
|
||||
DateRefresh();
|
||||
|
||||
if (!IsProcessing && LastProcessed != (AfterDate, BeforeDate, CurrentPage, OnlyCurrentCharacter))
|
||||
{
|
||||
// Page hasn't changed, so we reset it back to 1
|
||||
if (LastProcessed.Page == CurrentPage)
|
||||
CurrentPage = 1;
|
||||
|
||||
AdjustDates();
|
||||
IsProcessing = true;
|
||||
ProcessingStart = Environment.TickCount64 + 1_000; // + 1 second
|
||||
LastProcessed = (AfterDate, BeforeDate, CurrentPage, OnlyCurrentCharacter);
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
ulong? character = OnlyCurrentCharacter ? Plugin.ClientState.LocalContentId : null;
|
||||
|
||||
// We only want to fetch count if this is the first page
|
||||
if (CurrentPage == 1)
|
||||
Count = Plugin.MessageManager.Store.CountDateRange(AfterDate, BeforeDate, character);
|
||||
Messages = Plugin.MessageManager.Store.GetDateRange(AfterDate, BeforeDate, character, CurrentPage - 1).ToArray();
|
||||
|
||||
Filter();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.Log.Error(ex, "Failed reading messages from database");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsProcessing = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ImGuiHelpers.ScaledDummy(5.0f);
|
||||
|
||||
if (Filtered.IsEmpty)
|
||||
{
|
||||
ImGui.TextUnformatted(SimpleSearchTerm == "" ? "Nothing Found." : "Search had no match on this page.");
|
||||
return;
|
||||
}
|
||||
|
||||
using var child = ImRaii.Child("##tableChild");
|
||||
if (!child.Success)
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table("##messageHistory", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.Resizable);
|
||||
if (!table.Success)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Sender");
|
||||
ImGui.TableSetupColumn("Content");
|
||||
|
||||
ImGui.TableHeadersRow();
|
||||
foreach (var message in Filtered)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(message.Date.ToLocalTime().ToString(DateTimeFormat));
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
Plugin.ChatLogWindow.DrawChunks(message.Sender);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
Plugin.ChatLogWindow.DrawChunks(message.Content);
|
||||
}
|
||||
}
|
||||
|
||||
private void Filter()
|
||||
{
|
||||
if (SimpleSearchTerm == "")
|
||||
{
|
||||
Filtered = new ConcurrentStack<Message>(Messages.Reverse());
|
||||
return;
|
||||
}
|
||||
|
||||
Filtered = new ConcurrentStack<Message>(
|
||||
Messages.Reverse().Where(m =>
|
||||
ChunkUtil.ToRawString(m.Sender).Contains(SimpleSearchTerm) ||
|
||||
ChunkUtil.ToRawString(m.Content).Contains(SimpleSearchTerm))
|
||||
);
|
||||
}
|
||||
|
||||
private void DateRefresh()
|
||||
{
|
||||
MinDateString = AfterDate.ToString(DateFormat);
|
||||
MaxDateString = BeforeDate.ToString(DateFormat);
|
||||
}
|
||||
|
||||
private void AdjustDates()
|
||||
{
|
||||
AfterDate = new DateTime(AfterDate.Year, AfterDate.Month, AfterDate.Day, 0, 0, 0);
|
||||
BeforeDate = new DateTime(BeforeDate.Year, BeforeDate.Month, BeforeDate.Day, 23, 59, 59);
|
||||
}
|
||||
|
||||
private void DateReset()
|
||||
{
|
||||
AfterDate = DateTime.Now.AddDays(-5);
|
||||
BeforeDate = DateTime.Now;
|
||||
|
||||
AdjustDates();
|
||||
DateRefresh();
|
||||
}
|
||||
}
|
||||
@@ -139,6 +139,19 @@ internal static class ChunkUtil
|
||||
return chunks;
|
||||
}
|
||||
|
||||
internal static string ToRawString(List<Chunk> chunks)
|
||||
{
|
||||
if (chunks.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
foreach (var chunk in chunks)
|
||||
if (chunk is TextChunk text)
|
||||
builder.Append(text.Content);
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
internal static readonly RawPayload PeriodicRecruitmentLink = new([0x02, 0x27, 0x07, 0x08, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03]);
|
||||
|
||||
private static uint GetInteger(BinaryReader input)
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace ChatTwo.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 Vector4 Transparent = new(1, 1, 1, 0);
|
||||
private static readonly string[] DayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
private static readonly string[] MonthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
private static readonly int[] NumDaysPerMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
private static uint LastOpenComboID;
|
||||
private static readonly int MaxMonthWidthIndex = -1;
|
||||
|
||||
private static readonly DateTime Sample = DateTime.UnixEpoch;
|
||||
|
||||
static DateWidget()
|
||||
{
|
||||
if (MaxMonthWidthIndex == -1)
|
||||
{
|
||||
float maxMonthWidth = 0;
|
||||
for (var i = 0; i < 12; i++)
|
||||
{
|
||||
var mw = ImGui.CalcTextSize(MonthNames[i]).X;
|
||||
if (maxMonthWidth < mw)
|
||||
{
|
||||
maxMonthWidth = mw;
|
||||
MaxMonthWidthIndex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = "Date before {0} is not possible".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 (DatePicker(label, ref date, closeWhenMouseLeavesIt))
|
||||
dateString = date.ToString(format);
|
||||
}
|
||||
|
||||
public static bool DatePicker(string label, ref DateTime dateOut, bool closeWhenMouseLeavesIt, string leftArrow = "", string rightArrow = "")
|
||||
{
|
||||
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, 0, true);
|
||||
|
||||
var requiredMonthWidth = ImGui.CalcTextSize(MonthNames[MaxMonthWidthIndex]).X;
|
||||
var widthRequiredByCalendar = (2.0f * arrowLeftWidth) + (2.0f * arrowRightWidth) + requiredMonthWidth + 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));
|
||||
|
||||
if (!ImGui.BeginPopupContextItem(label, ImGuiPopupFlags.None))
|
||||
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;
|
||||
}
|
||||
|
||||
ImGui.PushFont(UiBuilder.MonoFont);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, style.FramePadding);
|
||||
ImGui.Spacing();
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, Transparent);
|
||||
|
||||
var yearString = $"{dateOut.Year}";
|
||||
var yearPartWidth = arrowLeftWidth + arrowRightWidth + ImGui.CalcTextSize(yearString).X;
|
||||
|
||||
var oldWindowRounding = style.WindowRounding;
|
||||
style.WindowRounding = 0;
|
||||
|
||||
ImGui.PushID(1234);
|
||||
if (ImGui.SmallButton(arrowLeft))
|
||||
dateOut = dateOut.AddMonths(-1);
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.TextUnformatted($"{Center(MonthNames[dateOut.Month - 1], 9)}");
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton(arrowRight))
|
||||
dateOut = dateOut.AddMonths(1);
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.SameLine(ImGui.GetWindowWidth() - yearPartWidth - style.WindowPadding.X - style.ItemSpacing.X * 4.0f);
|
||||
|
||||
ImGui.PushID(1235);
|
||||
if (ImGui.SmallButton(arrowLeft))
|
||||
dateOut = dateOut.AddYears(-1);
|
||||
ImGui.SameLine();
|
||||
|
||||
ImGui.Text($"{dateOut.Year}");
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton(arrowRight))
|
||||
dateOut = dateOut.AddYears(1);
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
var maxDayOfCurMonth = NumDaysPerMonth[dateOut.Month - 1]; // This could be calculated only when needed (but I guess it's fast in any case...)
|
||||
if (maxDayOfCurMonth == 28)
|
||||
{
|
||||
var year = dateOut.Year;
|
||||
var bis = ((year % 4) == 0) && ((year % 100) != 0 || (year % 400) == 0);
|
||||
if (bis)
|
||||
maxDayOfCurMonth = 29;
|
||||
}
|
||||
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, ImGuiColors.DalamudOrange);
|
||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, ImGuiColors.DalamudYellow);
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
// Display items
|
||||
var dayOfWeek = (int)new DateTime(dateOut.Year, dateOut.Month, 1).DayOfWeek;
|
||||
for (var dw = 0; dw < 7; dw++)
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
if (dw == 0)
|
||||
{
|
||||
var textColor = ImGuiColors.DalamudGrey;
|
||||
var l = (textColor.X + textColor.Y + textColor.Z) * 0.33334f;
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(l * 2.0f > 1 ? 1 : l * 2.0f, l * .5f, l * .5f, textColor.W));
|
||||
}
|
||||
|
||||
ImGui.Text($"{(dw == 0 ? "" : " ")}{DayNames[dw]}");
|
||||
if (dw == 0)
|
||||
ImGui.Separator();
|
||||
else
|
||||
ImGui.Spacing();
|
||||
|
||||
var curDay = dw - dayOfWeek; // Use dayOfWeek for spacing
|
||||
for (var row = 0; row < 7; row++)
|
||||
{
|
||||
var cday = curDay + (7 * row);
|
||||
if (cday >= 0 && cday < maxDayOfCurMonth)
|
||||
{
|
||||
ImGui.PushID(row * 10 + dw);
|
||||
if (ImGui.SmallButton(string.Format(cday < 9 ? " {0}" : "{0}", cday + 1)))
|
||||
{
|
||||
valueChanged = true;
|
||||
ImGui.SetItemDefaultFocus();
|
||||
dateOut = new DateTime(dateOut.Year, dateOut.Month, cday + 1);
|
||||
}
|
||||
|
||||
ImGui.PopID();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted(" ");
|
||||
}
|
||||
}
|
||||
|
||||
if (dw == 0)
|
||||
{
|
||||
ImGui.Separator();
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
|
||||
ImGui.EndGroup();
|
||||
|
||||
if (dw != 6)
|
||||
ImGui.SameLine(ImGui.GetWindowWidth() - ((6 - dw) * (ImGui.GetWindowWidth() / 7.0f)));
|
||||
}
|
||||
|
||||
style.WindowRounding = oldWindowRounding;
|
||||
ImGui.PopStyleColor(2);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopStyleVar();
|
||||
|
||||
var mustCloseCombo = valueChanged;
|
||||
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.PopFont();
|
||||
// ImGui issue #273849, children keep popups from closing automatically
|
||||
if (mustCloseCombo)
|
||||
ImGui.CloseCurrentPopup();
|
||||
ImGui.EndPopup();
|
||||
|
||||
return valueChanged;
|
||||
}
|
||||
|
||||
public static string Center(string source, int length)
|
||||
{
|
||||
var spaces = length - source.Length;
|
||||
var padLeft = (spaces / 2) + source.Length;
|
||||
return source.PadLeft(padLeft).PadRight(length);
|
||||
}
|
||||
}
|
||||
@@ -298,6 +298,25 @@ internal static class ImGuiUtil
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawArrows(ref int selected, int min, int max, float spacing, int id = 0)
|
||||
{
|
||||
// Prevents changing values from triggering EndDisable
|
||||
var isMin = selected == 1;
|
||||
var isMax = selected == max;
|
||||
|
||||
ImGui.SameLine(0, spacing);
|
||||
using (ImRaii.Disabled(isMin))
|
||||
{
|
||||
if (IconButton(FontAwesomeIcon.ArrowLeft, id.ToString())) selected--;
|
||||
}
|
||||
|
||||
ImGui.SameLine(0, spacing);
|
||||
using (ImRaii.Disabled(isMax))
|
||||
{
|
||||
if (IconButton(FontAwesomeIcon.ArrowRight, id+1.ToString())) selected++;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool TryToImGui(this VirtualKey key, out ImGuiKey result)
|
||||
{
|
||||
result = key switch {
|
||||
|
||||
Reference in New Issue
Block a user