diff options
| author | Markus Mittendrein <maxmitti@maxmitti.tk> | 2022-02-13 21:13:36 +0100 |
|---|---|---|
| committer | Markus Mittendrein <maxmitti@maxmitti.tk> | 2022-02-13 22:32:44 +0100 |
| commit | edd1359010ce32d5ff6a3b77fcb6bbe7e21b5703 (patch) | |
| tree | 82c00ef38730af6fa4dee2aaafc152922f2e7848 /DTMenu.c4d/Script.c | |
| parent | 8bb8f4eea328e75b5a2e17fa82d9392df034e4bc (diff) | |
| download | ObjectsAppend-edd1359010ce32d5ff6a3b77fcb6bbe7e21b5703.tar.gz ObjectsAppend-edd1359010ce32d5ff6a3b77fcb6bbe7e21b5703.zip | |
Update to map based DTMenu
Diffstat (limited to 'DTMenu.c4d/Script.c')
| -rw-r--r-- | DTMenu.c4d/Script.c | 2001 |
1 files changed, 1351 insertions, 650 deletions
diff --git a/DTMenu.c4d/Script.c b/DTMenu.c4d/Script.c index 35dc576..2089a8d 100644 --- a/DTMenu.c4d/Script.c +++ b/DTMenu.c4d/Script.c @@ -1,56 +1,37 @@ -#strict 2 +#strict 3 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_Symbol = 0; -static const DT_Menu_Settings_Object = 1; -static const DT_Menu_Settings_Extra = 2; -static const DT_Menu_Settings_Caption = 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_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 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_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_ShowSubMenu = 7; +static const Menu_React_OverrideReaction = -1; +static Menu_React_None; // constants with nil is somehow broken? +// static const Menu_React_None = nil; +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]; } @@ -61,6 +42,7 @@ 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("<c %x>%%s</c>", color)]; } global func Menu_ConditionReact_Format(string format) { return [Menu_ConditionReact_CustomFormat, format]; } @@ -68,18 +50,7 @@ global func Menu_ConditionReact_Format(string format) { return [Menu_ConditionRe static const Menu_Condition_Default = 0; static const Menu_Condition_AllowSelection = 1; static const Menu_Condition_DenySelection = 2; - -static const DT_Menu_Entry_Caption = 0; -static const DT_Menu_Entry_Callbacks = 1; -static const DT_Menu_Entry_Symbol = 2; -static const DT_Menu_Entry_Count = 3; -static const DT_Menu_Entry_InfoCaption = 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 Menu_Condition_Max = Menu_Condition_DenySelection; static const Menu_CallbackType_None = 0x0; static const Menu_CallbackType_Special2 = 0x1; @@ -92,57 +63,48 @@ 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_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_Symbol = 1; +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_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_EnumSubmenuCaption = 13; -static const DT_Menu_Adaptor_EnumSubmenuSymbol = 14; -static const DT_Menu_Adaptor_EnumAllowUnknown = 15; -static const DT_Menu_Adaptor_EnumInline = 16; - -static const Menu_Layout_Symbol = 1; -static const Menu_Layout_InfoCaption = 2; - -static const Menu_Layout__CaptionPos = 3; +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_Caption = 4; +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; @@ -150,21 +112,30 @@ static const Menu_Layout__NoFlagMask = 3; // ---------------------------------------------------------------------------- local settings; -local entries; local createEntries; -local entryCount; -local currentSelection; +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() @@ -172,18 +143,24 @@ func Destruction() Close(); } -func Create(array cSettings, array cEntries) +func Create(map cSettings) { - settings = cSettings; - entries = []; - createEntries = cEntries; - currentSelection = -1; - settings[DT_Menu_Settings_ConditionDisableMode] = settings[DT_Menu_Settings_ConditionDisableMode] || Menu_ConditionReact_Hide; - entryCount = 0; + settings = cSettings.Settings; + createEntries = cSettings.Entries; + + multiColumnMode = false; + columnCount = 0; - if(settings[DT_Menu_Settings_Vars]) + var actualColumnCount = GetLength(currentColumnSelections); + columnOffsets = CreateArray(actualColumnCount); + for(var i = 0; i < actualColumnCount; ++i) { - var settingVars = settings[DT_Menu_Settings_Vars]; + columnOffsets[i] = 0; + } + + if(settings.Vars) + { + var settingVars = settings.Vars; for(var i = 0; i < GetLength(settingVars); ++i) { @@ -192,54 +169,165 @@ func Create(array cSettings, array cEntries) vars[i] = settingVars[i]; } } - settings[DT_Menu_Settings_Vars] = 0; + settings.Vars = 0; } var factoryArgs = []; factoryArgs[Menu_CallbackArg_Menu] = this; - factoryArgs[Menu_CallbackArg_MenuObject] = settings[DT_Menu_Settings_Object]; + factoryArgs[Menu_CallbackArg_MenuObject] = settings.Object; + + var index = 0; + columnEntries[0] = []; + HandleEntries(createEntries, index, columnEntries[0], settings, factoryArgs, 0, true); + + if(columnCount > 1 && settings.Style != C4MN_Style_Context) + { + FatalError("CreateNewMenu: Multi column menus (and submenus as columns) are only supported with Menu_Style_Context()"); + } - HandleEntries(createEntries, entryCount, entries, factoryArgs); + if(settings.Parent) settings.Parent->Suspend(); + + if(!(settings.Object)) { FatalError("Assertion failed: CreateNewMenu: menu object is missing; assertion code: settings.Object"); } + 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); + } - if(settings[DT_Menu_Settings_Parent]) settings[DT_Menu_Settings_Parent]->Suspend(); + // 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); + } + } - CreateMenu(settings[DT_Menu_Settings_Symbol], settings[DT_Menu_Settings_Object], this, settings[DT_Menu_Settings_Extra], settings[DT_Menu_Settings_Caption], settings[DT_Menu_Settings_ExtraData], settings[DT_Menu_Settings_Style], true, MN7I); + if(submenuTitle) + { + title = Format("... {{MN7I:7}} %s", submenuTitle); + } + } - 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]); + 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(entries); + AddEntries(); - if(entryCount > 0) + if(GetLength(columnEntries[0]) > 0) + { + if(!refreshing) + { + SelectEntry(settings.Selection[0], settings.Selection[1]); + } + } + else { - SelectEntry(settings[DT_Menu_Settings_Selection]); + Log("WARNING: DT_Menu::Create: Created an empty menu"); } - if(settings[DT_Menu_Settings_Decoration]) + if(settings.Decoration) { - SetMenuDecoration(settings[DT_Menu_Settings_Decoration], settings[DT_Menu_Settings_Object]); + SetMenuDecoration(settings.Decoration, settings.Object); } if(!GetEffect("Menu", this)) AddEffect("Menu", this, 1, 1, this, 0); } -func SelectEntry(int selection) +func LeaveSubMenuColumn() { - if(selection < 0) selection = entryCount + selection; - SelectMenuItem(selection, settings[DT_Menu_Settings_Object]); + 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 ActivateEntry(int index, int action) +func SelectEntry(indexOrChain, int column) { - if(index < 0) index = entryCount + index; - if(index >= entryCount || index < 0) + if(GetType(indexOrChain) == C4V_Array) { - return; + 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) + { + column ??= 0; + 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"); } + indexOrChain ??= 0; + 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"); } + 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))); + } +} - SelectEntry(index); +func SelectTopEntry(int column) +{ + for(var i = 0; i < GetLength(columnEntries[column]); ++i) + { + if(columnEntries[column][i].Placeholder) + { + return SelectEntry(i, column); + } + } +} - var entry = entries[index]; - MenuItemCommand(entry[DT_Menu_Entry_Symbol], index, action); +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() @@ -264,14 +352,14 @@ global func MenuVar(int index) func FxMenuTimer(object target, int effectNumber, int effectTime) { - if(!settings[DT_Menu_Settings_Object]) + if(!settings.Object) { return Close(); } - else if(settings[DT_Menu_Settings_RequireAction]) + else if(settings.RequireAction) { - var obj = settings[DT_Menu_Settings_Object]; - var requirement = settings[DT_Menu_Settings_RequireAction]; + var obj = settings.Object; + var requirement = settings.RequireAction; if(GetAction(obj) != requirement[0] || (requirement[1] && GetActionTarget(0, obj) != requirement[1])) { return Close(); @@ -280,23 +368,23 @@ func FxMenuTimer(object target, int effectNumber, int effectTime) if(suspended) return; - if(msgBoardMode != 0 && !TestMessageBoard(GetOwner(settings[DT_Menu_Settings_Object]), true)) + if(msgBoardMode && !TestMessageBoard(GetOwner(settings.Object), true)) { - var entry = entries[msgBoardEntry]; - 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_Symbol], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]); + 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 = 0; + msgBoardMode = nil; } - if(!GetMenu(settings[DT_Menu_Settings_Object])) + if(!GetMenu(settings.Object)) { - if(settings[DT_Menu_Settings_KeepOpen] & (DT_Menu_KeepOpen_Refresh_Mask | DT_Menu_KeepOpen_Force) && !settings[DT_Menu_Settings_Closable]) + if(settings.KeepOpen & (Menu_KeepOpen_Refresh_Mask | Menu_KeepOpen_Force) && !settings.Closable) { - Refresh(currentSelection); + Refresh(GetSelection(Menu_Selection_WithColumn)); } else { @@ -304,9 +392,9 @@ func FxMenuTimer(object target, int effectNumber, int effectTime) } } - if(settings[DT_Menu_Settings_KeepOpen] & DT_Menu_KeepOpen_RefreshContinuously && !(effectTime % settings[DT_Menu_Settings_RefreshInterval])) + if(settings.KeepOpen & Menu_KeepOpen_RefreshContinuously && !(effectTime % settings.RefreshInterval)) { - Refresh(currentSelection); + Refresh(GetSelection(Menu_Selection_WithColumn)); } } @@ -316,8 +404,9 @@ func FxMenuStop(object target, int effectNumber, int reason, bool temp) { return; } - CloseMenu(settings[DT_Menu_Settings_Object]); - if(settings[DT_Menu_Settings_Parent]) settings[DT_Menu_Settings_Parent]->Suspend(true); + CloseMenu(settings.Object); + if(settings.InstantDescription.Enable) HideInstantDescription(); + if(settings.Parent) settings.Parent->Suspend(true); RemoveObject(this); } @@ -359,14 +448,20 @@ func CallCallbacks(array callbacks, int type, array args, defaultRet, bool noGlo } } + 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); + var globalRet = CallCallbacks(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); } } @@ -380,7 +475,7 @@ func Close(bool closeParents) { subMenu->Close(); } - if(closeParents && settings[DT_Menu_Settings_Parent]) settings[DT_Menu_Settings_Parent]->Close(true); + if(closeParents && settings.Parent) settings.Parent->Close(true); RemoveEffect("Menu", this); } @@ -389,173 +484,343 @@ func Suspend(bool cont) if(suspended == !cont) return; if(suspended = !cont) { - CloseMenu(settings[DT_Menu_Settings_Object]); + CloseMenu(settings.Object); } else if(!closing) { - Refresh(currentSelection); + Refresh(GetSelection(Menu_Selection_WithColumn)); } } -func HandleEntries(array factoryEntries, int& i, array& ret, array& factoryArgs) +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[0] == DT_Menu_Type_Entry) + if(!(GetType(entry) == C4V_Map)) { FatalError(Format("Assertion failed: \"DT_Menu::HandleEntries: invalid entry. must be a map, got %v\"; assertion code: GetType(entry) == C4V_Map", entry)); } + if(entry.Type == DT_Menu_Type_Entry) { - ret[i++] = entry[1]; + 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[0] == DT_Menu_Type_Factory) + else if(entry.Type == DT_Menu_Type_Factory) { - factoryArgs[Menu_CallbackArg_Args] = entry[1][1]; - - for(var callback in entry[1][0]) + for(var callback in entry.Callbacks) { + factoryArgs[Menu_CallbackArg_Args] = entry.Args; factoryArgs[Menu_CallbackArg_EntryNumber] = i; - var factoryResult = CallA(callback, BindCallbackArgs(factoryArgs, entry[1][2])); - if(GetType(factoryResult) == C4V_Array) + factoryArgs[Menu_CallbackArg_EntryColumn] = column; + var factoryResult = CallA(callback, BindCallbackArgs(factoryArgs, entry.Binding)); + + if(factoryResult == Menu_React_Close) { - var newEntries = []; - UncombineAndDistinguish(factoryResult, settings, newEntries); - HandleEntries(newEntries, i, ret, factoryArgs); + return Close(); } - else if(factoryResult == Menu_React_Close) + else { - return Close(); + var newSettings; + var newEntries; + if(GetType(factoryResult) == C4V_Map) + { + newSettings = factoryResult.Settings; + newEntries = factoryResult.Entries; + } + else if(GetType(factoryResult) == C4V_Array) + { + newEntries = factoryResult; + } + else + { + Log("WARNING: DT_Menu::HandleEntries: ignoring invalid return value of Factory: %v (%v)", factoryResult, callback); + } + + if(newSettings) + { + retSettings = Extend(retSettings, newSettings, true); + } + if(newEntries) + { + HandleEntries(newEntries, i, ret, retSettings, factoryArgs, column, isMainColumn); + } } - } + } } - } -} - -func AddEntries(array& entries) -{ - for(var i = 0; i < GetLength(entries); ++i) - { - var entry = entries[i]; - var condition = entry[DT_Menu_Entry_Condition], conditionRet; - var caption = entry[DT_Menu_Entry_Caption], noCommand = !entry[DT_Menu_Entry_Placeholder] || (entry[DT_Menu_Entry_Placeholder] != true && !entry[DT_Menu_Entry_Callbacks]); - if(condition) + else if(entry.Type == DT_Menu_Type_Columns) { - if(noCommand || condition[1] == Menu_Condition_DenySelection || (condition[1] == Menu_Condition_Default && !settings[DT_Menu_Settings_ConditionAllowSelection])) + if(multiColumnMode) { - noCommand = true; + FatalError("DT_Menu::HandleEntries: only one Menu_Columns() can appear in the whole menu"); } - conditionRet = CallA(condition[0], [entry[DT_Menu_Entry_Symbol], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]) || settings[DT_Menu_Settings_ConditionDisableMode]; - if(conditionRet == Menu_ConditionReact_Hide) + if(columnCount > 1) { - continue; + FatalError("DT_Menu::HandleEntries: Menu_Columns() and SubMenu columns can not be used together"); } - else if(conditionRet == Menu_ConditionReact_GrayOut) + + if(GetLength(columnEntries[0]) > 0) { - caption = Format("<c 808080>%s</c>", caption); + FatalError("DT_Menu::HandleEntries: if Menu_Columns() is used, all entries must occur inside it"); } - else if(GetType(conditionRet) == C4V_Array && conditionRet[0] == Menu_ConditionReact_CustomFormat) + + multiColumnMode = true; + + var j = 0; + for(var columnData in entry.Columns) { - caption = Format(conditionRet[1], caption); + 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()"); + } } - else + } + 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.Object); + } + + if(!multiColumnMode) + { + for(var i = 1; i < GetLength(columnEntries); ++i) + { + var column = columnEntries[i]; + if(column) { - noCommand = false; + var offset = currentColumnSelections[i - 1] + columnOffsets[i - 1]; + var entryCount = GetLength(column); + if(offset + entryCount > entriesCount) + { + offset = entriesCount - entryCount; + } + columnOffsets[i] = offset; } } + } - var symbol = entry[DT_Menu_Entry_Symbol], symbolID = 0, deleteSymbol = 0; - if(GetType(symbol) == C4V_Array) + for(var i = 0, entryIndex = 0; i < entriesCount; ++i) + { + for(var j = 0; j < columnCount; ++j) { - if(GetType(symbol[0]) == C4V_C4ID) + columnOffsets[j] ??= 0; + var rowIndex = i - columnOffsets[j]; + var entries = columnEntries[j]; + if(entries && rowIndex >= 0 && rowIndex < GetLength(entries)) { - symbolID = symbol[0]; - if(symbol[2]) + var entry = entries[rowIndex]; + var condition = entry.Condition, conditionRet; + var text = entry.Text, noCommand = !entry.Placeholder || (entry.Placeholder != true && !entry.Callbacks); + if(condition) { - symbol = [CreateSymbolDummy()->SetSymbol(symbolID, symbol[1])->SetColor(symbol[2]), true]; + 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("<c 808080>%s</c>", text); + } + else if(GetType(conditionRet) == C4V_Array && conditionRet[0] == Menu_ConditionReact_CustomFormat) + { + text = Format(conditionRet[1], text); + } + else + { + noCommand = false; + } } - else if(symbol[1]) + + var icon = entry.Icon, iconIndex = 0, iconID = 0, deleteIcon = 0; + if(GetType(icon) == C4V_Array) { - entry[DT_Menu_Entry_Extra] |= C4MN_Add_ImgIndexed; - entry[DT_Menu_Entry_XPar1] = symbol[1]; + 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(GetType(symbol[0]) == C4V_C4Object) - { - entry[DT_Menu_Entry_Extra] |= C4MN_Add_ImgObject; - entry[DT_Menu_Entry_XPar1] = symbol[0]; - deleteSymbol = symbol[1]; - symbolID = GetID(symbol[0]); - } - } - else - { - symbolID = symbol; - } - entry[DT_Menu_Entry_Symbol] = symbolID; + if(!entry.InstantDescriptionIcon) + { + entry.InstantDescriptionIcon = [iconID, iconIndex]; + } - entries[i] = entry; + if(settings.InstantDescription.Enable) + { + entry.Extra |= C4MN_Add_ForceNoDesc; + } - AddMenuItem(caption, !noCommand && "MenuItemCommand", symbolID, settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Count], i, entry[DT_Menu_Entry_InfoCaption], entry[DT_Menu_Entry_Extra], entry[DT_Menu_Entry_XPar1], entry[DT_Menu_Entry_XPar2]); + entry.Placeholder = !noCommand; - if(deleteSymbol) - { - RemoveObject(deleteSymbol); + 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, int itemNumber, int refreshDelayed) +func React(reaction, array entryIndex, int refreshDelayed) { if(reaction == Menu_React_Close) { - Close(!settings[DT_Menu_Settings_KeepParentOnClose]); + Close(!settings.KeepParentOnClose); } else if(reaction == Menu_React_Back) { - Close(); + if(currentColumn > 0 && !multiColumnMode) + { + LeaveSubMenuColumn(); + } + else + { + Close(); + } } - else if(reaction == Menu_React_Refresh || (settings[DT_Menu_Settings_KeepOpen] & DT_Menu_KeepOpen_Refresh)) + else if(reaction == Menu_React_Refresh || (settings.KeepOpen & Menu_KeepOpen_Refresh)) { - Refresh(itemNumber, refreshDelayed); + Refresh(entryIndex, refreshDelayed); } else if(GetType(reaction) == C4V_Array) { - var selection = currentSelection; + var selection = GetSelection(Menu_Selection_WithColumn); + var currentSelection = selection; if(reaction[0] == Menu_React_SelectionOffset) { - selection += reaction[1]; - selection %= entryCount; + selection[0] += reaction[1]; + selection[0] %= GetLength(columnEntries[currentColumn]); } else if(reaction[0] == Menu_React_SelectionChange) { - selection = BoundBy(reaction[1], 0, entryCount - 1); + selection[0] = BoundBy(reaction[1], 0, GetLength(columnEntries[selection[1]]) - 1); } if(selection != currentSelection) { - SelectEntry(selection); + SelectEntry(selection[0], selection[1]); } } } -func CheckCondition(array entry) +func CheckCondition(map entry) { - var condition = entry[DT_Menu_Entry_Condition]; - return !condition || (CallA(condition[0], [entry[DT_Menu_Entry_Symbol], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]) || settings[DT_Menu_Settings_ConditionDisableMode]) == Menu_ConditionReact_Show; + 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 entry = entries[itemNumber]; - var condition = entry[DT_Menu_Entry_Condition]; + 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[DT_Menu_Entry_Callbacks], action, [action, ID, settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]); + reaction = CallCallbacks(entry.Callbacks, action, [action, ID, settings.Object, entry.Args]); } else { - if(condition[1] == Menu_Condition_AllowSelection) + if(condition.AllowDisabledSelection == Menu_Condition_AllowSelection) { reaction = Menu_React_KeepOpen; } @@ -564,17 +829,33 @@ func MenuItemCommand(id ID, int itemNumber, int action) reaction = Menu_React_Refresh; } } - React(reaction, itemNumber); + React(reaction, [itemNumber, column]); } -func SubmenuItemCallback(int action, object menuObject, args, array allArgs) +// 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(((action & Menu_CallbackType_Defaults) && reaction == Menu_React_None) || reaction == Menu_React_ShowSubMenu) { - subMenu = CreateNewMenu(args[2], settings, this); + 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 @@ -583,21 +864,35 @@ func SubmenuItemCallback(int action, object menuObject, args, array allArgs) } } +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 = entries[selection]; + 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_Symbol], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args]]); + reaction = CallCallbacks(entry.Callbacks, Menu_CallbackType_Close, [Menu_CallbackType_Close, entry.Icon, settings.Object, entry.Args]); } - React(reaction, selection, true); + 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)) + if((settings.KeepOpen != Menu_KeepOpen_Not && settings.KeepOpen != Menu_KeepOpen_Permanent && !settings.Closable) || (reaction == Menu_React_KeepOpen)) { return true; } @@ -605,37 +900,208 @@ func MenuQueryCancel(int selection, object menuObject) func OnMenuSelection(int selection, object menuObject) { - if(selection != currentSelection) + 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]) { - var oldSelection = currentSelection; - var entry = entries[currentSelection]; - var deselectReaction = Menu_React_None; - var selectReaction = Menu_React_None; - if(!noSelectionCallbacks && CheckCondition(entry) && currentSelection != -1) + // navigate downwards when pressing right at the right border (thus selecting a dummy) + var selectedColumn = column; + for(; column > -1; --column) { - deselectReaction = CallCallbacks(entry[DT_Menu_Entry_Callbacks], Menu_CallbackType_Deselection, [Menu_CallbackType_Deselection, entry[DT_Menu_Entry_Symbol], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args], selection, oldSelection]); + if(columnEntries[column]) + { + break; + } } - entry = entries[currentSelection = selection]; + 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; + } - if(!noSelectionCallbacks && CheckCondition(entry)) + var skipHandling; + if(selection < 0) + { + if(currentColumn == column) { - selectReaction = CallCallbacks(entry[DT_Menu_Entry_Callbacks], Menu_CallbackType_Selection, [Menu_CallbackType_Selection, entry[DT_Menu_Entry_Symbol], settings[DT_Menu_Settings_Object], entry[DT_Menu_Entry_Args], selection, oldSelection]); + 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(deselectReaction != Menu_React_None) + 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) { - React(deselectReaction, currentSelection); + 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(selectReaction != Menu_React_None) + if(skipHandling) { - React(selectReaction, currentSelection); + 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(int selection, bool delayed) +func Refresh(array selection, bool delayed) { if(suspended) { @@ -648,102 +1114,80 @@ func Refresh(int selection, bool delayed) } else { - var disabledCallbacks; - if(!noSelectionCallbacks) + 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) { - disabledCallbacks = noSelectionCallbacks = true; + SelectEntry(BoundBy(selection, 0, GetLength(columnEntries[column]) - 1), column); + + if(settings.InstantDescription.Enable) + { + ShowInstantDescription(columnEntries[currentColumn][selection]); + } } - CloseMenu(settings[DT_Menu_Settings_Object]); - Create(settings, createEntries); - SelectEntry(BoundBy(selection, 0, entryCount - 1)); + noSelectionCallbacks = oldNoSelectionCallbacks; + } +} - if(disabledCallbacks) +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 { - noSelectionCallbacks = false; + 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__Setting(array setting) { return [DT_Menu_Type_Setting, setting]; } - -global func Menu_Symbol(id symbol) { return Menu__Setting([DT_Menu_Settings_Symbol, symbol]); } -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) { 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_Caption(string caption) { return Menu__Setting([DT_Menu_Settings_Caption, caption]); } -global func Menu_RefreshInterval(int interval) { return Menu__Setting([DT_Menu_Settings_RefreshInterval, interval + !interval]); } -global func Menu_Selection(int selection) { return Menu__Setting([DT_Menu_Settings_Selection, selection]); } -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) { return Menu__Setting([DT_Menu_Settings_Callbacks, callbacks]); } -global func Menu_Decoration(id decoration) { return Menu__Setting([DT_Menu_Settings_Decoration, decoration]); } -global func Menu_RequireAction(string action, object target) { 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) { 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() { return Menu__Setting([DT_Menu_Settings_Closable, true]); } -global func Menu_NotClosable() { return Menu__Setting([DT_Menu_Settings_Closable, false]); } +// ---------------------------------------------------------------------------- global func Menu_Callback(array callback, int types, array argBinding) { - argBinding = argBinding || [Menu_CallbackArg_Action, Menu_CallbackArg_Symbol, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_NewSelection, Menu_CallbackArg_OldSelection, Menu_CallbackArg_NewValue, Menu_CallbackArg_OldValue, Menu_CallbackArg_FromSubmenu]; - return [callback, types || Menu_CallbackType_Defaults, argBinding]; + types ??= Menu_CallbackType_Defaults; + 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, argBinding]; } -global func Menu_Entry_Caption(string Caption) { return [DT_Menu_Entry_Caption, Caption]; } -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_InfoCaption(string InfoCaption) { return [DT_Menu_Entry_InfoCaption, InfoCaption]; } -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_Symbol(symbol, extra, int color) -{ - if(GetType(symbol) == C4V_Array) - { - return [DT_Menu_Entry_Symbol, symbol]; - } - else - { - return [DT_Menu_Entry_Symbol, [symbol, extra, color]]; - } -} -global func Menu_Entry_Condition(callback, int allowDisabledSelection) { return [DT_Menu_Entry_Condition, [callback, allowDisabledSelection]]; } global func Menu_Entry_VariableCondition(array scopedVar, compare, int disableMode, bool invert, int allowDisabledSelection) { - return Menu_Entry_Condition(BindCallback(MN7I->MenuObjectCallback("VariableCondition"), [Bind(scopedVar), Bind(compare), Bind(disableMode), Bind(invert)]), 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 BindCallback(MN7I->MenuObjectCallback("VariableCondition"), [Bind(scopedVar), Bind(compare), Bind(disableMode), Bind(invert)]); } -global func VariableCondition(array scopedVar, compare, int disableMode, bool invert) +func VariableCondition(array scopedVar, compare, int disableMode, bool invert) { var disable = ScopedVar(scopedVar) != compare; if(invert) @@ -755,126 +1199,163 @@ global func VariableCondition(array scopedVar, compare, int disableMode, bool in return disableMode; } else - { return Menu_ConditionReact_Show; } } -global func Menu_Entry_Object(object obj) -{ - return Menu_Combined([Menu_Entry_Symbol(obj), Menu_Entry_Caption(GetName(obj)), Menu_Entry_InfoCaption(GetDesc(obj))]); -} -global func Menu_Entry(array settings) +/* +Menu_Entry({ + Text = <Text>, + Icon = <Icon>, + Count = <Count>, + Description = <Description, shown as a Tooltip or as InstantDescription>, + Placeholder = <May the placeholder entry be selected>: true | >false<, + InstantDescriptionIcon = [iconID, iconIndex] | [iconID, portraitName, color], + Callbacks = [<Menu_Callback()>...], + Args = <Args for Callbacks>, + Condition = { + Callback = <Callback>, + AllowDisabledSelection = >Menu_Condition_Default< | Menu_Condition_AllowSelection | Menu_Condition_DenySelection + }, + Extra = <Same as extra of AddMenuItem> >Also used internally<, + XPar1 = <Same as XPar1 of AddMenuItem> >Also used internally<, + XPar2 = <Same as XPar2 of AddMenuItem> >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(!settings) + if(!entry) { - settings = [Menu_Entry_Placeholder(false)]; + entry = { Placeholder = false }; + } + else + { + entry = Extend({ + Text = "", + Placeholder = -1, + Extra = 0, + }, entry, true); } - var namedArgs = []; - namedArgs[DT_Menu_Entry_Caption] = ""; - namedArgs[DT_Menu_Entry_Placeholder] = -1; - MN7I->NamedArgs(settings, namedArgs); - if(!namedArgs[DT_Menu_Entry_InfoCaption]) + if(!entry.Description) { - namedArgs[DT_Menu_Entry_Extra] |= C4MN_Add_ForceNoDesc; + entry.Extra |= C4MN_Add_ForceNoDesc; } - return [DT_Menu_Type_Entry, namedArgs]; + 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(array entrySettings, array menuEntry_Settings) +global func Menu_SubMenu(map submenuSettings, bool asColumn) { - 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]; - 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(!(submenuSettings.Entry)) { FatalError("Assertion failed: Menu_SubMenu: Non-selectable placeholder as submenu entry doesn't make sense; assertion code: submenuSettings.Entry"); } + if(!(submenuSettings && GetLength(submenuSettings) > 0)) { FatalError("Assertion failed: Menu_SubMenu: Empty submenu doesn't make sense; assertion code: submenuSettings && GetLength(submenuSettings) > 0"); } + 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 [DT_Menu_Type_Factory, [callbacks, args, binding || [Menu_CallbackArg_Args, Menu_CallbackArg_EntryNumber, Menu_CallbackArg_Menu]]]; + if(!(callbacks)) { FatalError("Assertion failed: Menu_Factory: callbacks are mandatory; assertion code: callbacks"); } + 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([Menu_Entry_Caption(text), Menu_Entry_Placeholder(allowSelection)]); + return Menu_Entry({ + Text = text, + Placeholder = allowSelection + }); } global func Menu_Blank(bool allowSelection) { - return Menu_Entry([Menu_Entry_Placeholder(allowSelection)]); + return Menu_Entry({ + Placeholder = allowSelection + }); } -func DeclineAcceptBack(string caption, symbol, string callback, array settings) +func DeclineAcceptBack(string text, icon, string callback, map settings) { - var ret = Menu_Entry([Menu_Entry_Caption(caption), 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]; - ExtraSymbol(ret[1][DT_Menu_Entry_Caption], ret[1][DT_Menu_Entry_Symbol], symbol); + 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(array settings) +global func Menu_Decline(map settings) { return MN7I->DeclineAcceptBack("$Decline$", [MN7I, 2], "DeclineAcceptCommand", settings); } -global func Menu_Accept(array settings) +global func Menu_Accept(map settings) { return MN7I->DeclineAcceptBack("$Accept$", [MN7I, 1], "DeclineAcceptCommand", settings); } -global func Menu_Back(array settings) +global func Menu_Back(map 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_EnumSubmenuSymbol(id EnumSubmenuSymbol) { return [DT_Menu_Adaptor_EnumSubmenuSymbol, EnumSubmenuSymbol]; } -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) { return [DT_Menu_Adaptor_EnumSubmenu, callbackType]; } -global func Menu_Adaptor_Limits(min, max, args) -{ - return [DT_Menu_Adaptor_Limits, [min, max, args]]; -} -global func Menu_Adaptor_StepSize(int step, force) { 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) - { - fieldVals[i][valuePos] = 1 << fieldVals[i][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_EnumSubmenuCaption(string menuCaption, string entryCaption) -{ - return [DT_Menu_Adaptor_EnumSubmenuCaption, [menuCaption, entryCaption]]; -} -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) { - layout = layout || [Menu_Layout_Value | Menu_Layout_Caption]; + 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) @@ -886,9 +1367,9 @@ func AdaptorLayout(array& layout, array& vals, bool valuesAsSeparateLists) layoutMap[noFlag] = index; } - if(val & Menu_Layout_Caption) + if(val & Menu_Layout_Text) { - layoutMap[Menu_Layout__CaptionPos] = index; + layoutMap[Menu_Layout__TextPos] = index; } if(val & Menu_Layout_Value) @@ -903,6 +1384,7 @@ func AdaptorLayout(array& layout, array& vals, bool valuesAsSeparateLists) ++index; } + if(!(layoutMap[Menu_Layout__ValuePos])) { FatalError("Assertion failed: AdaptorLayout: Menu_Layout_Value is mandatory; assertion code: layoutMap[Menu_Layout__ValuePos]"); } layout = layoutMap; @@ -923,28 +1405,120 @@ func AdaptorLayout(array& layout, array& vals, bool valuesAsSeparateLists) } } -global func Menu_Adaptor(array entrySettings, array adaptorSettings) +/* +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 = <ScopedVar> storing the value, + Callbacks = [<Menu_Callback()>...], + Args = <Args for Callbacks>, + MessageBoardText = <Text shown in front of the MessageBoard>, + Limits = [<Min>, <Max>] for Integer-Type, + StepSize = [<StepSize for increasing and decreasing>, <StepSize for MessageBoard input>] for Integer-Type, + NoEmptyString = <Disallow setting the value to an empty string for String-Type>: true | >false<, + EntryIndex = >Internal<: used for MessageBoard callbacks, + WrapAround = <Wrap around the allowed values at their limits for Integer- and Enum-Type>: true <else for Enum> | false <else for Integer>, + Enum = <only for Enum-Type>: { + Values = [<[Values according to Layout]>...], + Layout = [<[Menu_Layout_*, some can be combined with &]>...], + ValuesAsSeparateLists = <Values is a "struct of arrays" instead of "array of structs">: true | >false<, + Inline = <Always show all values inlined in the containing menu>: true | >false<, + AllowUnknown = <Are unknown values allowed for the specified var? If not, it will be overwritten.>: true | >false<, + SubMenu = <Settings specific to the optional submenu possibility>: { + On = <CallbackType when to show>: >Menu_CallbackType_Special2< | true to show as extra column | Menu_CallbackType_None <to disable> | Menu_CallbackType_*, + Icon = <Icon to use for the SubMenu-Header>, + Title = <Title to use for the SubMenu-Header>, + Text = <Text to use for the individual Entries>, + } + }, + BitField = <only for BitField-Type>: { + Values = [<[Values according to Layout]>...], + Layout = [<[Menu_Layout_*, some can be combined with &]>...], + ValuesAsSeparateLists = <Values is a "struct of arrays" instead of "array of structs">: true | >false<, + BitPositionAsValue = <Instead of the values being a bit-mask, the values denote the bit-position>: true | >false<, + Mask = >Internal< + } + } +}) */ + +global func Menu_Adaptor(map settings) { - var adaptorArgs = []; + if(!(settings.Adaptor && GetLength(settings.Adaptor) > 0)) { FatalError("Assertion failed: Menu_Adaptor: Adaptor without settings doesn't make sense; assertion code: settings.Adaptor && GetLength(settings.Adaptor) > 0"); } + if(!(settings.Entry && GetLength(settings.Entry) > 0)) { FatalError("Assertion failed: Menu_Adaptor: Non-selectable placeholder as adaptor entry doesn't make sense; assertion code: settings.Entry && GetLength(settings.Entry) > 0"); } - adaptorArgs[DT_Menu_Adaptor_WrapAround] = -1; - adaptorArgs[DT_Menu_Adaptor_EnumSubmenu] = Menu_CallbackType_Special2; + 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!"); + } + } - MN7I->NamedArgs(adaptorSettings, adaptorArgs); + settings = Extend({ + Adaptor = { + WrapAround = -1, + Enum = { + SubMenu = { + On = Menu_CallbackType_Special2, + } + } + } + }, settings, true); - if(adaptorArgs[DT_Menu_Adaptor_WrapAround] == -1) + if(settings.Adaptor.WrapAround == -1) { - if(adaptorArgs[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Integer) + if(settings.Adaptor.Type == Menu_AdaptorType_Integer) { - adaptorArgs[DT_Menu_Adaptor_WrapAround] = false; + settings.Adaptor.WrapAround = false; } - else if(adaptorArgs[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Enum) + else if(settings.Adaptor.Type == Menu_AdaptorType_Enum) { - adaptorArgs[DT_Menu_Adaptor_WrapAround] = true; + settings.Adaptor.WrapAround = true; } } - return Menu_Factory([MN7I->MenuObjectCallback("AdaptorFactory")], [Menu_Entry(entrySettings)[1], adaptorArgs, entrySettings]); + 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) + { + 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]; + } + } + settings.Adaptor.BitField.Values = fieldVals; + settings.Adaptor.BitField.Layout = layout; + + 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])); } + } + + } + 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) @@ -956,64 +1530,67 @@ func EnumValPos(array enumVals, array layout, val, bool allowUnknown) return i; } } + if(!allowUnknown) + { + Log("WARNING: DT_Menu::EnumValPos: Current value %v is not in enum: %v", val, enumVals); + } return -1; } -func BooleanToggleCaption(bool val, string& caption, &symbol) +func BooleanToggleText(bool val, string& text, &icon) { val = !!val; - ExtraSymbol(caption, symbol, [MN7I, 2 - val, !val && RGB(128, 128, 128)]); + ExtraIcon(text, icon, [MN7I, 2 - val, !val && RGB(128, 128, 128)]); } -func InlineSymbol(string& caption, symbol) +func InlineIcon(string& text, icon) { - if(symbol[2]) + if(icon[2]) { - caption = Format("<c %x>{{%i:%d}}</c> %s", symbol[2], symbol[0], symbol[1], caption); + text = Format("<c %x>{{%i:%d}}</c> %s", icon[2], icon[0], icon[1], text); } else { - caption = Format("{{%i:%d}} %s", symbol[0], symbol[1], caption); + text = Format("{{%i:%d}} %s", icon[0], icon[1], text); } } -func ExtraSymbol(string& caption, &symbol, extraSymbol) +func ExtraIcon(string& text, &icon, extraIcon) { - if(GetType(extraSymbol) == C4V_C4ID) + if(GetType(extraIcon) == C4V_C4ID) { - extraSymbol = [extraSymbol]; + extraIcon = [extraIcon]; } - if(symbol && extraSymbol && GetType(extraSymbol[0]) == C4V_C4ID) + if(icon && extraIcon && GetType(extraIcon[0]) == C4V_C4ID) { - InlineSymbol(caption, extraSymbol); + InlineIcon(text, extraIcon); } else { - symbol = extraSymbol; + icon = extraIcon; } } -func EnumEntrySettings(string& caption, &symbol, string& infoCaption, int index, array args, array entry) +func EnumEntrySettings(string& text, &icon, string& description, int index, map args, map entry) { - var layoutVals = args[DT_Menu_Adaptor_LayoutVals]; - var layout = layoutVals[1]; - layoutVals = layoutVals[0]; + var layoutVals = args.Enum.Values; + var layout = args.Enum.Layout; - caption = entry[DT_Menu_Entry_Caption]; - if(layout[Menu_Layout__CaptionPos]) + text = entry.Text; + if(layout[Menu_Layout__TextPos]) { - caption = Format(caption, layoutVals[index][layout[Menu_Layout__CaptionPos] - 1]); + text = Format(text, layoutVals[index][layout[Menu_Layout__TextPos] - 1]); } - if(layout[Menu_Layout_Symbol]) + if(layout[Menu_Layout_Icon]) { - ExtraSymbol(caption, symbol, layoutVals[index][layout[Menu_Layout_Symbol] - 1]); + ExtraIcon(text, icon, layoutVals[index][layout[Menu_Layout_Icon] - 1]); } - if(layout[Menu_Layout_InfoCaption]) + if(layout[Menu_Layout_Description]) { - infoCaption = Format(infoCaption, layoutVals[index][layout[Menu_Layout_InfoCaption] - 1]); + description = Format(description, layoutVals[index][layout[Menu_Layout_Description] - 1]); } } @@ -1034,42 +1611,47 @@ func AdaptorGetLimits(array limits) 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) +func AdaptorFactory(args, int entryIndex, int entryColumn) { var origArgs = args; var entry = args[0]; - var entrySettings = args[2]; - args = args[1]; - var caption = entry[DT_Menu_Entry_Caption]; - var infoCaption = entry[DT_Menu_Entry_InfoCaption]; - var symbol = entry[DT_Menu_Entry_Symbol]; - var val = ScopedVar(args[DT_Menu_Adaptor_Variable]); + 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; + var retSubMenu; - args[DT_Menu_Adaptor_Args] = entry[DT_Menu_Entry_Args]; - args[DT_Menu_Adaptor_EntryIndex] = entryIndex; - args[DT_Menu_Adaptor_Callbacks] = args[DT_Menu_Adaptor_Callbacks] || []; - if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Boolean) + args.Args = entry.Args; + args.EntryIndex = [entryIndex, entryColumn]; + args.Callbacks = args.Callbacks || []; + if(args.Type == Menu_AdaptorType_Boolean) { - BooleanToggleCaption(val, caption, symbol); + 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) + else if(args.Type == Menu_AdaptorType_String) { - caption = Format(entry[DT_Menu_Entry_Caption], val); + 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.Text, val); defaultMsgboardText = "$EnterText$"; } - else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Integer) + else if(args.Type == Menu_AdaptorType_Integer) { - var limits = AdaptorGetLimits(args[DT_Menu_Adaptor_Limits]), max, min; - var wrapAround = args[DT_Menu_Adaptor_WrapAround]; + 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"); } + val ??= 0; + var limits = AdaptorGetLimits(args.Limits), max, min; + var wrapAround = args.WrapAround; if(limits && !wrapAround) { if(val >= limits[1]) @@ -1081,162 +1663,172 @@ func AdaptorFactory(args, int entryIndex) min = true; } } - caption = Format("%s %s", Format(entry[DT_Menu_Entry_Caption], val), ["{{MN7I:4}}", "<c 808080>{{MN7I:4}}</c>"][min]); - ExtraSymbol(caption, symbol, [MN7I, 3, max && RGB(128, 128, 128)]); + text = Format(entry.Text, val) .. " " .. ["{{MN7I:4}}", "<c 808080>{{MN7I:4}}</c>"][min]; + ExtraIcon(text, icon, [MN7I, 3, max && RGB(128, 128, 128)]); defaultMsgboardText = "$EnterNumber$"; } - else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_ID) + else if(args.Type == Menu_AdaptorType_ID) { - caption = Format(entry[DT_Menu_Entry_Caption], val && GetName(0, val) || ""); + 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.Text, val && GetName(0, val) || ""); if(val) { - ExtraSymbol(caption, symbol, val); + ExtraIcon(text, icon, val); } defaultMsgboardText = "$EnterIDOrName$"; } - else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Enum) + else if(args.Type == Menu_AdaptorType_Enum) { - if(args[DT_Menu_Adaptor_EnumInline]) + if(args.Enum.Inline) { - return AdaptorEnumSubmenuFactory([args, entry]); + return AdaptorEnumSubMenuFactory([args, entry]); } - var layoutVals = args[DT_Menu_Adaptor_LayoutVals]; - var layout = layoutVals[1]; - layoutVals = layoutVals[0]; + var layoutVals = args.Enum.Values; + var layout = args.Enum.Layout; - var index = EnumValPos(layoutVals, layout, val, args[DT_Menu_Adaptor_EnumAllowUnknown]); + var index = EnumValPos(layoutVals, layout, val, args.Enum.AllowUnknown); if(index == -1) { - if(!args[DT_Menu_Adaptor_EnumAllowUnknown]) + if(!args.Enum.AllowUnknown) { - ScopedVar(args[DT_Menu_Adaptor_Variable]) = layoutVals[0][layout[Menu_Layout__ValuePos] - 1]; + ScopedVar(args.Variable) = layoutVals[0][layout[Menu_Layout__ValuePos] - 1]; } index = 0; } - var submenuSymbol = symbol; + var submenuIcon = icon; - EnumEntrySettings(caption, symbol, infoCaption, index, args, entry); + EnumEntrySettings(text, icon, description, index, args, entry); - if(args[DT_Menu_Adaptor_EnumSubmenu] != Menu_CallbackType_None) + if(args.Enum.SubMenu.On != Menu_CallbackType_None) { - args[DT_Menu_Adaptor_MessageBoardText] = args[DT_Menu_Adaptor_MessageBoardText] || defaultMsgboardText; + args.MessageBoardText = args.MessageBoardText || defaultMsgboardText; - retSubmenu = [Menu_Factory([MenuObjectCallback("AdaptorEnumSubmenuFactory")], [args, entry])]; + retSubMenu = [Menu_Factory([MenuObjectCallback("AdaptorEnumSubMenuFactory")], origArgs[1])]; } } - else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_BitField) + else if(args.Type == Menu_AdaptorType_BitField) { return AdaptorBitFieldItemsFactory(origArgs); } - args[DT_Menu_Adaptor_MessageBoardText] = args[DT_Menu_Adaptor_MessageBoardText] || defaultMsgboardText; - if(!retSubmenu) + args.MessageBoardText = args.MessageBoardText || defaultMsgboardText; + if(!retSubMenu) { - return [Menu_Entry([Menu_Combined(entrySettings), Menu_Entry_Caption(caption), Menu_Entry_Callbacks([Menu_Callback(MenuObjectCallback("AdaptorCommand"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])]), Menu_Entry_Symbol(symbol), Menu_Entry_InfoCaption(infoCaption), Menu_Entry_Args(args)])]; + 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([Menu_Combined(entrySettings), Menu_Entry_Caption(caption), Menu_Entry_Callbacks([Menu_Callback(MenuObjectCallback("AdaptorCommand"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])]), Menu_Entry_Symbol(symbol), Menu_Entry_InfoCaption(infoCaption), Menu_Entry_Args(args)], retSubmenu)]; + 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(array args) +func AdaptorEnumSubMenuFactory(map args) { - var entry = args[1]; - args = args[0]; - var layoutVals = args[DT_Menu_Adaptor_LayoutVals]; - var layout = layoutVals[1]; - layoutVals = layoutVals[0]; + var entry = args.Entry; + args = args.Adaptor; + var layoutVals = args.Enum.Values; + var layout = args.Enum.Layout; - var submenuCaption = args[DT_Menu_Adaptor_EnumSubmenuCaption]; - var submenuSymbol = []; + var submenuIcon = []; - if(submenuCaption && submenuCaption[0]) + if(args.Enum.SubMenu.Title) { - entry[DT_Menu_Entry_Caption] = submenuCaption[0]; + entry.Text = args.Enum.SubMenu.Title; } - var index = EnumValPos(layoutVals, layout, ScopedVar(args[DT_Menu_Adaptor_Variable]), args[DT_Menu_Adaptor_EnumAllowUnknown]); - var symbol, infoCaption; - EnumEntrySettings(submenuCaption, symbol, infoCaption, index, args, entry); + 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[DT_Menu_Adaptor_EnumInline]) + if(args.Enum.Inline) { - ret = []; + ret = { + Entries = [] + }; } else { - ret = [ - Menu_Selection(index), - Menu_KeepParentOnClose(), - Menu_DontKeepOpen(), - Menu_Caption(submenuCaption) - ]; + ret = { + Settings = { + Selection = [index, 0], + KeepParentOnClose = true, + KeepOpen = Menu_KeepOpen_Not, + Title = title + }, + Entries = [] + }; } - if(args[DT_Menu_Adaptor_EnumSubmenuSymbol]) + if(args.Enum.SubMenu.Icon) { - submenuSymbol = [args[DT_Menu_Adaptor_EnumSubmenuSymbol]]; + submenuIcon = [args.Enum.SubMenu.Icon]; } - else if(!submenuSymbol && symbol && GetType(symbol[0]) == C4V_C4ID) + else if(icon && GetType(icon[0]) == C4V_C4ID) { - submenuSymbol = symbol; + submenuIcon = icon; } - if(submenuSymbol && GetType(submenuSymbol[0]) == C4V_C4ID) + if(submenuIcon && GetType(submenuIcon[0]) == C4V_C4ID) { - ArrayAppend(ret, Menu_Symbol(submenuSymbol[0])); + ret.Settings.Icon = submenuIcon[0]; } - var caption; - - var submenuCaption = args[DT_Menu_Adaptor_EnumSubmenuCaption]; - if(submenuCaption && submenuCaption[1]) + var text; + if(args.Enum.SubMenu.Text) { - entry[DT_Menu_Entry_Caption] = submenuCaption[1]; + entry.Text = args.Enum.SubMenu.Text; } for(var i = 0; i < GetLength(layoutVals); ++i) { - symbol = 0; - EnumEntrySettings(caption, symbol, infoCaption, i, args, entry); - if(args[DT_Menu_Adaptor_EnumInline]) + icon = 0; + EnumEntrySettings(text, icon, description, i, args, entry); + if(args.Enum.Inline) { - BooleanToggleCaption(i == index, caption, symbol); + BooleanToggleText(i == index, text, icon); } - ArrayAppend(ret, Menu_Entry([Menu_Entry_Caption(caption), Menu_Entry_Symbol(symbol), Menu_Entry_InfoCaption(infoCaption), Menu_Entry_Callbacks([Menu_Callback(MenuObjectCallback("AdaptorEnumSubmenuItem"), Menu_CallbackType_Defaults, [Menu_CallbackArg_Args, Menu_CallbackArg_All])]), Menu_Entry_Args([i, args])])); + 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) +func AdaptorEnumSubMenuItem(args, array allArgs) { var index = args[0]; args = args[1]; - var val = ScopedVar(args[DT_Menu_Adaptor_Variable]); + var val = ScopedVar(args.Variable); var oldVal = val; - var enumVals = args[DT_Menu_Adaptor_LayoutVals]; - var layout = enumVals[1]; - enumVals = enumVals[0]; + var enumVals = args.Enum.Values; + var layout = args.Enum.Layout; - ScopedVar(args[DT_Menu_Adaptor_Variable]) = val = enumVals[index][layout[Menu_Layout__ValuePos] - 1]; + ScopedVar(args.Variable) = val = enumVals[index][layout[Menu_Layout__ValuePos] - 1]; - allArgs[Menu_CallbackArg_Args] = args[DT_Menu_Adaptor_Args]; - allArgs[Menu_CallbackArg_FromSubmenu] = true; + allArgs[Menu_CallbackArg_Args] = args.Args; + allArgs[Menu_CallbackArg_FromSubMenu] = true; - var reaction = AdaptorCommandCallChangedCallback(args[DT_Menu_Adaptor_Callbacks], val, oldVal, allArgs); + var reaction = AdaptorCommandCallChangedCallback(args.Callbacks, val, oldVal, allArgs); if(reaction != Menu_React_None) { return reaction; } - if(args[DT_Menu_Adaptor_EnumInline]) + if(args.Enum.Inline) { return Menu_React_Refresh; } @@ -1247,47 +1839,47 @@ func AdaptorEnumSubmenuItem(args, array allArgs) func AdaptorBitFieldItemsFactory(args) { var entry = args[0]; - var entrySettings = args[2]; - args = args[1]; + var entrySettings = args[1].Entry; + args = args[1].Adaptor; - var caption = entry[DT_Menu_Entry_Caption]; - var infoCaption = entry[DT_Menu_Entry_InfoCaption]; - var symbol = entry[DT_Menu_Entry_Symbol]; - var fieldValue = ScopedVar(args[DT_Menu_Adaptor_Variable]); + var text = entry.Text; + var description = entry.Description; + var icon = entry.Icon; + var fieldValue = ScopedVar(args.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 layoutVals = args.BitField.Values; + var layout = args.BitField.Layout; var ret = []; for(var val in layoutVals) { var mask = val[layout[Menu_Layout__ValuePos] - 1]; - caption = entry[DT_Menu_Entry_Caption] || "%s"; - if(layout[Menu_Layout__CaptionPos]) + text = entry.Text || "%s"; + if(layout[Menu_Layout__TextPos]) { - caption = Format(caption, val[layout[Menu_Layout__CaptionPos] - 1]); + text = Format(text, val[layout[Menu_Layout__TextPos] - 1]); } - symbol = entry[DT_Menu_Entry_Symbol]; - BooleanToggleCaption((fieldValue & mask) == mask, caption, symbol); - if(layout[Menu_Layout_Symbol]) + icon = entry.Icon; + BooleanToggleText((fieldValue & mask) == mask, text, icon); + if(layout[Menu_Layout_Icon]) { - ExtraSymbol(caption, symbol, val[layout[Menu_Layout_Symbol] - 1]); + ExtraIcon(text, icon, val[layout[Menu_Layout_Icon] - 1]); } - if(layout[Menu_Layout_InfoCaption]) + if(layout[Menu_Layout_Description]) { - infoCaption = Format(infoCaption, val[layout[Menu_Layout_InfoCaption] - 1]); + description = Format(description, val[layout[Menu_Layout_Description] - 1]); } - args[DT_Menu_Adaptor_Mask] = mask; + args.BitField.Mask = mask; - ArrayAppend(ret, Menu_Entry([Menu_Combined(entrySettings), Menu_Entry_Caption(caption), Menu_Entry_Callbacks([Menu_Callback(MenuObjectCallback("AdaptorCommand"), Menu_CallbackType_All, [Menu_CallbackArg_Action, Menu_CallbackArg_MenuObject, Menu_CallbackArg_Args, Menu_CallbackArg_All])]), Menu_Entry_Symbol(symbol), Menu_Entry_InfoCaption(infoCaption), Menu_Entry_Args(args)])); + 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 ret; + return { Entries = ret }; } func WrapOrBind(int val, array limits, bool wrap) @@ -1339,24 +1931,24 @@ func AdaptorCommandCallChangedCallback(callbacks, val, oldVal, array allArgs) func AdaptorCommand(int action, object obj, args, array allArgs) { - var val = ScopedVar(args[DT_Menu_Adaptor_Variable]); + var val = ScopedVar(args.Variable); var oldVal = val; - allArgs[Menu_CallbackArg_Args] = args[DT_Menu_Adaptor_Args]; + allArgs[Menu_CallbackArg_Args] = args.Args; - var reaction = CallCallbacks(args[DT_Menu_Adaptor_Callbacks], action, allArgs, Menu_React_Refresh, true); + var reaction = CallCallbacks(args.Callbacks, action, allArgs, Menu_React_Refresh, true); if(reaction != Menu_React_Refresh) { return reaction; } - if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Boolean) + if(args.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); + ScopedVar(args.Variable) = val = !val; + reaction = AdaptorCommandCallChangedCallback(args.Callbacks, val, oldVal, allArgs); if(reaction != Menu_React_None) { return reaction; @@ -1367,11 +1959,11 @@ func AdaptorCommand(int action, object obj, args, array allArgs) return Menu_React_KeepOpen; } } - else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Integer) + else if(args.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 limits = AdaptorGetLimits(args.Limits); + var wrapAround = args.WrapAround; + var step = args.StepSize; var stepSize = 1; if(step) { @@ -1382,8 +1974,8 @@ func AdaptorCommand(int action, object obj, args, array allArgs) if(msgBoardMode == 0) { msgBoardMode = Menu_AdaptorType_Integer + 1; - msgBoardEntry = args[DT_Menu_Adaptor_EntryIndex]; - CallMessageBoard(this, false, args[DT_Menu_Adaptor_MessageBoardText], GetOwner(obj)); + msgBoardEntry = args.EntryIndex; + CallMessageBoard(this, false, args.MessageBoardText, GetOwner(obj)); } return Menu_React_KeepOpen; } @@ -1404,8 +1996,8 @@ func AdaptorCommand(int action, object obj, args, array allArgs) if(val != oldVal) { - ScopedVar(args[DT_Menu_Adaptor_Variable]) = val; - reaction = AdaptorCommandCallChangedCallback(args[DT_Menu_Adaptor_Callbacks], val, oldVal, allArgs); + ScopedVar(args.Variable) = val; + reaction = AdaptorCommandCallChangedCallback(args.Callbacks, val, oldVal, allArgs); if(reaction != Menu_React_None) { return reaction; @@ -1416,15 +2008,15 @@ func AdaptorCommand(int action, object obj, args, array allArgs) return Menu_React_KeepOpen; } } - else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_String) + 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[DT_Menu_Adaptor_EntryIndex]; - CallMessageBoard(this, false, args[DT_Menu_Adaptor_MessageBoardText], GetOwner(obj)); + msgBoardEntry = args.EntryIndex; + CallMessageBoard(this, false, args.MessageBoardText, GetOwner(obj)); } } if(action == Menu_CallbackType_Close) @@ -1436,44 +2028,42 @@ func AdaptorCommand(int action, object obj, args, array allArgs) return Menu_React_KeepOpen; } } - else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_ID) + 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[DT_Menu_Adaptor_EntryIndex]; - CallMessageBoard(this, false, args[DT_Menu_Adaptor_MessageBoardText], GetOwner(obj)); + msgBoardEntry = args.EntryIndex; + CallMessageBoard(this, false, args.MessageBoardText, GetOwner(obj)); } } return Menu_React_KeepOpen; } - else if(args[DT_Menu_Adaptor_Type] == Menu_AdaptorType_Enum) + else if(args.Type == Menu_AdaptorType_Enum) { - if(GetType(args[DT_Menu_Adaptor_EnumSubmenu]) == C4V_Bool) + if(GetType(args.Enum.SubMenu.On) == C4V_Bool) { if(action & Menu_CallbackType_Defaults) { - return Menu_React_ShowSubmenu; + return Menu_React_ShowSubMenu; } else { return Menu_React_None; } } - - if(args[DT_Menu_Adaptor_EnumSubmenu] & action) + else if(args.Enum.SubMenu.On & action) { - return Menu_React_ShowSubmenu; + return Menu_React_ShowSubMenu; } - var enumVals = args[DT_Menu_Adaptor_LayoutVals]; - var layout = enumVals[1]; - enumVals = enumVals[0]; + var enumVals = args.Enum.Values; + var layout = args.Enum.Layout; - var index = EnumValPos(enumVals, layout, val, args[DT_Menu_Adaptor_EnumAllowUnknown]); - var wrapAround = args[DT_Menu_Adaptor_WrapAround]; + var index = EnumValPos(enumVals, layout, val, args.Enum.AllowUnknown); + var wrapAround = args.WrapAround; if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2)) { @@ -1490,18 +2080,18 @@ func AdaptorCommand(int action, object obj, args, array allArgs) 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); + 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[DT_Menu_Adaptor_Type] == Menu_AdaptorType_BitField) + else if(args.Type == Menu_AdaptorType_BitField) { if(action & (Menu_CallbackType_Normal | Menu_CallbackType_Special2)) { - var mask = args[DT_Menu_Adaptor_Mask]; + var mask = args.BitField.Mask; if((val & mask) == mask) { val &= ~mask; @@ -1511,8 +2101,8 @@ func AdaptorCommand(int action, object obj, args, array allArgs) val |= mask; } - ScopedVar(args[DT_Menu_Adaptor_Variable]) = val; - reaction = AdaptorCommandCallChangedCallback(args[DT_Menu_Adaptor_Callbacks], val, oldVal, allArgs); + ScopedVar(args.Variable) = val; + reaction = AdaptorCommandCallChangedCallback(args.Callbacks, val, oldVal, allArgs); if(reaction != Menu_React_None) { return reaction; @@ -1529,17 +2119,17 @@ func AdaptorCommand(int action, object obj, args, array allArgs) func InputCallback(string input, int plr) { - var entry = entries[msgBoardEntry]; - var args = entry[DT_Menu_Entry_Args]; - var callbackArgs = args[DT_Menu_Adaptor_Args]; - var oldVal = ScopedVar(args[DT_Menu_Adaptor_Variable]); + 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[DT_Menu_Adaptor_Limits]); - var step = args[DT_Menu_Adaptor_StepSize]; - if(GetType(int) == C4V_Int || GetType(int) == C4V_Any) + var limits = AdaptorGetLimits(args.Limits); + var step = args.StepSize; + if(GetType(int) == C4V_Int) { if(step && step[1]) { @@ -1554,7 +2144,7 @@ func InputCallback(string input, int plr) { int = BoundBy(int, limits[0], limits[1]); } - ScopedVar(args[DT_Menu_Adaptor_Variable]) = val = int; + ScopedVar(args.Variable) = val = int; } else { @@ -1563,13 +2153,13 @@ func InputCallback(string input, int plr) } else if(msgBoardMode - 1 == Menu_AdaptorType_String) { - if(args[DT_Menu_Adaptor_NoEmptyString] && (!input || input == "")) + if(args.NoEmptyString && (!input || input == "")) { val = oldVal; } else { - ScopedVar(args[DT_Menu_Adaptor_Variable]) = input; + ScopedVar(args.Variable) = input; } } else if(msgBoardMode - 1 == Menu_AdaptorType_ID) @@ -1577,12 +2167,12 @@ func InputCallback(string input, int plr) 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; + ScopedVar(args.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_Symbol], settings[DT_Menu_Settings_Object], callbackArgs, 0, 0, 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); @@ -1591,74 +2181,84 @@ func InputCallback(string input, int plr) Refresh(msgBoardEntry); } -global func Menu_Combined(array 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] = 0; // 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); +/* +CreateNewMenu({ + Settings = { + Icon = <Icon>, + Title = <Title>, + Object = <Object for which the menu is shown>, + Style = <Same as Style of CreateMenu>, + Size = [<Columns>, <Rows>] passed to SetMenuSize(), + Selecion = <Initial Selection>, + KeepOpen = <Keep Open Mode>: Menu_KeepOpen_*, + RefreshInterval = <Interval in Frames> for Menu_KeepOpen_RefreshContinuously, + Callbacks = [<Menu_Callback()...>] global Callbacks, + Decoration = <Decoration ID> passed to SetMenuDecoration(), + RequireAction = [<Action Name>] | [<Action Name>, <Action Target>] required action (and target) for the Settings.Object; if not fulfilled the menu will close itself, + KeepParentOnClose = <Should the parent menu stay open when the submenu closes>: true | >false<, + Vars = [<Initial values of MenuVar() ScopedVariables>...], + Closable = <Can the menu be closed by pressing dig / clicking the X in the corner>: true | >false<, + InstantDescription = <If enabled, shows the description of an entry immediately in a separate CustomMessage()>: { + Enable = true | >false<, + Decoration = <Decoration> passed to CustomMessage(); >DT_Menu_DefaultInstantDescriptionDecoration<, + Position = [<x>, <y>] passed to CustomMessage(); >[40, -60]<, + Flags = <Flags> passed to CustomMessage(); >MSG_Left | MSG_Bottom< + }, + Extra = <Same as extra of CreateMenu>, + ExtraData = <Same as extraData of CreateMenu>, + Condition = { + DisableMode = <How to handle disabled entries>: >Menu_ConditionReact_Hide< | Menu_ConditionReact_*, + AllowSelection = <Allow disabled entries to be selected>: true | >false< + }, + Parent = >Internal<: Used for submenus + }, + Entries = + [Menu_Columns()] | + [<Menu_Entry() | Menu_Text() | Menu_Blank() | Menu_Decline() | Menu_Accept() | Menu_Back() | Menu_Adaptor() | | Menu_SubMenu() | Menu_Factory()>...] +}) */ + +global func CreateNewMenu(map settings, map inheritSettings, object parentMenu) +{ + inheritSettings = inheritSettings || {}; 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) -{ - for(var val in combined) - { - 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]; + settings = Extend(Extend(Extend( + { + Settings = { + Condition = { + DisableMode = Menu_ConditionReact_Hide + }, + InstantDescription = { + Decoration = DT_Menu_DefaultInstantDescriptionDecoration, + Flags = MSG_Left | MSG_Bottom, + Position = [40, -60] + }, + KeepOpen = 0, } - else - { - NamedArg(val[1], settings); + }, { Settings = inheritSettings }, true), { + Settings = { + Selection = [], // Selection can't be inherited + Callbacks = [], // Global callbacks can't be inherited (maybe filter callbacks) + Parent = parentMenu } - } - else if(val[0] == DT_Menu_Type_Entry || val[0] == DT_Menu_Type_Factory) - { - entries[GetLength(entries)] = val; - } - else if(val[0] == DT_Menu_Combined) - { - UncombineAndDistinguish(val[1], settings, entries); - } + }, true), + settings, true); + + if(GetType(settings.Settings.Selection) == C4V_Int) + { + settings.Settings.Selection = [settings.Settings.Selection, 0]; } -} -func NamedArgs(array namedArgs, array& args) -{ - for(var arg in namedArgs) + if(inheritSettings.Style) { - if(arg[0] == DT_Menu_Combined) - { - NamedArgs(arg[1], args); - } - else - { - NamedArg(arg, args); - } + // EqualItemHeight can't be inherited + settings.Settings.Style &= ~C4MN_Style_EqualItemHeight; } + + menuObj->Create(settings); + + return menuObj; } func DeclineAcceptBackCommand(int action, args, int defaultAction, array allArgs) @@ -1702,7 +2302,7 @@ func CallCustom(callback, args) { if(GetType(callback) == C4V_Array && GetLength(callback) == 2 && callback[0] == CallbackTarget_Scenario - 1) { - return CallA(ObjectCallback(callback[1], this), args, ...); + return CallA(ObjectCallback(callback[1], true), args, ...); } else { @@ -1736,20 +2336,121 @@ func CheckCustomScopedVar(array variable) func Update() { - return Refresh(currentSelection); + return Refresh(GetSelection(Menu_Selection_WithColumn)); } -func GetSelection() +func GetSelection(int mode) { - return currentSelection; + mode ??= Menu_Selection_Simple; + 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; + } + + ret[] = currentColumnSelections[i]; + } + + return ret; + } } func GetObject() { - return settings[DT_Menu_Settings_Object]; + return 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; + + +func ValidateDeco(id deco) +{ + return !deco || (deco->~FrameDecorationBackClr() || deco->~FrameDecorationBorderTop() || deco->~FrameDecorationBorderLeft() || deco->~FrameDecorationBorderRight() || deco->~FrameDecorationBorderBottom()); } -func CreateSymbolDummy() +func ValidateMenuCallbacks(array callbacks) { - return CreateContents(DT_Menu_SymbolDummy); -}
\ No newline at end of file + 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, ...); +} |
