Source: gui/session/unit_commands.js

// The number of currently visible buttons (used to optimise showing/hiding)
var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Alert": 0, "Barter": 0, "Construction": 0, "Command": 0, "AllyCommand": 0, "Stance": 0, "Gate": 0, "Pack": 0};

/**
 * Set the position of a panel object according to the index,
 * from left to right, from top to bottom.
 * Will wrap around to subsequent rows if the index
 * is larger than rowLength.
 */
function setPanelObjectPosition(object, index, rowLength, vMargin = 1, hMargin = 1)
{
	var size = object.size;
	// horizontal position
	var oWidth = size.right - size.left;
	var hIndex = index % rowLength;
	size.left = hIndex * (oWidth + vMargin);
	size.right = size.left + oWidth;
	// vertical position
	var oHeight = size.bottom - size.top;
	var vIndex = Math.floor(index / rowLength);
	size.top = vIndex * (oHeight + hMargin);
	size.bottom = size.top + oHeight;
	object.size = size;
}

/**
 * Helper function for updateUnitCommands; sets up "unit panels"
 * (i.e. panels with rows of icons) for the currently selected unit.
 *
 * @param guiName Short identifier string of this panel. See g_SelectionPanels.
 * @param unitEntState Entity state of the selected unit with the lowest id.
 * @param payerState Player state
 */
function setupUnitPanel(guiName, unitEntState, playerState)
{
	if (!g_SelectionPanels[guiName])
	{
		error("unknown guiName used '" + guiName + "'");
		return;
	}
	let selection = g_Selection.toList();

	var items = g_SelectionPanels[guiName].getItems(unitEntState, selection);

	if (!items || !items.length)
		return;

	var numberOfItems = items.length;
	var garrisonGroups = new EntityGroups();

	// Determine how many buttons there should be
	var maxNumberOfItems = g_SelectionPanels[guiName].getMaxNumberOfItems();
	if (maxNumberOfItems < numberOfItems)
		numberOfItems = maxNumberOfItems;

	var rowLength = g_SelectionPanels[guiName].rowLength || 8;

	if (g_SelectionPanels[guiName].resizePanel)
		g_SelectionPanels[guiName].resizePanel(numberOfItems, rowLength);

	// Make buttons
	for (let i = 0; i < numberOfItems; ++i)
	{
		var item = items[i];

		// If a tech has been researched it leaves an empty slot
		if (!item)
		{
			if (g_SelectionPanels[guiName].hideItem)
				g_SelectionPanels[guiName].hideItem(i, rowLength);
			continue;
		}

		// STANDARD DATA
		// add standard data
		var data = {
			"i": i,
			"item": item,
			"selection": selection,
			"playerState": playerState,
			"unitEntState": unitEntState,
			"rowLength": rowLength,
			"numberOfItems": numberOfItems,
		};

		// add standard gui objects to the data
		// depending on the actual XML, some of this may be undefined
		data.button = Engine.GetGUIObjectByName("unit"+guiName+"Button["+i+"]");
		data.icon = Engine.GetGUIObjectByName("unit"+guiName+"Icon["+i+"]");
		data.guiSelection = Engine.GetGUIObjectByName("unit"+guiName+"Selection["+i+"]");
		data.countDisplay = Engine.GetGUIObjectByName("unit"+guiName+"Count["+i+"]");


		// DEFAULTS
		if (data.button)
		{
			data.button.hidden = false;
			data.button.enabled = true;
			data.button.tooltip = "";
			data.button.caption = "";
		}

		// GENERAL DATA
		// add general data, and a chance to abort on faulty data
		if (g_SelectionPanels[guiName].addData)
		{
			var success = g_SelectionPanels[guiName].addData(data);
			if (!success)
				continue; // ignore faulty data
		}

		// SET CONTENT
		// run all content setters
		for (var f in g_SelectionPanels[guiName])
		{
			if (f.match(/^set/))
				g_SelectionPanels[guiName][f](data);
		}

		// Special case: position
		if (!g_SelectionPanels[guiName].setPosition)
			setPanelObjectPosition(data.button, i, rowLength);

		// TODO: we should require all entities to have icons, so this case never occurs
		if (data.icon && !data.icon.sprite)
			data.icon.sprite = "bkFillBlack";

	}

	// Hide any buttons we're no longer using
	for (let i = numberOfItems; i < g_unitPanelButtons[guiName]; ++i)
		if (g_SelectionPanels[guiName].hideItem)
			g_SelectionPanels[guiName].hideItem(i, rowLength);
		else
			Engine.GetGUIObjectByName("unit"+guiName+"Button["+i+"]").hidden = true;

	// remember the number of items
	g_unitPanelButtons[guiName] = numberOfItems;
	g_SelectionPanels[guiName].used = true;
}

