Bläddra i källkod

Migrate interface to Dalamud windowing system

SheepGoMeh 2 år sedan
förälder
incheckning
a070e19335

+ 0 - 423
HuntBuddy/Interface.cs

@@ -1,423 +0,0 @@
-using System.Linq;
-using System.Numerics;
-using System.Threading.Tasks;
-using Dalamud.Interface;
-using ImGuiNET;
-
-namespace HuntBuddy
-{
-	public class Interface
-	{
-		private readonly Plugin plugin;
-
-		public bool DrawInterface;
-		private bool drawConfigurationInterface;
-
-		public Interface(Plugin plugin)
-		{
-			this.plugin = plugin;
-		}
-
-		public unsafe bool Draw()
-		{
-			var draw = true;
-
-			ImGui.SetNextWindowSize(new Vector2(400 * ImGui.GetIO().FontGlobalScale, 500), ImGuiCond.Once);
-
-			var windowFlags = ImGuiWindowFlags.NoDocking;
-
-			if (this.plugin.Configuration.LockWindowPositions)
-			{
-				windowFlags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;
-			}
-
-			if (!ImGui.Begin($"{this.plugin.Name}", ref draw, windowFlags))
-			{
-				return draw;
-			}
-
-			if (!this.plugin.MobHuntEntriesReady)
-			{
-				ImGui.Text("Reloading data ...");
-				ImGui.End();
-				return draw;
-			}
-
-			if (Interface.IconButton(FontAwesomeIcon.Redo, "Reload"))
-			{
-				ImGui.End();
-				this.plugin.MobHuntEntriesReady = false;
-				Task.Run(() => this.plugin.ReloadData());
-				return draw;
-			}
-
-			if (ImGui.IsItemHovered())
-			{
-				ImGui.BeginTooltip();
-				ImGui.Text("Click this button to reload daily hunt data");
-				ImGui.EndTooltip();
-			}
-
-			ImGui.SameLine();
-
-			if (Interface.IconButton(FontAwesomeIcon.Cog, "Config"))
-			{
-				this.drawConfigurationInterface = !this.drawConfigurationInterface;
-			}
-
-			foreach (var expansionEntry in this.plugin.MobHuntEntries.Where(
-				         expansionEntry =>
-					         ImGui.TreeNode(expansionEntry.Key)))
-			{
-				foreach (var entry in expansionEntry.Value.Where(
-					         entry =>
-					         {
-						         var treeOpen = ImGui.TreeNodeEx(entry.Key.Value, ImGuiTreeNodeFlags.AllowItemOverlap);
-						         ImGui.SameLine();
-						         var killedCount = entry.Value.Count(
-							         x =>
-								         this.plugin.MobHuntStruct->CurrentKills[x.CurrentKillsOffset] ==
-								         x.NeededKills);
-
-						         if (killedCount != entry.Value.Count)
-						         {
-							         ImGui.Text($"({killedCount}/{entry.Value.Count})");
-						         }
-						         else
-						         {
-							         ImGui.TextColored(
-								         new Vector4(0f, 1f, 0f, 1f),
-								         $"({killedCount}/{entry.Value.Count})");
-						         }
-
-						         return treeOpen;
-					         }))
-				{
-					//ImGui.Indent();
-					foreach (var mobHuntEntry in entry.Value)
-					{
-						if (Location.Database.ContainsKey(mobHuntEntry.MobHuntId))
-						{
-							if (Interface.IconButton(FontAwesomeIcon.MapMarkerAlt, $"pin##{mobHuntEntry.MobHuntId}"))
-							{
-								Location.CreateMapMarker(
-									mobHuntEntry.TerritoryType,
-									mobHuntEntry.MapId,
-									mobHuntEntry.MobHuntId,
-									mobHuntEntry.Name,
-									Location.OpenType.None);
-							}
-
-							if (ImGui.IsItemHovered())
-							{
-								ImGui.BeginTooltip();
-								ImGui.Text("Place marker on the map");
-								ImGui.EndTooltip();
-							}
-
-							ImGui.SameLine();
-
-							if (Interface.IconButton(FontAwesomeIcon.MapMarkedAlt, $"open##{mobHuntEntry.MobHuntId}"))
-							{
-								var includeArea = this.plugin.Configuration.IncludeAreaOnMap;
-								if (ImGui.IsKeyDown(ImGuiKey.ModShift))
-								{
-									includeArea = !includeArea;
-								}
-								Location.CreateMapMarker(
-									mobHuntEntry.TerritoryType,
-									mobHuntEntry.MapId,
-									mobHuntEntry.MobHuntId,
-									mobHuntEntry.Name,
-									includeArea ? Location.OpenType.ShowOpen : Location.OpenType.MarkerOpen);
-							}
-
-							if (ImGui.IsItemHovered())
-							{
-								var color = ImGui.IsKeyDown(ImGuiKey.ModShift) ? new Vector4(0f, 0.7f, 0f, 1f) : new Vector4(0.7f, 0.7f, 0.7f, 1f);
-								ImGui.BeginTooltip();
-								if (this.plugin.Configuration.IncludeAreaOnMap)
-								{
-									ImGui.Text("Show hunt area on the map");
-									ImGui.TextColored(
-										color,
-										"Hold [SHIFT] to show the location only");
-								}
-								else
-								{
-									ImGui.Text("Show hunt location on the map");
-									ImGui.TextColored(
-										color,
-										"Hold [SHIFT] to include the area");
-								}
-								ImGui.EndTooltip();
-							}
-
-							ImGui.SameLine();
-
-							if (Plugin.TeleportConsumer?.IsAvailable == true)
-							{
-								if (Interface.IconButton(FontAwesomeIcon.StreetView, $"t##{mobHuntEntry.MobHuntId}"))
-								{
-									Location.TeleportToNearestAetheryte(
-										mobHuntEntry.TerritoryType,
-										mobHuntEntry.MapId,
-										mobHuntEntry.MobHuntId);
-								}
-
-								if (ImGui.IsItemHovered())
-								{
-									ImGui.BeginTooltip();
-									ImGui.Text("Teleport to nearest aetheryte");
-									ImGui.EndTooltip();
-								}
-
-								ImGui.SameLine();
-							}
-						}
-
-						var currentKills = this.plugin.MobHuntStruct->CurrentKills[mobHuntEntry.CurrentKillsOffset];
-						ImGui.Text(mobHuntEntry.Name);
-						if (ImGui.IsItemHovered())
-						{
-							ImGui.PushStyleColor(ImGuiCol.PopupBg, Vector4.Zero);
-							ImGui.BeginTooltip();
-							this.DrawHuntIcon(mobHuntEntry);
-							ImGui.PopStyleColor();
-							ImGui.EndTooltip();
-						}
-
-						ImGui.SameLine();
-						if (currentKills != mobHuntEntry.NeededKills)
-						{
-							ImGui.Text($"({currentKills}/{mobHuntEntry.NeededKills})");
-						}
-						else
-						{
-							ImGui.TextColored(
-								new Vector4(0f, 1f, 0f, 1f),
-								$"({currentKills}/{mobHuntEntry.NeededKills})");
-						}
-					}
-
-					//ImGui.Unindent();
-					ImGui.TreePop();
-				}
-
-				ImGui.TreePop();
-			}
-
-			ImGui.End();
-
-			if (this.drawConfigurationInterface)
-			{
-				this.DrawConfiguration();
-			}
-
-			return draw;
-		}
-
-		public unsafe void DrawLocalHunts()
-		{
-			if (!this.plugin.Configuration.ShowLocalHunts ||
-			    this.plugin.CurrentAreaMobHuntEntries.IsEmpty ||
-			    this.plugin.CurrentAreaMobHuntEntries.Count(
-				    x =>
-					    this.plugin.MobHuntStruct->CurrentKills[x.CurrentKillsOffset] == x.NeededKills) ==
-			    this.plugin.CurrentAreaMobHuntEntries.Count)
-			{
-				return;
-			}
-
-			ImGui.SetNextWindowSize(Vector2.Zero, ImGuiCond.Always);
-
-			var windowFlags = ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.NoDocking;
-
-			if (this.plugin.Configuration.HideLocalHuntBackground)
-			{
-				windowFlags |= ImGuiWindowFlags.NoBackground;
-			}
-
-			if (this.plugin.Configuration.LockWindowPositions)
-			{
-				windowFlags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;
-			}
-
-			if (!ImGui.Begin("Hunts in current area", windowFlags))
-			{
-				return;
-			}
-
-			foreach (var mobHuntEntry in this.plugin.CurrentAreaMobHuntEntries)
-			{
-				var currentKills = this.plugin.MobHuntStruct->CurrentKills[mobHuntEntry.CurrentKillsOffset];
-
-				if (this.plugin.Configuration.HideCompletedHunts && currentKills == mobHuntEntry.NeededKills)
-				{
-					continue;
-				}
-
-				if (Location.Database.ContainsKey(mobHuntEntry.MobHuntId))
-				{
-					if (Interface.IconButton(FontAwesomeIcon.MapMarkerAlt, $"pin##{mobHuntEntry.MobHuntId}"))
-					{
-						Location.CreateMapMarker(
-							mobHuntEntry.TerritoryType,
-							mobHuntEntry.MapId,
-							mobHuntEntry.MobHuntId,
-							mobHuntEntry.Name,
-							Location.OpenType.None);
-					}
-
-					if (ImGui.IsItemHovered())
-					{
-						ImGui.BeginTooltip();
-						ImGui.Text("Place marker on the map");
-						ImGui.EndTooltip();
-					}
-
-					ImGui.SameLine();
-
-					if (Interface.IconButton(FontAwesomeIcon.MapMarkedAlt, $"open##{mobHuntEntry.MobHuntId}"))
-					{
-						var includeArea = this.plugin.Configuration.IncludeAreaOnMap;
-						if (ImGui.IsKeyDown(ImGuiKey.ModShift))
-						{
-							includeArea = !includeArea;
-						}
-						Location.CreateMapMarker(
-							mobHuntEntry.TerritoryType,
-							mobHuntEntry.MapId,
-							mobHuntEntry.MobHuntId,
-							mobHuntEntry.Name,
-							includeArea ? Location.OpenType.ShowOpen : Location.OpenType.MarkerOpen);
-					}
-
-					if (ImGui.IsItemHovered())
-					{
-						var color = ImGui.IsKeyDown(ImGuiKey.ModShift) ? new Vector4(0f, 0.7f, 0f, 1f) : new Vector4(0.7f, 0.7f, 0.7f, 1f);
-						ImGui.BeginTooltip();
-						if (this.plugin.Configuration.IncludeAreaOnMap)
-						{
-							ImGui.Text("Show hunt area on the map");
-							ImGui.TextColored(
-								color,
-								"Hold [SHIFT] to show the location only");
-						}
-						else
-						{
-							ImGui.Text("Show hunt location on the map");
-							ImGui.TextColored(
-								color,
-								"Hold [SHIFT] to include the area");
-						}
-						ImGui.EndTooltip();
-					}
-
-					ImGui.SameLine();
-				}
-
-				ImGui.Text($"{mobHuntEntry.Name} ({currentKills}/{mobHuntEntry.NeededKills})");
-
-				if (!this.plugin.Configuration.ShowLocalHuntIcons)
-				{
-					continue;
-				}
-
-				this.DrawHuntIcon(mobHuntEntry);
-			}
-
-			ImGui.End();
-		}
-
-		private void DrawConfiguration()
-		{
-			ImGui.SetNextWindowSize(Vector2.Zero, ImGuiCond.Always);
-
-			var windowFlags = ImGuiWindowFlags.NoDocking;
-
-			if (this.plugin.Configuration.LockWindowPositions)
-			{
-				windowFlags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;
-			}
-
-			if (!ImGui.Begin($"{this.plugin.Name} settings", windowFlags))
-			{
-				return;
-			}
-
-			var save = false;
-
-			save |= ImGui.Checkbox("Lock plugin window positions", ref this.plugin.Configuration.LockWindowPositions);
-			save |= ImGui.Checkbox("Include hunt area on map by default", ref this.plugin.Configuration.IncludeAreaOnMap);
-			save |= ImGui.Checkbox("Show hunts in local area", ref this.plugin.Configuration.ShowLocalHunts);
-			save |= ImGui.Checkbox(
-				"Show icons of hunts in local area",
-				ref this.plugin.Configuration.ShowLocalHuntIcons);
-			save |= ImGui.Checkbox(
-				"Hide background of local hunts window",
-				ref this.plugin.Configuration.HideLocalHuntBackground);
-			save |= ImGui.Checkbox(
-				"Hide completed targets in local hunts window",
-				ref this.plugin.Configuration.HideCompletedHunts);
-			save |= ImGui.SliderFloat("Hunt icon scale", ref this.plugin.Configuration.IconScale, 0.2f, 2f, "%.2f");
-			if (ImGui.ColorEdit4("Hunt icon background colour", ref this.plugin.Configuration.IconBackgroundColour))
-			{
-				this.plugin.Configuration.IconBackgroundColourU32 =
-					ImGui.ColorConvertFloat4ToU32(this.plugin.Configuration.IconBackgroundColour);
-				save = true;
-			}
-
-			if (save)
-			{
-				this.plugin.Configuration.Save();
-			}
-
-			ImGui.End();
-		}
-
-		private static bool IconButton(FontAwesomeIcon icon, string? id = null)
-		{
-			ImGui.PushFont(UiBuilder.IconFont);
-
-			var text = icon.ToIconString();
-			if (id != null)
-			{
-				text += $"##{id}";
-			}
-
-			var result = ImGui.Button(text);
-
-			ImGui.PopFont();
-
-			return result;
-		}
-
-		private void DrawHuntIcon(MobHuntEntry mobHuntEntry)
-		{
-			var cursorPos = ImGui.GetCursorScreenPos();
-			var imageSize = mobHuntEntry.ExpansionId < 3 ? new Vector2(192f, 128f) : new Vector2(210f);
-			imageSize *= ImGui.GetIO().FontGlobalScale * this.plugin.Configuration.IconScale;
-
-			ImGui.InvisibleButton("canvas", imageSize);
-
-			var drawList = ImGui.GetWindowDrawList();
-			if (mobHuntEntry is { ExpansionId: 4, IsEliteMark: false }) // Endwalker uses circle for non elite mobs
-			{
-				drawList.AddCircleFilled(
-					cursorPos + (imageSize / 2f),
-					imageSize.X / 2f,
-					this.plugin.Configuration.IconBackgroundColourU32);
-			}
-			else
-			{
-				drawList.AddRectFilled(
-					cursorPos,
-					cursorPos + imageSize,
-					this.plugin.Configuration.IconBackgroundColourU32);
-			}
-
-			drawList.AddImage(mobHuntEntry.Icon.ImGuiHandle, cursorPos, cursorPos + imageSize);
-		}
-	}
-}

