mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
feat: add new ConsoleWindow
This commit is contained in:
parent
dc992e15ae
commit
c287e28832
5 changed files with 529 additions and 190 deletions
|
|
@ -29,7 +29,7 @@ namespace Dalamud.Interface.Internal
|
||||||
private readonly CreditsWindow creditsWindow;
|
private readonly CreditsWindow creditsWindow;
|
||||||
private readonly DataWindow dataWindow;
|
private readonly DataWindow dataWindow;
|
||||||
private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow;
|
private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow;
|
||||||
private readonly LogWindow logWindow;
|
private readonly ConsoleWindow consoleWindow;
|
||||||
private readonly PluginStatWindow pluginStatWindow;
|
private readonly PluginStatWindow pluginStatWindow;
|
||||||
private readonly PluginInstallerWindow pluginWindow;
|
private readonly PluginInstallerWindow pluginWindow;
|
||||||
private readonly ScratchpadWindow scratchpadWindow;
|
private readonly ScratchpadWindow scratchpadWindow;
|
||||||
|
|
@ -60,7 +60,7 @@ namespace Dalamud.Interface.Internal
|
||||||
this.creditsWindow = new CreditsWindow(dalamud) { IsOpen = false };
|
this.creditsWindow = new CreditsWindow(dalamud) { IsOpen = false };
|
||||||
this.dataWindow = new DataWindow(dalamud) { IsOpen = false };
|
this.dataWindow = new DataWindow(dalamud) { IsOpen = false };
|
||||||
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow();
|
this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow();
|
||||||
this.logWindow = new LogWindow(dalamud) { IsOpen = this.dalamud.Configuration.LogOpenAtStartup };
|
this.consoleWindow = new ConsoleWindow { IsOpen = this.dalamud.Configuration.LogOpenAtStartup };
|
||||||
this.pluginStatWindow = new PluginStatWindow(dalamud) { IsOpen = false };
|
this.pluginStatWindow = new PluginStatWindow(dalamud) { IsOpen = false };
|
||||||
this.pluginWindow = new PluginInstallerWindow(dalamud) { IsOpen = false };
|
this.pluginWindow = new PluginInstallerWindow(dalamud) { IsOpen = false };
|
||||||
this.scratchpadWindow = new ScratchpadWindow(dalamud) { IsOpen = false };
|
this.scratchpadWindow = new ScratchpadWindow(dalamud) { IsOpen = false };
|
||||||
|
|
@ -72,7 +72,7 @@ namespace Dalamud.Interface.Internal
|
||||||
this.windowSystem.AddWindow(this.creditsWindow);
|
this.windowSystem.AddWindow(this.creditsWindow);
|
||||||
this.windowSystem.AddWindow(this.dataWindow);
|
this.windowSystem.AddWindow(this.dataWindow);
|
||||||
this.windowSystem.AddWindow(this.gamepadModeNotifierWindow);
|
this.windowSystem.AddWindow(this.gamepadModeNotifierWindow);
|
||||||
this.windowSystem.AddWindow(this.logWindow);
|
this.windowSystem.AddWindow(this.consoleWindow);
|
||||||
this.windowSystem.AddWindow(this.pluginStatWindow);
|
this.windowSystem.AddWindow(this.pluginStatWindow);
|
||||||
this.windowSystem.AddWindow(this.pluginWindow);
|
this.windowSystem.AddWindow(this.pluginWindow);
|
||||||
this.windowSystem.AddWindow(this.scratchpadWindow);
|
this.windowSystem.AddWindow(this.scratchpadWindow);
|
||||||
|
|
@ -105,7 +105,7 @@ namespace Dalamud.Interface.Internal
|
||||||
this.windowSystem.RemoveAllWindows();
|
this.windowSystem.RemoveAllWindows();
|
||||||
|
|
||||||
this.creditsWindow.Dispose();
|
this.creditsWindow.Dispose();
|
||||||
this.logWindow.Dispose();
|
this.consoleWindow.Dispose();
|
||||||
this.scratchpadWindow.Dispose();
|
this.scratchpadWindow.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +157,7 @@ namespace Dalamud.Interface.Internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the <see cref="LogWindow"/>.
|
/// Opens the <see cref="LogWindow"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void OpenLogWindow() => this.logWindow.IsOpen = true;
|
public void OpenLogWindow() => this.consoleWindow.IsOpen = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the <see cref="PluginStatWindow"/>.
|
/// Opens the <see cref="PluginStatWindow"/>.
|
||||||
|
|
@ -229,7 +229,7 @@ namespace Dalamud.Interface.Internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toggles the <see cref="LogWindow"/>.
|
/// Toggles the <see cref="LogWindow"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ToggleLogWindow() => this.logWindow.Toggle();
|
public void ToggleLogWindow() => this.consoleWindow.Toggle();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Toggles the <see cref="PluginStatWindow"/>.
|
/// Toggles the <see cref="PluginStatWindow"/>.
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,11 @@ namespace Dalamud.Interface.Internal
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ImFontPtr IconFont { get; private set; }
|
public static ImFontPtr IconFont { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an included monospaced font.
|
||||||
|
/// </summary>
|
||||||
|
public static ImFontPtr MonoFont { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets an action that is exexuted when fonts are rebuilt.
|
/// Gets or sets an action that is exexuted when fonts are rebuilt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -464,6 +469,13 @@ namespace Dalamud.Interface.Internal
|
||||||
GCHandleType.Pinned);
|
GCHandleType.Pinned);
|
||||||
IconFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathIcon, 17.0f, null, iconRangeHandle.AddrOfPinnedObject());
|
IconFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathIcon, 17.0f, null, iconRangeHandle.AddrOfPinnedObject());
|
||||||
|
|
||||||
|
var fontPathMono = Path.Combine(this.dalamud.AssetDirectory.FullName, "UIRes", "Inconsolata-Regular.ttf");
|
||||||
|
|
||||||
|
if (!File.Exists(fontPathMono))
|
||||||
|
ShowFontError(fontPathMono);
|
||||||
|
|
||||||
|
MonoFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathMono, 16.0f);
|
||||||
|
|
||||||
Log.Verbose("[FONT] Invoke OnBuildFonts");
|
Log.Verbose("[FONT] Invoke OnBuildFonts");
|
||||||
this.OnBuildFonts?.Invoke();
|
this.OnBuildFonts?.Invoke();
|
||||||
Log.Verbose("[FONT] OnBuildFonts OK!");
|
Log.Verbose("[FONT] OnBuildFonts OK!");
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,7 @@ namespace Dalamud.Interface.Internal
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event on a log line being emitted.
|
/// Event on a log line being emitted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<(string Line, LogEventLevel Level)> OnLogLine;
|
public event EventHandler<(string Line, LogEventLevel Level, DateTimeOffset TimeStamp)> OnLogLine;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event on a log line being emitted.
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<LogEvent> OnLogEvent;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default instance.
|
/// Gets the default instance.
|
||||||
|
|
@ -43,15 +38,14 @@ namespace Dalamud.Interface.Internal
|
||||||
/// <param name="logEvent">Log event to be emitted.</param>
|
/// <param name="logEvent">Log event to be emitted.</param>
|
||||||
public void Emit(LogEvent logEvent)
|
public void Emit(LogEvent logEvent)
|
||||||
{
|
{
|
||||||
var message = $"[{DateTimeOffset.Now:HH:mm:ss.fff}][{logEvent.Level}] {logEvent.RenderMessage(this.formatProvider)}";
|
var message = logEvent.RenderMessage(this.formatProvider);
|
||||||
|
|
||||||
if (logEvent.Exception != null)
|
if (logEvent.Exception != null)
|
||||||
{
|
{
|
||||||
message += "\n" + logEvent.Exception;
|
message += "\n" + logEvent.Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.OnLogEvent?.Invoke(this, logEvent);
|
this.OnLogLine?.Invoke(this, (message, logEvent.Level, logEvent.Timestamp));
|
||||||
this.OnLogLine?.Invoke(this, (message, logEvent.Level));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
508
Dalamud/Interface/Internal/Windows/ConsoleWindow.cs
Normal file
508
Dalamud/Interface/Internal/Windows/ConsoleWindow.cs
Normal file
|
|
@ -0,0 +1,508 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Dalamud.Configuration.Internal;
|
||||||
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace Dalamud.Interface.Internal.Windows
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The window that displays the Dalamud log file in-game.
|
||||||
|
/// </summary>
|
||||||
|
internal class ConsoleWindow : Window, IDisposable
|
||||||
|
{
|
||||||
|
private readonly List<LogEntry> logText = new();
|
||||||
|
private readonly object renderLock = new();
|
||||||
|
|
||||||
|
private readonly string[] logLevelStrings = new[] { "None", "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" };
|
||||||
|
|
||||||
|
private List<LogEntry> filteredLogText = new();
|
||||||
|
private bool autoScroll;
|
||||||
|
private bool openAtStartup;
|
||||||
|
|
||||||
|
private bool? lastCmdSuccess;
|
||||||
|
|
||||||
|
private string commandText = string.Empty;
|
||||||
|
|
||||||
|
private string textFilter = string.Empty;
|
||||||
|
private LogEventLevel? levelFilter = null;
|
||||||
|
private bool isFiltered = false;
|
||||||
|
|
||||||
|
private int historyPos;
|
||||||
|
private List<string> history = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ConsoleWindow"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dalamud">The Dalamud instance.</param>
|
||||||
|
public ConsoleWindow()
|
||||||
|
: base("Dalamud Console")
|
||||||
|
{
|
||||||
|
this.autoScroll = Dalamud.Instance.Configuration.LogAutoScroll;
|
||||||
|
this.openAtStartup = Dalamud.Instance.Configuration.LogOpenAtStartup;
|
||||||
|
SerilogEventSink.Instance.OnLogLine += this.OnLogLine;
|
||||||
|
|
||||||
|
this.Size = new Vector2(500, 400);
|
||||||
|
this.SizeCondition = ImGuiCond.FirstUseEver;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<LogEntry> LogEntries => this.isFiltered ? this.filteredLogText : this.logText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose of managed and unmanaged resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
SerilogEventSink.Instance.OnLogLine -= this.OnLogLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear the window of all log entries.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
lock (this.renderLock)
|
||||||
|
{
|
||||||
|
this.logText.Clear();
|
||||||
|
this.filteredLogText.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a single log line to the display.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">The line to add.</param>
|
||||||
|
/// <param name="level">The level of the event.</param>
|
||||||
|
/// <param name="offset">The <see cref="DateTimeOffset"/> of the event.</param>
|
||||||
|
public void HandleLogLine(string line, LogEventLevel level, DateTimeOffset offset)
|
||||||
|
{
|
||||||
|
if (line.IndexOfAny(new[] { '\n', '\r' }) != -1)
|
||||||
|
{
|
||||||
|
var subLines = line.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
this.AddAndFilter(subLines[0], level, offset, false);
|
||||||
|
|
||||||
|
for (var i = 1; i < subLines.Length; i++)
|
||||||
|
{
|
||||||
|
this.AddAndFilter(subLines[i], level, offset, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.AddAndFilter(line, level, offset, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
// Options menu
|
||||||
|
if (ImGui.BeginPopup("Options"))
|
||||||
|
{
|
||||||
|
if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
|
||||||
|
{
|
||||||
|
Dalamud.Instance.Configuration.LogAutoScroll = this.autoScroll;
|
||||||
|
Dalamud.Instance.Configuration.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Checkbox("Open at startup", ref this.openAtStartup))
|
||||||
|
{
|
||||||
|
Dalamud.Instance.Configuration.LogOpenAtStartup = this.openAtStartup;
|
||||||
|
Dalamud.Instance.Configuration.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
var prevLevel = (int)Dalamud.Instance.LogLevelSwitch.MinimumLevel;
|
||||||
|
if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast<LogEventLevel>().Select(x => x.ToString()).ToArray(), 6))
|
||||||
|
{
|
||||||
|
Dalamud.Instance.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel;
|
||||||
|
Dalamud.Instance.Configuration.LogLevel = (LogEventLevel)prevLevel;
|
||||||
|
Dalamud.Instance.Configuration.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TEST */
|
||||||
|
|
||||||
|
if (ImGui.Button("Multiline test"))
|
||||||
|
{
|
||||||
|
Log.Verbose("This is a test\nTruly a multi-line test\nas it has always been.\nThank you.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Single line test"))
|
||||||
|
{
|
||||||
|
Log.Debug("This is a test");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("WARNING!!"))
|
||||||
|
{
|
||||||
|
Log.Warning("I want to warn you");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Add 50000"))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 50000; i++)
|
||||||
|
Log.Debug("TEST " + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Exception"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var a = 0;
|
||||||
|
var b = 1;
|
||||||
|
var c = b / a;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e, "There has been a serious problem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter menu
|
||||||
|
if (ImGui.BeginPopup("Filters"))
|
||||||
|
{
|
||||||
|
ImGui.Checkbox("Enabled", ref this.isFiltered);
|
||||||
|
|
||||||
|
if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
{
|
||||||
|
this.Refilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm.");
|
||||||
|
|
||||||
|
var filterVal = this.levelFilter.HasValue ? (int)this.levelFilter.Value + 1 : 0;
|
||||||
|
if (ImGui.Combo("Level", ref filterVal, this.logLevelStrings, 7))
|
||||||
|
{
|
||||||
|
this.levelFilter = (LogEventLevel)(filterVal - 1);
|
||||||
|
this.Refilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.PushFont(InterfaceManager.IconFont);
|
||||||
|
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Cog.ToIconString()))
|
||||||
|
ImGui.OpenPopup("Options");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Search.ToIconString()))
|
||||||
|
ImGui.OpenPopup("Filters");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var clear = ImGui.Button(FontAwesomeIcon.Trash.ToIconString());
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
var copy = ImGui.Button(FontAwesomeIcon.Copy.ToIconString());
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.HorizontalScrollbar);
|
||||||
|
|
||||||
|
if (clear)
|
||||||
|
{
|
||||||
|
this.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy)
|
||||||
|
{
|
||||||
|
ImGui.LogToClipboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
|
|
||||||
|
ImGuiListClipperPtr clipper;
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PushFont(InterfaceManager.MonoFont);
|
||||||
|
|
||||||
|
var childPos = ImGui.GetWindowPos();
|
||||||
|
var childDrawList = ImGui.GetWindowDrawList();
|
||||||
|
var childSize = ImGui.GetWindowSize();
|
||||||
|
|
||||||
|
lock (this.renderLock)
|
||||||
|
{
|
||||||
|
clipper.Begin(this.LogEntries.Count);
|
||||||
|
while (clipper.Step())
|
||||||
|
{
|
||||||
|
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
||||||
|
{
|
||||||
|
var line = this.LogEntries[i];
|
||||||
|
|
||||||
|
if (!line.IsMultiline)
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level));
|
||||||
|
|
||||||
|
ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns);
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.PopStyleColor(3);
|
||||||
|
|
||||||
|
if (!line.IsMultiline)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(line.TimeStamp.ToString("HH:mm:ss.fff"));
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosX(92);
|
||||||
|
ImGui.TextUnformatted("|");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosX(100);
|
||||||
|
ImGui.TextUnformatted(this.GetTextForLogEventLevel(line.Level));
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetCursorPosX(135);
|
||||||
|
ImGui.TextUnformatted(line.Line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clipper.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
|
||||||
|
if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY())
|
||||||
|
{
|
||||||
|
ImGui.SetScrollHereY(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw dividing line
|
||||||
|
childDrawList.AddLine(new Vector2(childPos.X + 127, childPos.Y), new Vector2(childPos.X + 127, childPos.Y + childSize.Y), 0x4FFFFFFF, 1.0f);
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
var hadColor = false;
|
||||||
|
if (this.lastCmdSuccess.HasValue)
|
||||||
|
{
|
||||||
|
hadColor = true;
|
||||||
|
if (this.lastCmdSuccess.Value)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.HealerGreen - new Vector4(0, 0, 0, 0.7f));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.DalamudRed - new Vector4(0, 0, 0, 0.7f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80);
|
||||||
|
|
||||||
|
var getFocus = false;
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if (ImGui.InputText("##commandbox", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback))
|
||||||
|
{
|
||||||
|
this.ProcessCommand();
|
||||||
|
getFocus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetItemDefaultFocus();
|
||||||
|
if (getFocus)
|
||||||
|
ImGui.SetKeyboardFocusHere(-1); // Auto focus previous widget
|
||||||
|
|
||||||
|
|
||||||
|
if (hadColor)
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
|
if (ImGui.Button("Send"))
|
||||||
|
{
|
||||||
|
this.ProcessCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessCommand()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this.historyPos = -1;
|
||||||
|
for (int i = this.history.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (this.history[i] == this.commandText)
|
||||||
|
{
|
||||||
|
this.history.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.history.Add(this.commandText);
|
||||||
|
|
||||||
|
if (this.commandText == "clear" || this.commandText == "cls")
|
||||||
|
{
|
||||||
|
this.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastCmdSuccess = Dalamud.Instance.CommandManager.ProcessCommand("/" + this.commandText);
|
||||||
|
this.commandText = string.Empty;
|
||||||
|
|
||||||
|
//TODO: Force scroll to bottom
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error during command dispatch");
|
||||||
|
this.lastCmdSuccess = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data)
|
||||||
|
{
|
||||||
|
var ptr = new ImGuiInputTextCallbackDataPtr(data);
|
||||||
|
|
||||||
|
switch (data->EventFlag)
|
||||||
|
{
|
||||||
|
case ImGuiInputTextFlags.CallbackCompletion:
|
||||||
|
var textBytes = new byte[data->BufTextLen];
|
||||||
|
Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen);
|
||||||
|
var text = Encoding.UTF8.GetString(textBytes);
|
||||||
|
|
||||||
|
var words = text.Split();
|
||||||
|
|
||||||
|
// We can't do any completion for parameters at the moment since it just calls into CommandHandler
|
||||||
|
if (words.Length > 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// TODO: Improve this, add partial completion
|
||||||
|
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484
|
||||||
|
var candidates = Dalamud.Instance.CommandManager.Commands.Where(x => x.Key.Contains("/" + words[0])).ToList();
|
||||||
|
if (candidates.Count > 0)
|
||||||
|
{
|
||||||
|
ptr.DeleteChars(0, ptr.BufTextLen);
|
||||||
|
ptr.InsertChars(0, candidates[0].Key.Replace("/", string.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ImGuiInputTextFlags.CallbackHistory:
|
||||||
|
var prevPos = this.historyPos;
|
||||||
|
|
||||||
|
if (ptr.EventKey == ImGuiKey.UpArrow)
|
||||||
|
{
|
||||||
|
if (this.historyPos == -1)
|
||||||
|
this.historyPos = this.history.Count - 1;
|
||||||
|
else if (this.historyPos > 0)
|
||||||
|
this.historyPos--;
|
||||||
|
}
|
||||||
|
else if (data->EventKey == ImGuiKey.DownArrow)
|
||||||
|
{
|
||||||
|
if (this.historyPos != -1)
|
||||||
|
{
|
||||||
|
if (++this.historyPos >= this.history.Count)
|
||||||
|
{
|
||||||
|
this.historyPos = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevPos != this.historyPos)
|
||||||
|
{
|
||||||
|
var historyStr = this.historyPos >= 0 ? this.history[this.historyPos] : string.Empty;
|
||||||
|
|
||||||
|
ptr.DeleteChars(0, ptr.BufTextLen);
|
||||||
|
ptr.InsertChars(0, historyStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddAndFilter(string line, LogEventLevel level, DateTimeOffset offset, bool isMultiline)
|
||||||
|
{
|
||||||
|
var entry = new LogEntry
|
||||||
|
{
|
||||||
|
IsMultiline = isMultiline,
|
||||||
|
Level = level,
|
||||||
|
Line = line,
|
||||||
|
TimeStamp = offset,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.logText.Add(entry);
|
||||||
|
|
||||||
|
if (!this.isFiltered)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.IsFilterApplicable(entry))
|
||||||
|
this.filteredLogText.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsFilterApplicable(LogEntry entry)
|
||||||
|
{
|
||||||
|
if (this.levelFilter.HasValue)
|
||||||
|
{
|
||||||
|
return entry.Level == this.levelFilter.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(this.textFilter))
|
||||||
|
return entry.Line.Contains(this.textFilter);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Refilter()
|
||||||
|
{
|
||||||
|
lock (this.renderLock)
|
||||||
|
{
|
||||||
|
this.filteredLogText = this.logText.Where(this.IsFilterApplicable).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetTextForLogEventLevel(LogEventLevel level) => level switch
|
||||||
|
{
|
||||||
|
LogEventLevel.Error => "ERR",
|
||||||
|
LogEventLevel.Verbose => "VRB",
|
||||||
|
LogEventLevel.Debug => "DBG",
|
||||||
|
LogEventLevel.Information => "INF",
|
||||||
|
LogEventLevel.Warning => "WRN",
|
||||||
|
LogEventLevel.Fatal => "FTL",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private uint GetColorForLogEventLevel(LogEventLevel level) => level switch
|
||||||
|
{
|
||||||
|
LogEventLevel.Error => 0x800000EE,
|
||||||
|
LogEventLevel.Verbose => 0x00000000,
|
||||||
|
LogEventLevel.Debug => 0x00000000,
|
||||||
|
LogEventLevel.Information => 0x00000000,
|
||||||
|
LogEventLevel.Warning => 0x8A0070EE,
|
||||||
|
LogEventLevel.Fatal => 0xFF00000A,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"),
|
||||||
|
};
|
||||||
|
|
||||||
|
private void OnLogLine(object sender, (string Line, LogEventLevel Level, DateTimeOffset Offset) logEvent)
|
||||||
|
{
|
||||||
|
this.HandleLogLine(logEvent.Line, logEvent.Level, logEvent.Offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LogEntry
|
||||||
|
{
|
||||||
|
public string Line { get; set; }
|
||||||
|
|
||||||
|
public LogEventLevel Level { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset TimeStamp { get; set; }
|
||||||
|
|
||||||
|
public bool IsMultiline { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
using Dalamud.Configuration.Internal;
|
|
||||||
using Dalamud.Game.Command;
|
|
||||||
using Dalamud.Interface.Colors;
|
|
||||||
using Dalamud.Interface.Windowing;
|
|
||||||
using ImGuiNET;
|
|
||||||
using Serilog;
|
|
||||||
using Serilog.Events;
|
|
||||||
|
|
||||||
namespace Dalamud.Interface.Internal.Windows
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The window that displays the Dalamud log file in-game.
|
|
||||||
/// </summary>
|
|
||||||
internal class LogWindow : Window, IDisposable
|
|
||||||
{
|
|
||||||
private readonly CommandManager commandManager;
|
|
||||||
private readonly DalamudConfiguration configuration;
|
|
||||||
private readonly List<(string Line, Vector4 Color)> logText = new();
|
|
||||||
private readonly object renderLock = new();
|
|
||||||
private bool autoScroll;
|
|
||||||
private bool openAtStartup;
|
|
||||||
|
|
||||||
private string commandText = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="LogWindow"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dalamud">The Dalamud instance.</param>
|
|
||||||
public LogWindow(Dalamud dalamud)
|
|
||||||
: base("Dalamud LOG")
|
|
||||||
{
|
|
||||||
this.commandManager = dalamud.CommandManager;
|
|
||||||
this.configuration = dalamud.Configuration;
|
|
||||||
this.autoScroll = this.configuration.LogAutoScroll;
|
|
||||||
this.openAtStartup = this.configuration.LogOpenAtStartup;
|
|
||||||
SerilogEventSink.Instance.OnLogLine += this.OnLogLine;
|
|
||||||
|
|
||||||
this.Size = new Vector2(500, 400);
|
|
||||||
this.SizeCondition = ImGuiCond.FirstUseEver;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dispose of managed and unmanaged resources.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
SerilogEventSink.Instance.OnLogLine -= this.OnLogLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clear the window of all log entries.
|
|
||||||
/// </summary>
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
lock (this.renderLock)
|
|
||||||
{
|
|
||||||
this.logText.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a single log line to the display.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="line">The line to add.</param>
|
|
||||||
/// <param name="color">The line coloring.</param>
|
|
||||||
public void AddLog(string line, Vector4 color)
|
|
||||||
{
|
|
||||||
lock (this.renderLock)
|
|
||||||
{
|
|
||||||
this.logText.Add((line, color));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void Draw()
|
|
||||||
{
|
|
||||||
// Options menu
|
|
||||||
if (ImGui.BeginPopup("Options"))
|
|
||||||
{
|
|
||||||
if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll))
|
|
||||||
{
|
|
||||||
this.configuration.LogAutoScroll = this.autoScroll;
|
|
||||||
this.configuration.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.Checkbox("Open at startup", ref this.openAtStartup))
|
|
||||||
{
|
|
||||||
this.configuration.LogOpenAtStartup = this.openAtStartup;
|
|
||||||
this.configuration.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main window
|
|
||||||
if (ImGui.Button("Options"))
|
|
||||||
ImGui.OpenPopup("Options");
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
var clear = ImGui.Button("Clear");
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
var copy = ImGui.Button("Copy");
|
|
||||||
|
|
||||||
ImGui.Text("Enter command: ");
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
ImGui.InputText("##commandbox", ref this.commandText, 255);
|
|
||||||
ImGui.SameLine();
|
|
||||||
|
|
||||||
if (ImGui.Button("Send"))
|
|
||||||
{
|
|
||||||
if (this.commandManager.ProcessCommand(this.commandText))
|
|
||||||
{
|
|
||||||
Log.Information("Command was dispatched.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Information($"Command {this.commandText} is not registered.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.HorizontalScrollbar);
|
|
||||||
|
|
||||||
if (clear)
|
|
||||||
{
|
|
||||||
this.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (copy)
|
|
||||||
{
|
|
||||||
ImGui.LogToClipboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
|
||||||
|
|
||||||
lock (this.renderLock)
|
|
||||||
{
|
|
||||||
foreach (var (line, color) in this.logText)
|
|
||||||
{
|
|
||||||
ImGui.TextColored(color, line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
|
|
||||||
if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY())
|
|
||||||
{
|
|
||||||
ImGui.SetScrollHereY(1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnLogLine(object sender, (string Line, LogEventLevel Level) logEvent)
|
|
||||||
{
|
|
||||||
var color = logEvent.Level switch
|
|
||||||
{
|
|
||||||
LogEventLevel.Error => ImGuiColors.DalamudRed,
|
|
||||||
LogEventLevel.Verbose => ImGuiColors.DalamudWhite,
|
|
||||||
LogEventLevel.Debug => ImGuiColors.DalamudWhite2,
|
|
||||||
LogEventLevel.Information => ImGuiColors.DalamudWhite,
|
|
||||||
LogEventLevel.Warning => ImGuiColors.DalamudOrange,
|
|
||||||
LogEventLevel.Fatal => ImGuiColors.DalamudRed,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(logEvent.Level.ToString(), "Invalid LogEventLevel"),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.AddLog(logEvent.Line, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue