0ad/binaries/data/mods/public/gui/session/minimap/MiniMapPanel.js
guerringuerrin a55f3ae0dc Add minimap position and scale configuration options and expanded mode toggle
- Toggle expanded minimap mode
- Choose between round and square minimap shapes
- `gui.session.minimap.position`, lets you switch between two positioning modes: panel-left and screen-left
- `gui.session.minimap.size`, change size between 1.0 and 2.0, default is 1.0.
2026-08-14 12:21:49 -03:00

334 lines
11 KiB
JavaScript

/**
* This class is concerned with managing the different elements of the minimap panel.
*/
class MiniMapPanel
{
constructor(playerViewControl, diplomacyColors, idleWorkerClasses)
{
this.loadConfig();
this.themes = {
"square": {
"panelSprite": "mapPanel",
"circleSprite": "stretched:session/minimap_circle_modern.png",
"flare": {
"baseSize": "4 4 113 119",
"anchors": ["left", "top"],
"sprite": "stretched:session/minimap-player-flare.png",
"sprite_over": "stretched:session/minimap-player-flare-highlight.png",
"sprite_disabled": "stretched:session/minimap-player-flare-disabled.png",
"mouse_event_mask": "texture:session/minimap-player-flare.png",
"observer_sprite": "stretched:session/minimap-observer-flare.png",
"observer_sprite_over": "stretched:session/minimap-observer-flare-highlight.png"
},
"diplomacy": {
"baseSize": "4 100%-121 119 100%-4",
"anchors": ["left", "bottom"],
"sprite_on": "stretched:session/minimap-diplomacy-on.png",
"sprite_off": "stretched:session/minimap-diplomacy-off.png",
"sprite_on_over": "stretched:session/minimap-diplomacy-on-highlight.png",
"sprite_off_over": "stretched:session/minimap-diplomacy-off-highlight.png",
"mouse_event_mask": "texture:session/minimap-diplomacy-on.png"
},
"idle": {
"baseSize": "100%-119 100%-121 100%-4 100%-4",
"anchors": ["right", "bottom"],
"sprite": "stretched:session/minimap-idle.png",
"sprite_over": "stretched:session/minimap-idle-highlight.png",
"sprite_disabled": "stretched:session/minimap-idle-disabled.png",
"mouse_event_mask": "texture:session/minimap-idle.png"
},
"idleText": {
"baseSize": "100%-119 100%-121 100%-3 100%-2",
"anchors": ["right", "bottom"],
"text_align": "right",
"text_valign": "bottom"
}
},
"round": {
"panelSprite": "",
"mapOffset": -10,
"circleSprite": "stretched:session/minimap_expanded_circle.png",
"flare": {
"baseSize": "100%-48 100%-28 100%-24 100%-4",
"sprite": "stretched:session/minimap-round-flare.png",
"sprite_over": "stretched:session/minimap-round-flare-highlight.png",
"sprite_disabled": "stretched:session/minimap-round-flare-disabled.png",
"mouse_event_mask": "texture:session/minimap-round-flare.png",
"observer_sprite": "stretched:session/minimap-round-observer-flare.png",
"observer_sprite_over": "stretched:session/minimap-round-observer-flare-highlight.png"
},
"diplomacy": {
"baseSize": "22 100%-28 46 100%-4",
"sprite_on": "stretched:session/minimap-round-diplomacy-on.png",
"sprite_off": "stretched:session/minimap-round-diplomacy-off.png",
"sprite_on_over": "stretched:session/minimap-round-diplomacy-on-highlight.png",
"sprite_off_over": "stretched:session/minimap-round-diplomacy-off-highlight.png",
"mouse_event_mask": "texture:session/minimap-round-diplomacy-off.png"
},
"idle": {
"baseSize": "100%-28 100%-46 100%-4 100%-22",
"sprite": "stretched:session/minimap-round-idle.png",
"sprite_over": "stretched:session/minimap-round-idle-highlight.png",
"sprite_disabled": "stretched:session/minimap-round-idle-disabled.png",
"mouse_event_mask": "texture:session/minimap-round-idle.png"
},
"idleText": {
"baseSize": "100%-28 100%-46 100%-4 100%-22",
"text_align": "center",
"text_valign": "center"
}
}
};
this.expandedState = {
"panelSprite": "",
"hideButtons": true,
"circleSprite": "stretched:session/minimap_expanded_circle.png",
"computeLayout": this.computeExpandedLayout.bind(this)
};
this.panel = Engine.GetGUIObjectByName("minimapPanel");
this.minimapCircle = Engine.GetGUIObjectByName("minimapCircle");
this.minimapMap = Engine.GetGUIObjectByName("minimap");
this.minimapBackgroundTexture = Engine.GetGUIObjectByName("minimapBackgroundTexture");
this.session = Engine.GetGUIObjectByName("session");
this.supplementalPanel = Engine.GetGUIObjectByName("supplementalSelectionDetails");
this.idleWorkerButton = new MiniMapIdleWorkerButton(playerViewControl, idleWorkerClasses);
this.totalNumberIdleWorkers = Engine.GetGUIObjectByName("totalNumberIdleWorkers");
this.flareButton = new MiniMapFlareButton(playerViewControl);
this.miniMap = new MiniMap();
this.diplomacyColors = diplomacyColors;
this.diplomacyColorsButton = new MiniMapDiplomacyColorsButton(diplomacyColors, this.themes[this.shape].diplomacy);
const panelRect = this.panel.getComputedSize();
this.defaultPanelWidth = panelRect.right - panelRect.left;
this.defaultPanelHeight = panelRect.bottom - panelRect.top;
playerViewControl.registerViewedPlayerChangeHandler(this.rebuild.bind(this));
registerHotkeyChangeHandler(this.rebuild.bind(this));
registerConfigChangeHandler(this.onConfigChange.bind(this));
Engine.SetGlobalHotkey("session.minimap.expand.toggle", "Press", this.toggleExpanded.bind(this));
this.expanded = false;
this.applyLayout();
}
loadConfig()
{
this.position = Engine.ConfigDB_GetValue("user", "gui.session.minimap.position");
this.sizeScale = Engine.ConfigDB_GetValue("user", "gui.session.minimap.size");
this.shape = Engine.ConfigDB_GetValue("user", "gui.session.minimap.shape") || "square";
}
get currentTheme()
{
if (this.expanded)
return this.expandedState;
return {
...this.themes[this.shape],
"hideButtons": false,
"computeLayout": this.computeNormalLayout.bind(this)
};
}
isObserver()
{
return g_IsObserver;
}
toggleExpanded()
{
this.expanded = !this.expanded;
this.applyLayout();
}
onConfigChange(changes)
{
if (![...changes].some(change =>
change == "gui.session.minimap.position" ||
change == "gui.session.minimap.size" ||
change == "gui.session.minimap.shape"))
return;
this.loadConfig();
this.applyLayout();
}
applyLayout()
{
const theme = this.currentTheme;
this.panel.size = theme.computeLayout();
this.panel.sprite = theme.panelSprite;
this.minimapCircle.sprite = theme.circleSprite;
this.minimapCircle.hidden = false;
const offset = theme.mapOffset ?? 0;
this.minimapBackgroundTexture.size = `4 ${4 + offset} 100%-4 100%-${4 - offset}`;
this.minimapCircle.size = `4 ${4 + offset} 100%-4 100%-${4 - offset}`;
this.minimapMap.size = `8 ${8 + offset} 100%-8 100%-${8 - offset}`;
this.refreshButtons(theme);
}
refreshButtons(theme)
{
const hidden = theme.hideButtons;
this.flareButton.flareButton.hidden = hidden;
this.idleWorkerButton.idleWorkerButton.hidden = hidden;
this.idleWorkerButton.totalNumberIdleWorkers.hidden = hidden;
this.diplomacyColorsButton.diplomacyColorsButton.hidden = hidden;
if (hidden)
return;
const scale = parseFloat(this.sizeScale) || 1.0;
const flareConfig = this.isObserver() && theme.flare?.observer_sprite ?
{ ...theme.flare, "sprite": theme.flare.observer_sprite, "sprite_over": theme.flare.observer_sprite_over } :
theme.flare;
this.applyButtonSprites(this.flareButton.flareButton, flareConfig);
this.applyScaledSize(this.flareButton.flareButton, theme.flare, scale);
this.applyButtonSprites(this.idleWorkerButton.idleWorkerButton, theme.idle);
this.applyScaledSize(this.idleWorkerButton.idleWorkerButton, theme.idle, scale);
this.applyScaledSize(this.totalNumberIdleWorkers, theme.idleText, scale);
if (theme.idleText.text_align !== undefined)
this.totalNumberIdleWorkers.text_align = theme.idleText.text_align;
if (theme.idleText.text_valign !== undefined)
this.totalNumberIdleWorkers.text_valign = theme.idleText.text_valign;
this.diplomacyColorsButton.theme = theme.diplomacy;
this.applyButtonSprites(this.diplomacyColorsButton.diplomacyColorsButton, theme.diplomacy);
this.diplomacyColorsButton.onDiplomacyColorsChange(this.diplomacyColors.isEnabled?.() ?? false);
this.applyScaledSize(this.diplomacyColorsButton.diplomacyColorsButton, theme.diplomacy, scale);
}
applyButtonSprites(button, config)
{
if (!config)
return;
if (config.mouse_event_mask !== undefined)
button.mouse_event_mask = config.mouse_event_mask;
if (!config.sprite)
return;
button.sprite = config.sprite;
if (config.sprite_over !== undefined)
button.sprite_over = config.sprite_over;
if (config.sprite_disabled !== undefined)
button.sprite_disabled = config.sprite_disabled;
}
applyScaledSize(element, cfg, scale)
{
if (!cfg)
return;
const original = cfg.baseSize.split(/\s+/);
const parts = original.map(part =>
{
const match = part.match(/^100%-(\d+)$/);
if (match)
return `100%-${Math.round(+match[1] * scale)}`;
if (/^-?\d+$/.test(part))
return `${Math.round(+part * scale)}`;
return part;
});
if (!cfg.anchors)
{
element.size = parts.join(" ");
return;
}
const scaled = { "left": parts[0], "top": parts[1], "right": parts[2], "bottom": parts[3] };
for (const anchor of cfg.anchors)
{
const idx = ["left", "top", "right", "bottom"].indexOf(anchor);
scaled[anchor] = original[idx];
}
element.size = [scaled.left, scaled.top, scaled.right, scaled.bottom].join(" ");
}
computeNormalLayout()
{
return this.position === "panel-left" ?
this.computePanelLeftLayout() :
this.computeScreenLeftLayout();
}
getScaledPanelSize()
{
const scale = parseFloat(this.sizeScale) || 1.0;
return {
"width": Math.round(this.defaultPanelWidth * scale),
"height": Math.round(this.defaultPanelHeight * scale)
};
}
computePanelLeftLayout()
{
const { width, height } = this.getScaledPanelSize();
const parentRect = this.panel.parent.getComputedSize();
const parentHeight = parentRect.bottom - parentRect.top;
return {
"left": this.defaultPanelWidth - width,
"top": parentHeight - height,
"right": this.defaultPanelWidth,
"bottom": parentHeight
};
}
computeScreenLeftLayout()
{
const { width, height } = this.getScaledPanelSize();
const windowSize = this.session.getComputedSize();
const parentRect = this.panel.parent.getComputedSize();
const screenTop = (windowSize.bottom - windowSize.top) - height;
const maxRight = this.supplementalPanel.getComputedSize().left;
let left = 0;
let right = width;
if (right > maxRight)
{
right = maxRight;
left = right - width;
}
return {
"left": left - parentRect.left,
"top": screenTop - parentRect.top,
"right": right - parentRect.left,
"bottom": screenTop + height - parentRect.top
};
}
computeExpandedLayout()
{
const windowSize = this.session.getComputedSize();
const scale = 0.55;
const marginLeft = 20;
const verticalOffset = -40;
const screenHeight = windowSize.bottom - windowSize.top;
const size = Math.floor(screenHeight * scale);
const screenTop = Math.floor((screenHeight - size) / 2) + verticalOffset;
const parentRect = this.panel.parent.getComputedSize();
const top = screenTop - parentRect.top;
const left = marginLeft - parentRect.left;
return {
"left": left,
"top": top,
"right": left + size,
"bottom": top + size
};
}
flare(target, playerID)
{
return this.miniMap.flare(target, playerID);
}
isMouseOverMiniMap()
{
return this.miniMap.isMouseOverMiniMap();
}
rebuild()
{
this.applyLayout();
this.setCivBackgroundTexture();
}
setCivBackgroundTexture()
{
const playerCiv = g_ViewedPlayer > 0 ? g_Players[g_ViewedPlayer].civ : "gaia";
this.minimapBackgroundTexture.sprite = `stretched:session/icons/bkg/background_circle_${playerCiv}.png`;
}
}