+ 24 - 11
HuntBuddy/Plugin.cs

@@ -6,6 +6,7 @@ using System.Globalization;
 using System.Linq;
 using System.Threading.Tasks;
 using Dalamud.Interface.Internal;
+using Dalamud.Interface.Windowing;
 using Dalamud.Plugin;
 using Dalamud.Plugin.Services;
 using Dalamud.Utility;
@@ -13,6 +14,7 @@ using Lumina.Excel.GeneratedSheets;
 using HuntBuddy.Attributes;
 using HuntBuddy.Ipc;
 using HuntBuddy.Structs;
+using HuntBuddy.Windows;
 using ImGuiNET;
 using Lumina.Extensions;
 
@@ -23,7 +25,6 @@ namespace HuntBuddy
 		public string Name => "Hunt Buddy";
 
 		private readonly PluginCommandManager<Plugin> commandManager;
-		private readonly Interface pluginInterface;
 		private ObtainedBillEnum lastState;
 		// Dictionary<string ExpansionName, Dictionary<KeyValuePair<uint MobTerritoryType, string MobTerritoryName>, List<MobHuntEntry MobsInZone>>>
 		public readonly Dictionary<string, Dictionary<KeyValuePair<uint, string>, List<MobHuntEntry>>> MobHuntEntries;
