#strict 3
static const DT_Menu = MN7I;
static const DT_Menu_MenuVar = -1;
// Enums and bitfields
static const Menu_KeepOpen_Not = 0x0;
static const Menu_KeepOpen_Keep = 0x1;
static const Menu_KeepOpen_Force = 0x2;
static const Menu_KeepOpen_Permanent = 0x4;
static const Menu_KeepOpen_Refresh = 0x8;
static const Menu_KeepOpen_RefreshContinuously = 0x10;
static const Menu_KeepOpen_Refresh_Mask = 0x18; // Menu_KeepOpen_Refresh | Menu_KeepOpen_RefreshContinuously
static const DT_Menu_Type_Entry = 0;
static const DT_Menu_Type_Factory = 1;
static const DT_Menu_Type_Columns = 2;
static const DT_Menu_Action_Normal = 0;
static const DT_Menu_Action_Special2 = 1;
static const DT_Menu_Action_Close = 2;
static const Menu_React_Close = 1;
static const Menu_React_Refresh = 2;
static const Menu_React_KeepOpen = 3;
static const Menu_React_Back = 4;
static const Menu_React_SelectionOffset = 5;
static const Menu_React_SelectionChange = 6;
static const Menu_React_ShowSubMenu = 7;
static const Menu_React_OverrideReaction = -1;
static const Menu_React_None = nil;
global func Menu_React_OffsetSelection(int offset) { return [Menu_React_SelectionOffset, offset]; }
global func Menu_React_OverrideSelection(int override) { return [Menu_React_SelectionChange, override]; }
global func Menu_React_Override(override) { return [Menu_React_OverrideReaction, override]; }
static const Menu_ConditionReact_Default = 0;
static const Menu_ConditionReact_Show = 1;
static const Menu_ConditionReact_Hide = 2;
static const Menu_ConditionReact_GrayOut = 3;
static const Menu_ConditionReact_CustomFormat = 4;
global func Menu_ConditionReact_CustomColor(int color) { return [Menu_ConditionReact_CustomFormat, Format("%%s", color)]; }
global func Menu_ConditionReact_Format(string format) { return [Menu_ConditionReact_CustomFormat, format]; }
static const Menu_Condition_Default = 0;
static const Menu_Condition_AllowSelection = 1;
static const Menu_Condition_DenySelection = 2;
static const Menu_CallbackType_None = 0x0;
static const Menu_CallbackType_Special2 = 0x1;
static const Menu_CallbackType_Normal = 0x2;
static const Menu_CallbackType_Close = 0x4;
static const Menu_CallbackType_Selection = 0x8;
static const Menu_CallbackType_Deselection = 0x10;
static const Menu_CallbackType_ValueChanged = 0x20;
static const Menu_CallbackType_InputAborted = 0x40;
static const Menu_CallbackType_Defaults = 0x3; // Menu_CallbackType_Normal | Menu_CallbackType_Special2
static const Menu_CallbackType_All = 0x7f; // Menu_CallbackType_Normal | Menu_CallbackType_Special2 | Menu_CallbackType_Close | Menu_CallbackType_Selection | Menu_CallbackType_Deselection | Menu_CallbackType_ValueChanged | Menu_CallbackType_InputAborted
static const Menu_AdaptorType_Boolean = 1;
static const Menu_AdaptorType_Integer = 2;
static const Menu_AdaptorType_String = 3;
static const Menu_AdaptorType_ID = 4;
static const Menu_AdaptorType_Enum = 5;
static const Menu_AdaptorType_BitField = 6;
static const Menu_Adaptor_Limits_Max = 0x7fffffff;
static const Menu_Adaptor_Limits_Min = 0x80000000;
static const Menu_CallbackArg_All = -1;
static const Menu_CallbackArg_Action = 0;
static const Menu_CallbackArg_Icon = 1;
static const Menu_CallbackArg_MenuObject = 2;
static const Menu_CallbackArg_Args = 3;
static const Menu_CallbackArg_NewSelection = 4;
static const Menu_CallbackArg_OldSelection = 5;
static const Menu_CallbackArg_NewValue = 6;
static const Menu_CallbackArg_OldValue = 7;
static const Menu_CallbackArg_FromSubMenu = 8;
static const Menu_CallbackArg_Menu = 9;
static const Menu_CallbackArg_Returned = 10;
static const Menu_CallbackArg_Menu = 11;
static const Menu_CallbackArg_EntryNumber = 12;
static const Menu_CallbackArg_EntryColumn = 13;
static const Menu_CallbackArg_NewSelectionColumn = 14;
static const Menu_CallbackArg_OldSelectionColumn = 15;
static const Menu_Selection_Simple = 0;
static const Menu_Selection_WithColumn = 1;
static const Menu_Selection_SubMenuColumnChain = 2;
static const Menu_Layout_Icon = 1;
static const Menu_Layout_Description = 2;
static const Menu_Layout__TextPos = 3;
static const Menu_Layout__ValuePos = 4;
// static const Menu_Layout__InputValuePos = 5; TODO
static const Menu_Layout_Text = 4;
static const Menu_Layout_Value = 8;
// static const Menu_Layout_InputValue = 16; TODO
static const Menu_Layout__NoFlagMask = 3;
// ----------------------------------------------------------------------------
local settings;
local createEntries;
local currentColumn;
local columnCount;
local columnEntries;
local columnOffsets;
local currentColumnSelections;
local shouldChangeColumn;
local suspended;
local closing;
local refreshing;
local msgBoardMode;
local msgBoardEntry;
local noSelectionCallbacks;
local multiColumnMode;
local subMenu;
local vars;
func Initialize()
{
currentColumnSelections = [-1];
columnEntries = [];
msgBoardEntry = [];
vars = [];
currentColumn = 0;
}
func Destruction()
{
Close();
}
func Create(map cSettings)
{
settings = cSettings.Settings;
createEntries = cSettings.Entries;
multiColumnMode = false;
columnCount = 0;
var actualColumnCount = GetLength(currentColumnSelections);
columnOffsets = CreateArray(actualColumnCount);
for(var i = 0; i < actualColumnCount; ++i)
{
columnOffsets[i] = 0;
}
if(settings.Vars)
{
var settingVars = settings.Vars;
for(var i = 0; i < GetLength(settingVars); ++i)
{
if(settingVars[i])
{
vars[i] = settingVars[i];
}
}
settings.Vars = 0;
}
var factoryArgs = [];
factoryArgs[Menu_CallbackArg_Menu] = this;
factoryArgs[Menu_CallbackArg_MenuObject] = settings.Object;
var index = 0;
columnEntries[0] = [];
HandleEntries(createEntries, index, columnEntries[0], settings, factoryArgs, 0, true);
if(settings.Parent) settings.Parent->Suspend();
var title = settings.Title;
if(!multiColumnMode)
{
var submenuTitle;
// update submenu columns if necessary and build title
for(var i = 0; i < GetLength(currentColumnSelections); ++i)
{
// TODO: cascade? then remove the columnSelection == -1 check; and check if shrinking menus still work correctly
if(!columnEntries[i])
{
currentColumnSelections[i] = -1;
break;
}
var columnSelection = currentColumnSelections[i];
if((columnSelection == -1) || columnSelection >= GetLength(columnEntries[i]))
{
currentColumnSelections[i] = GetLength(columnEntries[i]) - 1;
break;
}
var submenuEntries = columnEntries[i][columnSelection].SubMenuColumnEntries;
if(submenuEntries)
{
columnEntries[i + 1] = submenuEntries;
var submenuSettings = columnEntries[i][columnSelection].SubMenuInitColumnData.Settings;
/*var */submenuTitle = submenuSettings.Title || "";
if(submenuSettings.Icon)
{
submenuTitle = Format("{{%i}} %s", submenuSettings.Icon, submenuTitle);
}
// because of the engine such long titles are not nice; maybe do "... > last SubMenu Title"
// the engine makes entry columns at least as long as the title is...
// title = Format("%s {{MN7I:7}} %s", title, submenuTitle);
}
}
if(submenuTitle)
{
title = Format("... {{MN7I:7}} %s", submenuTitle);
}
}
CreateMenu(settings.Icon, settings.Object, this, settings.Extra, title, settings.ExtraData, settings.Style, true, MN7I);
if(settings.Size) SetMenuSize(settings.Size[0], settings.Size[1], settings.Object);
AddEntries();
if(GetLength(columnEntries[0]) > 0)
{
if(!refreshing)
{
SelectEntry(settings.Selection[0], settings.Selection[1]);
}
}
if(settings.Decoration)
{
SetMenuDecoration(settings.Decoration, settings.Object);
}
if(!GetEffect("Menu", this)) AddEffect("Menu", this, 1, 1, this, 0);
}
func LeaveSubMenuColumn()
{
SelectEntry(currentColumnSelections[currentColumn - 1], currentColumn - 1);
Refresh(GetSelection(Menu_Selection_WithColumn));
}
func SelectEntry(indexOrChain, int column)
{
if(GetType(indexOrChain) == C4V_Array)
{
for(var i = 0; i < GetLength(indexOrChain); ++i)
{
SelectEntry(indexOrChain[i], i);
}
}
else if(!indexOrChain || GetType(indexOrChain) == C4V_Int)
{
column ??= 0;
indexOrChain ??= 0;
var entryCount = GetLength(columnEntries[column]);
if(indexOrChain < 0) indexOrChain = entryCount + indexOrChain;
shouldChangeColumn = true;
SelectMenuItem(EncodeSelection(indexOrChain, column), settings.Object);
shouldChangeColumn = false;
}
else
{
FatalError(Format("DT_Menu::SelectEntry: indexOrChain must be an integer or an array of integers but got %d", GetType(indexOrChain)));
}
}
func SelectTopEntry(int column)
{
for(var i = 0; i < GetLength(columnEntries[column]); ++i)
{
if(columnEntries[column][i].Placeholder)
{
return SelectEntry(i, column);
}
}
}
func SelectBottomEntry(int column)
{
for(var i = GetLength(columnEntries[column]) - 1; i >= 0; --i)
{
if(columnEntries[column][i].Placeholder)
{
return SelectEntry(i, column);
}
}
}
func ActivateEntry(int action, indexOrChain, int column)
{
SelectEntry(indexOrChain);
var index = indexOrChain;
if(GetType(indexOrChain) == C4V_Array)
{
column = GetLength(indexOrChain) - 1;
index = indexOrChain[column];
}
var entry = columnEntries[column][index];
MenuItemCommand(entry.Icon, EncodeSelection(index, column), action);
}
func SubMenu()
{
return subMenu;
}
func &Var(int index)
{
return vars[index];
}
global func MenuVar(int index)
{
return [DT_Menu_MenuVar, index];
}
/*func FxMenuStart(object target, int effectNumber, int temp)
{
}*/
func FxMenuTimer(object target, int effectNumber, int effectTime)
{
if(!settings.Object)
{
return Close();
}
else if(settings.RequireAction)
{
var obj = settings.Object;
var requirement = settings.RequireAction;
if(GetAction(obj) != requirement[0] || (requirement[1] && GetActionTarget(0, obj) != requirement[1]))
{
return Close();
}
}
if(suspended) return;
if(msgBoardMode && !TestMessageBoard(GetOwner(settings.Object), true))
{
var entry = columnEntries[msgBoardEntry[1]][msgBoardEntry[0]];
var args = entry.Args;
var reaction = CallCallbacks(args?.Callbacks, Menu_CallbackType_InputAborted, [Menu_CallbackType_InputAborted, entry.Icon, settings.Object, entry.Args]);
if(reaction != Menu_React_None)
{
React(reaction, msgBoardEntry);
}
msgBoardMode = nil;
}
if(!GetMenu(settings.Object))
{
if(settings.KeepOpen & (Menu_KeepOpen_Refresh_Mask | Menu_KeepOpen_Force) && !settings.Closable)
{
Refresh(GetSelection(Menu_Selection_WithColumn));
}
else
{
return FX_Execute_Kill;
}
}
if(settings.KeepOpen & Menu_KeepOpen_RefreshContinuously && !(effectTime % settings.RefreshInterval))
{
Refresh(GetSelection(Menu_Selection_WithColumn));
}
}
func FxMenuStop(object target, int effectNumber, int reason, bool temp)
{
if(temp)
{
return;
}
CloseMenu(settings.Object);
if(settings.InstantDescription.Enable) HideInstantDescription();
if(settings.Parent) settings.Parent->Suspend(true);
RemoveObject(this);
}
func BindCallbackArgs(array args, array binding)
{
var ret = CreateArray(GetLength(binding));
var i = 0;
for(var arg in binding)
{
if(arg == Menu_CallbackArg_All)
{
ret[i] = args;
}
else
{
ret[i] = args[arg];
}
++i;
}
return ret;
}
func CallCallbacks(array callbacks, int type, array args, defaultRet, bool noGlobalCallbacks)
{
var ret = defaultRet;
args[Menu_CallbackArg_Menu] = this;
if(callbacks)
{
for(var callback in callbacks)
{
if(callback[1] & type)
{
ret = CallA(callback[0], BindCallbackArgs(args, callback[2]));
break;
}
}
}
if(!noGlobalCallbacks)
{
args[Menu_CallbackArg_Returned] = ret;
var globalRet = CallCallbacks(settings.Callbacks, type, args, defaultRet, true);
if(GetType(globalRet) == C4V_Array && globalRet[0] == Menu_React_OverrideReaction)
{
ret = globalRet[1];
}
}
return ret;
}
func Close(bool closeParents)
{
closing = true;
if(subMenu)
{
subMenu->Close();
}
if(closeParents && settings.Parent) settings.Parent->Close(true);
RemoveEffect("Menu", this);
}
func Suspend(bool cont)
{
if(suspended == !cont) return;
if(suspended = !cont)
{
CloseMenu(settings.Object);
}
else if(!closing)
{
Refresh(GetSelection(Menu_Selection_WithColumn));
}
}
func HandleEntries(array factoryEntries, int& i, array& ret, map& retSettings, array& factoryArgs, int column, bool isMainColumn)
{
if(column >= columnCount)
{
columnCount = column + 1;
}
for(var entry in factoryEntries)
{
if(entry.Type == DT_Menu_Type_Entry)
{
if(multiColumnMode && isMainColumn)
{
FatalError("DT_Menu::HandleEntries: if Menu_Columns() is used, all entries must occur inside it");
}
// is it an extra column submenu?
if(entry.SubMenuInitColumnData)
{
if(multiColumnMode)
{
FatalError("DT_Menu::HandleEntries: Menu_Columns() and SubMenu columns can not be used together");
}
if(!entry.SubMenuInitColumnData.Settings)
{
entry.SubMenuInitColumnData.Settings = {};
}
var index = 0;
HandleEntries(entry.SubMenuInitColumnData.Entries, index, entry.SubMenuColumnEntries = [], entry.SubMenuInitColumnData.Settings, factoryArgs, column + 1, false);
}
ret[i++] = entry;
}
else if(entry.Type == DT_Menu_Type_Factory)
{
for(var callback in entry.Callbacks)
{
factoryArgs[Menu_CallbackArg_Args] = entry.Args;
factoryArgs[Menu_CallbackArg_EntryNumber] = i;
factoryArgs[Menu_CallbackArg_EntryColumn] = column;
var factoryResult = CallA(callback, BindCallbackArgs(factoryArgs, entry.Binding));
if(factoryResult == Menu_React_Close)
{
return Close();
}
else
{
var newSettings;
var newEntries;
if(GetType(factoryResult) == C4V_Map)
{
newSettings = factoryResult.Settings;
newEntries = factoryResult.Entries;
}
else if(GetType(factoryResult) == C4V_Array)
{
newEntries = factoryResult;
}
if(newSettings)
{
retSettings = Extend(retSettings, newSettings, true);
}
if(newEntries)
{
HandleEntries(newEntries, i, ret, retSettings, factoryArgs, column, isMainColumn);
}
}
}
}
else if(entry.Type == DT_Menu_Type_Columns)
{
if(multiColumnMode)
{
FatalError("DT_Menu::HandleEntries: only one Menu_Columns() can appear in the whole menu");
}
if(columnCount > 1)
{
FatalError("DT_Menu::HandleEntries: Menu_Columns() and SubMenu columns can not be used together");
}
if(GetLength(columnEntries[0]) > 0)
{
FatalError("DT_Menu::HandleEntries: if Menu_Columns() is used, all entries must occur inside it");
}
multiColumnMode = true;
var j = 0;
for(var columnData in entry.Columns)
{
columnEntries[j] = [];
var index = 0;
var tempSettings = {};
// NOTE: First instead of tempSettings, settings was used. Seems wrong.
HandleEntries(columnData, index, columnEntries[j], tempSettings, factoryArgs, j, false);
++j;
if(GetLength(tempSettings) > 0)
{
FatalError("DT_Menu::HandleEntries: menu settings can not appear in Menu_Columns()");
}
}
}
}
}
func AddEntries()
{
// TODO: determine maximum count of all possible submenu columns; or just ignore because it can be changing anyway?
var entriesCount = 0;
for(var column in columnEntries)
{
if(column)
{
entriesCount = Max(entriesCount, GetLength(column));
}
}
if(columnCount > 1)
{
SetMenuSize(columnCount, entriesCount, settings.Object);
}
if(!multiColumnMode)
{
for(var i = 1; i < GetLength(columnEntries); ++i)
{
var column = columnEntries[i];
if(column)
{
var offset = currentColumnSelections[i - 1] + columnOffsets[i - 1];
var entryCount = GetLength(column);
if(offset + entryCount > entriesCount)
{
offset = entriesCount - entryCount;
}
columnOffsets[i] = offset;
}
}
}
for(var i = 0, entryIndex = 0; i < entriesCount; ++i)
{
for(var j = 0; j < columnCount; ++j)
{
columnOffsets[j] ??= 0;
var rowIndex = i - columnOffsets[j];
var entries = columnEntries[j];
if(entries && rowIndex >= 0 && rowIndex < GetLength(entries))
{
var entry = entries[rowIndex];
var condition = entry.Condition, conditionRet;
var text = entry.Text, noCommand = !entry.Placeholder || (entry.Placeholder != true && !entry.Callbacks);
if(condition)
{
if(noCommand || condition.AllowDisabledSelection == Menu_Condition_DenySelection || (condition.AllowDisabledSelection == Menu_Condition_Default && !settings.Condition.AllowSelection))
{
noCommand = true;
}
conditionRet = CallA(condition.Callback, [entry.Icon, settings.Object, entry.Args]) || settings.Condition.DisableMode;
if(conditionRet == Menu_ConditionReact_Hide)
{
continue;
}
else if(conditionRet == Menu_ConditionReact_GrayOut)
{
text = Format("%s", text);
}
else if(GetType(conditionRet) == C4V_Array && conditionRet[0] == Menu_ConditionReact_CustomFormat)
{
text = Format(conditionRet[1], text);
}
else
{
noCommand = false;
}
}
var icon = entry.Icon, iconIndex = 0, iconID = 0, deleteIcon = 0;
if(GetType(icon) == C4V_Array)
{
if(GetType(icon[0]) == C4V_C4ID)
{
iconID = icon[0];
if(icon[2])
{
icon = [CreateIconDummy()->SetIcon(iconID, icon[1])->SetColor(icon[2]), true];
}
else if(icon[1])
{
entry.Extra |= C4MN_Add_ImgIndexed;
iconIndex = icon[1];
entry.XPar1 = icon[1];
}
}
if(GetType(icon[0]) == C4V_C4Object)
{
entry.Extra |= C4MN_Add_ImgObject;
entry.XPar1 = icon[0];
if(icon[1])
{
deleteIcon = icon[0];
}
if(!iconID)
{
iconID = GetID(icon[0]);
}
}
}
else
{
iconID = icon;
}
entry.Icon = iconID;
if(!entry.InstantDescriptionIcon)
{
entry.InstantDescriptionIcon = [iconID, iconIndex];
}
if(settings.InstantDescription.Enable)
{
entry.Extra |= C4MN_Add_ForceNoDesc;
}
entry.Placeholder = !noCommand;
columnEntries[j][rowIndex] = entry;
var showDesc = !(entry.Extra & C4MN_Add_ForceNoDesc);
if(entry.SubMenuInitColumnData && currentColumnSelections[j] == rowIndex)
{
text = Format("%s {{MN7I:7}}", text);
}
AddMenuItem(text, !noCommand && "MenuItemCommand", iconID, settings.Object, entry.Count, entryIndex, showDesc && entry.Description || nil, entry.Extra, entry.XPar1, entry.XPar2);
if(deleteIcon)
{
RemoveObject(deleteIcon);
}
}
else
{
AddMenuItem("", "DummyItemCommand", nil, settings.Object, nil, entryIndex, nil, C4MN_Add_ForceNoDesc);
}
++entryIndex;
}
}
}
func React(reaction, array entryIndex, int refreshDelayed)
{
if(reaction == Menu_React_Close)
{
Close(!settings.KeepParentOnClose);
}
else if(reaction == Menu_React_Back)
{
if(currentColumn > 0 && !multiColumnMode)
{
LeaveSubMenuColumn();
}
else
{
Close();
}
}
else if(reaction == Menu_React_Refresh || (settings.KeepOpen & Menu_KeepOpen_Refresh))
{
Refresh(entryIndex, refreshDelayed);
}
else if(GetType(reaction) == C4V_Array)
{
var selection = GetSelection(Menu_Selection_WithColumn);
var currentSelection = selection;
if(reaction[0] == Menu_React_SelectionOffset)
{
selection[0] += reaction[1];
selection[0] %= GetLength(columnEntries[currentColumn]);
}
else if(reaction[0] == Menu_React_SelectionChange)
{
selection[0] = BoundBy(reaction[1], 0, GetLength(columnEntries[selection[1]]) - 1);
}
if(selection != currentSelection)
{
SelectEntry(selection[0], selection[1]);
}
}
}
func CheckCondition(map entry)
{
var condition = entry.Condition;
return !condition || (CallA(condition.Callback, [entry.Icon, settings.Object, entry.Args]) || settings.Condition.DisableMode) == Menu_ConditionReact_Show;
}
func MenuItemCommand(id ID, int itemNumber, int action)
{
var column = DecodeSelection(itemNumber);
var entry = columnEntries[column][itemNumber];
var condition = entry.Condition;
action = action || Menu_CallbackType_Normal;
var reaction;
if(CheckCondition(entry))
{
reaction = CallCallbacks(entry.Callbacks, action, [action, ID, settings.Object, entry.Args]);
}
else
{
if(condition.AllowDisabledSelection == Menu_Condition_AllowSelection)
{
reaction = Menu_React_KeepOpen;
}
else
{
reaction = Menu_React_Refresh;
}
}
React(reaction, [itemNumber, column]);
}
// for entries that shouldn't be able to be selected
// just in else if(conditionRet == Menu_ConditionReact_) they get activated through clicking
func DummyItemCommand(id ID, int itemNumber, int action)
{
var column = DecodeSelection(itemNumber);
React(Menu_React_KeepOpen, [itemNumber, column]);
}
func SubMenuItemCallback(int action, object menuObject, args, array allArgs)
{
allArgs[Menu_CallbackArg_Args] = args[1];
var reaction = CallCallbacks(args[0], action, allArgs, Menu_React_None);
if(((action & Menu_CallbackType_Defaults) && reaction == Menu_React_None) || reaction == Menu_React_ShowSubMenu)
{
if(args[3])
{
var targetSelection = columnEntries[currentColumn][currentColumnSelections[currentColumn]].SubMenuInitColumnData.Settings.Selection;
SelectEntry(targetSelection && targetSelection[0], currentColumn + 1);
}
else
{
subMenu = CreateNewMenu(args[2], settings, this);
}
return Menu_React_None;
}
else
{
return reaction;
}
}
func DecodeSelection(int& selection)
{
var column = selection % columnCount;
selection /= columnCount;
selection -= columnOffsets[column];
return column;
}
func EncodeSelection(int selection, int column)
{
return (selection + columnOffsets[column]) * columnCount + column;
}
func MenuQueryCancel(int selection, object menuObject)
{
var column = DecodeSelection(selection);
var reaction;
if(selection != -1)
{
var entry = columnEntries[column][selection];
if(CheckCondition(entry))
{
reaction = CallCallbacks(entry.Callbacks, Menu_CallbackType_Close, [Menu_CallbackType_Close, entry.Icon, settings.Object, entry.Args]);
}
React(reaction, [selection, column], true);
}
if((settings.KeepOpen != Menu_KeepOpen_Not && settings.KeepOpen != Menu_KeepOpen_Permanent && !settings.Closable) || (reaction == Menu_React_KeepOpen))
{
return true;
}
}
func OnMenuSelection(int selection, object menuObject)
{
var column = DecodeSelection(selection);
var oldColumnSelection = currentColumnSelections[column];
var oldColumnOldSelection = currentColumnSelections[currentColumn];
// selections can be out of range if dummy entries get selected
// let's emulate some engine behavior then
// TODO: Fix stack overflow when the target selection can't be selected (Placeholder)
if(!columnEntries[column])
{
// navigate downwards when pressing right at the right border (thus selecting a dummy)
var selectedColumn = column;
for(; column > -1; --column)
{
if(columnEntries[column])
{
break;
}
}
if(column >= 0)
{
var offset = 1;
if(selectedColumn == columnCount - 1 && currentColumn == 0)
{
// but upwards if it was actually pressing left at the left border
offset = 0;
column = 0;
}
SelectEntry(BoundBy(selection - columnOffsets[column] + offset, 0, GetLength(columnEntries[column]) - 1), column);
}
return;
}
var skipHandling;
if(selection < 0)
{
if(currentColumn == column)
{
if(oldColumnSelection == 0)
{
// wrap around if the last selection was the top already
SelectBottomEntry(column);
}
else
{
// otherwise maybe wrapped around from the bottom or just hovered it with the mouse
SelectTopEntry(column);
}
return;
}
skipHandling = true;
}
if(selection >= GetLength(columnEntries[column]))
{
if(currentColumn == column)
{
if(oldColumnSelection == GetLength(columnEntries[column]) - 1)
{
// wrap around if the last selection was the bottom already
SelectTopEntry(column);
}
else
{
// otherwise maybe wrapped around from the top or just hovered it with the mouse
SelectBottomEntry(column);
}
return;
}
else if(multiColumnMode)
{
SelectBottomEntry(column);
return;
}
skipHandling = true;
}
if(selection != oldColumnSelection || column != currentColumn)
{
var oldColumn = currentColumn;
currentColumn = column;
if(!skipHandling)
{
var entry = columnEntries[oldColumn][oldColumnOldSelection];
var deselectReaction = Menu_React_None;
var selectReaction = Menu_React_None;
if(!noSelectionCallbacks && CheckCondition(entry) && oldColumnSelection != -1)
{
var args = [Menu_CallbackType_Deselection, entry.Icon, settings.Object, entry.Args, selection, oldColumnSelection];
args[Menu_CallbackArg_NewSelectionColumn] = currentColumn;
args[Menu_CallbackArg_OldSelectionColumn] = oldColumn;
deselectReaction = CallCallbacks(entry.Callbacks, Menu_CallbackType_Deselection, args);
}
var oldIsColumnSubMenu = !!entry.SubMenuInitColumnData;
currentColumnSelections[column] = selection;
entry = columnEntries[column][selection];
if(!noSelectionCallbacks && CheckCondition(entry))
{
var args = [Menu_CallbackType_Selection, entry.Icon, settings.Object, entry.Args, selection, oldColumnSelection];
args[Menu_CallbackArg_NewSelectionColumn] = currentColumn;
args[Menu_CallbackArg_OldSelectionColumn] = oldColumn;
selectReaction = CallCallbacks(entry.Callbacks, Menu_CallbackType_Selection, args);
}
if(deselectReaction != Menu_React_None)
{
React(deselectReaction, GetSelection(Menu_Selection_WithColumn));
}
if(selectReaction != Menu_React_None)
{
React(selectReaction, GetSelection(Menu_Selection_WithColumn));
}
selection = currentColumnSelections[column];
}
// navigating between submenu columns; not in multiColumnMode
if(!multiColumnMode)
{
// navigated from one column to another without going over the left/right border
if(selection + columnOffsets[currentColumn] == oldColumnOldSelection + columnOffsets[oldColumn])
{
if(currentColumn == oldColumn + 1 && oldIsColumnSubMenu)
{
var targetSelection = columnEntries[currentColumn - 1][oldColumnOldSelection].SubMenuInitColumnData.Settings.Selection;
targetSelection = targetSelection && targetSelection[0];
// column submenu was entered through pressing right; select the first entry
// but only if it's not there yet
if(selection != targetSelection)
{
SelectEntry(targetSelection, currentColumn);
return;
}
}
else if(currentColumn == oldColumn - 1)
{
// column submenu was left through pressing left; select the submenu entry
// but only if it's not there yet
if(selection != oldColumnSelection)
{
SelectEntry(oldColumnSelection, currentColumn);
return;
}
}
}
else if(currentColumn != oldColumn && !shouldChangeColumn) // with going over the left/right border; we don’t want that
{
currentColumn = oldColumn;
SelectEntry(oldColumnOldSelection, oldColumn);
return;
}
}
if(skipHandling)
{
currentColumn = oldColumn;
return;
}
if(settings.InstantDescription.Enable)
{
ShowInstantDescription(columnEntries[currentColumn][selection]);
}
// update submenu column if necessary
if(!multiColumnMode && selection != oldColumnSelection)
{
if(entry.SubMenuInitColumnData)
{
SetLength(columnEntries, column + 2);
for(var i = column + 1; i < columnCount; ++i)
{
currentColumnSelections[i] = -1;
}
Refresh([selection, column]);
return;
}
else
{
var oldEntries = columnEntries[column + 1];
if (oldEntries != nil)
{
SetLength(columnEntries, column + 1);
currentColumnSelections[column + 1] = -1;
if(oldEntries)
{
Refresh([selection, column]);
return;
}
}
}
}
}
}
func Refresh(array selection, bool delayed)
{
if(suspended)
{
return;
}
if(delayed)
{
ScheduleCall(this, "Refresh", 1, 0, selection);
}
else
{
var oldNoSelectionCallbacks = noSelectionCallbacks;
noSelectionCallbacks = true;
CloseMenu(settings.Object);
var oldRefreshing = refreshing;
refreshing = true;
Create({ Settings = settings, Entries = createEntries });
refreshing = oldRefreshing;
var column = BoundBy(selection[1], 0, columnCount);
selection = selection[0];
if(GetLength(columnEntries[column]) > 0)
{
SelectEntry(BoundBy(selection, 0, GetLength(columnEntries[column]) - 1), column);
if(settings.InstantDescription.Enable)
{
ShowInstantDescription(columnEntries[currentColumn][selection]);
}
}
noSelectionCallbacks = oldNoSelectionCallbacks;
}
}
func ShowInstantDescription(map entry)
{
if(entry.Description)
{
var icon = entry.InstantDescriptionIcon;
var iconString;
if(GetType(icon[1]) == C4V_String)
{
iconString = Format("Portrait:%i::%x::%s", icon[0], icon[2], icon[1]);
}
else
{
iconString = Format("%i:%d", icon[0] || DT_Menu_IconDummy, icon[1]);
}
var pos = settings.InstantDescription.Position;
CustomMessage("@" .. entry.Description, 0, GetOwner(settings.Object), pos[0], pos[1], 0, settings.InstantDescription.Decoration, iconString, settings.InstantDescription.Flags);
}
else
{
HideInstantDescription();
}
}
func HideInstantDescription()
{
CustomMessage("", 0, GetOwner(settings.Object), 0, 0, 0, 0, Format("%i", DT_Menu_IconDummy), settings.InstantDescription.Flags);
}
// ----------------------------------------------------------------------------
global func Menu_Callback(array callback, int types, array argBinding)
{
types ??= Menu_CallbackType_Defaults;
argBinding = argBinding || [Menu_CallbackArg_Action, Menu_CallbackArg_Icon, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_NewSelection, Menu_CallbackArg_OldSelection, Menu_CallbackArg_NewValue, Menu_CallbackArg_OldValue, Menu_CallbackArg_FromSubMenu];
return [callback, types, argBinding];
}
global func Menu_Entry_VariableCondition(array scopedVar, compare, int disableMode, bool invert, int allowDisabledSelection)
{
return BindCallback(MN7I->MenuObjectCallback("VariableCondition"), [Bind(scopedVar), Bind(compare), Bind(disableMode), Bind(invert)]);
}
func VariableCondition(array scopedVar, compare, int disableMode, bool invert)
{
var disable = ScopedVar(scopedVar) != compare;
if(invert)
{
disable = !disable;
}
if(disable)
{
return disableMode;
}
else
{
return Menu_ConditionReact_Show;
}
}
/*
Menu_Entry({
Text = ,
Icon = ,
Count = ,
Description = ,
Placeholder = : true | >false<,
InstantDescriptionIcon = [iconID, iconIndex] | [iconID, portraitName, color],
Callbacks = [...],
Args = ,
Condition = {
Callback = ,
AllowDisabledSelection = >Menu_Condition_Default< | Menu_Condition_AllowSelection | Menu_Condition_DenySelection
},
Extra = >Also used internally<,
XPar1 = >Also used internally<,
XPar2 = >Also used internally<,
SubMenuInitColumnData = >Internal<: For SubMenus as columns,
SubMenuColumnEntries = >Internal<: For SubMenus as columns,
Type = >Internal<: DT_Menu_Type_Entry | DT_Menu_Type_Factory | DT_Menu_Type_Columns
}) */
global func Menu_Entry(map entry)
{
if(!entry)
{
entry = { Placeholder = false };
}
else
{
entry = Extend({
Text = "",
Placeholder = -1,
Extra = 0,
}, entry, true);
}
if(!entry.Description)
{
entry.Extra |= C4MN_Add_ForceNoDesc;
}
entry.Type = DT_Menu_Type_Entry;
if(entry.Icon && GetType(entry.Icon) != C4V_Array)
{
if(GetType(entry.Icon) == C4V_C4Object)
{
entry.Icon = [entry.Icon, true];
}
else
{
entry.Icon = [entry.Icon];
}
}
return entry;
}
global func Menu_SubMenu(map submenuSettings, bool asColumn)
{
var ret = Menu_Entry(submenuSettings.Entry);
ret.Args = [ret.Callbacks, ret.Args, submenuSettings, asColumn];
ret.Callbacks = [Menu_Callback(MN7I->MenuObjectCallback("SubMenuItemCallback"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])];
if(asColumn)
{
ret.SubMenuInitColumnData = submenuSettings;
}
return ret;
}
global func Menu_Factory(array callbacks, args, array binding)
{
return {
Type = DT_Menu_Type_Factory,
Callbacks = callbacks,
Args = args,
Binding = binding || [Menu_CallbackArg_Args, Menu_CallbackArg_EntryNumber, Menu_CallbackArg_Menu]
};
}
// Menu_Columns
// ([
// [ first column entries ],
// [ second column entries ],
// ...
// ])
global func Menu_Columns(array columns)
{
for(var column in columns)
{
if(column.Settings && GetLength(column.Settings) > 0)
{
FatalError("DT_Menu::Menu_Columns: menu settings can not appear in Menu_Columns()");
}
}
return {
Type = DT_Menu_Type_Columns,
Columns = columns
};
}
global func Menu_Text(string text, bool allowSelection)
{
return Menu_Entry({
Text = text,
Placeholder = allowSelection
});
}
global func Menu_Blank(bool allowSelection)
{
return Menu_Entry({
Placeholder = allowSelection
});
}
func DeclineAcceptBack(string text, icon, string callback, map settings)
{
var ret = Menu_Entry(Extend({
Text = text,
}, settings || {}, true));
ret.Args = [ret.Callbacks, ret.Args];
ret.Callbacks = [Menu_Callback(MN7I->MenuObjectCallback(callback), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_Args, Menu_CallbackArg_All])];
ExtraIcon(ret.Text, ret.Icon, icon);
return ret;
}
global func Menu_Decline(map settings)
{
return MN7I->DeclineAcceptBack("$Decline$", [MN7I, 2], "DeclineAcceptCommand", settings);
}
global func Menu_Accept(map settings)
{
return MN7I->DeclineAcceptBack("$Accept$", [MN7I, 1], "DeclineAcceptCommand", settings);
}
global func Menu_Back(map settings)
{
return MN7I->DeclineAcceptBack("$Back$", [MN7I, 5], "BackCommand", settings);
}
func AdaptorLayout(array& layout, array& vals, bool valuesAsSeparateLists)
{
layout = layout || [Menu_Layout_Value | Menu_Layout_Text];
var layoutMap = [];
var index = 1;
for(var val in layout)
{
var noFlag = val & Menu_Layout__NoFlagMask;
if(noFlag)
{
layoutMap[noFlag] = index;
}
if(val & Menu_Layout_Text)
{
layoutMap[Menu_Layout__TextPos] = index;
}
if(val & Menu_Layout_Value)
{
layoutMap[Menu_Layout__ValuePos] = index;
}
// if(val & Menu_Layout_InputValue) TODO
// {
// layoutMap[Menu_Layout__InputValuePos] = index;
// }
++index;
}
layout = layoutMap;
if(valuesAsSeparateLists)
{
var tempVals = vals;
var vals = [];
for(var i = 0; i < GetLength(tempVals[0]); ++i)
{
vals[i] = [];
for(var j = 0; j < GetLength(tempVals); ++j)
{
vals[i][j] = tempVals[j][i];
}
}
}
}
/*
Menu_Adaptor({
Entry = {
sames as for Menu_Entry
},
Adaptor = {
Type = Menu_AdaptorType_Boolean | Menu_AdaptorType_Integer | Menu_AdaptorType_String | Menu_AdaptorType_ID | Menu_AdaptorType_Enum | Menu_AdaptorType_BitField,
Variable = storing the value,
Callbacks = [...],
Args = ,
MessageBoardText = ,
Limits = [, ] for Integer-Type,
StepSize = [, ] for Integer-Type,
NoEmptyString = : true | >false<,
EntryIndex = >Internal<: used for MessageBoard callbacks,
WrapAround = : true | false ,
Enum = : {
Values = [<[Values according to Layout]>...],
Layout = [<[Menu_Layout_*, some can be combined with &]>...],
ValuesAsSeparateLists = : true | >false<,
Inline = : true | >false<,
AllowUnknown = : true | >false<,
SubMenu = : {
On = : >Menu_CallbackType_Special2< | true to show as extra column | Menu_CallbackType_None | Menu_CallbackType_*,
Icon = ,
Title = ,
Text = ,
}
},
BitField = : {
Values = [<[Values according to Layout]>...],
Layout = [<[Menu_Layout_*, some can be combined with &]>...],
ValuesAsSeparateLists = : true | >false<,
BitPositionAsValue = : true | >false<,
Mask = >Internal<
}
}
}) */
global func Menu_Adaptor(map settings)
{
if(!settings.Adaptor.Type)
{
if(settings.Adaptor.Enum)
{
settings.Adaptor.Type = Menu_AdaptorType_Enum;
}
else if(settings.Adaptor.BitField)
{
settings.Adaptor.Type = Menu_AdaptorType_BitField;
}
else
{
FatalError("Menu_Adaptor: Adaptor.Type is missing!");
}
}
settings = Extend({
Adaptor = {
WrapAround = -1,
Enum = {
SubMenu = {
On = Menu_CallbackType_Special2,
}
}
}
}, settings, true);
if(settings.Adaptor.WrapAround == -1)
{
if(settings.Adaptor.Type == Menu_AdaptorType_Integer)
{
settings.Adaptor.WrapAround = false;
}
else if(settings.Adaptor.Type == Menu_AdaptorType_Enum)
{
settings.Adaptor.WrapAround = true;
}
}
if(settings.Adaptor.Type == Menu_AdaptorType_BitField)
{
var layout = settings.Adaptor.BitField.Layout;
var fieldVals = settings.Adaptor.BitField.Values;
MN7I->AdaptorLayout(layout, fieldVals, settings.Adaptor.BitField.ValuesAsSeparateLists);
if(settings.Adaptor.BitField.BitPositionAsValue)
{
var valuePos = layout[Menu_Layout__ValuePos] - 1;
for(var i = 0; i < GetLength(fieldVals); ++i)
{
fieldVals[i][valuePos] = 1 << fieldVals[i][valuePos];
}
}
settings.Adaptor.BitField.Values = fieldVals;
settings.Adaptor.BitField.Layout = layout;
}
else if(settings.Adaptor.Type == Menu_AdaptorType_Enum)
{
MN7I->AdaptorLayout(settings.Adaptor.Enum.Layout, settings.Adaptor.Enum.Values, settings.Adaptor.Enum.ValuesAsSeparateLists);
}
return Menu_Factory([MN7I->MenuObjectCallback("AdaptorFactory")], [Menu_Entry(settings.Entry), settings], [Menu_CallbackArg_Args, Menu_CallbackArg_EntryNumber, Menu_CallbackArg_EntryColumn]);
}
func EnumValPos(array enumVals, array layout, val, bool allowUnknown)
{
for(var i = 0; i < GetLength(enumVals); ++i)
{
if(enumVals[i][layout[Menu_Layout__ValuePos] - 1] == val)
{
return i;
}
}
return -1;
}
func BooleanToggleText(bool val, string& text, &icon)
{
val = !!val;
ExtraIcon(text, icon, [MN7I, 2 - val, !val && RGB(128, 128, 128)]);
}
func InlineIcon(string& text, icon)
{
if(icon[2])
{
text = Format("{{%i:%d}} %s", icon[2], icon[0], icon[1], text);
}
else
{
text = Format("{{%i:%d}} %s", icon[0], icon[1], text);
}
}
func ExtraIcon(string& text, &icon, extraIcon)
{
if(GetType(extraIcon) == C4V_C4ID)
{
extraIcon = [extraIcon];
}
if(icon && extraIcon && GetType(extraIcon[0]) == C4V_C4ID)
{
InlineIcon(text, extraIcon);
}
else
{
icon = extraIcon;
}
}
func EnumEntrySettings(string& text, &icon, string& description, int index, map args, map entry)
{
var layoutVals = args.Enum.Values;
var layout = args.Enum.Layout;
text = entry.Text;
if(layout[Menu_Layout__TextPos])
{
text = Format(text, layoutVals[index][layout[Menu_Layout__TextPos] - 1]);
}
if(layout[Menu_Layout_Icon])
{
ExtraIcon(text, icon, layoutVals[index][layout[Menu_Layout_Icon] - 1]);
}
if(layout[Menu_Layout_Description])
{
description = Format(description, layoutVals[index][layout[Menu_Layout_Description] - 1]);
}
}
func AdaptorGetLimits(array limits)
{
var ret = CreateArray(2);
if(!limits)
{
limits = [Menu_Adaptor_Limits_Min, Menu_Adaptor_Limits_Max];
}
for(var i = 0; i < 2; ++i)
{
if(!limits[i] || GetType(limits[i]) == C4V_Int)
{
ret[i] = limits[i];
}
else
{
ret[i] = Call(limits[i], limits[3]);
}
}
return ret;
}
func AdaptorFactory(args, int entryIndex, int entryColumn)
{
var origArgs = args;
var entry = args[0];
var entrySettings = args[1].Entry;
args = args[1].Adaptor;
var text = entry.Text;
var description = entry.Description;
var icon = entry.Icon;
var val = ScopedVar(args.Variable);
var defaultMsgboardText = "$EnterValue$";
var retSubMenu;
args.Args = entry.Args;
args.EntryIndex = [entryIndex, entryColumn];
args.Callbacks = args.Callbacks || [];
if(args.Type == Menu_AdaptorType_Boolean)
{
BooleanToggleText(val, text, icon);
}
else if(args.Type == Menu_AdaptorType_String)
{
text = Format(entry.Text, val);
defaultMsgboardText = "$EnterText$";
}
else if(args.Type == Menu_AdaptorType_Integer)
{
val ??= 0;
var limits = AdaptorGetLimits(args.Limits), max, min;
var wrapAround = args.WrapAround;
if(limits && !wrapAround)
{
if(val >= limits[1])
{
max = true;
}
if(val <= limits[0])
{
min = true;
}
}
text = Format(entry.Text, val) .. " " .. ["{{MN7I:4}}", "{{MN7I:4}}"][min];
ExtraIcon(text, icon, [MN7I, 3, max && RGB(128, 128, 128)]);
defaultMsgboardText = "$EnterNumber$";
}
else if(args.Type == Menu_AdaptorType_ID)
{
text = Format(entry.Text, val && GetName(0, val) || "");
if(val)
{
ExtraIcon(text, icon, val);
}
defaultMsgboardText = "$EnterIDOrName$";
}
else if(args.Type == Menu_AdaptorType_Enum)
{
if(args.Enum.Inline)
{
return AdaptorEnumSubMenuFactory([args, entry]);
}
var layoutVals = args.Enum.Values;
var layout = args.Enum.Layout;
var index = EnumValPos(layoutVals, layout, val, args.Enum.AllowUnknown);
if(index == -1)
{
if(!args.Enum.AllowUnknown)
{
ScopedVar(args.Variable) = layoutVals[0][layout[Menu_Layout__ValuePos] - 1];
}
index = 0;
}
var submenuIcon = icon;
EnumEntrySettings(text, icon, description, index, args, entry);
if(args.Enum.SubMenu.On != Menu_CallbackType_None)
{
args.MessageBoardText = args.MessageBoardText || defaultMsgboardText;
retSubMenu = [Menu_Factory([MenuObjectCallback("AdaptorEnumSubMenuFactory")], origArgs[1])];
}
}
else if(args.Type == Menu_AdaptorType_BitField)
{
return AdaptorBitFieldItemsFactory(origArgs);
}
args.MessageBoardText = args.MessageBoardText || defaultMsgboardText;
if(!retSubMenu)
{
return [Menu_Entry(Extend(entrySettings, { Text = text, Callbacks = [Menu_Callback(MenuObjectCallback("AdaptorCommand"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])], Icon = icon, Description = description, Args = args }, true))];
}
else
{
return [Menu_SubMenu({ Entry = Extend(entrySettings, { Text = text, Callbacks = [Menu_Callback(MenuObjectCallback("AdaptorCommand"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])], Icon = icon, Description = description, Args = args }, true), Entries = retSubMenu }, GetType(args.Enum.SubMenu.On) == C4V_Bool && args.Enum.SubMenu.On)];
}
}
func AdaptorEnumSubMenuFactory(map args)
{
var entry = args.Entry;
args = args.Adaptor;
var layoutVals = args.Enum.Values;
var layout = args.Enum.Layout;
var submenuIcon = [];
if(args.Enum.SubMenu.Title)
{
entry.Text = args.Enum.SubMenu.Title;
}
var index = EnumValPos(layoutVals, layout, ScopedVar(args.Variable), args.Enum.AllowUnknown);
var title, icon, description;
EnumEntrySettings(title, icon, description, index, args, entry);
var ret;
if(args.Enum.Inline)
{
ret = {
Entries = []
};
}
else
{
ret = {
Settings = {
Selection = [index, 0],
KeepParentOnClose = true,
KeepOpen = Menu_KeepOpen_Not,
Title = title
},
Entries = []
};
}
if(args.Enum.SubMenu.Icon)
{
submenuIcon = [args.Enum.SubMenu.Icon];
}
else if(icon && GetType(icon[0]) == C4V_C4ID)
{
submenuIcon = icon;
}
if(submenuIcon && GetType(submenuIcon[0]) == C4V_C4ID)
{
ret.Settings.Icon = submenuIcon[0];
}
var text;
if(args.Enum.SubMenu.Text)
{
entry.Text = args.Enum.SubMenu.Text;
}
for(var i = 0; i < GetLength(layoutVals); ++i)
{
icon = 0;
EnumEntrySettings(text, icon, description, i, args, entry);
if(args.Enum.Inline)
{
BooleanToggleText(i == index, text, icon);
}
else if(i == index)
{
ExtraIcon(text, icon, [MN7I, 1]);
}
ret.Entries[] = Menu_Entry({
Text = text,
Icon = icon,
Description = description,
Callbacks = [Menu_Callback(MenuObjectCallback("AdaptorEnumSubMenuItem"), Menu_CallbackType_Defaults, [Menu_CallbackArg_Args, Menu_CallbackArg_All])],
Args = [i, args]
});
}
return ret;
}
func AdaptorEnumSubMenuItem(args, array allArgs)
{
var index = args[0];
args = args[1];
var val = ScopedVar(args.Variable);
var oldVal = val;
var enumVals = args.Enum.Values;
var layout = args.Enum.Layout;
ScopedVar(args.Variable) = val = enumVals[index][layout[Menu_Layout__ValuePos] - 1];
allArgs[Menu_CallbackArg_Args] = args.Args;
allArgs[Menu_CallbackArg_FromSubMenu] = true;
var reaction = AdaptorCommandCallChangedCallback(args.Callbacks, val, oldVal, allArgs);
if(reaction != Menu_React_None)
{
return reaction;
}
if(args.Enum.Inline)
{
return Menu_React_Refresh;
}
return Menu_React_Back;
}
func AdaptorBitFieldItemsFactory(args)
{
var entry = args[0];
var entrySettings = args[1].Entry;
args = args[1].Adaptor;
var text = entry.Text;
var description = entry.Description;
var icon = entry.Icon;
var fieldValue = ScopedVar(args.Variable);
var layoutVals = args.BitField.Values;
var layout = args.BitField.Layout;
var ret = [];
for(var val in layoutVals)
{
var mask = val[layout[Menu_Layout__ValuePos] - 1];
text = entry.Text || "%s";
if(layout[Menu_Layout__TextPos])
{
text = Format(text, val[layout[Menu_Layout__TextPos] - 1]);
}
icon = entry.Icon;
BooleanToggleText((fieldValue & mask) == mask, text, icon);
if(layout[Menu_Layout_Icon])
{
ExtraIcon(text, icon, val[layout[Menu_Layout_Icon] - 1]);
}
if(layout[Menu_Layout_Description])
{
description = Format(description, val[layout[Menu_Layout_Description] - 1]);
}
args.BitField.Mask = mask;
ret[] = Menu_Entry(Extend(entrySettings, { Text = text, Callbacks = [Menu_Callback(MenuObjectCallback("AdaptorCommand"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])], Icon = icon, Description = description, Args = args }, true));
}
return { Entries = ret };
}
func WrapOrBind(int val, array limits, bool wrap)
{
if(!limits)
{
return val;
}
var min = limits[0];
var max = limits[1];
if(val < min)
{
if(wrap)
{
return max;
}
else
{
return min;
}
}
else if(val > max)
{
if(wrap)
{
return min;
}
else
{
return max;
}
}
else
{
return val;
}
}
func AdaptorCommandCallChangedCallback(callbacks, val, oldVal, array allArgs)
{
allArgs[Menu_CallbackArg_Action] = Menu_CallbackType_ValueChanged;
allArgs[Menu_CallbackArg_NewValue] = val;
allArgs[Menu_CallbackArg_OldValue] = oldVal;
return CallCallbacks(callbacks, Menu_CallbackType_ValueChanged, allArgs, ...);
}
func AdaptorCommand(int action, object obj, args, array allArgs)
{
var val = ScopedVar(args.Variable);
var oldVal = val;
allArgs[Menu_CallbackArg_Args] = args.Args;
var reaction = CallCallbacks(args.Callbacks, action, allArgs, Menu_React_Refresh, true);
if(reaction != Menu_React_Refresh)
{
return reaction;
}
if(args.Type == Menu_AdaptorType_Boolean)
{
if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2))
{
ScopedVar(args.Variable) = val = !val;
reaction = AdaptorCommandCallChangedCallback(args.Callbacks, val, oldVal, allArgs);
if(reaction != Menu_React_None)
{
return reaction;
}
}
else
{
return Menu_React_KeepOpen;
}
}
else if(args.Type == Menu_AdaptorType_Integer)
{
var limits = AdaptorGetLimits(args.Limits);
var wrapAround = args.WrapAround;
var step = args.StepSize;
var stepSize = 1;
if(step)
{
stepSize = step[0];
}
if(action == Menu_CallbackType_Special2)
{
if(msgBoardMode == 0)
{
msgBoardMode = Menu_AdaptorType_Integer + 1;
msgBoardEntry = args.EntryIndex;
CallMessageBoard(this, false, args.MessageBoardText, GetOwner(obj));
}
return Menu_React_KeepOpen;
}
else if(action == Menu_CallbackType_Normal)
{
val += stepSize;
}
else if(action == Menu_CallbackType_Close)
{
val -= stepSize;
}
else
{
return Menu_React_KeepOpen;
}
val = WrapOrBind(val, limits, wrapAround);
if(val != oldVal)
{
ScopedVar(args.Variable) = val;
reaction = AdaptorCommandCallChangedCallback(args.Callbacks, val, oldVal, allArgs);
if(reaction != Menu_React_None)
{
return reaction;
}
}
else
{
return Menu_React_KeepOpen;
}
}
else if(args.Type == Menu_AdaptorType_String)
{
if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2))
{
if(msgBoardMode == 0)
{
msgBoardMode = Menu_AdaptorType_String + 1;
msgBoardEntry = args.EntryIndex;
CallMessageBoard(this, false, args.MessageBoardText, GetOwner(obj));
}
}
if(action == Menu_CallbackType_Close)
{
return Menu_React_None;
}
else
{
return Menu_React_KeepOpen;
}
}
else if(args.Type == Menu_AdaptorType_ID)
{
if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2))
{
if(msgBoardMode == 0)
{
msgBoardMode = Menu_AdaptorType_ID + 1;
msgBoardEntry = args.EntryIndex;
CallMessageBoard(this, false, args.MessageBoardText, GetOwner(obj));
}
}
return Menu_React_KeepOpen;
}
else if(args.Type == Menu_AdaptorType_Enum)
{
if(GetType(args.Enum.SubMenu.On) == C4V_Bool)
{
if(action & Menu_CallbackType_Defaults)
{
return Menu_React_ShowSubMenu;
}
else
{
return Menu_React_None;
}
}
else if(args.Enum.SubMenu.On & action)
{
return Menu_React_ShowSubMenu;
}
var enumVals = args.Enum.Values;
var layout = args.Enum.Layout;
var index = EnumValPos(enumVals, layout, val, args.Enum.AllowUnknown);
var wrapAround = args.WrapAround;
if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2))
{
++index;
}
else if(action == Menu_CallbackType_Close)
{
--index;
}
else
{
return Menu_React_None;
}
index = WrapOrBind(index, [0, GetLength(enumVals) - 1], wrapAround);
ScopedVar(args.Variable) = val = enumVals[index][layout[Menu_Layout__ValuePos] - 1];
reaction = AdaptorCommandCallChangedCallback(args.Callbacks, val, oldVal, allArgs);
if(reaction != Menu_React_None)
{
return reaction;
}
}
else if(args.Type == Menu_AdaptorType_BitField)
{
if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2))
{
var mask = args.BitField.Mask;
if((val & mask) == mask)
{
val &= ~mask;
}
else
{
val |= mask;
}
ScopedVar(args.Variable) = val;
reaction = AdaptorCommandCallChangedCallback(args.Callbacks, val, oldVal, allArgs);
if(reaction != Menu_React_None)
{
return reaction;
}
}
else
{
return Menu_React_KeepOpen;
}
}
return Menu_React_Refresh;
}
func InputCallback(string input, int plr)
{
var entry = columnEntries[msgBoardEntry[1]][msgBoardEntry[0]];
var args = entry.Args;
var callbackArgs = args.Args;
var oldVal = ScopedVar(args.Variable);
var val = input;
if(msgBoardMode - 1 == Menu_AdaptorType_Integer)
{
var int = ParseInt(input);
var limits = AdaptorGetLimits(args.Limits);
var step = args.StepSize;
if(GetType(int) == C4V_Int)
{
if(step && step[1])
{
var s = step[0];
if(GetType(step[1]) == C4V_Int)
{
s = step[1];
}
int = ((int + s / 2) / s) * s;
}
if(limits)
{
int = BoundBy(int, limits[0], limits[1]);
}
ScopedVar(args.Variable) = val = int;
}
else
{
val = oldVal;
}
}
else if(msgBoardMode - 1 == Menu_AdaptorType_String)
{
if(args.NoEmptyString && (!input || input == ""))
{
val = oldVal;
}
else
{
ScopedVar(args.Variable) = input;
}
}
else if(msgBoardMode - 1 == Menu_AdaptorType_ID)
{
val = GetIDByName(input); // WARNING: desyncs between clients with different languages
if(!val && GetLength(input) == 4) val = C4Id(input);
if(!GetName(0, val)) val = 0;
ScopedVar(args.Variable) = val;
}
msgBoardMode = 0;
if(val != oldVal)
{
var reaction = CallCallbacks(args.Callbacks, Menu_CallbackType_ValueChanged, [Menu_CallbackType_ValueChanged, entry.Icon, settings.Object, callbackArgs, 0, 0, val, oldVal]);
if(reaction != Menu_React_None)
{
return React(reaction, msgBoardEntry);
}
}
Refresh(msgBoardEntry);
}
/*
CreateNewMenu({
Settings = {
Icon = ,
Title = ,
Object =