using System; using System.Numerics; using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility.Raii; using KamiLib.Window; using Mappy.Controllers; using Mappy.Data; using FFXIVClientStructs.FFXIV.Client.UI.Agent; namespace Mappy.Windows; public class MinimapWindow : Window { public MinimapWindow() : base("HSMappy Minimap###HSMappyMinimap", new Vector2(200.0f, 200.0f)) { DisableWindowSounds = true; Flags |= ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoScrollWithMouse; } public override bool DrawConditions() => IntegrationsController.ShouldShowMinimap() && System.SystemConfig.ShowMinimap; public override void PreOpenCheck() { if (Service.ClientState is { IsLoggedIn: false } or { IsPvP: true }) IsOpen = false; } protected override unsafe void DrawContents() { var agent = AgentMap.Instance(); // Try loading from Lumina first so minimap can show without ever opening the area map if (!System.MapRenderer.HasMinimapCacheFor(agent->CurrentMapId) && agent->SelectedMapId != agent->CurrentMapId) System.MapRenderer.TryEnsureLuminaCacheFor(agent->CurrentMapId); var mapLoaded = agent->SelectedMapId == agent->CurrentMapId || System.MapRenderer.HasMinimapCacheFor(agent->CurrentMapId); if (!mapLoaded) { // Map data could not be loaded for this area (no Lumina path matched). Minimap shows automatically when data is available. const string hint = "Map unavailable for this area."; var textSize = ImGui.CalcTextSize(hint); var pos = (ImGui.GetWindowSize() - textSize) * 0.5f; ImGui.SetCursorPos(new Vector2(Math.Max(0, pos.X), Math.Max(20, pos.Y))); ImGui.TextColored(new Vector4(0.7f, 0.7f, 0.7f, 0.9f), hint); UpdateStyle(); UpdateSizePosition(); return; } UpdateStyle(); UpdateSizePosition(); // Compensate for window padding: draw the minimap child so it fills the full window (no black bands). var padding = ImGui.GetStyle().WindowPadding; var winSize = ImGui.GetWindowSize(); ImGui.SetCursorPos(new Vector2(-padding.X, -padding.Y)); var contentSize = winSize; if (contentSize.X <= 0 || contentSize.Y <= 0) return; using (ImRaii.PushStyle(ImGuiStyleVar.Alpha, System.SystemConfig.MinimapOpacity)) using (ImRaii.PushStyle(ImGuiStyleVar.ChildBorderSize, 0f)) using (ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, Vector2.Zero)) using (var child = ImRaii.Child("minimap_render", contentSize, false, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)) { if (child) { // Use window size so map fills the full window; renderer clamps draw position so map always covers the view. System.MapRenderer.DrawMinimapContents(contentSize); // Mouse wheel over minimap: zoom in/out, and consume wheel so the window doesn't scroll if (ImGui.IsItemHovered()) { var io = ImGui.GetIO(); var wheel = io.MouseWheel; if (wheel != 0) { var zoom = System.SystemConfig.MinimapZoom; zoom -= wheel * 0.012f; // Small step so zoom is incremental between max out (0.1) and max in (0.03) System.SystemConfig.MinimapZoom = Math.Clamp(zoom, 0.03f, 0.112f); SystemConfig.Save(); } // Consume wheel so the window doesn't scroll when at min/max zoom or when we handled it io.MouseWheel = 0f; io.MouseWheelH = 0f; } } } // Restore default padding for the next window is done in plugin Draw callback (PopStyleVar after all windows). } public override void OnOpen() { ImGui.SetWindowPos(System.SystemConfig.MinimapPosition); ImGui.SetWindowSize(new Vector2(System.SystemConfig.MinimapSize, System.SystemConfig.MinimapSize)); } private void UpdateStyle() { if (System.SystemConfig.MinimapLockPosition) Flags |= ImGuiWindowFlags.NoMove; else Flags &= ~ImGuiWindowFlags.NoMove; } private void UpdateSizePosition() { var config = System.SystemConfig; var windowPosition = ImGui.GetWindowPos(); var windowSize = ImGui.GetWindowSize(); var configSize = config.MinimapSize; // Size is config-only (set in Mappy settings); always apply config size to window. if (Math.Abs(windowSize.X - configSize) > 0.1f || Math.Abs(windowSize.Y - configSize) > 0.1f) ImGui.SetWindowSize(new Vector2(configSize, configSize)); if (!ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows)) { // Not focused: apply config position to window if (windowPosition != config.MinimapPosition) ImGui.SetWindowPos(config.MinimapPosition); } else { // Focused: save window position to config (size is changed only via settings) if (config.MinimapPosition != windowPosition) { config.MinimapPosition = windowPosition; SystemConfig.Save(); } } } }