@@ -33,6 +34,11 @@ namespace HuntBuddy
 		public readonly Configuration Configuration;
 		public static TeleportConsumer? TeleportConsumer;
 		
+		private WindowSystem WindowSystem { get; }
+		
+		private MainWindow MainWindow { get; }
+		private ConfigurationWindow ConfigurationWindow { get; }
+
 		public static Plugin Instance { get; internal set; } = null!;
 
 		public Plugin(DalamudPluginInterface pluginInterface)
@@ -42,7 +48,6 @@ namespace HuntBuddy
 			pluginInterface.Create<Service>();
 
 			this.commandManager = new PluginCommandManager<Plugin>(this, Service.Commands);
-			this.pluginInterface = new Interface(this);
 			this.MobHuntEntries = new Dictionary<string, Dictionary<KeyValuePair<uint, string>, List<MobHuntEntry>>>();
 			this.CurrentAreaMobHuntEntries = new ConcurrentBag<MobHuntEntry>();
 			this.Configuration = (Configuration)(Service.PluginInterface.GetPluginConfig() ?? new Configuration());
@@ -55,11 +60,18 @@ namespace HuntBuddy
 					(MobHuntStruct*)Service.SigScanner.GetStaticAddressFromSig(
 						"48 8D 0D ?? ?? ?? ?? 8B D8 0F B6 52");
 			}