/**
 * Updates the selection panels where buttons are supposed to
 * depend on the context.
 * Runs in the main session loop via updateSelectionDetails().
 * Delegates to setupUnitPanel to set up individual subpanels,
 * appropriately activated depending on the selected unit's state.
 *
 * @param entState Entity state of the selected unit with the lowest id.
 * @param supplementalDetailsPanel Reference to the
 *        "supplementalSelectionDetails" GUI Object
 * @param commandsPanel Reference to the "commandsPanel" GUI Object
 * @param selection Array of currently selected entity IDs.
 */
function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection)
{
	for (let panel in g_SelectionPanels)
		g_SelectionPanels[panel].used = false;

	// If the selection is friendly units, add the command panels

	// Get player state to check some constraints
	// e.g. presence of a hero or build limits
	let playerStates = GetSimState().players;
	let playerState = playerStates[Engine.GetPlayerID()];

	if (controlsPlayer(entState.player) || g_IsObserver)
	{
		for (var guiName of g_PanelsOrder)
		{
			if (
				g_SelectionPanels[guiName].conflictsWith &&
				g_SelectionPanels[guiName].conflictsWith.some(p => g_SelectionPanels[p].used)
			)
				continue;

			setupUnitPanel(guiName, entState, playerStates[entState.player]);
		}

		supplementalDetailsPanel.hidden = false;
		commandsPanel.hidden = false;
	}
	else if (playerState.isMutualAlly[entState.player]) // owned by allied player
	{
		// TODO if there's a second panel needed for a different player
		// we should consider adding the players list to g_SelectionPanels
		setupUnitPanel("Garrison", entState, playerState);
		setupUnitPanel("AllyCommand", entState, playerState);

		supplementalDetailsPanel.hidden = !g_SelectionPanels.Garrison.used;

		commandsPanel.hidden = true;
	}
	else // owned by another player
	{
		supplementalDetailsPanel.hidden = true;
		commandsPanel.hidden = true;
	}

	// Hides / unhides Unit Panels (panels should be grouped by type, not by order, but we will leave that for another time)
	for (var panelName in g_SelectionPanels)
		Engine.GetGUIObjectByName("unit" + panelName + "Panel").hidden = !g_SelectionPanels[panelName].used;
}

// Force hide commands panels
function hideUnitCommands()
{
	for (var panelName in g_SelectionPanels)
		Engine.GetGUIObjectByName("unit" + panelName + "Panel").hidden = true;
}

// Get all of the available entities which can be trained by the selected entities
function getAllTrainableEntities(selection)
{
	var trainableEnts = [];
	var state;
	// Get all buildable and trainable entities
	for (let ent of selection)
	{
		if ((state = GetEntityState(ent)) && state.production && state.production.entities.length)
			trainableEnts = trainableEnts.concat(state.production.entities);
	}

	// Remove duplicates
	removeDupes(trainableEnts);
	return trainableEnts;
}

function getAllTrainableEntitiesFromSelection()
{
	if (!g_allTrainableEntities)
		g_allTrainableEntities = getAllTrainableEntities(g_Selection.toList());

	return g_allTrainableEntities;
}

// Get all of the available entities which can be built by the selected entities
function getAllBuildableEntities(selection)
{
	return Engine.GuiInterfaceCall("GetAllBuildableEntities", { "entities": selection });
}

function getAllBuildableEntitiesFromSelection()
{
	if (!g_allBuildableEntities)
		g_allBuildableEntities = getAllBuildableEntities(g_Selection.toList());

	return g_allBuildableEntities;
}

function getNumberOfRightPanelButtons()
{
	var sum = 0;

	for (let prop of ["Construction", "Training", "Pack", "Gate"])
		if (g_SelectionPanels[prop].used)
			sum += g_unitPanelButtons[prop];

	return sum;
}