#strict 2 static const DT_Menu = MN7I; static const DT_Menu_Combined = -1; static const DT_Menu_MenuVar = -1; // Enums and bitfields static const DT_Menu_Settings_Icon = 0; static const DT_Menu_Settings_Object = 1; static const DT_Menu_Settings_Extra = 2; static const DT_Menu_Settings_Title = 3; static const DT_Menu_Settings_ExtraData = 4; static const DT_Menu_Settings_Style = 5; static const DT_Menu_Settings_KeepOpen = 6; static const DT_Menu_Settings_Parent = 7; static const DT_Menu_Settings_Size = 8; static const DT_Menu_Settings_RefreshInterval = 9; static const DT_Menu_Settings_Selection = 10; static const DT_Menu_Settings_ConditionDisableMode = 11; static const DT_Menu_Settings_ConditionAllowSelection = 12; static const DT_Menu_Settings_Callbacks = 13; static const DT_Menu_Settings_Decoration = 14; static const DT_Menu_Settings_RequireAction = 15; static const DT_Menu_Settings_KeepParentOnClose = 16; static const DT_Menu_Settings_Vars = 17; static const DT_Menu_Settings_Closable = 18; static const DT_Menu_Settings_InstantDescription = 19; static const DT_Menu_Settings_InstantDescriptionFlags = 20; static const DT_Menu_Settings_InstantDescriptionPosition = 21; static const DT_Menu_Settings_InstantDescriptionDecoration = 22; static const DT_Menu_KeepOpen_Not = 0x0; static const DT_Menu_KeepOpen_Keep = 0x1; static const DT_Menu_KeepOpen_Force = 0x2; static const DT_Menu_KeepOpen_Permanent = 0x4; static const DT_Menu_KeepOpen_Refresh = 0x8; static const DT_Menu_KeepOpen_RefreshContinuously = 0x10; static const DT_Menu_KeepOpen_Refresh_Mask = 0x18; // DT_Menu_KeepOpen_Refresh | DT_Menu_KeepOpen_RefreshContinuously static const DT_Menu_Type_Setting = 0; static const DT_Menu_Type_Entry = 1; static const DT_Menu_Type_Factory = 2; static const DT_Menu_Type_Columns = 3; 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_OverrideReaction = -1; static const Menu_React_None = 0; 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_Max = Menu_React_ShowSubmenu; 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; static const Menu_ConditionReact_Max = Menu_ConditionReact_GrayOut; 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_Condition_Max = Menu_Condition_DenySelection; static const DT_Menu_Entry_Text = 0; static const DT_Menu_Entry_Callbacks = 1; static const DT_Menu_Entry_Icon = 2; static const DT_Menu_Entry_Count = 3; static const DT_Menu_Entry_Description = 4; static const DT_Menu_Entry_Extra = 5; static const DT_Menu_Entry_XPar1 = 6; static const DT_Menu_Entry_XPar2 = 7; static const DT_Menu_Entry_Args = 8; static const DT_Menu_Entry_Condition = 9; static const DT_Menu_Entry_Placeholder = 10; static const DT_Menu_Entry_InstantDescriptionIcon = 11; static const DT_Menu_Entry_SubMenuColumnData = 12; 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 = 0; static const Menu_AdaptorType_Integer = 1; static const Menu_AdaptorType_String = 2; static const Menu_AdaptorType_ID = 3; static const Menu_AdaptorType_Enum = 4; static const Menu_AdaptorType_BitField = 5; 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_CallbackArg_Min = Menu_CallbackArg_All; static const Menu_CallbackArg_Max = Menu_CallbackArg_EntryNumber; static const DT_Menu_Adaptor_Type = 0; static const DT_Menu_Adaptor_Variable = 1; static const DT_Menu_Adaptor_Callbacks = 2; static const DT_Menu_Adaptor_MessageBoardText = 3; static const DT_Menu_Adaptor_Limits = 4; static const DT_Menu_Adaptor_StepSize = 5; static const DT_Menu_Adaptor_LayoutVals = 6; static const DT_Menu_Adaptor_NoEmptyString = 7; static const DT_Menu_Adaptor_EntryIndex = 8; static const DT_Menu_Adaptor_Mask = 9; static const DT_Menu_Adaptor_WrapAround = 10; static const DT_Menu_Adaptor_Args = 11; static const DT_Menu_Adaptor_EnumSubmenu = 12; static const DT_Menu_Adaptor_EnumSubmenuTitleAndText = 13; static const DT_Menu_Adaptor_EnumSubmenuIcon = 14; static const DT_Menu_Adaptor_EnumAllowUnknown = 15; static const DT_Menu_Adaptor_EnumInline = 16; 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 suspended; local closing; local refreshing; local msgBoardMode; local msgBoardEntry; local noSelectionCallbacks; local multiColumnMode; local subMenu; local vars; func Initialize() { currentColumnSelections = [-1]; columnEntries = []; msgBoardEntry = []; vars = []; } func Destruction() { Close(); } func Create(array cSettings, array cEntries) { settings = cSettings; createEntries = cEntries; settings[DT_Menu_Settings_ConditionDisableMode] = settings[DT_Menu_Settings_ConditionDisableMode] || Menu_ConditionReact_Hide; settings[DT_Menu_Settings_InstantDescriptionDecoration] = settings[DT_Menu_Settings_InstantDescriptionDecoration] || DT_Menu_DefaultInstantDescriptionDecoration; if(!settings[DT_Menu_Settings_InstantDescriptionPosition]) { settings[DT_Menu_Settings_InstantDescriptionFlags] = settings[DT_Menu_Settings_InstantDescriptionFlags] || MSG_Left | MSG_Bottom; settings[DT_Menu_Settings_InstantDescriptionPosition] = [40, -60]; } multiColumnMode = false; columnCount = 0; columnOffsets = []; if(settings[DT_Menu_Settings_Vars]) { var settingVars = settings[DT_Menu_Settings_Vars]; for(var i = 0; i < GetLength(settingVars); ++i) { if(settingVars[i]) { vars[i] = settingVars[i]; } } settings[DT_Menu_Settings_Vars] = 0; } var factoryArgs = []; factoryArgs[Menu_CallbackArg_Menu] = this; factoryArgs[Menu_CallbackArg_MenuObject] = settings[DT_Menu_Settings_Object]; var index = 0; columnEntries[0] = []; HandleEntries(createEntries, index, columnEntries[0], settings, factoryArgs, 0, true); if(columnCount > 1 && settings[DT_Menu_Settings_Style] != C4MN_Style_Context) { FatalError("CreateNewMenu: Multi column menus (and submenus as columns) are only supported with Menu_Style_Context()"); } if(settings[DT_Menu_Settings_Parent]) settings[DT_Menu_Settings_Parent]->Suspend(); if(!(settings[DT_Menu_Settings_Object])) { FatalError("Assertion failed: CreateNewMenu: menu object is missing; assertion code: settings[DT_Menu_Settings_Object]"); } var title = settings[DT_Menu_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 var columnSelection = currentColumnSelections[i]; if((columnSelection == -1) || !columnEntries[i] || columnSelection >= GetLength(columnEntries[i])) { currentColumnSelections[i] = GetLength(columnEntries[i]) - 1; break; } var submenuData = columnEntries[i][columnSelection][DT_Menu_Entry_SubMenuColumnData]; if(submenuData) { columnEntries[i + 1] = submenuData[2]; var submenuSettings = submenuData[0]; /*var */submenuTitle = submenuSettings[DT_Menu_Settings_Title] || ""; if(submenuSettings[DT_Menu_Settings_Icon]) { submenuTitle = Format("{{%i}} %s", submenuSettings[DT_Menu_Settings_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[DT_Menu_Settings_Icon], settings[DT_Menu_Settings_Object], this, settings[DT_Menu_Settings_Extra], title, settings[DT_Menu_Settings_ExtraData], settings[DT_Menu_Settings_Style], true, MN7I); if(GetType(settings[DT_Menu_Settings_Size]) == C4V_Array) SetMenuSize(settings[DT_Menu_Settings_Size][0], settings[DT_Menu_Settings_Size][1], settings[DT_Menu_Settings_Object]); AddEntries(); if(GetLength(columnEntries[0]) > 0) { if(!refreshing) { SelectEntry(settings[DT_Menu_Settings_Selection][0], settings[DT_Menu_Settings_Selection][1]); } } else { Log("WARNING: DT_Menu::Create: Created an empty menu"); } if(settings[DT_Menu_Settings_Decoration]) { SetMenuDecoration(settings[DT_Menu_Settings_Decoration], settings[DT_Menu_Settings_Object]); } if(!GetEffect("Menu", this)) AddEffect("Menu", this, 1, 1, this, 0); } func LeaveSubMenuColumn() { if(!(!multiColumnMode && currentColumn > 0)) { FatalError("Assertion failed: LeaveSubMenuColumn: currentColumn is not a submenu; can't leave; assertion code: !multiColumnMode && currentColumn > 0"); } SelectEntry(currentColumnSelections[currentColumn - 1], currentColumn - 1); Refresh(GetSelection(Menu_Selection_WithColumn)); } func SelectEntry(indexOrChain, int column) { if(GetType(indexOrChain) == C4V_Array) { if(!(GetLength(indexOrChain) <= columnCount)) { FatalError("Assertion failed: SelectEntry: index chain is longer than there are columns; assertion code: GetLength(indexOrChain) <= columnCount"); } for(var i = 0; i < GetLength(indexOrChain); ++i) { SelectEntry(indexOrChain[i], i); } } else if(!indexOrChain || GetType(indexOrChain) == C4V_Int) { if(!(column >= 0 && column < GetLength(columnEntries) && GetType(columnEntries[column]) == C4V_Array)) { FatalError("Assertion failed: SelectEntry: column index out of range; assertion code: column >= 0 && column < GetLength(columnEntries) && GetType(columnEntries[column]) == C4V_Array"); } var entryCount = GetLength(columnEntries[column]); if(indexOrChain < 0) indexOrChain = entryCount + indexOrChain; if(!(indexOrChain >= 0 && indexOrChain < entryCount)) { FatalError("Assertion failed: SelectEntry: selection index out of range; assertion code: indexOrChain >= 0 && indexOrChain < entryCount"); } SelectMenuItem(EncodeSelection(indexOrChain, column), settings[DT_Menu_Settings_Object]); } 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][DT_Menu_Entry_Placeholder]) { return SelectEntry(i, column); } } } func SelectBottomEntry(int column) { for(var i = GetLength(columnEntries[column]) - 1; i >= 0; --i) { if(columnEntries[column][i][DT_Menu_Entry_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[DT_Menu_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[DT_Menu_Settings_Object]) { return Close(); } else if(settings[DT_Menu_Settings_RequireAction]) { var obj = settings[DT_Menu_Settings_Object]; var requirement = settings[DT_Menu_Settings_RequireAction]; if(GetAction(obj) != requirement[0] || (requirement[1] && GetActionTarget(0, obj) != requirement[1])) { return Close(); } } if(suspended) return; if(msgBoardMode != 0 && !TestMessageBoard(GetOwner(settings[DT_Menu_Settings_Object]), true)) { var entry = columnEntries[msgBoardEntry[1]][msgBoardEntry[0]]; var args = entry[DT_Menu_Entry_Args]; var reaction = CallCallbacks(args[DT_Menu_Adaptor_Callbacks], Menu_CallbackType_InputAborted, [Menu_CallbackType_InputAborted, entry[DT_Menu_Entry_Icon], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]); if(reaction != Menu_React_None) { React(reaction, msgBoardEntry); } msgBoardMode = 0; } if(!GetMenu(settings[DT_Menu_Settings_Object])) { if(settings[DT_Menu_Settings_KeepOpen] & (DT_Menu_KeepOpen_Refresh_Mask | DT_Menu_KeepOpen_Force) && !settings[DT_Menu_Settings_Closable]) { Refresh(GetSelection(Menu_Selection_WithColumn)); } else { return FX_Execute_Kill; } } if(settings[DT_Menu_Settings_KeepOpen] & DT_Menu_KeepOpen_RefreshContinuously && !(effectTime % settings[DT_Menu_Settings_RefreshInterval])) { Refresh(GetSelection(Menu_Selection_WithColumn)); } } func FxMenuStop(object target, int effectNumber, int reason, bool temp) { if(temp) { return; } CloseMenu(settings[DT_Menu_Settings_Object]); if(settings[DT_Menu_Settings_InstantDescription]) HideInstantDescription(); if(settings[DT_Menu_Settings_Parent]) settings[DT_Menu_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(!(!ret || (GetType(ret) == C4V_Int && ret > 0 && ret <= Menu_React_Max) || (GetType(ret) == C4V_Array && ret[0] > 0 && ret[0] <= Menu_React_Max && (!ret[1] || GetType(ret[1]) == C4V_Int)))) { FatalError(Format("Assertion failed: CallCallbacks: invalid return value detected: %v; assertion code: !ret || (GetType(ret) == C4V_Int && ret > 0 && ret <= Menu_React_Max) || (GetType(ret) == C4V_Array && ret[0] > 0 && ret[0] <= Menu_React_Max && (!ret[1] || GetType(ret[1]) == C4V_Int))", ret)); } if(!noGlobalCallbacks) { args[Menu_CallbackArg_Returned] = ret; var globalRet = CallCallbacks(settings[DT_Menu_Settings_Callbacks], type, args, defaultRet, true); if(GetType(globalRet) == C4V_Array && globalRet[0] == Menu_React_OverrideReaction) { ret = globalRet[1]; if(!(!ret || (GetType(ret) == C4V_Int && ret > 0 && ret <= Menu_React_Max) || (GetType(ret) == C4V_Array && ret[0] > 0 && ret[0] <= Menu_React_Max && (!ret[1] || GetType(ret[1]) == C4V_Int)))) { FatalError(Format("Assertion failed: CallCallbacks: invalid return value detected: %v; assertion code: !ret || (GetType(ret) == C4V_Int && ret > 0 && ret <= Menu_React_Max) || (GetType(ret) == C4V_Array && ret[0] > 0 && ret[0] <= Menu_React_Max && (!ret[1] || GetType(ret[1]) == C4V_Int))", ret)); } } else if(globalRet) { Log("WARNING: DT_Menu::CallCallbacks: ignoring non-zero return value of global callback because override is not specified: %v", globalRet); } } return ret; } func Close(bool closeParents) { closing = true; if(subMenu) { subMenu->Close(); } if(closeParents && settings[DT_Menu_Settings_Parent]) settings[DT_Menu_Settings_Parent]->Close(true); RemoveEffect("Menu", this); } func Suspend(bool cont) { if(suspended == !cont) return; if(suspended = !cont) { CloseMenu(settings[DT_Menu_Settings_Object]); } else if(!closing) { Refresh(GetSelection(Menu_Selection_WithColumn)); } } func HandleEntries(array factoryEntries, int& i, array& ret, array& retSettings, array& factoryArgs, int column, bool isMainColumn) { if(column >= columnCount) { columnCount = column + 1; } for(var entry in factoryEntries) { if(entry[0] == 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[2]) { if(multiColumnMode) { FatalError("DT_Menu::HandleEntries: Menu_Columns() and Submenu columns can not be used together"); } var index = 0; HandleEntries(entry[2][1], index, entry[2][2], entry[2][0], factoryArgs, column + 1, false); entry[1][DT_Menu_Entry_SubMenuColumnData] = entry[2]; } ret[i++] = entry[1]; } else if(entry[0] == DT_Menu_Type_Factory) { for(var callback in entry[1][0]) { factoryArgs[Menu_CallbackArg_Args] = entry[1][1]; factoryArgs[Menu_CallbackArg_EntryNumber] = i; factoryArgs[Menu_CallbackArg_EntryColumn] = column; var factoryResult = CallA(callback, BindCallbackArgs(factoryArgs, entry[1][2])); if(GetType(factoryResult) == C4V_Array) { var newEntries = []; UncombineAndDistinguish(factoryResult, retSettings, newEntries); HandleEntries(newEntries, i, ret, retSettings, factoryArgs, column, isMainColumn); } else if(factoryResult == Menu_React_Close) { return Close(); } else { Log("WARNING: DT_Menu::HandleEntries: ignoring invalid return value of Factory: %v (%v)", factoryResult, callback); } } } else if(entry[0] == 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[1]) { columnEntries[j] = []; var index = 0; var tempSettings = []; HandleEntries(columnData, index, columnEntries[j], settings, factoryArgs, j, false); ++j; if(GetLength(tempSettings) > 0) { FatalError("DT_Menu::HandleEntries: menu settings can not appear in Menu_Columns()"); } } } else { Log("WARNING: DT_Menu::HandleEntries: ignoring invalid Non-Entry/Factory: %v", entry); } } } 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[DT_Menu_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) { var rowIndex = i - columnOffsets[j]; var entries = columnEntries[j]; if(entries && rowIndex >= 0 && rowIndex < GetLength(entries)) { var entry = entries[rowIndex]; var condition = entry[DT_Menu_Entry_Condition], conditionRet; var text = entry[DT_Menu_Entry_Text], noCommand = !entry[DT_Menu_Entry_Placeholder] || (entry[DT_Menu_Entry_Placeholder] != true && !entry[DT_Menu_Entry_Callbacks]); if(condition) { if(noCommand || condition[1] == Menu_Condition_DenySelection || (condition[1] == Menu_Condition_Default && !settings[DT_Menu_Settings_ConditionAllowSelection])) { noCommand = true; } conditionRet = CallA(condition[0], [entry[DT_Menu_Entry_Icon], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]) || settings[DT_Menu_Settings_ConditionDisableMode]; 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[DT_Menu_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[DT_Menu_Entry_Extra] |= C4MN_Add_ImgIndexed; iconIndex = icon[1]; entry[DT_Menu_Entry_XPar1] = icon[1]; } } if(GetType(icon[0]) == C4V_C4Object) { entry[DT_Menu_Entry_Extra] |= C4MN_Add_ImgObject; entry[DT_Menu_Entry_XPar1] = icon[0]; if(icon[1]) { deleteIcon = icon[0]; } if(!iconID) { iconID = GetID(icon[0]); } } } else { iconID = icon; } entry[DT_Menu_Entry_Icon] = iconID; if(!entry[DT_Menu_Entry_InstantDescriptionIcon]) { entry[DT_Menu_Entry_InstantDescriptionIcon] = [iconID, iconIndex]; } if(settings[DT_Menu_Settings_InstantDescription]) { entry[DT_Menu_Entry_Extra] |= C4MN_Add_ForceNoDesc; } entry[DT_Menu_Entry_Placeholder] = !noCommand; columnEntries[j][rowIndex] = entry; var showDesc = !(entry[DT_Menu_Entry_Extra] & C4MN_Add_ForceNoDesc); if(entry[DT_Menu_Entry_SubMenuColumnData] && currentColumnSelections[j] == rowIndex) { text = Format("%s {{MN7I:7}}", text); } AddMenuItem(text, !noCommand && "MenuItemCommand", iconID, settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Count], entryIndex, showDesc && entry[DT_Menu_Entry_Description], entry[DT_Menu_Entry_Extra], entry[DT_Menu_Entry_XPar1], entry[DT_Menu_Entry_XPar2]); if(deleteIcon) { RemoveObject(deleteIcon); } } else { AddMenuItem("", "DummyItemCommand", 0, settings[DT_Menu_Settings_Object], 0, entryIndex, 0, C4MN_Add_ForceNoDesc); } ++entryIndex; } } } func React(reaction, array entryIndex, int refreshDelayed) { if(reaction == Menu_React_Close) { Close(!settings[DT_Menu_Settings_KeepParentOnClose]); } else if(reaction == Menu_React_Back) { if(currentColumn > 0 && !multiColumnMode) { LeaveSubMenuColumn(); } else { Close(); } } else if(reaction == Menu_React_Refresh || (settings[DT_Menu_Settings_KeepOpen] & DT_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(array entry) { var condition = entry[DT_Menu_Entry_Condition]; return !condition || (CallA(condition[0], [entry[DT_Menu_Entry_Icon], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]) || settings[DT_Menu_Settings_ConditionDisableMode]) == Menu_ConditionReact_Show; } func MenuItemCommand(id ID, int itemNumber, int action) { var column = DecodeSelection(itemNumber); var entry = columnEntries[column][itemNumber]; var condition = entry[DT_Menu_Entry_Condition]; action = action || Menu_CallbackType_Normal; var reaction; if(CheckCondition(entry)) { reaction = CallCallbacks(entry[DT_Menu_Entry_Callbacks], action, [action, ID, settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]); } else { if(condition[1] == 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]][DT_Menu_Entry_SubMenuColumnData][0][DT_Menu_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[DT_Menu_Entry_Callbacks], Menu_CallbackType_Close, [Menu_CallbackType_Close, entry[DT_Menu_Entry_Icon], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]); } React(reaction, [selection, column], true); } if((settings[DT_Menu_Settings_KeepOpen] != DT_Menu_KeepOpen_Not && settings[DT_Menu_Settings_KeepOpen] != DT_Menu_KeepOpen_Permanent && !settings[DT_Menu_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 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[DT_Menu_Entry_Icon], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args], selection, oldColumnSelection]; args[Menu_CallbackArg_NewSelectionColumn] = currentColumn; args[Menu_CallbackArg_OldSelectionColumn] = oldColumn; deselectReaction = CallCallbacks(entry[DT_Menu_Entry_Callbacks], Menu_CallbackType_Deselection, args); } var oldIsColumnSubmenu = !!entry[DT_Menu_Entry_SubMenuColumnData]; currentColumnSelections[column] = selection; entry = columnEntries[column][selection]; if(!noSelectionCallbacks && CheckCondition(entry)) { var args = [Menu_CallbackType_Selection, entry[DT_Menu_Entry_Icon], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args], selection, oldColumnSelection]; args[Menu_CallbackArg_NewSelectionColumn] = currentColumn; args[Menu_CallbackArg_OldSelectionColumn] = oldColumn; selectReaction = CallCallbacks(entry[DT_Menu_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 && (selection + columnOffsets[currentColumn] == oldColumnOldSelection + columnOffsets[oldColumn])) { if(currentColumn == oldColumn + 1 && oldIsColumnSubmenu) { var targetSelection = columnEntries[currentColumn - 1][oldColumnOldSelection][DT_Menu_Entry_SubMenuColumnData][0][DT_Menu_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; } } } if(skipHandling) { currentColumn = oldColumn; return; } if(settings[DT_Menu_Settings_InstantDescription]) { ShowInstantDescription(columnEntries[currentColumn][selection]); } // update submenu column if necessary if(!multiColumnMode && selection != oldColumnSelection) { if(entry[DT_Menu_Entry_SubMenuColumnData]) { 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]; 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[DT_Menu_Settings_Object]); var oldRefreshing = refreshing; refreshing = true; Create(settings, 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[DT_Menu_Settings_InstantDescription]) { ShowInstantDescription(columnEntries[currentColumn][selection]); } } noSelectionCallbacks = oldNoSelectionCallbacks; } } func ShowInstantDescription(array entry) { if(entry[DT_Menu_Entry_Description]) { var icon = entry[DT_Menu_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[DT_Menu_Settings_InstantDescriptionPosition]; CustomMessage(Format("@%s", entry[DT_Menu_Entry_Description]), 0, GetOwner(settings[DT_Menu_Settings_Object]), pos[0], pos[1], 0, settings[DT_Menu_Settings_InstantDescriptionDecoration], iconString, settings[DT_Menu_Settings_InstantDescriptionFlags]); } else { HideInstantDescription(); } } func HideInstantDescription() { CustomMessage("", 0, GetOwner(settings[DT_Menu_Settings_Object]), 0, 0, 0, 0, Format("%i", DT_Menu_IconDummy), settings[DT_Menu_Settings_InstantDescriptionFlags]); } // ---------------------------------------------------------------------------- global func Menu__Setting(array setting) { return [DT_Menu_Type_Setting, setting]; } global func Menu_Icon(id icon) { return Menu__Setting([DT_Menu_Settings_Icon, icon]); } global func Menu_Object(object obj) { return Menu__Setting([DT_Menu_Settings_Object, obj]); } global func Menu__Extra(int extra, int data) { return Menu_Combined([Menu__Setting([DT_Menu_Settings_Extra, extra]), Menu_ExtraData(data)]); } global func Menu_Size(int width, int height) { if(!(width >= 0 && height >= 0)) { FatalError("Assertion failed: Menu_Size: Negative Menu_Size doesn't make sense; assertion code: width >= 0 && height >= 0"); }return Menu__Setting([DT_Menu_Settings_Size, [width, height]]); } global func Menu_ExtraData(int data) { return Menu__Setting([DT_Menu_Settings_ExtraData, data]); } global func Menu_Title(string title) { return Menu__Setting([DT_Menu_Settings_Title, title]); } global func Menu_RefreshInterval(int interval) { if(!(interval >= 0)) { FatalError("Assertion failed: Menu_RefreshInterval: Negative interval doesn't make sense; assertion code: interval >= 0"); }return Menu__Setting([DT_Menu_Settings_RefreshInterval, interval + !interval]); } global func Menu_Selection(int selection, int column) { return Menu__Setting([DT_Menu_Settings_Selection, [selection, column]]); } global func Menu__Style(int style) { return Menu__Setting([DT_Menu_Settings_Style, style]); } global func Menu__KeepOpen(int mode) { return Menu__Setting([DT_Menu_Settings_KeepOpen, mode]); } global func Menu_Extra_None() { return Menu__Extra(C4MN_Extra_None); } global func Menu_Extra_Components() { return Menu__Extra(C4MN_Extra_Components); } global func Menu_Extra_Value() { return Menu__Extra(C4MN_Extra_Value); } global func Menu_Extra_MagicValue(int compare) { return Menu__Extra(C4MN_Extra_MagicValue, compare); } global func Menu_Extra_Info() { return Menu__Extra(C4MN_Extra_Info); } global func Menu_Extra_ComponentsMagic() { return Menu__Extra(C4MN_Extra_ComponentsMagic); } global func Menu_Style_Normal() { return Menu__Style(C4MN_Style_Normal); } global func Menu_Style_Context() { return Menu__Style(C4MN_Style_Context); } global func Menu_Style_Info() { return Menu__Style(C4MN_Style_Info); } global func Menu_Style_Dialog() { return Menu__Style(C4MN_Style_Dialog); } global func Menu_Style_EqualItemHeight() { return Menu__Style(C4MN_Style_EqualItemHeight); } global func Menu_DontKeepOpen() { return Menu__KeepOpen(DT_Menu_KeepOpen_Not); } global func Menu_KeepOpen() { return Menu__KeepOpen(DT_Menu_KeepOpen_Keep); } global func Menu_ForceKeepOpen() { return Menu__KeepOpen(DT_Menu_KeepOpen_Force); } global func Menu_Refresh() { return Menu__KeepOpen(DT_Menu_KeepOpen_Refresh); } global func Menu_RefreshContinuously(int interval) { return Menu_Combined([Menu__KeepOpen(DT_Menu_KeepOpen_RefreshContinuously), Menu_RefreshInterval(interval)]); } global func Menu_Permanent() { return Menu__KeepOpen(DT_Menu_KeepOpen_Permanent); } global func Menu_ConditionAllowSelection() { return Menu__Setting([DT_Menu_Settings_ConditionAllowSelection, true]);} global func Menu_ConditionDenySelection() { return Menu__Setting([DT_Menu_Settings_ConditionAllowSelection, false]);} global func Menu_Callbacks(array callbacks) { if(!(MN7I->ValidateMenuCallbacks(callbacks))) { FatalError("Assertion failed: Menu_Callbacks: Invalid callbacks used; assertion code: MN7I->ValidateMenuCallbacks(callbacks)"); }return Menu__Setting([DT_Menu_Settings_Callbacks, callbacks]); } global func Menu_Decoration(id decoration) { if(!(MN7I->ValidateDeco(decoration))) { FatalError("Assertion failed: Menu_Decoration: Invalid decoration used; assertion code: MN7I->ValidateDeco(decoration)"); }return Menu__Setting([DT_Menu_Settings_Decoration, decoration]); } global func Menu_RequireAction(string action, object target) { if(!(action)) { FatalError("Assertion failed: Menu_RequireAction: action is mandatory; assertion code: action"); }return Menu__Setting([DT_Menu_Settings_RequireAction, [action, target]]); } global func Menu_KeepParentOnClose(bool dontKeep) { return Menu__Setting([DT_Menu_Settings_KeepParentOnClose, !dontKeep]); } global func Menu_ConditionDisableMode(mode) { if(!(!mode || (GetType(mode) == C4V_Int && mode >= 0 && mode <= Menu_ConditionReact_Max) || (GetType(mode) == C4V_Array && mode[0] == Menu_ConditionReact_CustomFormat && GetType(mode[1]) == C4V_String))) { FatalError("Assertion failed: Menu_ConditionDisableMode: invalid mode; assertion code: !mode || (GetType(mode) == C4V_Int && mode >= 0 && mode <= Menu_ConditionReact_Max) || (GetType(mode) == C4V_Array && mode[0] == Menu_ConditionReact_CustomFormat && GetType(mode[1]) == C4V_String)"); }return Menu__Setting([DT_Menu_Settings_ConditionDisableMode, mode]);} global func Menu_Vars(array vars) { return Menu__Setting([DT_Menu_Settings_Vars, vars]); } global func Menu_Closable(bool notClosable) { return Menu__Setting([DT_Menu_Settings_Closable, !notClosable]); } global func Menu_InstantDescription(bool disable) { return Menu__Setting([DT_Menu_Settings_InstantDescription, !disable]); } global func Menu_InstantDescriptionDecoration(id decoration) { return Menu__Setting([DT_Menu_Settings_InstantDescriptionDecoration, decoration]); } global func Menu_InstantDescriptionMessageFlags(int flags) { return Menu__Setting([DT_Menu_Settings_InstantDescriptionFlags, flags]); } global func Menu_InstantDescriptionPosition(int x, int y) { return Menu__Setting([DT_Menu_Settings_InstantDescriptionPosition, [x, y]]); } global func Menu_Callback(array callback, int types, array argBinding) { if(!(MN7I->CheckCallback(callback))) { FatalError("Assertion failed: Menu_Callback invalid callback used; assertion code: MN7I->CheckCallback(callback)"); } if(!((types & ~Menu_CallbackType_All) == 0)) { FatalError("Assertion failed: Menu_Callback: invalid callback type(s) used; assertion code: (types & ~Menu_CallbackType_All) == 0"); } 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]; if(!(MN7I->ValidateMenuCallbackArgBinding(argBinding))) { FatalError("Assertion failed: Menu_Callback: invalid argBinding used; assertion code: MN7I->ValidateMenuCallbackArgBinding(argBinding)"); } return [callback, types || Menu_CallbackType_Defaults, argBinding]; } global func Menu_Entry_Text(string Text) { return [DT_Menu_Entry_Text, Text]; } global func Menu_Entry_Callbacks(array Callbacks) { return [DT_Menu_Entry_Callbacks, Callbacks]; } global func Menu_Entry_Count(int Count) { return [DT_Menu_Entry_Count, Count]; } global func Menu_Entry_Description(string Description) { return [DT_Menu_Entry_Description, Description]; } global func Menu_Entry_Extra(int Extra) { return [DT_Menu_Entry_Extra, Extra]; } global func Menu_Entry_XPar1(XPar1) { return [DT_Menu_Entry_XPar1, XPar1]; } global func Menu_Entry_XPar2(XPar2) { return [DT_Menu_Entry_XPar2, XPar2]; } global func Menu_Entry_Args(Args) { return [DT_Menu_Entry_Args, Args]; } global func Menu_Entry_Placeholder(bool Placeholder) { return [DT_Menu_Entry_Placeholder, Placeholder]; } global func Menu_Entry_Icon(icon, extra, int color) { var checkIcon, checkExtra, checkColor; if(GetType(icon) == C4V_Array) { checkIcon = icon[0]; checkExtra = icon[1]; checkColor = icon[2]; } else { checkIcon = icon; checkExtra = extra; checkColor = color; } if(!(!checkIcon || GetType(checkIcon) == C4V_C4ID || GetType(checkIcon) == C4V_C4Object)) { FatalError("Assertion failed: Menu_Entry_Icon: Invalid icon; assertion code: !checkIcon || GetType(checkIcon) == C4V_C4ID || GetType(checkIcon) == C4V_C4Object"); } if(GetType(checkIcon) == C4V_C4ID) { if(checkExtra) { if(!(GetType(checkExtra) == C4V_Int && checkExtra >= 0)) { FatalError("Assertion failed: Menu_Entry_Icon: Only positive integers are allowed for icon extra with icon id; assertion code: GetType(checkExtra) == C4V_Int && checkExtra >= 0"); } } if(!(!checkColor || GetType(checkColor) == C4V_Int)) { FatalError(Format("Assertion failed: Menu_Entry_Icon: Got invalid color: %v; assertion code: !checkColor || GetType(checkColor) == C4V_Int", checkColor)); } } else { if(checkExtra) { if(!(GetType(checkExtra) == C4V_Bool)) { FatalError("Assertion failed: Menu_Entry_Icon: Only booleans are allowed for icon extra with icon object; assertion code: GetType(checkExtra) == C4V_Bool"); } } if(!(!checkColor)) { FatalError("Assertion failed: Menu_Entry_Icon: Colorizing is not supported for icon objects; assertion code: !checkColor"); } } if(GetType(icon) == C4V_Array) { return [DT_Menu_Entry_Icon, icon]; } else { return [DT_Menu_Entry_Icon, [icon, extra, color]]; } } global func Menu_Entry_Condition(callback, int allowDisabledSelection) { if(!(MN7I->CheckCallback(callback))) { FatalError("Assertion failed: Menu_Entry_Condition: invalid callback used; assertion code: MN7I->CheckCallback(callback)"); }return [DT_Menu_Entry_Condition, [callback, allowDisabledSelection]]; } global func Menu_Entry_VariableCondition(array scopedVar, compare, int disableMode, bool invert, int allowDisabledSelection) { if(!(MN7I->CheckScopedVar(scopedVar))) { FatalError("Assertion failed: Menu_Entry_VariableCondition: Invalid scopedVar used; assertion code: MN7I->CheckScopedVar(scopedVar)"); } if(!(!disableMode || (GetType(disableMode) == C4V_Int && disableMode >= 0 && disableMode <= Menu_ConditionReact_Max) || (GetType(disableMode) == C4V_Array && disableMode[0] == Menu_ConditionReact_CustomFormat && GetType(disableMode[1]) == C4V_String))) { FatalError("Assertion failed: Menu_ConditionDisableMode: invalid disableMode; assertion code: !disableMode || (GetType(disableMode) == C4V_Int && disableMode >= 0 && disableMode <= Menu_ConditionReact_Max) || (GetType(disableMode) == C4V_Array && disableMode[0] == Menu_ConditionReact_CustomFormat && GetType(disableMode[1]) == C4V_String)"); } if(!(!allowDisabledSelection || (GetType(allowDisabledSelection) == C4V_Int && allowDisabledSelection >= 0 && allowDisabledSelection <= Menu_Condition_Max))) { FatalError("Assertion failed: Menu_ConditionAllowSelection: invalid allowDisabledSelection value; assertion code: !allowDisabledSelection || (GetType(allowDisabledSelection) == C4V_Int && allowDisabledSelection >= 0 && allowDisabledSelection <= Menu_Condition_Max)"); } return Menu_Entry_Condition(BindCallback(MN7I->MenuObjectCallback("VariableCondition"), [Bind(scopedVar), Bind(compare), Bind(disableMode), Bind(invert)]), allowDisabledSelection); } global 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; } } global func Menu_Entry_InstantDescriptionIcon(id icon, variation, int color) { if(!(icon)) { FatalError("Assertion failed: Menu_Entry_InstantDescriptionIcon: icon is mandatory; assertion code: icon"); } if(!(!variation || GetType(variation) == C4V_Int || GetType(variation) == C4V_String)) { FatalError("Assertion failed: Menu_Entry_InstantDescriptionIcon: variation must either be an integer to index the icon or a string to select the corresponding portrait; assertion code: !variation || GetType(variation) == C4V_Int || GetType(variation) == C4V_String"); } if(!(!color || GetType(variation) == C4V_String)) { FatalError("Assertion failed: Menu_Entry_InstantDescriptionIcon: color can be only set in conjunction with setting a portrait (i.e. setting variation to the portrait name); assertion code: !color || GetType(variation) == C4V_String"); } return [DT_Menu_Entry_InstantDescriptionIcon, [icon, variation, color]]; } global func Menu_Entry_Object(object obj) { if(!(obj)) { FatalError("Assertion failed: Menu_Entry_Object: obj is mandatory; assertion code: obj"); } return Menu_Combined([Menu_Entry_Icon(obj), Menu_Entry_Text(GetName(obj)), Menu_Entry_Description(GetDesc(obj))]); } global func Menu_Entry(array settings) { if(!settings) { settings = [Menu_Entry_Placeholder(false)]; } var namedArgs = []; namedArgs[DT_Menu_Entry_Text] = ""; namedArgs[DT_Menu_Entry_Placeholder] = -1; MN7I->NamedArgs(settings, namedArgs); if(!namedArgs[DT_Menu_Entry_Description]) { namedArgs[DT_Menu_Entry_Extra] |= C4MN_Add_ForceNoDesc; } return [DT_Menu_Type_Entry, namedArgs]; } global func Menu_SubMenu(array entrySettings, array menuEntry_Settings, bool asColumn) { if(!(entrySettings)) { FatalError("Assertion failed: Menu_SubMenu: Non-selectable placeholder as submenu entry doesn't make sense; assertion code: entrySettings"); } if(!(menuEntry_Settings && GetLength(menuEntry_Settings) > 0)) { FatalError("Assertion failed: Menu_SubMenu: Empty submenu doesn't make sense; assertion code: menuEntry_Settings && GetLength(menuEntry_Settings) > 0"); } var ret = Menu_Entry(entrySettings); ret[1][DT_Menu_Entry_Args] = [ret[1][DT_Menu_Entry_Callbacks], ret[1][DT_Menu_Entry_Args], menuEntry_Settings, asColumn]; ret[1][DT_Menu_Entry_Callbacks] = [Menu_Callback(MN7I->MenuObjectCallback("SubmenuItemCallback"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])]; if(asColumn) { // [menu settings (only a few settings are used), menu entries, readily populated entries (later in HandleEntries)] var menuData = [[], [], []]; MN7I->UncombineAndDistinguish(menuEntry_Settings, menuData[0], menuData[1]); ret[2] = menuData; } return ret; } global func Menu_Factory(array callbacks, args, array binding) { if(!(callbacks)) { FatalError("Assertion failed: Menu_Factory: callbacks are mandatory; assertion code: callbacks"); } return [DT_Menu_Type_Factory, [callbacks, args, binding || [Menu_CallbackArg_Args, Menu_CallbackArg_EntryNumber, Menu_CallbackArg_Menu]]]; } // Menu_Columns // ([ // [ first column entries ], // [ second column entries ], // ... // ]) global func Menu_Columns(array columns) { var preparedColunmns = CreateArray(GetLength(columns)); for(var i = 0; i < GetLength(columns); ++i) { preparedColunmns[i] = []; var tempSettings = []; MN7I->UncombineAndDistinguish(columns[i], tempSettings, preparedColunmns[i]); if(GetLength(tempSettings) > 0) { FatalError("DT_Menu::Menu_Columns: menu settings can not appear in Menu_Columns()"); } } return [DT_Menu_Type_Columns, preparedColunmns]; } global func Menu_Text(string text, bool allowSelection) { return Menu_Entry([Menu_Entry_Text(text), Menu_Entry_Placeholder(allowSelection)]); } global func Menu_Blank(bool allowSelection) { return Menu_Entry([Menu_Entry_Placeholder(allowSelection)]); } func DeclineAcceptBack(string text, icon, string callback, array settings) { var ret = Menu_Entry([Menu_Entry_Text(text), Menu_Combined(settings || [])]); ret[1][DT_Menu_Entry_Args] = [ret[1][DT_Menu_Entry_Callbacks], ret[1][DT_Menu_Entry_Args]]; ret[1][DT_Menu_Entry_Callbacks] = Menu_Entry_Callbacks([Menu_Callback(MN7I->MenuObjectCallback(callback), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_Args, Menu_CallbackArg_All])])[1]; ExtraIcon(ret[1][DT_Menu_Entry_Text], ret[1][DT_Menu_Entry_Icon], icon); return ret; } global func Menu_Decline(array settings) { return MN7I->DeclineAcceptBack("$Decline$", [MN7I, 2], "DeclineAcceptCommand", settings); } global func Menu_Accept(array settings) { return MN7I->DeclineAcceptBack("$Accept$", [MN7I, 1], "DeclineAcceptCommand", settings); } global func Menu_Back(array settings) { return MN7I->DeclineAcceptBack("$Back$", [MN7I, 5], "BackCommand", settings); } global func Menu_Adaptor_Type(int Type) { return [DT_Menu_Adaptor_Type, Type]; } global func Menu_Adaptor_Variable(array Variable) { return [DT_Menu_Adaptor_Variable, Variable]; } global func Menu_Adaptor_Callbacks(array Callbacks) { return [DT_Menu_Adaptor_Callbacks, Callbacks]; } global func Menu_Adaptor_MessageBoardText(string MessageBoardText) { return [DT_Menu_Adaptor_MessageBoardText, MessageBoardText]; } global func Menu_Adaptor_WrapAround(bool WrapAround) { return [DT_Menu_Adaptor_WrapAround, WrapAround]; } global func Menu_Adaptor_EnumSubmenuIcon(id EnumSubmenuIcon) { return [DT_Menu_Adaptor_EnumSubmenuIcon, EnumSubmenuIcon]; } global func Menu_Adaptor_EnumAllowUnknown(bool EnumAllowUnknown) { return [DT_Menu_Adaptor_EnumAllowUnknown, EnumAllowUnknown]; } global func Menu_Adaptor_EnumInline(bool EnumInline) { return [DT_Menu_Adaptor_EnumInline, EnumInline]; } global func Menu_Adaptor_EnumSubmenu(int callbackType) { if(!((callbackType & ~Menu_CallbackType_All) == 0)) { FatalError("Assertion failed: Menu_Adaptor_EnumSubmenu: invalid callback type(s) used; assertion code: (callbackType & ~Menu_CallbackType_All) == 0"); }return [DT_Menu_Adaptor_EnumSubmenu, callbackType]; } global func Menu_Adaptor_EnumColumnSubmenu() { return [DT_Menu_Adaptor_EnumSubmenu, true]; } global func Menu_Adaptor_Limits(min, max, args) { if((!min || GetType(min) == C4V_Int) && (!max || GetType(max) == C4V_Int)) { if(!(min <= max)) { FatalError("Assertion failed: Menu_Adaptor_Limits: max cannot be smaller than min; assertion code: min <= max"); } } else { if(min && GetType(min) != C4V_Int) { if(!(MN7I->CheckCallback(min))) { FatalError(Format("Assertion failed: Menu_Adaptor_Limits: invalid callback used for min: %v; assertion code: MN7I->CheckCallback(min)", min)); } } if(max && GetType(max) != C4V_Int) { if(!(MN7I->CheckCallback(max))) { FatalError(Format("Assertion failed: Menu_Adaptor_Limits: invalid callback used for max: %v; assertion code: MN7I->CheckCallback(max)", max)); } } } return [DT_Menu_Adaptor_Limits, [min, max, args]]; } global func Menu_Adaptor_StepSize(int step, force) { if(!(step > 0)) { FatalError("Assertion failed: negative or zero step doesn't make sense; assertion code: step > 0"); } if(!(force >= 0)) { FatalError("Assertion failed: negative force step doesn't make sense; assertion code: force >= 0"); }return [DT_Menu_Adaptor_StepSize, [step, force]]; } global func Menu_Adaptor_Enum(array enumVals, array layout, bool valuesAsSeparateLists) { MN7I->AdaptorLayout(layout, enumVals, valuesAsSeparateLists); return Menu_Combined([Menu_Adaptor_Type(Menu_AdaptorType_Enum), [DT_Menu_Adaptor_LayoutVals, [enumVals, layout]]]); } global func Menu_Adaptor_BitField(array fieldVals, array layout, bool valuesAsSeparateLists, bool bitPositionAsValue) { MN7I->AdaptorLayout(layout, fieldVals, valuesAsSeparateLists); if(bitPositionAsValue) { var valuePos = layout[Menu_Layout__ValuePos] - 1; for(var i = 0; i < GetLength(fieldVals); ++i) { if(!(!fieldVals[i][valuePos] || GetType(fieldVals[i][valuePos]) == C4V_Int)) { FatalError(Format("Assertion failed: Menu_Adaptor_BitField: Bit value must be an integer; got: %v; assertion code: !fieldVals[i][valuePos] || GetType(fieldVals[i][valuePos]) == C4V_Int", fieldVals[i][valuePos])); } if(!(fieldVals[i][valuePos] >= 0 && fieldVals[i][valuePos] < 32)) { FatalError("Assertion failed: Menu_Adaptor_BitField: Bit value out of range; assertion code: fieldVals[i][valuePos] >= 0 && fieldVals[i][valuePos] < 32"); } fieldVals[i][valuePos] = 1 << fieldVals[i][valuePos]; } } var valuePos = layout[Menu_Layout__ValuePos] - 1; for(var fieldPart in fieldVals) { if(!(GetType(fieldPart[valuePos]) == C4V_Int)) { FatalError(Format("Assertion failed: Menu_Adaptor_BitField: Flag mask (Menu_Layout_Value) must be a non-zero integer; got %v; assertion code: GetType(fieldPart[valuePos]) == C4V_Int", fieldPart[valuePos])); } } return Menu_Combined([Menu_Adaptor_Type(Menu_AdaptorType_BitField), [DT_Menu_Adaptor_LayoutVals, [fieldVals, layout]]]); } global func Menu_Adaptor_NoEmptyString() { return [DT_Menu_Adaptor_NoEmptyString, true]; } global func Menu_Adaptor_EnumSubmenuTitleAndText(string menuTitle, string entryText) { return [DT_Menu_Adaptor_EnumSubmenuTitleAndText, [menuTitle, entryText]]; } global func Menu_Adaptor_Boolean() { return Menu_Adaptor_Type(Menu_AdaptorType_Boolean); } global func Menu_Adaptor_Integer() { return Menu_Adaptor_Type(Menu_AdaptorType_Integer); } global func Menu_Adaptor_String() { return Menu_Adaptor_Type(Menu_AdaptorType_String); } global func Menu_Adaptor_ID() { return Menu_Adaptor_Type(Menu_AdaptorType_ID); } func AdaptorLayout(array& layout, array& vals, bool valuesAsSeparateLists) { if(!(vals)) { FatalError("Assertion failed: AdaptorLayout: vals are mandatory; assertion code: vals"); } 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; } if(!(layoutMap[Menu_Layout__ValuePos])) { FatalError("Assertion failed: AdaptorLayout: Menu_Layout_Value is mandatory; assertion code: layoutMap[Menu_Layout__ValuePos]"); } 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]; } } } for(var fieldPart in vals) { if(layout[Menu_Layout_Icon]) { Menu_Entry_Icon(fieldPart[layout[Menu_Layout_Icon] - 1]); } // this debug checks the icon } } global func Menu_Adaptor(array entrySettings, array adaptorSettings) { if(!(entrySettings)) { FatalError("Assertion failed: Menu_Adaptor: Non-selectable placeholder as adaptor entry doesn't make sense; assertion code: entrySettings"); } if(!(adaptorSettings && GetLength(adaptorSettings) > 0)) { FatalError("Assertion failed: Menu_Adaptor: Adaptor without settings doesn't make sense; assertion code: adaptorSettings && GetLength(adaptorSettings) > 0"); } var adaptorArgs = []; adaptorArgs[DT_Menu_Adaptor_WrapAround] = -1; adaptorArgs[DT_Menu_Adaptor_EnumSubmenu] = Menu_CallbackType_Special2; MN7I->NamedArgs(adaptorSettings, adaptorArgs); if(adaptorArgs[DT_Menu_Adaptor_WrapAround] == -1) { if(adaptorArgs[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Integer) { adaptorArgs[DT_Menu_Adaptor_WrapAround] = false; } else if(adaptorArgs[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Enum) { adaptorArgs[DT_Menu_Adaptor_WrapAround] = true; } } return Menu_Factory([MN7I->MenuObjectCallback("AdaptorFactory")], [Menu_Entry(entrySettings)[1], adaptorArgs, entrySettings], [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; } } if(!allowUnknown) { Log("WARNING: DT_Menu::EnumValPos: Current value %v is not in enum: %v", val, enumVals); } 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, array args, array entry) { var layoutVals = args[DT_Menu_Adaptor_LayoutVals]; var layout = layoutVals[1]; layoutVals = layoutVals[0]; text = entry[DT_Menu_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]); if(!(!ret[i] || GetType(ret[i]) == C4V_Int)) { FatalError(Format("Assertion failed: Menu_Adaptor_Limits: Got invalid callback value for %s: %v; assertion code: !ret[i] || GetType(ret[i]) == C4V_Int", ["min", "max"][i], ret[i])); } } } return ret; } func AdaptorFactory(args, int entryIndex, int entryColumn) { var origArgs = args; var entry = args[0]; var entrySettings = args[2]; args = args[1]; var text = entry[DT_Menu_Entry_Text]; var description = entry[DT_Menu_Entry_Description]; var icon = entry[DT_Menu_Entry_Icon]; var val = ScopedVar(args[DT_Menu_Adaptor_Variable]); var defaultMsgboardText = "$EnterValue$"; var retSubmenu; args[DT_Menu_Adaptor_Args] = entry[DT_Menu_Entry_Args]; args[DT_Menu_Adaptor_EntryIndex] = [entryIndex, entryColumn]; args[DT_Menu_Adaptor_Callbacks] = args[DT_Menu_Adaptor_Callbacks] || []; if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Boolean) { if(!(GetType(val) == C4V_Any || GetType(val) == C4V_Bool)) { FatalError("Assertion failed: AdaptorFactory: Value of Menu_Adaptor_Variable has wrong type; assertion code: GetType(val) == C4V_Any || GetType(val) == C4V_Bool"); } BooleanToggleText(val, text, icon); } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_String) { if(!(GetType(val) == C4V_Any || GetType(val) == C4V_String)) { FatalError("Assertion failed: AdaptorFactory: Value of Menu_Adaptor_Variable has wrong type; assertion code: GetType(val) == C4V_Any || GetType(val) == C4V_String"); } text = Format(entry[DT_Menu_Entry_Text], val); defaultMsgboardText = "$EnterText$"; } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Integer) { if(!(GetType(val) == C4V_Any || GetType(val) == C4V_Int)) { FatalError("Assertion failed: AdaptorFactory: Value of Menu_Adaptor_Variable has wrong type; assertion code: GetType(val) == C4V_Any || GetType(val) == C4V_Int"); } var limits = AdaptorGetLimits(args[DT_Menu_Adaptor_Limits]), max, min; var wrapAround = args[DT_Menu_Adaptor_WrapAround]; if(limits && !wrapAround) { if(val >= limits[1]) { max = true; } if(val <= limits[0]) { min = true; } } text = Format("%s %s", Format(entry[DT_Menu_Entry_Text], val), ["{{MN7I:4}}", "{{MN7I:4}}"][min]); ExtraIcon(text, icon, [MN7I, 3, max && RGB(128, 128, 128)]); defaultMsgboardText = "$EnterNumber$"; } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_ID) { if(!(GetType(val) == C4V_Any || GetType(val) == C4V_C4ID)) { FatalError("Assertion failed: AdaptorFactory: Value of Menu_Adaptor_Variable has wrong type; assertion code: GetType(val) == C4V_Any || GetType(val) == C4V_C4ID"); } text = Format(entry[DT_Menu_Entry_Text], val && GetName(0, val) || ""); if(val) { ExtraIcon(text, icon, val); } defaultMsgboardText = "$EnterIDOrName$"; } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Enum) { if(args[DT_Menu_Adaptor_EnumInline]) { return AdaptorEnumSubmenuFactory([args, entry]); } var layoutVals = args[DT_Menu_Adaptor_LayoutVals]; var layout = layoutVals[1]; layoutVals = layoutVals[0]; var index = EnumValPos(layoutVals, layout, val, args[DT_Menu_Adaptor_EnumAllowUnknown]); if(index == -1) { if(!args[DT_Menu_Adaptor_EnumAllowUnknown]) { ScopedVar(args[DT_Menu_Adaptor_Variable]) = layoutVals[0][layout[Menu_Layout__ValuePos] - 1]; } index = 0; } var submenuIcon = icon; EnumEntrySettings(text, icon, description, index, args, entry); if(args[DT_Menu_Adaptor_EnumSubmenu] != Menu_CallbackType_None) { args[DT_Menu_Adaptor_MessageBoardText] = args[DT_Menu_Adaptor_MessageBoardText] || defaultMsgboardText; retSubmenu = [Menu_Factory([MenuObjectCallback("AdaptorEnumSubmenuFactory")], [args, entry])]; } } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_BitField) { return AdaptorBitFieldItemsFactory(origArgs); } args[DT_Menu_Adaptor_MessageBoardText] = args[DT_Menu_Adaptor_MessageBoardText] || defaultMsgboardText; if(!retSubmenu) { return [Menu_Entry([Menu_Combined(entrySettings), Menu_Entry_Text(text), Menu_Entry_Callbacks([Menu_Callback(MenuObjectCallback("AdaptorCommand"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])]), Menu_Entry_Icon(icon), Menu_Entry_Description(description), Menu_Entry_Args(args)])]; } else { return [Menu_SubMenu([Menu_Combined(entrySettings), Menu_Entry_Text(text), Menu_Entry_Callbacks([Menu_Callback(MenuObjectCallback("AdaptorCommand"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])]), Menu_Entry_Icon(icon), Menu_Entry_Description(description), Menu_Entry_Args(args)], retSubmenu, GetType(args[DT_Menu_Adaptor_EnumSubmenu]) == C4V_Bool)]; } } func AdaptorEnumSubmenuFactory(array args) { var entry = args[1]; args = args[0]; var layoutVals = args[DT_Menu_Adaptor_LayoutVals]; var layout = layoutVals[1]; layoutVals = layoutVals[0]; var submenuTitleAndText = args[DT_Menu_Adaptor_EnumSubmenuTitleAndText]; var submenuIcon = []; if(submenuTitleAndText && submenuTitleAndText[0]) { entry[DT_Menu_Entry_Text] = submenuTitleAndText[0]; } var index = EnumValPos(layoutVals, layout, ScopedVar(args[DT_Menu_Adaptor_Variable]), args[DT_Menu_Adaptor_EnumAllowUnknown]); var title, icon, description; EnumEntrySettings(title, icon, description, index, args, entry); var ret; if(args[DT_Menu_Adaptor_EnumInline]) { ret = []; } else { ret = [ Menu_Selection(index), Menu_KeepParentOnClose(), Menu_DontKeepOpen(), Menu_Title(title) ]; } if(args[DT_Menu_Adaptor_EnumSubmenuIcon]) { submenuIcon = [args[DT_Menu_Adaptor_EnumSubmenuIcon]]; } else if(icon && GetType(icon[0]) == C4V_C4ID) { submenuIcon = icon; } if(submenuIcon && GetType(submenuIcon[0]) == C4V_C4ID) { ArrayAppend(ret, Menu_Icon(submenuIcon[0])); } var text; var submenuTitleAndText = args[DT_Menu_Adaptor_EnumSubmenuTitleAndText]; if(submenuTitleAndText && submenuTitleAndText[1]) { entry[DT_Menu_Entry_Text] = submenuTitleAndText[1]; } for(var i = 0; i < GetLength(layoutVals); ++i) { icon = 0; EnumEntrySettings(text, icon, description, i, args, entry); if(args[DT_Menu_Adaptor_EnumInline]) { BooleanToggleText(i == index, text, icon); } ArrayAppend(ret, Menu_Entry([Menu_Entry_Text(text), Menu_Entry_Icon(icon), Menu_Entry_Description(description), Menu_Entry_Callbacks([Menu_Callback(MenuObjectCallback("AdaptorEnumSubmenuItem"), Menu_CallbackType_Defaults, [Menu_CallbackArg_Args, Menu_CallbackArg_All])]), Menu_Entry_Args([i, args])])); } return ret; } func AdaptorEnumSubmenuItem(args, array allArgs) { var index = args[0]; args = args[1]; var val = ScopedVar(args[DT_Menu_Adaptor_Variable]); var oldVal = val; var enumVals = args[DT_Menu_Adaptor_LayoutVals]; var layout = enumVals[1]; enumVals = enumVals[0]; ScopedVar(args[DT_Menu_Adaptor_Variable]) = val = enumVals[index][layout[Menu_Layout__ValuePos] - 1]; allArgs[Menu_CallbackArg_Args] = args[DT_Menu_Adaptor_Args]; allArgs[Menu_CallbackArg_FromSubmenu] = true; var reaction = AdaptorCommandCallChangedCallback(args[DT_Menu_Adaptor_Callbacks], val, oldVal, allArgs); if(reaction != Menu_React_None) { return reaction; } if(args[DT_Menu_Adaptor_EnumInline]) { return Menu_React_Refresh; } return Menu_React_Back; } func AdaptorBitFieldItemsFactory(args) { var entry = args[0]; var entrySettings = args[2]; args = args[1]; var text = entry[DT_Menu_Entry_Text]; var description = entry[DT_Menu_Entry_Description]; var icon = entry[DT_Menu_Entry_Icon]; var fieldValue = ScopedVar(args[DT_Menu_Adaptor_Variable]); if(!(GetType(fieldValue) == C4V_Any || GetType(fieldValue) == C4V_Int)) { FatalError("Assertion failed: AdaptorFactory: Value of Menu_Adaptor_Variable has wrong type; assertion code: GetType(fieldValue) == C4V_Any || GetType(fieldValue) == C4V_Int"); } var layoutVals = args[DT_Menu_Adaptor_LayoutVals]; var layout = layoutVals[1]; layoutVals = layoutVals[0]; var ret = []; for(var val in layoutVals) { var mask = val[layout[Menu_Layout__ValuePos] - 1]; text = entry[DT_Menu_Entry_Text] || "%s"; if(layout[Menu_Layout__TextPos]) { text = Format(text, val[layout[Menu_Layout__TextPos] - 1]); } icon = entry[DT_Menu_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[DT_Menu_Adaptor_Mask] = mask; ArrayAppend(ret, Menu_Entry([Menu_Combined(entrySettings), Menu_Entry_Text(text), Menu_Entry_Callbacks([Menu_Callback(MenuObjectCallback("AdaptorCommand"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])]), Menu_Entry_Icon(icon), Menu_Entry_Description(description), Menu_Entry_Args(args)])); } return 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[DT_Menu_Adaptor_Variable]); var oldVal = val; allArgs[Menu_CallbackArg_Args] = args[DT_Menu_Adaptor_Args]; var reaction = CallCallbacks(args[DT_Menu_Adaptor_Callbacks], action, allArgs, Menu_React_Refresh, true); if(reaction != Menu_React_Refresh) { return reaction; } if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Boolean) { if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2)) { ScopedVar(args[DT_Menu_Adaptor_Variable]) = val = !val; reaction = AdaptorCommandCallChangedCallback(args[DT_Menu_Adaptor_Callbacks], val, oldVal, allArgs); if(reaction != Menu_React_None) { return reaction; } } else { return Menu_React_KeepOpen; } } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Integer) { var limits = AdaptorGetLimits(args[DT_Menu_Adaptor_Limits]); var wrapAround = args[DT_Menu_Adaptor_WrapAround]; var step = args[DT_Menu_Adaptor_StepSize]; var stepSize = 1; if(step) { stepSize = step[0]; } if(action == Menu_CallbackType_Special2) { if(msgBoardMode == 0) { msgBoardMode = Menu_AdaptorType_Integer + 1; msgBoardEntry = args[DT_Menu_Adaptor_EntryIndex]; CallMessageBoard(this, false, args[DT_Menu_Adaptor_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[DT_Menu_Adaptor_Variable]) = val; reaction = AdaptorCommandCallChangedCallback(args[DT_Menu_Adaptor_Callbacks], val, oldVal, allArgs); if(reaction != Menu_React_None) { return reaction; } } else { return Menu_React_KeepOpen; } } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_String) { if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2)) { if(msgBoardMode == 0) { msgBoardMode = Menu_AdaptorType_String + 1; msgBoardEntry = args[DT_Menu_Adaptor_EntryIndex]; CallMessageBoard(this, false, args[DT_Menu_Adaptor_MessageBoardText], GetOwner(obj)); } } if(action == Menu_CallbackType_Close) { return Menu_React_None; } else { return Menu_React_KeepOpen; } } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_ID) { if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2)) { if(msgBoardMode == 0) { msgBoardMode = Menu_AdaptorType_ID + 1; msgBoardEntry = args[DT_Menu_Adaptor_EntryIndex]; CallMessageBoard(this, false, args[DT_Menu_Adaptor_MessageBoardText], GetOwner(obj)); } } return Menu_React_KeepOpen; } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Enum) { if(GetType(args[DT_Menu_Adaptor_EnumSubmenu]) == C4V_Bool) { if(action & Menu_CallbackType_Defaults) { return Menu_React_ShowSubmenu; } else { return Menu_React_None; } } else if(args[DT_Menu_Adaptor_EnumSubmenu] & action) { return Menu_React_ShowSubmenu; } var enumVals = args[DT_Menu_Adaptor_LayoutVals]; var layout = enumVals[1]; enumVals = enumVals[0]; var index = EnumValPos(enumVals, layout, val, args[DT_Menu_Adaptor_EnumAllowUnknown]); var wrapAround = args[DT_Menu_Adaptor_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[DT_Menu_Adaptor_Variable]) = val = enumVals[index][layout[Menu_Layout__ValuePos] - 1]; reaction = AdaptorCommandCallChangedCallback(args[DT_Menu_Adaptor_Callbacks], val, oldVal, allArgs); if(reaction != Menu_React_None) { return reaction; } } else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_BitField) { if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2)) { var mask = args[DT_Menu_Adaptor_Mask]; if((val & mask) == mask) { val &= ~mask; } else { val |= mask; } ScopedVar(args[DT_Menu_Adaptor_Variable]) = val; reaction = AdaptorCommandCallChangedCallback(args[DT_Menu_Adaptor_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[DT_Menu_Entry_Args]; var callbackArgs = args[DT_Menu_Adaptor_Args]; var oldVal = ScopedVar(args[DT_Menu_Adaptor_Variable]); var val = input; if(msgBoardMode - 1 == Menu_AdaptorType_Integer) { var int = ParseInt(input); var limits = AdaptorGetLimits(args[DT_Menu_Adaptor_Limits]); var step = args[DT_Menu_Adaptor_StepSize]; if(GetType(int) == C4V_Int || GetType(int) == C4V_Any) { 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[DT_Menu_Adaptor_Variable]) = val = int; } else { val = oldVal; } } else if(msgBoardMode - 1 == Menu_AdaptorType_String) { if(args[DT_Menu_Adaptor_NoEmptyString] && (!input || input == "")) { val = oldVal; } else { ScopedVar(args[DT_Menu_Adaptor_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[DT_Menu_Adaptor_Variable]) = val; } msgBoardMode = 0; if(val != oldVal) { var reaction = CallCallbacks(args[DT_Menu_Adaptor_Callbacks], Menu_CallbackType_ValueChanged, [Menu_CallbackType_ValueChanged, entry[DT_Menu_Entry_Icon], settings[DT_Menu_Settings_Object], callbackArgs, 0, 0, val, oldVal]); if(reaction != Menu_React_None) { return React(reaction, msgBoardEntry); } } Refresh(msgBoardEntry); } global func Menu_Combined(array combined) { if(!(combined)) { FatalError("Assertion failed: Menu_Combined: combined is mandatory; assertion code: combined"); }return [DT_Menu_Combined, combined]; } global func CreateNewMenu(array menu, inheritSettings, object parentMenu) { var settings = inheritSettings; if(GetType(settings) != C4V_Array) settings = []; settings[DT_Menu_Settings_Style] &= ~C4MN_Style_EqualItemHeight; // EqualItemHeight can't be inherited settings[DT_Menu_Settings_Selection] = []; // Selection can't be inherited settings[DT_Menu_Settings_Callbacks] = 0; // Global callbacks can't be inherited (maybe filter callbacks) settings[DT_Menu_Settings_Parent] = parentMenu; var entries = []; MN7I->UncombineAndDistinguish(menu, settings, entries); var menuObj = CreateObject(MN7I); menuObj->Create(settings, entries); return menuObj; } func NamedArg(array namedArg, array &args) { args[namedArg[0]] = namedArg[1]; } func UncombineAndDistinguish(array combined, array &settings, array &entries) { var line = 0; for(var val in combined) { if(!(val)) { FatalError(Format("Assertion failed: UncombineAndDistinguish: entry/setting of value 0 found at entry/setting number %d; assertion code: val", line)); } if(val[0] == DT_Menu_Type_Setting) { if((val[1][0] == DT_Menu_Settings_Style && val[1][1] == C4MN_Style_EqualItemHeight) || settings[DT_Menu_Settings_Style] == C4MN_Style_EqualItemHeight) { settings[DT_Menu_Settings_Style] |= val[1][1]; } else { NamedArg(val[1], settings); } } else if(val[0] == DT_Menu_Type_Entry || val[0] == DT_Menu_Type_Factory || val[0] == DT_Menu_Type_Columns) { entries[GetLength(entries)] = val; } else if(val[0] == DT_Menu_Combined) { UncombineAndDistinguish(val[1], settings, entries); } else { Log("WARNING: DT_Menu::UncombineAndDistinguish: Unknown entry/settings type %v with value %v", val[0], val[1]); } ++line; } } func NamedArgs(array namedArgs, array& args) { for(var arg in namedArgs) { if(arg[0] == DT_Menu_Combined) { NamedArgs(arg[1], args); } else { NamedArg(arg, args); } } } func DeclineAcceptBackCommand(int action, args, int defaultAction, array allArgs) { if(args[0]) { allArgs[Menu_CallbackArg_Args] = args[1]; var reaction = CallCallbacks(args[0], action, allArgs); if(reaction != Menu_React_None) { return reaction; } } if(action & Menu_CallbackType_Defaults) { return defaultAction; } else { return Menu_React_None; } } func DeclineAcceptCommand(int action, args, array allArgs) { return DeclineAcceptBackCommand(action, args, Menu_React_Close, allArgs); } func BackCommand(int action, args, array allArgs) { return DeclineAcceptBackCommand(action, args, Menu_React_Back, allArgs); } func MenuObjectCallback(string name) { return [CallbackTarget_Scenario - 1, name]; } func CallCustom(callback, args) { if(GetType(callback) == C4V_Array && GetLength(callback) == 2 && callback[0] == CallbackTarget_Scenario - 1) { return CallA(ObjectCallback(callback[1], true), args, ...); } else { return _inherited(callback, args, ...); } } func CustomScopedVar(array variable) { if(variable && variable[0] == DT_Menu_MenuVar) { return ArrayVar(variable[1], LocalVar("vars")); } else { return _inherited(variable, ...); } } func CheckCustomScopedVar(array variable) { if(variable && variable[0] == DT_Menu_MenuVar && ((GetType(variable[1]) == C4V_Int && variable[1] > 0) || variable[1] == 0)) { return true; } else { return _inherited(variable, ...); } } func Update() { return Refresh(GetSelection(Menu_Selection_WithColumn)); } func GetSelection(int mode) { if(mode == Menu_Selection_Simple) { return currentColumnSelections[currentColumn]; } else if(mode == Menu_Selection_WithColumn) { return [currentColumnSelections[currentColumn], currentColumn]; } else if(mode == Menu_Selection_SubMenuColumnChain) { if(multiColumnMode) { FatalError("DT_Menu::GetSelection: Menu_Selection_SubMenuColumnChain doesn't make sense when Menu_Columns() are used"); } var ret = []; for(var i = 0; i < columnEntries; ++i) { if(!columnEntries[i]) { break; } ArrayAppend(ret, currentColumnSelections[i]); } return ret; } } func GetObject() { return settings[DT_Menu_Settings_Object]; } func CreateIconDummy() { return CreateContents(DT_Menu_IconDummy); } // deprecated compatibility functions and constants // Symbol -> Icon static const Menu_Layout_Symbol = Menu_Layout_Icon; static const Menu_CallbackArg_Symbol = Menu_CallbackArg_Icon; global func Menu_Symbol() { return Menu_Icon(...); } global func Menu_Entry_Symbol() { return Menu_Entry_Icon(...); } // InfoCaption -> Description static const Menu_Layout_InfoCaption = Menu_Layout_Description; global func Menu_Entry_InfoCaption() { return Menu_Entry_Description(...); } // Menu_Caption -> Menu_Title global func Menu_Caption() { return Menu_Title(...); } // Menu_Entry_Caption -> Menu_Entry_Text static const Menu_Layout_Caption = Menu_Layout_Text; global func Menu_Entry_Caption() { return Menu_Entry_Text(...); } // Menu_Adaptor_EnumSubmenuCaption -> Menu_Adaptor_EnumSubmenuTitleAndText global func Menu_Adaptor_EnumSubmenuCaption() { return Menu_Adaptor_EnumSubmenuTitleAndText(...); } func ValidateDeco(id deco) { return !deco || (deco->~FrameDecorationBackClr() || deco->~FrameDecorationBorderTop() || deco->~FrameDecorationBorderLeft() || deco->~FrameDecorationBorderRight() || deco->~FrameDecorationBorderBottom()); } func ValidateMenuCallbacks(array callbacks) { if(!callbacks) { return false; } for(var callback in callbacks) { if(!ValidateMenuCallback(callback)) { return false; } } return true; } func ValidateMenuCallbackArgBinding(array binding) { for(var arg in binding) { if(arg > Menu_CallbackArg_Max || arg < Menu_CallbackArg_Min) { return false; } } return true; } func ValidateMenuCallback(array callback) { if(!callback) { return false; } return GetLength(callback) == 3 && GetType(callback[1]) == C4V_Int && (callback[1] & ~Menu_CallbackType_All) == 0 && CheckCallback(callback[0]) && ValidateMenuCallbackArgBinding(callback[2]); } func CheckCallback(callback) { var ret = _inherited(callback, ...); if(ret && GetType(callback) == C4V_String) { Log("WARNING: DT_Menu::CheckCallback: A string only callback will not work as expected (yet): \"%s\"", callback); } return ret; } func CheckCustomCallback(callback) { if(GetType(callback) == C4V_Array && GetLength(callback) == 2 && callback[0] == (CallbackTarget_Scenario - 1) && GetType(callback[1]) == C4V_String && callback[1] != "") { return true; } return _inherited(callback, ...); }