+			
+			this.MainWindow = new MainWindow();
+			this.ConfigurationWindow = new ConfigurationWindow();
+			
+			this.WindowSystem = new WindowSystem("HuntBuddy");
+			this.WindowSystem.AddWindow(this.MainWindow);
+			this.WindowSystem.AddWindow(new LocalHuntsWindow());
+			this.WindowSystem.AddWindow(this.ConfigurationWindow);
 
 			Plugin.TeleportConsumer = new TeleportConsumer();
 			Service.ClientState.TerritoryChanged += this.ClientStateOnTerritoryChanged;
-			Service.PluginInterface.UiBuilder.Draw += this.DrawInterface;
-			Service.PluginInterface.UiBuilder.Draw += this.pluginInterface.DrawLocalHunts;
+			Service.PluginInterface.UiBuilder.Draw += this.WindowSystem.Draw;
 			Service.PluginInterface.UiBuilder.OpenConfigUi += this.OpenConfigUi;
 			Service.Framework.Update += this.FrameworkOnUpdate;
 		}
@@ -88,14 +100,14 @@ namespace HuntBuddy
 			}
 		}
 
-		private void OpenConfigUi()
+		private void DrawInterface()
 		{
-			this.pluginInterface.DrawInterface = !this.pluginInterface.DrawInterface;
+			this.MainWindow.Toggle();
 		}
 
