feat: add basic tutorial system
This commit is contained in:
parent
400ff9e56a
commit
daa1be394a
|
@ -40,6 +40,7 @@ internal class ConfigInfo {
|
|||
public Dictionary<Guid, ushort> ChannelColors = new();
|
||||
public Dictionary<Guid, string> ChannelMarkers = new();
|
||||
public Dictionary<Guid, XivChatType> ChannelChannels = new();
|
||||
public int TutorialStep;
|
||||
|
||||
internal string GetName(Guid id) => this.Channels.TryGetValue(id, out var channel)
|
||||
? channel.Name
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<int, string?[]> 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]
|
||||
|
|
Loading…
Reference in New Issue