From e27643133646baa1110a931491d4b9afd2f6ee41 Mon Sep 17 00:00:00 2001 From: Player701 Date: Tue, 27 Oct 2020 11:25:39 +0300 Subject: [PATCH] - Implemented AddListMenu MENUDEF directive to allow extending list menus. Syntax: AddListMenu "MenuName" [ "SubMenuName"] { ... } By default, new items will be added to the end of the menu. Specifying before/after allows inserting items before or after a specific submenu item. --- src/common/menu/menu.h | 1 + src/common/menu/menudef.cpp | 126 +++++++++++++++++++++++++++++++----- 2 files changed, 112 insertions(+), 15 deletions(-) diff --git a/src/common/menu/menu.h b/src/common/menu/menu.h index 86db161ce2..d6f200bfc7 100644 --- a/src/common/menu/menu.h +++ b/src/common/menu/menu.h @@ -93,6 +93,7 @@ public: bool mAnimatedTransition; int mVirtWidth; int mVirtHeight; + bool mCustomSizeSet; void Reset(); }; diff --git a/src/common/menu/menudef.cpp b/src/common/menu/menudef.cpp index cd8128ba21..f510b6d7a3 100644 --- a/src/common/menu/menudef.cpp +++ b/src/common/menu/menudef.cpp @@ -267,7 +267,7 @@ static bool CheckSkipOptionBlock(FScanner &sc) // //============================================================================= -static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &sizeset, bool &sizecompatible) +static void DoParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &sizecompatible, int insertIndex) { sc.MustGetStringName("{"); while (!sc.CheckString("}")) @@ -282,7 +282,7 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &siz if (!CheckSkipGameBlock(sc)) { // recursively parse sub-block - ParseListMenuBody(sc, desc, sizeset, sizecompatible); + DoParseListMenuBody(sc, desc, sizecompatible, insertIndex); } } else if (sc.Compare("ifnotgame")) @@ -290,7 +290,7 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &siz if (!CheckSkipGameBlock(sc, false)) { // recursively parse sub-block - ParseListMenuBody(sc, desc, sizeset, sizecompatible); + DoParseListMenuBody(sc, desc, sizecompatible, insertIndex); } } else if (sc.Compare("ifoption")) @@ -298,7 +298,7 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &siz if (!CheckSkipOptionBlock(sc)) { // recursively parse sub-block - ParseListMenuBody(sc, desc, sizeset, sizecompatible); + DoParseListMenuBody(sc, desc, sizecompatible, insertIndex); } } else if (sc.Compare("Class")) @@ -380,7 +380,7 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &siz } else if (sc.Compare("size")) { - sizeset = true; + desc->mCustomSizeSet = true; if (sc.CheckNumber()) { desc->mVirtWidth = sc.Number; @@ -417,6 +417,17 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &siz PClass *cls = PClass::FindClass(buildname); if (cls != nullptr && cls->IsDescendantOf("ListMenuItem")) { + bool inserting = insertIndex >= 0; + bool isSelectable = cls->IsDescendantOf("ListMenuItemSelectable"); + double oldYpos = desc->mYpos; + + // [Player701] If this is a selectable item and we're inserting in the middle, + // set the Y position of the descriptor to the Y of the item we're inserting before. + if (isSelectable && inserting) + { + desc->mYpos = desc->mItems[insertIndex]->mYpos; + } + auto func = dyn_cast(cls->FindSymbol("Init", true)); if (func != nullptr && !(func->Variants[0].Flags & (VARF_Protected | VARF_Private))) // skip internal classes which have a protected init method. { @@ -527,11 +538,36 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &siz DMenuItemBase *item = (DMenuItemBase*)cls->CreateNew(); params[0] = item; VMCallWithDefaults(func->Variants[0].Implementation, params, nullptr, 0); - desc->mItems.Push((DMenuItemBase*)item); - if (cls->IsDescendantOf("ListMenuItemSelectable")) + if (!inserting) { - desc->mYpos += desc->mLinespacing; + desc->mItems.Push(item); + } + else + { + // [Player701] Insert item in between + desc->mItems.Insert(insertIndex, item); + insertIndex++; + } + + if (isSelectable) + { + if (inserting) + { + // [Player701] If we've inserted a selectable item, + // shift all following selectable items downwards + // NB: index has been incremented, so we're not affecting the newly inserted item here. + for (unsigned int i = insertIndex; i < desc->mItems.Size(); i++) + { + auto item = desc->mItems[i]; + if (item->GetClass()->IsDescendantOf("ListMenuItemSelectable")) + { + desc->mItems[i]->mYpos += desc->mLinespacing; + } + } + } + + desc->mYpos = oldYpos + desc->mLinespacing; if (desc->mSelectedItem == -1) desc->mSelectedItem = desc->mItems.Size() - 1; } success = true; @@ -549,6 +585,18 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &siz } } +static void ParseListMenuBody(FScanner& sc, DListMenuDescriptor* desc, int insertIndex) +{ + bool sizecompatible = true; + DoParseListMenuBody(sc, desc, sizecompatible, insertIndex); + if (!desc->mCustomSizeSet && !sizecompatible) + { + // No custom size and incompatible items, + // so force clean scaling for this menu + desc->mVirtWidth = -1; + } +} + //============================================================================= // // @@ -696,16 +744,59 @@ static void ParseListMenu(FScanner &sc) desc->mCenter = false; desc->mFromEngine = fileSystem.GetFileContainer(sc.LumpNum) == 0; // flags menu if the definition is from the IWAD. desc->mVirtWidth = -2; + desc->mCustomSizeSet = false; - bool sizeset = false; - bool sizecompatible = true; - ParseListMenuBody(sc, desc, sizeset, sizecompatible); - if (!sizeset && sizecompatible) // allow unclean scaling on this menu + ParseListMenuBody(sc, desc, -1); + ReplaceMenu(sc, desc); +} + +//============================================================================= +// +// [Player701] Allow extending list menus +// +//============================================================================= + +static void ParseAddListMenu(FScanner& sc) +{ + sc.MustGetString(); + + DMenuDescriptor** pOld = MenuDescriptors.CheckKey(sc.String); + if (pOld == nullptr || *pOld == nullptr || !(*pOld)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) { - desc->mVirtWidth = -2; + sc.ScriptError("%s is not a list menu that can be extended", sc.String); + return; } - ReplaceMenu(sc, desc); + bool before = sc.CheckString("BEFORE"); + bool after = sc.CheckString("AFTER"); + + int insertIndex = -1; + + if (before || after) + { + // Find an existing menu item to use as insertion point + sc.MustGetString(); + + auto n = (*pOld)->mItems.Size(); + for (unsigned int i = 0; i < n; i++) + { + auto item = (*pOld)->mItems[i]; + + if (item->mAction == sc.String) + { + insertIndex = before ? i : i + 1; + break; + } + } + + // Inserting after the last item is the same as inserting at the end + if (insertIndex == n) insertIndex = -1; + + // Don't error out if we haven't found a suitable item + // to avoid backwards compatibility issues. + } + + ParseListMenuBody(sc, (DListMenuDescriptor*)(*pOld), insertIndex); } //============================================================================= @@ -1051,6 +1142,7 @@ static void ParseAddOptionMenu(FScanner &sc) if (pOld == nullptr || *pOld == nullptr || !(*pOld)->IsKindOf(RUNTIME_CLASS(DOptionMenuDescriptor))) { sc.ScriptError("%s is not an option menu that can be extended", sc.String); + return; } ParseOptionMenuBody(sc, (DOptionMenuDescriptor*)(*pOld)); } @@ -1300,12 +1392,16 @@ void M_ParseMenuDefs() else if (sc.Compare("DEFAULTLISTMENU")) { bool s = false; - ParseListMenuBody(sc, DefaultListMenuSettings, s, s); + DoParseListMenuBody(sc, DefaultListMenuSettings, s, -1); if (DefaultListMenuSettings->mItems.Size() > 0) { I_FatalError("You cannot add menu items to the menu default settings."); } } + else if (sc.Compare("ADDLISTMENU")) + { + ParseAddListMenu(sc); + } else if (sc.Compare("OPTIONVALUE")) { ParseOptionValue(sc);