-		private void DrawInterface()
+		public void OpenConfigUi()
 		{
-			this.pluginInterface.DrawInterface = this.pluginInterface.DrawInterface && this.pluginInterface.Draw();
+			this.ConfigurationWindow.Toggle();
 		}
 
 		private void Dispose(bool disposing)
@@ -108,9 +120,10 @@ namespace HuntBuddy
 			this.MobHuntEntriesReady = false;
 			Service.ClientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged;
 			Service.Framework.Update -= this.FrameworkOnUpdate;
-			Service.PluginInterface.UiBuilder.Draw -= this.DrawInterface;
-			Service.PluginInterface.UiBuilder.Draw -= this.pluginInterface.DrawLocalHunts;
+			Service.PluginInterface.UiBuilder.Draw -= this.WindowSystem.Draw;
 			Service.PluginInterface.UiBuilder.OpenConfigUi -= this.OpenConfigUi;
+			
+			this.WindowSystem.RemoveAllWindows();
 
 			this.commandManager.Dispose();
 		}
@@ -211,7 +224,7 @@ namespace HuntBuddy
 						}
 						break;
 					default:
-						this.OpenConfigUi();
+						this.DrawInterface();
 						break;
 				}
 			}

+ 73 - 0
HuntBuddy/Utils/InterfaceUtil.cs

@@ -0,0 +1,73 @@
+using System.Numerics;
+using Dalamud.Interface;
+using ImGuiNET;
+
+namespace HuntBuddy.Utils;
+
+/// <summary>
+/// Interface utilities class.
+/// </summary>
+public static class InterfaceUtil
+{
+    /// <summary>
+    /// Draws hunt icons from game images.
+    /// </summary>
+    /// <param name="mobHuntEntry"><see cref="MobHuntEntry"/> containing relevant information.</param>
+    public static void DrawHuntIcon(MobHuntEntry mobHuntEntry)
+    {
+        var cursorPos = ImGui.GetCursorScreenPos();
+        var imageSize = mobHuntEntry.ExpansionId < 3 ? new Vector2(192f, 128f) : new Vector2(210f);
+        imageSize *= ImGui.GetIO().FontGlobalScale * Plugin.Instance.Configuration.IconScale;
+
+        ImGui.InvisibleButton("canvas", imageSize);
+
+        var drawList = ImGui.GetWindowDrawList();
+        if (mobHuntEntry is { ExpansionId: 4, IsEliteMark: false }) // Endwalker uses circle for non elite mobs
+        {
+            drawList.AddCircleFilled(
+                cursorPos + (imageSize / 2f),
+                imageSize.X / 2f,
+                Plugin.Instance.Configuration.IconBackgroundColourU32);
+        }
+        else
+        {
+            drawList.AddRectFilled(
+                cursorPos,
+                cursorPos + imageSize,
+                Plugin.Instance.Configuration.IconBackgroundColourU32);
+        }
+
+        drawList.AddImage(mobHuntEntry.Icon.ImGuiHandle, cursorPos, cursorPos + imageSize);
+    }
+
+    /// <summary>
+    /// Renders a button with an icon.
+    /// </summary>
+    /// <param name="icon">Desired <see cref="FontAwesomeIcon"/> to be rendered.</param>
+    /// <param name="id">Button ID.</param>
+    /// <returns>True if pressed.</returns>
+    public static bool IconButton(FontAwesomeIcon icon, string? id)
+    {
+        ImGui.PushFont(UiBuilder.IconFont);
+
+        var text = icon.ToIconString();
+
+        if (id != null)
+        {
+            text += $"##{id}";
+        }
+
+        var result = ImGui.Button(text);
+
+        ImGui.PopFont();
+
+        return result;
+    }
+
+    /// <summary>
+    /// Renders a button with an icon.
+    /// </summary>
+    /// <param name="icon">Desired <see cref="FontAwesomeIcon"/> to be rendered.</param>
+    /// <returns>True if pressed.</returns>
+    public static bool IconButton(FontAwesomeIcon icon) => IconButton(icon, null);
+}

+ 55 - 0
HuntBuddy/Windows/ConfigurationWindow.cs

