diff --git a/client/ExtraChat/Configuration.cs b/client/ExtraChat/Configuration.cs index 49f61fd..4604db1 100644 --- a/client/ExtraChat/Configuration.cs +++ b/client/ExtraChat/Configuration.cs @@ -40,6 +40,7 @@ internal class ConfigInfo { public Dictionary ChannelColors = new(); public Dictionary ChannelMarkers = new(); public Dictionary ChannelChannels = new(); + public int TutorialStep; internal string GetName(Guid id) => this.Channels.TryGetValue(id, out var channel) ? channel.Name diff --git a/client/ExtraChat/Ui/PluginUi.cs b/client/ExtraChat/Ui/PluginUi.cs index edbe9f1..f5bec3d 100644 --- a/client/ExtraChat/Ui/PluginUi.cs +++ b/client/ExtraChat/Ui/PluginUi.cs @@ -123,12 +123,28 @@ internal class PluginUi : IDisposable { ImGui.EndTabItem(); } + if (ImGui.BeginTabItem("Help")) { + this.DrawHelp(); + ImGui.EndTabItem(); + } + ImGui.EndTabBar(); } ImGui.End(); } + private void DrawHelp() { + ImGui.PushTextWrapPos(); + + if (ImGui.Button("Reset tutorial")) { + this.Plugin.ConfigInfo.TutorialStep = 0; + this.Plugin.SaveConfig(); + } + + ImGui.PopTextWrapPos(); + } + private void DrawSettings() { var anyChanged = false; @@ -434,6 +450,8 @@ internal class PluginUi : IDisposable { private string _rename = string.Empty; private void DrawList() { + var anyChanged = false; + ImGui.PushFont(UiBuilder.IconFont); var syncButton = ImGui.CalcTextSize(FontAwesomeIcon.Sync.ToIconString()).X @@ -450,12 +468,16 @@ internal class PluginUi : IDisposable { Task.Run(async () => await this.Plugin.Client.ListAll()); } + anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 1); + ImGui.SameLine(addOffset); if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString())) { ImGui.OpenPopup("create-channel-popup"); } + anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 0); + ImGui.PopFont(); if (ImGui.BeginPopup("create-channel-popup")) { @@ -480,8 +502,13 @@ internal class PluginUi : IDisposable { ImGui.EndPopup(); } + if (this.Plugin.Client.Channels.Count == 0) { + ImGui.TextUnformatted("You aren't in any linkshells yet. Try creating or joining one first."); + goto AfterTable; + } + if (ImGui.BeginTable("ecls-list", 2, ImGuiTableFlags.Resizable | ImGuiTableFlags.SizingFixedFit)) { - ImGui.TableSetupColumn("##channels", ImGuiTableColumnFlags.None); + ImGui.TableSetupColumn("##channels", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("##members", ImGuiTableColumnFlags.WidthStretch); ImGui.TableNextRow(); @@ -503,6 +530,7 @@ internal class PluginUi : IDisposable { if (ImGui.TableSetColumnIndex(0)) { if (ImGui.BeginChild("channel-list", childSize)) { + var first = true; foreach (var id in orderedChannels) { this.Plugin.ConfigInfo.Channels.TryGetValue(id, out var info); var name = info?.Name ?? "???"; @@ -522,6 +550,12 @@ internal class PluginUi : IDisposable { Task.Run(async () => await this.Plugin.Client.ListMembers(id)); } + if (first) { + first = false; + anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 2); + anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 3); + } + if (ImGui.BeginPopupContextItem()) { var invited = this.Plugin.Client.InvitedChannels.ContainsKey(id); if (invited) { @@ -663,6 +697,7 @@ internal class PluginUi : IDisposable { rank = Rank.Member; } + var first = true; foreach (var member in channel.Members) { if (!member.Online) { ImGui.PushStyleColor(ImGuiCol.Text, disabledColour); @@ -676,6 +711,12 @@ internal class PluginUi : IDisposable { } } + if (first) { + first = false; + anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 4); + anyChanged |= ImGuiUtil.Tutorial(this.Plugin, 5); + } + if (ImGui.BeginPopupContextItem($"{this._selectedChannel}-{member.Name}@{member.World}-context")) { if (rank == Rank.Admin) { if (member.Rank is not (Rank.Admin or Rank.Invited)) { @@ -734,5 +775,10 @@ internal class PluginUi : IDisposable { ImGui.EndTable(); } + + AfterTable: + if (anyChanged) { + this.Plugin.SaveConfig(); + } } } diff --git a/client/ExtraChat/Util/ImGuiUtil.cs b/client/ExtraChat/Util/ImGuiUtil.cs index a856626..679f943 100644 --- a/client/ExtraChat/Util/ImGuiUtil.cs +++ b/client/ExtraChat/Util/ImGuiUtil.cs @@ -1,3 +1,4 @@ +using System.Numerics; using System.Text; using Dalamud.Interface; using ImGuiNET; @@ -5,6 +6,56 @@ using ImGuiNET; namespace ExtraChat.Util; internal static class ImGuiUtil { + private static readonly Dictionary Tutorials = new() { + [0] = new[] { + "Create a linkshell", + + "You can use this button to create a new linkshell.", + "Alternatively, you can be invited by a friend to join a linkshell.", + }, + [1] = new[] { + "Refresh data", + + "This button will refresh all data about your linkshells.", + "Generally, you shouldn't need to press this. Clicking on a linkshell refreshes the member list.", + }, + [2] = new[] { + "Manage linkshells you're in", + + "Clicking on a linkshell in this list will show you its members in the pane to the right.", + "You can also right-click the linkshell name to open a menu with various options.", + "If you were invited to a linkshell, you can accept the invitation in this menu.", + }, + [3] = new[] { + "Talking in a linkshell", + + "The number displayed before the linkshell name is the linkshell's number.", + "You can change this number by right-clicking.", + "This number is used to determine the command you should use to talk in the linkshell.", + "For example, linkshell 1 would use the command /ecl1, linkshell 2 would use /ecl2, etc.", + null, + "Click on this linkshell to see the member list.", + }, + [4] = new[] { + "Members in a linkshell", + + "Inside the member list, each member is shown with an optional symbol indicating their rank.", + null, + "Admins have this symbol: ★", + "Moderators have this symbol: ☆", + "Normal members have no symbol.", + "Invited members have this symbol: ?", + null, + "Members also appear dimmed when they are offline.", + }, + [5] = new[] { + "Managing members in a linkshell", + + "Moderators and admins of a linkshell can right-click on members in the member list to open a menu with various options.", + "Many options require holding the Control key to enable so that they aren't accidentally activated.", + }, + }; + internal static bool IconButton(FontAwesomeIcon icon, string? id = null, string? tooltip = null) { var label = icon.ToIconString(); if (id != null) { @@ -62,6 +113,75 @@ internal static class ImGuiUtil { return selectable && confirmHeld; } + + internal static bool Tutorial(Plugin plugin, int step) { + var save = false; + ref var current = ref plugin.ConfigInfo.TutorialStep; + if (current < 0 || current != step) { + return save; + } + + if (!Tutorials.TryGetValue(step, out var strings)) { + return save; + } + + var max = Tutorials.Keys.Max(); + + const string popupId = "extrachat-tutorial"; + ImGui.OpenPopup(popupId); + + ImGui.GetForegroundDrawList().AddRect( + ImGui.GetItemRectMin() - new Vector2(2) * ImGuiHelpers.GlobalScale, + ImGui.GetItemRectMax() + new Vector2(2) * ImGuiHelpers.GlobalScale, + ImGui.GetColorU32(new Vector4(1, 0, 0, 1)) + ); + + ImGui.SetNextWindowPos(ImGui.GetItemRectMax() + new Vector2(2) * ImGuiHelpers.GlobalScale); + ImGui.SetNextWindowSize(new Vector2(350, 0) * ImGuiHelpers.GlobalScale); + if (!ImGui.BeginPopup(popupId, ImGuiWindowFlags.AlwaysAutoResize)) { + return save; + } + + ImGui.PushFont(UiBuilder.DefaultFont); + ImGui.PushTextWrapPos(); + + ImGui.TextUnformatted(strings[0]); + ImGui.Separator(); + + foreach (var body in strings[1..]) { + if (body == null) { + ImGui.Spacing(); + continue; + } + + ImGui.TextUnformatted(body); + } + + if (step == max) { + if (ImGui.Button("Finish")) { + current = -1; + save = true; + } + } else { + if (ImGui.Button("Next")) { + current += 1; + save = true; + } + } + + ImGui.SameLine(); + + if (ImGui.Button("Skip tutorial")) { + current = -1; + save = true; + } + + ImGui.PopTextWrapPos(); + ImGui.PopFont(); + ImGui.EndPopup(); + + return save; + } } [Flags]