diff --git a/MarketBoardPlugin/GUI/MarketBoardWindow.cs b/MarketBoardPlugin/GUI/MarketBoardWindow.cs index 1d210cc..716a9a0 100644 --- a/MarketBoardPlugin/GUI/MarketBoardWindow.cs +++ b/MarketBoardPlugin/GUI/MarketBoardWindow.cs @@ -8,19 +8,19 @@ namespace MarketBoardPlugin.GUI using System.Collections.Generic; using System.ComponentModel; using System.Globalization; - using System.IO; using System.Linq; using System.Numerics; - using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Dalamud.Game.Text; using Dalamud.Interface; using Dalamud.Interface.Internal; + using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; using ImGuiNET; + using ImPlotNET; using Lumina.Excel.GeneratedSheets; using MarketBoardPlugin.Extensions; using MarketBoardPlugin.Helpers; @@ -47,6 +47,10 @@ public class MarketBoardWindow : Window, IDisposable private readonly List classJobs; + private readonly IFontHandle defaultFontHandle; + + private readonly IFontHandle titleFontHandle; + private Dictionary> sortedCategoriesAndItems; private bool isDisposed; @@ -95,8 +99,6 @@ public class MarketBoardWindow : Window, IDisposable private int selectedHistory = -1; - private ImFontPtr fontPtr; - private bool hasListingsHQColumnWidthBeenSet; private bool hasHistoryHQColumnWidthBeenSet; @@ -140,10 +142,32 @@ public MarketBoardWindow(MBPlugin plugin) MBPlugin.Framework.Update += this.HandleFrameworkUpdateEvent; MBPlugin.GameGui.HoveredItemChanged += this.HandleHoveredItemChange; - MBPlugin.PluginInterface.UiBuilder.BuildFonts += this.HandleBuildFonts; + + this.defaultFontHandle = MBPlugin.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e => + e.OnPreBuild(toolkit => + toolkit.AddFontFromStream( + this.GetType().Assembly.GetManifestResourceStream("MarketBoardPlugin.Resources.NotoSans-Medium-NNBSP.otf"), + new SafeFontConfig() + { + SizePx = UiBuilder.DefaultFontSizePx, + GlyphRanges = FontAtlasBuildToolkitUtilities.ToGlyphRange(char.ConvertFromUtf32(0x202F)), + MergeFont = toolkit.AddDalamudDefaultFont(-1), + }, + false, + "NNBSP"))); + + this.titleFontHandle = MBPlugin.PluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e => + e.OnPreBuild(toolkit => + toolkit.AddDalamudDefaultFont(MBPlugin.PluginInterface.UiBuilder.DefaultFontSpec.SizePx * 1.5f))); MBPlugin.PluginInterface.UiBuilder.RebuildFonts(); + var imPlotStylePtr = ImPlot.GetStyle(); + + imPlotStylePtr.Use24HourClock = DateTimeFormatInfo.CurrentInfo.ShortTimePattern.Contains('H', StringComparison.InvariantCulture); + imPlotStylePtr.UseISO8601 = DateTimeFormatInfo.CurrentInfo.ShortDatePattern != "M/d/yyyy"; + imPlotStylePtr.UseLocalTime = true; + #if DEBUG this.worldList.Add(("Chaos", "Chaos")); this.worldList.Add(("Moogle", "Moogle")); @@ -199,6 +223,8 @@ public override void Draw() var scale = ImGui.GetIO().FontGlobalScale; + using var fontDispose = this.defaultFontHandle.Push(); + // Item List Column Setup ImGui.BeginChild("itemListColumn", new Vector2(267, 0) * scale, true); @@ -445,13 +471,13 @@ void SelectClassJob(ClassJob classJob) ImGui.SetCursorPos(new Vector2(40, 40)); } - ImGui.PushFont(this.fontPtr); + this.titleFontHandle.Push(); ImGui.SameLine(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (ImGui.GetFontSize() / 2.0f) + (19 * scale)); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (ImGui.GetFontSize() / 2.0f) + (20 * scale)); ImGui.Text(this.selectedItem?.Name); ImGui.SameLine(ImGui.GetContentRegionAvail().X - (250 * scale)); ImGui.SetCursorPosY(0); - ImGui.PopFont(); + this.titleFontHandle.Pop(); ImGui.BeginGroup(); ImGui.SetNextItemWidth(250 * scale); if (ImGui.BeginCombo("##worldCombo", this.selectedWorld > -1 ? this.worldList[this.selectedWorld].Item2 : string.Empty)) @@ -500,18 +526,18 @@ void SelectClassJob(ClassJob classJob) { if (ImGui.BeginTabItem("Market Data##marketDataTab")) { - ImGui.PushFont(this.fontPtr); + this.titleFontHandle.Push(); int usedTile = this.plugin.Config.RecentHistoryDisabled ? 1 : 2; var tableHeight = (ImGui.GetContentRegionAvail().Y / usedTile) - (ImGui.GetTextLineHeightWithSpacing() * 2); ImGui.Text("Current listings (Includes 5%% GST)"); - ImGui.PopFont(); + this.titleFontHandle.Pop(); ImGui.BeginChild("currentListings", new Vector2(0.0f, tableHeight)); ImGui.Columns(5, "currentListingsColumns"); if (!this.hasListingsHQColumnWidthBeenSet) { - ImGui.SetColumnWidth(0, 30.0f); + ImGui.SetColumnWidth(0, ImGui.GetTextLineHeightWithSpacing() * 1.5f); this.hasListingsHQColumnWidthBeenSet = true; } @@ -585,16 +611,16 @@ void SelectClassJob(ClassJob classJob) { ImGui.Separator(); - ImGui.PushFont(this.fontPtr); + this.titleFontHandle.Push(); ImGui.Text("Recent history"); - ImGui.PopFont(); + this.titleFontHandle.Pop(); ImGui.BeginChild("recentHistory", new Vector2(0.0f, tableHeight)); ImGui.Columns(6, "recentHistoryColumns"); if (!this.hasHistoryHQColumnWidthBeenSet) { - ImGui.SetColumnWidth(0, 30.0f); + ImGui.SetColumnWidth(0, ImGui.GetTextLineHeightWithSpacing() * 1.5f); this.hasHistoryHQColumnWidthBeenSet = true; } @@ -669,66 +695,58 @@ void SelectClassJob(ClassJob classJob) ImGui.Separator(); if (ImGui.BeginTabItem("Charts##chartsTab")) { + this.titleFontHandle.Push(); var tableHeight = (ImGui.GetContentRegionAvail().Y / 2) - (ImGui.GetTextLineHeightWithSpacing() * 2); - var marketDataRecentHistory = this.marketData?.RecentHistory - .GroupBy(h => DateTimeOffset.FromUnixTimeSeconds(h.Timestamp).LocalDateTime.Date) - .Select(g => (Date: g.Key, PriceAvg: (float)g.Average(h => h.PricePerUnit), - QtySum: (float)g.Sum(h => h.Quantity))) - .ToList(); + this.titleFontHandle.Pop(); - if (marketDataRecentHistory != null && marketDataRecentHistory.Count > 0) + if (this.marketData?.RecentHistory != null && this.marketData?.RecentHistory.Count > 0) { - for (var day = marketDataRecentHistory.Min(h => h.Date); - day <= marketDataRecentHistory.Max(h => h.Date); - day = day.AddDays(1)) + this.titleFontHandle.Push(); + ImGui.Text("Price variations (per unit)"); + this.titleFontHandle.Pop(); + + if (ImPlot.BeginPlot("##pricePlot", new Vector2(-1, tableHeight))) { - if (!marketDataRecentHistory.Exists(h => h.Date == day)) + var now = DateTimeOffset.Now; + var x = new List(); + var y = new List(); + + foreach (var historyEntry in this.marketData?.RecentHistory) { - marketDataRecentHistory.Add((day, 0, 0)); + x.Add(historyEntry.Timestamp); + y.Add(historyEntry.PricePerUnit); } - } - - marketDataRecentHistory = marketDataRecentHistory - .OrderBy(h => h.Date) - .ToList(); - ImGui.PushFont(this.fontPtr); - ImGui.Text("Price variations (per unit)"); - ImGui.PopFont(); - - var pricePlotValues = marketDataRecentHistory - .Select(h => h.PriceAvg) - .ToArray(); - ImGui.SetNextItemWidth(-1); - ImGui.PlotLines( - "##pricePlot", - ref pricePlotValues[0], - pricePlotValues.Length, - 0, - null, - float.MaxValue, - float.MaxValue, - new Vector2(0, tableHeight)); + ImPlot.SetupAxesLimits(now.AddDays(-7).ToUnixTimeSeconds(), now.ToUnixTimeSeconds(), 0, y.Max(), ImPlotCond.Always); + ImPlot.SetupAxisScale(ImAxis.X1, ImPlotScale.Time); + ImPlot.SetNextMarkerStyle(ImPlotMarker.Circle); + ImPlot.PlotLine("Price", ref x.ToArray()[0], ref y.ToArray()[0], x.Count); + ImPlot.EndPlot(); + } ImGui.Separator(); - ImGui.PushFont(this.fontPtr); + this.titleFontHandle.Push(); ImGui.Text("Traded volumes"); - ImGui.PopFont(); - - var qtyPlotValues = marketDataRecentHistory - .Select(h => h.QtySum) - .ToArray(); - ImGui.SetNextItemWidth(-1); - ImGui.PlotHistogram( - "##qtyPlot", - ref qtyPlotValues[0], - qtyPlotValues.Length, - 0, - null, - float.MaxValue, - float.MaxValue, - new Vector2(0, tableHeight)); + this.titleFontHandle.Pop(); + + if (ImPlot.BeginPlot("##qtyPlot", new Vector2(-1, tableHeight))) + { + var now = DateTimeOffset.Now; + var x = new List(); + var y = new List(); + + foreach (var historyEntry in this.marketData?.RecentHistory) + { + x.Add(historyEntry.Timestamp); + y.Add(historyEntry.Quantity); + } + + ImPlot.SetupAxesLimits(now.AddDays(-7).ToUnixTimeSeconds(), now.ToUnixTimeSeconds(), 0, y.Max(), ImPlotCond.Always); + ImPlot.SetupAxisScale(ImAxis.X1, ImPlotScale.Time); + ImPlot.PlotBars("Quantities", ref x.ToArray()[0], ref y.ToArray()[0], x.Count, 3600); + ImPlot.EndPlot(); + } } ImGui.EndTabItem(); @@ -814,8 +832,9 @@ protected virtual void Dispose(bool disposing) { MBPlugin.Framework.Update -= this.HandleFrameworkUpdateEvent; MBPlugin.GameGui.HoveredItemChanged -= this.HandleHoveredItemChange; - MBPlugin.PluginInterface.UiBuilder.BuildFonts -= this.HandleBuildFonts; this.selectedItemIcon?.Dispose(); + this.defaultFontHandle?.Dispose(); + this.titleFontHandle?.Dispose(); } this.isDisposed = true; @@ -930,31 +949,6 @@ private Dictionary> SortCategoriesAndItems() } } - private unsafe void HandleBuildFonts() - { - var fontPath = Path.Combine(MBPlugin.PluginInterface.DalamudAssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf"); - this.fontPtr = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPath, 24.0f); - - ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); - fontConfig.MergeMode = true; - fontConfig.NativePtr->DstFont = UiBuilder.DefaultFont.NativePtr; - - var fontRangeHandle = GCHandle.Alloc( - new ushort[] - { - 0x202F, - 0x202F, - 0, - }, - GCHandleType.Pinned); - - var otherPath = Path.Combine(MBPlugin.PluginInterface.AssemblyLocation.DirectoryName, "Resources", "NotoSans-Medium.otf"); - ImGui.GetIO().Fonts.AddFontFromFileTTF(otherPath, 17.0f, fontConfig, fontRangeHandle.AddrOfPinnedObject()); - - fontConfig.Destroy(); - fontRangeHandle.Free(); - } - private void HandleFrameworkUpdateEvent(IFramework framework) { if (MBPlugin.ClientState.LocalContentId != 0 && this.playerId != MBPlugin.ClientState.LocalContentId) @@ -1074,6 +1068,7 @@ private void RefreshMarketData() this.marketData = null; } + this.marketData = await UniversalisClient .GetMarketData( this.selectedItem.RowId, diff --git a/MarketBoardPlugin/MarketBoardPlugin.csproj b/MarketBoardPlugin/MarketBoardPlugin.csproj index 8eee492..a55c45d 100644 --- a/MarketBoardPlugin/MarketBoardPlugin.csproj +++ b/MarketBoardPlugin/MarketBoardPlugin.csproj @@ -1,7 +1,7 @@ - net7.0-windows + net8.0-windows MarketBoardPlugin Florian "fmauNeko" Maunier Library @@ -11,9 +11,9 @@ PdbOnly true git rev-parse --short HEAD - 1.3.4 - 1.3.4 - 1.3.4 + 1.4.0 + 1.4.0 + 1.4.0 Florian Maunier Market board plugin for Dalamud. Copyright (c) Florian Maunier. All rights reserved. @@ -43,9 +43,11 @@ - - PreserveNewest - + + + + + @@ -56,7 +58,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -94,4 +96,4 @@ - + \ No newline at end of file diff --git a/MarketBoardPlugin/Resources/NotoSans-Medium-NNBSP.otf b/MarketBoardPlugin/Resources/NotoSans-Medium-NNBSP.otf new file mode 100644 index 0000000..d1aa4fe Binary files /dev/null and b/MarketBoardPlugin/Resources/NotoSans-Medium-NNBSP.otf differ diff --git a/MarketBoardPlugin/Resources/NotoSans-Medium.otf b/MarketBoardPlugin/Resources/NotoSans-Medium.otf deleted file mode 100644 index ceeb295..0000000 Binary files a/MarketBoardPlugin/Resources/NotoSans-Medium.otf and /dev/null differ diff --git a/MarketBoardPlugin/packages.lock.json b/MarketBoardPlugin/packages.lock.json index 11c8022..e408eae 100644 --- a/MarketBoardPlugin/packages.lock.json +++ b/MarketBoardPlugin/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - "net7.0-windows7.0": { + "net8.0-windows7.0": { "Dalamud.ContextMenu": { "type": "Direct", "requested": "[1.3.1, )", @@ -32,11 +32,11 @@ }, "StyleCop.Analyzers": { "type": "Direct", - "requested": "[1.2.0-beta.507, )", - "resolved": "1.2.0-beta.507", - "contentHash": "/FtugDT66cKJJ+GGH7rNpG6UDrT4iIWz45M6lrXXHobDUFDHw+q5VgkbiR+6ffTO564ge7w6fQh/eoQhVdJO8Q==", + "requested": "[1.2.0-beta.556, )", + "resolved": "1.2.0-beta.556", + "contentHash": "llRPgmA1fhC0I0QyFLEcjvtM2239QzKr/tcnbsjArLMJxJlu0AA5G7Fft0OI30pHF3MW63Gf4aSSsjc5m82J1Q==", "dependencies": { - "StyleCop.Analyzers.Unstable": "1.2.0.507" + "StyleCop.Analyzers.Unstable": "1.2.0.556" } }, "Dalamud.Loc": { @@ -44,26 +44,35 @@ "resolved": "1.1.2", "contentHash": "Sy7ooP1+mGtoCCPltgUNATz3UbnRFihVcDfg9wB2rdsfY5FDfCW/k3jpLZiJZc3xXHyttzwttJeVC3tE3JsEGw==" }, + "JetBrains.Annotations": { + "type": "Transitive", + "resolved": "2023.3.0", + "contentHash": "PHfnvdBUdGaTVG9bR/GEfxgTwWM0Z97Y6X3710wiljELBISipSfF5okn/vz+C2gfO+ihoEyVPjaJwn8ZalVukA==" + }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", + "resolved": "8.0.0", + "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" + "resolved": "8.0.0", + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" }, "StyleCop.Analyzers.Unstable": { "type": "Transitive", - "resolved": "1.2.0.507", - "contentHash": "gTY3IQdRqDJ4hbhSA3e/R48oE8b/OiKfvwkt1QdNVfrJK2gMHBV8ldaHJ885jxWZfllK66soa/sdcjh9bX49Tw==" + "resolved": "1.2.0.556", + "contentHash": "zvn9Mqs/ox/83cpYPignI8hJEM2A93s2HkHs8HYMOAQW0PkampyoErAiIyKxgTLqbbad29HX/shv/6LGSjPJNQ==" }, "ottergui": { - "type": "Project" + "type": "Project", + "dependencies": { + "JetBrains.Annotations": "[2023.3.0, )", + "Microsoft.Extensions.DependencyInjection": "[8.0.0, )" + } } } } diff --git a/OtterGui b/OtterGui index a4f9b28..1d93651 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit a4f9b285c82f84ff0841695c0787dbba93afc59b +Subproject commit 1d9365164655a7cb38172e1311e15e19b1def6db