@@ -0,0 +1,55 @@
+using System.Numerics;
+using Dalamud.Interface.Windowing;
+using ImGuiNET;
+
+namespace HuntBuddy.Windows;
+
+/// <summary>
+/// Configuration window.
+/// </summary>
+public class ConfigurationWindow : Window
+{
+    public ConfigurationWindow() : base(
+        $"{Plugin.Instance.Name} configuration",
+        ImGuiWindowFlags.NoDocking,
+        true)
+    {
+        this.Size = Vector2.Zero;
+        this.SizeCondition = ImGuiCond.Always;
+        
+        if (Plugin.Instance.Configuration.LockWindowPositions)
+        {
+            this.Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;
+        }
+    }
+
+    public override void Draw()
+    {
+        var save = false;
+
+        save |= ImGui.Checkbox("Lock plugin window positions", ref Plugin.Instance.Configuration.LockWindowPositions);
+        save |= ImGui.Checkbox("Include hunt area on map by default", ref Plugin.Instance.Configuration.IncludeAreaOnMap);
+        save |= ImGui.Checkbox("Show hunts in local area", ref Plugin.Instance.Configuration.ShowLocalHunts);
+        save |= ImGui.Checkbox(
+            "Show icons of hunts in local area",
+            ref Plugin.Instance.Configuration.ShowLocalHuntIcons);
+        save |= ImGui.Checkbox(
+            "Hide background of local hunts window",
+            ref Plugin.Instance.Configuration.HideLocalHuntBackground);
+        save |= ImGui.Checkbox(
+            "Hide completed targets in local hunts window",
+            ref Plugin.Instance.Configuration.HideCompletedHunts);
+        save |= ImGui.SliderFloat("Hunt icon scale", ref Plugin.Instance.Configuration.IconScale, 0.2f, 2f, "%.2f");
+        if (ImGui.ColorEdit4("Hunt icon background colour", ref Plugin.Instance.Configuration.IconBackgroundColour))
+        {
+            Plugin.Instance.Configuration.IconBackgroundColourU32 =
+                ImGui.ColorConvertFloat4ToU32(Plugin.Instance.Configuration.IconBackgroundColour);
+            save = true;
+        }
+
+        if (save)
+        {
+            Plugin.Instance.Configuration.Save();
+        }
+    }
+}

+ 128 - 0
HuntBuddy/Windows/LocalHuntsWindow.cs

@@ -0,0 +1,128 @@
+using System.Linq;
+using System.Numerics;
+using Dalamud.Interface;
+using Dalamud.Interface.Windowing;
+using HuntBuddy.Utils;
+using ImGuiNET;
+
+namespace HuntBuddy.Windows;
+
+/// <summary>
+/// Local hunts window.
+/// </summary>
+public class LocalHuntsWindow : Window
+{
+    public LocalHuntsWindow() : base(
+        $"{Plugin.Instance.Name}",
+        ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.NoDocking,
+        true)
+    {
+        this.Size = Vector2.Zero;
+        this.SizeCondition = ImGuiCond.Always;
+
+        if (Plugin.Instance.Configuration.HideLocalHuntBackground)
+        {
+            this.Flags |= ImGuiWindowFlags.NoBackground;
+        }
+
+        if (Plugin.Instance.Configuration.LockWindowPositions)
+        {
+            this.Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;
+        }
+
+        this.IsOpen = true;
+    }
+
+    public override unsafe bool DrawConditions() =>
+        Plugin.Instance.Configuration.ShowLocalHunts &&
+        !Plugin.Instance.CurrentAreaMobHuntEntries.IsEmpty &&
+        Plugin.Instance.CurrentAreaMobHuntEntries.Count(
+            x => Plugin.Instance.MobHuntStruct->CurrentKills[x.CurrentKillsOffset] == x.NeededKills) !=
+        Plugin.Instance.CurrentAreaMobHuntEntries.Count;
+
+    public override unsafe void Draw()
+    {
+        foreach (var mobHuntEntry in Plugin.Instance.CurrentAreaMobHuntEntries)
+        {
+            var currentKills = Plugin.Instance.MobHuntStruct->CurrentKills[mobHuntEntry.CurrentKillsOffset];
+
+            if (Plugin.Instance.Configuration.HideCompletedHunts && currentKills == mobHuntEntry.NeededKills)
+            {
+                continue;
+            }
+
+            if (Location.Database.ContainsKey(mobHuntEntry.MobHuntId))
+            {
+                if (InterfaceUtil.IconButton(FontAwesomeIcon.MapMarkerAlt, $"pin##{mobHuntEntry.MobHuntId}"))
+                {
+                    Location.CreateMapMarker(
+                        mobHuntEntry.TerritoryType,
+                        mobHuntEntry.MapId,
+                        mobHuntEntry.MobHuntId,
+                        mobHuntEntry.Name,
+                        Location.OpenType.None);
+                }
+
+                if (ImGui.IsItemHovered())
+                {
+                    ImGui.BeginTooltip();
+                    ImGui.Text("Place marker on the map");
+                    ImGui.EndTooltip();
+                }
+
+                ImGui.SameLine();
+
+                if (InterfaceUtil.IconButton(FontAwesomeIcon.MapMarkedAlt, $"open##{mobHuntEntry.MobHuntId}"))
+                {
+                    var includeArea = Plugin.Instance.Configuration.IncludeAreaOnMap;
+                    if (ImGui.IsKeyDown(ImGuiKey.ModShift))
+                    {
+                        includeArea = !includeArea;
+                    }
+
+                    Location.CreateMapMarker(
+                        mobHuntEntry.TerritoryType,
+                        mobHuntEntry.MapId,
+                        mobHuntEntry.MobHuntId,
+                        mobHuntEntry.Name,
+                        includeArea ? Location.OpenType.ShowOpen : Location.OpenType.MarkerOpen);
+                }
+
+                if (ImGui.IsItemHovered())
+                {
+                    var color = ImGui.IsKeyDown(ImGuiKey.ModShift)
+                        ? new Vector4(0f, 0.7f, 0f, 1f)
+                        : new Vector4(0.7f, 0.7f, 0.7f, 1f);
+                    ImGui.BeginTooltip();
+                    if (Plugin.Instance.Configuration.IncludeAreaOnMap)
+                    {
+                        ImGui.Text("Show hunt area on the map");
+                        ImGui.TextColored(
+                            color,
+                            "Hold [SHIFT] to show the location only");
+                    }
+                    else
+                    {
+                        ImGui.Text("Show hunt location on the map");
+                        ImGui.TextColored(
+                            color,
+                            "Hold [SHIFT] to include the area");
+                    }
+
+                    ImGui.EndTooltip();
+                }
+
+                ImGui.SameLine();
+            }
+
+            ImGui.Text($"{mobHuntEntry.Name} ({currentKills}/{mobHuntEntry.NeededKills})");
+
+            if (!Plugin.Instance.Configuration.ShowLocalHuntIcons)
+            {
+                continue;
+            }
+
+            InterfaceUtil.DrawHuntIcon(mobHuntEntry);
+        }
+    }
+}

+ 207 - 0
HuntBuddy/Windows/MainWindow.cs

@@ -0,0 +1,207 @@
+using System.Linq;
+using System.Numerics;
+using System.Threading.Tasks;
+using Dalamud.Interface;
+using Dalamud.Interface.Windowing;
+using ImGuiNET;
+using HuntBuddy.Utils;
+
+namespace HuntBuddy.Windows;
+
+/// <summary>
+/// Main plugin window.
+/// </summary>
+public class MainWindow : Window
+{
+    public MainWindow() : base(
+        $"{Plugin.Instance.Name}",
+        ImGuiWindowFlags.NoDocking,
+        true)
+    {
+        this.Size = new Vector2(400 * ImGui.GetIO().FontGlobalScale, 500);
+        this.SizeCondition = ImGuiCond.Once;
+
+        if (Plugin.Instance.Configuration.LockWindowPositions)
+        {
+            this.Flags |= ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove;
+        }
+    }
+
+    public override unsafe void Draw()
+    {
+        if (!Plugin.Instance.MobHuntEntriesReady)
+        {
+            ImGui.Text("Reloading data ...");
+            ImGui.End();
+            return;
+        }
+
+        if (InterfaceUtil.IconButton(FontAwesomeIcon.Redo, "Reload"))
+        {
+            ImGui.End();
+            Plugin.Instance.MobHuntEntriesReady = false;
+            Task.Run(Plugin.Instance.ReloadData);
+            return;
+        }
+
+        if (ImGui.IsItemHovered())
+        {
+            ImGui.BeginTooltip();
+            ImGui.Text("Click this button to reload daily hunt data");
+            ImGui.EndTooltip();
+        }
+
+        ImGui.SameLine();
+
+        if (InterfaceUtil.IconButton(FontAwesomeIcon.Cog, "Config"))
+        {
+            Plugin.Instance.OpenConfigUi();
+        }
+
+        foreach (var expansionEntry in Plugin.Instance.MobHuntEntries.Where(
+                     expansionEntry =>
+                         ImGui.TreeNode(expansionEntry.Key)))
+        {
+            foreach (var entry in expansionEntry.Value.Where(
+                         entry =>
+                         {
+                             var treeOpen = ImGui.TreeNodeEx(entry.Key.Value, ImGuiTreeNodeFlags.AllowItemOverlap);
+                             ImGui.SameLine();
+                             var killedCount = entry.Value.Count(
+                                 x =>
+                                     Plugin.Instance.MobHuntStruct->CurrentKills[x.CurrentKillsOffset] ==
+                                     x.NeededKills);
+
+                             if (killedCount != entry.Value.Count)
+                             {
+                                 ImGui.Text($"({killedCount}/{entry.Value.Count})");
+                             }
+                             else
+                             {
+                                 ImGui.TextColored(
+                                     new Vector4(0f, 1f, 0f, 1f),
+                                     $"({killedCount}/{entry.Value.Count})");
+                             }
+
+                             return treeOpen;
+                         }))
+            {
+                foreach (var mobHuntEntry in entry.Value)
+                {
+                    if (Location.Database.ContainsKey(mobHuntEntry.MobHuntId))
+                    {
+                        if (InterfaceUtil.IconButton(FontAwesomeIcon.MapMarkerAlt, $"pin##{mobHuntEntry.MobHuntId}"))
+                        {
+                            Location.CreateMapMarker(
+                                mobHuntEntry.TerritoryType,
+                                mobHuntEntry.MapId,
+                                mobHuntEntry.MobHuntId,
+                                mobHuntEntry.Name,
+                                Location.OpenType.None);
+                        }
+
+                        if (ImGui.IsItemHovered())
+                        {
+                            ImGui.BeginTooltip();
+                            ImGui.Text("Place marker on the map");
+                            ImGui.EndTooltip();
+                        }
+
+                        ImGui.SameLine();
+
+                        if (InterfaceUtil.IconButton(FontAwesomeIcon.MapMarkedAlt, $"open##{mobHuntEntry.MobHuntId}"))
+                        {
+                            var includeArea = Plugin.Instance.Configuration.IncludeAreaOnMap;
+                            if (ImGui.IsKeyDown(ImGuiKey.ModShift))
+                            {
+                                includeArea = !includeArea;
+                            }
+
+                            Location.CreateMapMarker(
+                                mobHuntEntry.TerritoryType,
+                                mobHuntEntry.MapId,
+                                mobHuntEntry.MobHuntId,
+                                mobHuntEntry.Name,
+                                includeArea ? Location.OpenType.ShowOpen : Location.OpenType.MarkerOpen);
+                        }
+
+                        if (ImGui.IsItemHovered())
+                        {
+                            var color = ImGui.IsKeyDown(ImGuiKey.ModShift)
+                                ? new Vector4(0f, 0.7f, 0f, 1f)
+                                : new Vector4(0.7f, 0.7f, 0.7f, 1f);
+                            ImGui.BeginTooltip();
+                            if (Plugin.Instance.Configuration.IncludeAreaOnMap)
+                            {
+                                ImGui.Text("Show hunt area on the map");
+                                ImGui.TextColored(
+                                    color,
+                                    "Hold [SHIFT] to show the location only");
+                            }
+                            else
+                            {
+                                ImGui.Text("Show hunt location on the map");
+                                ImGui.TextColored(
+                                    color,
+                                    "Hold [SHIFT] to include the area");
+                            }
+
+                            ImGui.EndTooltip();
+                        }
+
+                        ImGui.SameLine();
+
+                        if (Plugin.TeleportConsumer?.IsAvailable == true)
+                        {
+                            if (InterfaceUtil.IconButton(FontAwesomeIcon.StreetView, $"t##{mobHuntEntry.MobHuntId}"))
+                            {
+                                Location.TeleportToNearestAetheryte(
+                                    mobHuntEntry.TerritoryType,
+                                    mobHuntEntry.MapId,
+                                    mobHuntEntry.MobHuntId);
+                            }
+
+                            if (ImGui.IsItemHovered())
+                            {
+                                ImGui.BeginTooltip();
+                                ImGui.Text("Teleport to nearest aetheryte");
+                                ImGui.EndTooltip();
+                            }
+
+                            ImGui.SameLine();
+                        }
+                    }
+
+                    var currentKills = Plugin.Instance.MobHuntStruct->CurrentKills[mobHuntEntry.CurrentKillsOffset];
+                    ImGui.Text(mobHuntEntry.Name);
+                    if (ImGui.IsItemHovered())
+                    {
+                        ImGui.PushStyleColor(ImGuiCol.PopupBg, Vector4.Zero);
+                        ImGui.BeginTooltip();
+                        InterfaceUtil.DrawHuntIcon(mobHuntEntry);
+                        ImGui.PopStyleColor();
+                        ImGui.EndTooltip();
+                    }
+
+                    ImGui.SameLine();
+                    if (currentKills != mobHuntEntry.NeededKills)
+                    {
+                        ImGui.Text($"({currentKills}/{mobHuntEntry.NeededKills})");
+                    }
+                    else
+                    {
+                        ImGui.TextColored(
+                            new Vector4(0f, 1f, 0f, 1f),
+                            $"({currentKills}/{mobHuntEntry.NeededKills})");
+                    }
+                }
+
+                ImGui.TreePop();
+            }
+
+            ImGui.TreePop();
+        }
+
+        ImGui.End();
+    }
+}