From 07b3e2789e31962adc1775f0e11c2b83a8780121 Mon Sep 17 00:00:00 2001 From: JeffR Date: Mon, 9 May 2022 16:17:22 -0500 Subject: [PATCH] Adds proper documentation and explains some of the navigation/menu usage behavior via the BaseUI example menus --- .../game/data/UI/guis/MainMenuButtons.tscript | 51 ++++++++- .../game/data/UI/guis/chooseLevelDlg.tscript | 2 - .../game/data/UI/guis/mainMenu.tscript | 29 ++++- .../game/data/UI/guis/optionsMenu.tscript | 12 ++ .../data/UI/scripts/menuInputHandling.tscript | 108 +++++++++++++++++- .../data/UI/scripts/menuNavigation.tscript | 63 ++++++++++ 6 files changed, 260 insertions(+), 5 deletions(-) diff --git a/Templates/BaseGame/game/data/UI/guis/MainMenuButtons.tscript b/Templates/BaseGame/game/data/UI/guis/MainMenuButtons.tscript index 2dd07c18a..73f38e4f6 100644 --- a/Templates/BaseGame/game/data/UI/guis/MainMenuButtons.tscript +++ b/Templates/BaseGame/game/data/UI/guis/MainMenuButtons.tscript @@ -6,19 +6,52 @@ function MainMenuButtons::onSleep(%this) { } -//Optional, as the check defaults to true, but here as an example case +//============================================================================== +// This gets called by the MainMenuGUI beacuse it, being a UINavigation classed control +// set MainMenuButtonList as it's root page. +// This is an optional function, but is called as part of the validation that the page +// CAN be opened, so it's shown here as an example function MainMenuButtonList::canOpen(%this) { return true; } +//============================================================================== +// This gets called by the MainMenuGUI beacuse it, being a UINavigation classed control +// set MainMenuButtonList as it's root page. +// Once the page is added to the MainMenuGUI's UINavigation page stack, onOpen here is called +// Which allows us to actually do the work we need to do for display function MainMenuButtonList::onOpen(%this) { + //Here, we set the MainMenuButtonList - a GuiStackControl with the MenuList class + // to be the active menu list. + // This means that when the MainMenuGUI's MainMenuInputHandler receives an input + // or we have one of the buttons in the MainMenuButtonHolder be actioned, we know + // we're working/navigating this list of buttons specifically + // In practice, it sets the $activeMenuList global variable so the various menu class code + // can reference it consistently MainMenuButtonList.setAsActiveMenuList(); + //Because MainMenuGUI set it's MainMenuButtonHolder as the active button container, we can reference it + //by $activeMenuButtonContainer and set the menu button/accelerator prompts we need for the MainMenuButtonList + //specifically. + //In particular, being a simple list of buttons, the only one we NEED is a simple activate, so we'll + //disable all the other ones to keep them clear in case they were set by other pages at some point $activeMenuButtonContainer-->button1.disable(); $activeMenuButtonContainer-->button2.disable(); $activeMenuButtonContainer-->button3.disable(); + + //Here we set the 4th button in the container + //All the buttons have the MenuInputButton class, which, like the other classes + //help keep things accessible and consistent. Here we use that class's set function to + //configure the accelerator behavior + //The first parameter sets the gamepad button to the 'A' button + //The second sets the keyboard button to Enter or Return + //Third is what the displayed text will be + //And finally we set the command when the button is clicked, or either key inputs are caught by + //our MenuInputHandler + //The menu buttons automatically detect which input device you're using and swap the display between + //gamepad or keyboard for consistent behavior $activeMenuButtonContainer-->button4.set("btn_a", "Return", "Go", "MainMenuButtonList.activate();"); $activeMenuButtonContainer-->button5.disable(); } @@ -34,6 +67,7 @@ function MainMenuButtonList::onClose(%this) { } +//Our actual commands when we activate the buttons function openSinglePlayerMenu() { $pref::HostMultiPlayer=false; @@ -43,16 +77,31 @@ function openSinglePlayerMenu() function openMultiPlayerMenu() { $pref::HostMultiPlayer=true; + + //Here, like the other commands, we add a new page onto the stack + //In this case, we'll push the ChooseLevelDlg control onto the stack. This will + //invoke the canClose() and then onClose() functions for MainMenuButtonList + //before calling the onOpen() for ChooseLevelDlg then displaying. MainMenuGui.pushPage(ChooseLevelDlg); } function openJoinServerMenu() { + //Here, like the other commands, we add a new page onto the stack + //In this case, we'll push the JoinServerMenu control onto the stack. This will + //invoke the canClose() and then onClose() functions for MainMenuButtonList + //before calling the onOpen() for JoinServerMenu then displaying. MainMenuGui.pushPage(JoinServerMenu); } function openOptionsMenu() { + //Here, like the other commands, we add a new page onto the stack + //In this case, we'll push the OptionsMenu control onto the stack. This will + //invoke the canClose() and then onClose() functions for MainMenuButtonList + //before calling the onOpen() for OptionsMenu then displaying. + //The options menu additionally has an example of why we may want to capitalize on the + //canClose() call. MainMenuGui.pushPage(OptionsMenu); } diff --git a/Templates/BaseGame/game/data/UI/guis/chooseLevelDlg.tscript b/Templates/BaseGame/game/data/UI/guis/chooseLevelDlg.tscript index d15887f72..11dcec6de 100644 --- a/Templates/BaseGame/game/data/UI/guis/chooseLevelDlg.tscript +++ b/Templates/BaseGame/game/data/UI/guis/chooseLevelDlg.tscript @@ -102,8 +102,6 @@ function ChooseLevelDlg::onOpen(%this) else LevelSelectTitle.setText("CREATE SERVER"); - ChooseLevelButtonHolder.setActive(); - $activeMenuButtonContainer-->button1.disable(); $activeMenuButtonContainer-->button2.disable(); $activeMenuButtonContainer-->button3.disable(); diff --git a/Templates/BaseGame/game/data/UI/guis/mainMenu.tscript b/Templates/BaseGame/game/data/UI/guis/mainMenu.tscript index 0fc08009e..bae675ea3 100644 --- a/Templates/BaseGame/game/data/UI/guis/mainMenu.tscript +++ b/Templates/BaseGame/game/data/UI/guis/mainMenu.tscript @@ -5,17 +5,44 @@ function MainMenuGui::onAdd(%this) function MainMenuGui::onWake(%this) { + //In the BaseUI example case, the MainMenuGUI acts as our background + //So it's a logical control to set as our UINavigation as well. So we + //set the MainMenuGUI's superClass to UINavigation to integrate it into + //that namespace to open up page navigation + + //At the same time, the MainMenuGUI control has the button holder, set to + //the MenuInputButtonContainer class, allowing us to set it as the active button + //holder here, prepping it for catching any button inputs to active commands + //Specifically, it sets the $activeMenuButtonContainer to be this, which allows + //other controls to manage what the behavior of the buttons is consistently + //without needing to worry about hardcoded names MainMenuButtonHolder.setActive(); + + //We also have the MainMenuInputHandler, a GuiInputCtrl with the MenuInputHandler class + //This allows us to catch any input/axis event and pass it along to the active menuList + //or button containers to navigate the menus + //We set up this catch by making said control our first responder, here MainMenuInputHandler.setFirstResponder(); + //Lastly, we go ahead and display some actual navigable content up on our main menu here + //In this case, we set the MainMenuButtonList as our root page, so we always come back + //to having the main menu buttons on screen if every other page is closed. + //This will ultimately call MainMenuButtonList::onOpen(), so to see where the navigation + //chain continues, see that function. %this.setRootPage(MainMenuButtonList); + //We also go ahead and mark for any future pages being added to the UINavigation's page stack + //to be prompted to resize when added. This isn't required, but helps keep pages formated to + //the current size of the UINavigation, which is useful when dealing with aspect ratio or resolution + //changes. %this.resizePages = true; } function MainMenuButtonHolder::onWake(%this) { - //clean slate + //Because the blan slate MainMenuGUI doesn't have anything we need to bother with inputs on + //we just go ahead and disable all the buttons in our MainMenuButtonHolder to have + // a clean slate %this-->button1.disable(); %this-->button2.disable(); %this-->button3.disable(); diff --git a/Templates/BaseGame/game/data/UI/guis/optionsMenu.tscript b/Templates/BaseGame/game/data/UI/guis/optionsMenu.tscript index 5d04d4588..e6e314abd 100644 --- a/Templates/BaseGame/game/data/UI/guis/optionsMenu.tscript +++ b/Templates/BaseGame/game/data/UI/guis/optionsMenu.tscript @@ -116,8 +116,17 @@ function OptionsMenu::onOpen(%this) $activeMenuButtonContainer-->button5.set("btn_b", "Escape", "Back", %this @ ".navigation.popPage();"); } +//We capitalize on the canClose test here, because we want to prompt for unapplied options changes before +//backing out. So when the UINavigation test canClose, we can see if we have unapplied settings and prompt +//that via the message box and return false. +//This gives the user a chance to choose how they wish to proceed before we allow the +//UINavigation to move away from the options menu function OptionsMenu::canClose(%this) { + //Another special case is us catching the 'back/pop' action by just shifting from one + //menu list to another. In this case, we check if we were on the settings list as our active MenuList + //if so, then the back/pop just moves us to the Category list as our active and we inform the + //UINavigation to not close the page if(OptionsMenuSettingsList.isActiveMenuList()) { OptionsMenuCategoryList.setAsActiveMenuList(); @@ -126,6 +135,9 @@ function OptionsMenu::canClose(%this) } else { + //Here, we're on the category list as our active, so we're actually trying to leae the page + //If we have unapplied changes, we want to prompt about them before closing the page and navigating away + //If we don't, then we can process the popPage as normal and let the OptionsMenu close if(%this.unappliedChanges.count() != 0) { MessageBoxOKCancel("Discard Changes?", "You have unapplied changes to your settings, do you wish to apply or discard them?", diff --git a/Templates/BaseGame/game/data/UI/scripts/menuInputHandling.tscript b/Templates/BaseGame/game/data/UI/scripts/menuInputHandling.tscript index 78cefa95c..36f96568f 100644 --- a/Templates/BaseGame/game/data/UI/scripts/menuInputHandling.tscript +++ b/Templates/BaseGame/game/data/UI/scripts/menuInputHandling.tscript @@ -26,18 +26,30 @@ btn_start = Start btn_back = Back/Select */ +//============================================================================== +/// Summary: /// This is used with the main UI menu lists, when a non-axis input event is called /// such as pressing a button /// It is called from the engine +/// +/// \param %device (string) The name of the device the input event is coming fromt +/// \param %action (string) The specific key/input action +/// \param %state (bool) The down/up state of the event sent function UIMenuButtonList::onInputEvent(%this, %device, %action, %state) { if(%state) $activeMenuButtonContainer.processInputs(%device, %action); } +//============================================================================== +/// Summary: /// This is used with the main UI menu lists, when an axis input event is called /// such as moving a joystick /// It is called from the engine +/// +/// \param %device (string) The name of the device the input event is coming fromt +/// \param %action (string) The specific key/input action +/// \param %axisVal (float) The float value of the axis event function UIMenuButtonList::onAxisEvent(%this, %device, %action, %axisVal) { //Skip out of the value is too low as it could just be noise or miscalibrated defaults @@ -47,6 +59,8 @@ function UIMenuButtonList::onAxisEvent(%this, %device, %action, %axisVal) $activeMenuButtonContainer.processAxisEvent(%device, %action); } +//============================================================================== +/// Summary: /// Sets the command and text for the specified button. If %text and %command /// are left empty, the button will be disabled and hidden. /// @@ -83,6 +97,9 @@ function MenuInputButton::set(%this, %gamepadButton, %keyboardButton, %text, %co %this.refresh(); } +//============================================================================== +/// Summary: +/// Disables the MenuInputButton, marking it as not to consume inputs or display function MenuInputButton::disable(%this) { %this.setText(""); @@ -91,6 +108,8 @@ function MenuInputButton::disable(%this) %this.setVisible(false); } +//============================================================================== +/// Summary: /// Refreshes the specific button, updating it's visbility status and the displayed input image function MenuInputButton::refresh(%this) { @@ -203,6 +222,8 @@ function MenuInputButton::refresh(%this) return true; } +//============================================================================== +/// Summary: /// Refreshes a menu input container, updating the buttons inside it function MenuInputButtonContainer::refresh(%this) { @@ -215,6 +236,8 @@ function MenuInputButtonContainer::refresh(%this) } } +//============================================================================== +/// Summary: /// Sets the given MenuInputButtonContainer as the active one. This directs input events /// to it's buttons, ensures it's visible, and auto-hides the old active container if it was set function MenuInputButtonContainer::setActive(%this) @@ -227,6 +250,8 @@ function MenuInputButtonContainer::setActive(%this) $activeMenuButtonContainer.refresh(); } +//============================================================================== +/// Summary: /// Checks the input manager for if we have a gamepad active and gets it's name /// If we have one, also sets the active input type to gamepad function MenuInputButtonContainer::checkGamepad(%this) @@ -241,12 +266,17 @@ function MenuInputButtonContainer::checkGamepad(%this) $activeControllerType = "gamepad"; } + //============================================================================== +/// Summary: /// This is called by the earlier inputs callback that comes from the menu list /// this allows us to first check what the input type is, and if the device is different /// (such as going from keyboard and mouse to gamepad) we can refresh the buttons to update /// the display /// Then we process the input to see if it matches to any of the button maps for our /// MenuInputButtons. If we have a match, we execute it's command. +/// +/// \param %device (string) The device that is causing the input event +/// \param %action (string) The name of the input action function MenuInputButtonContainer::processInputs(%this, %device, %action) { //check to see if our status has changed @@ -305,10 +335,16 @@ function MenuInputButtonContainer::processInputs(%this, %device, %action) } } +//============================================================================== +/// Summary: /// This is called by the earlier inputs callback that comes from the menu list /// this allows us to first check what the input type is, and if the device is different /// (such as going from keyboard and mouse to gamepad) we can refresh the buttons to update /// the display +/// +/// \param %device (string) The name of the device the input event is coming fromt +/// \param %action (string) The specific key/input action +/// \param %axisVal (float) The float value of the axis event function MenuInputButtonContainer::processAxisEvent(%this, %device, %action, %axisVal) { //check to see if our status has changed @@ -367,6 +403,15 @@ function onSDLDeviceDisconnected(%sdlIndex) // This'll let the active menu list be navigated, as well as buttons be processed // and ultimately handled by the Input Buttons above //============================================================================== +//============================================================================== +/// Summary: +/// This is used with the main UI menu lists, when an axis input event is called +/// such as moving a joystick +/// It is called from the engine +/// +/// \param %device (string) The name of the device the input event is coming fromt +/// \param %action (string) The specific key/input action +/// \param %axisVal (float) The float value of the axis event function MenuInputHandler::onAxisEvent(%this, %device, %action, %value) { //this is to force a refresh of the menu @@ -416,6 +461,15 @@ function MenuInputHandler::onAxisEvent(%this, %device, %action, %value) } } +//============================================================================== +/// Summary: +/// This is used with the main UI menu lists, when a non-axis input event is called +/// such as pressing a button +/// It is called from the engine +/// +/// \param %device (string) The name of the device the input event is coming fromt +/// \param %action (string) The specific key/input action +/// \param %state (bool) The down/up state of the event sent function MenuInputHandler::onInputEvent(%this, %device, %action, %state) { if(%action $= "upov" || %action $= "dpov" || %action $= "lpov" || %action $= "rpov") @@ -432,6 +486,10 @@ function MenuInputHandler::onInputEvent(%this, %device, %action, %state) // Menu List processing // These functions manage the navigation and activation of the Menu Lists //============================================================================== +//============================================================================== +/// Summary: +/// Is the GUIContainer with this MenuList namespace the 'active' menulist as far +/// as UI interfaces is concerned? function MenuList::isActiveMenuList(%this) { if($activeMenuList == %this) @@ -440,6 +498,14 @@ function MenuList::isActiveMenuList(%this) return false; } +//============================================================================== +/// Summary: +/// Sets the GUIContainer with this MenuList namespace as the active menulist. +/// This means that any input events caught in MenuInputHandlers is directed at +/// this menu list to navigate it +/// +/// \param %startPosition (Point2F) The X and Y starting positions of the selection for this menuList +/// \param %menuMode (string) Indicates the mode/type of menuList, allowing for special behaviors depending on type function MenuList::setAsActiveMenuList(%this, %startPosition, %menuMode) { if(%startPosition $= "") @@ -456,7 +522,9 @@ function MenuList::setAsActiveMenuList(%this, %startPosition, %menuMode) %this.refresh(); } - +//============================================================================== +/// Summary: +/// Activates the currently highlighted child object function MenuList::activate(%this) { //check for a highlighted element @@ -467,6 +535,11 @@ function MenuList::activate(%this) } } +//============================================================================== +/// Summary: +/// refreshes the menuList, updating children highlight status and if there is +/// a button pointer control defined on our list, we update it's position as +/// needed function MenuList::refresh(%this) { %selectedObject = -1; @@ -516,6 +589,10 @@ function MenuList::refresh(%this) } } +//============================================================================== +/// Summary: +/// Selects the next 'up' child item in the menuList. If the current is the topmost +/// then nothing happens function MenuList::navigateUp(%this) { $activeMenuList.ListPosition.y -= 1; @@ -525,6 +602,10 @@ function MenuList::navigateUp(%this) %this.refresh(); } +//============================================================================== +/// Summary: +/// Selects the next 'down' child item in the menuList. If the current is the bottommost +/// then nothing happens function MenuList::navigateDown(%this) { $activeMenuList.ListPosition.y += 1; @@ -534,6 +615,10 @@ function MenuList::navigateDown(%this) %this.refresh(); } +//============================================================================== +/// Summary: +/// Selects the next 'left' child item in the menuList. If the current item is the leftmost +/// then nothing happens function MenuList::navigateLeft() { //Atm, we're only handling specific control types, namely options entries, but @@ -564,6 +649,10 @@ function MenuList::navigateLeft() } } +//============================================================================== +/// Summary: +/// Selects the next 'right' child item in the menuList. If the current item is the rightmost +/// then nothing happens function MenuList::navigateRight() { %btn = $activeMenuList.getObject($activeMenuList.ListPosition.y); @@ -590,13 +679,24 @@ function MenuList::navigateRight() } } +//============================================================================== +/// Summary: +/// Gets the current vertical positionally selected child object function MenuList::getActiveRow(%this) { return $activeMenuList.ListPosition.y; } +//============================================================================== +/// Summary: +/// Called from the engine when a GUIButtonBase-derived class with MenuListButton namespace class +/// has its highlighting status changed. Allows us to react to this change of state and trigger refreshse +/// or other events to keep the navigation tracking up to date +/// +/// \param %state (bool) The on/off state of the button being highlighted function MenuListButton::onHighlighted(%this, %state) { + echo("MenuListButton::onHighlighted() - " @ %this.internalName @ " was " @ %state @ " highlighted"); %parentContainer = %this.getParent(); if(%parentContainer.class $= "MenuList" || %parentContainer.superClass $= "MenuList") { @@ -607,6 +707,7 @@ function MenuListButton::onHighlighted(%this, %state) %parentContainer.buttonPointerCtrl.setHidden(false); %buttonCenter = %this.getGlobalCenter(); + echo(" - button center:" @ %buttonCenter); if(%parentContainer.centerButtonPointerCtrl) { @@ -617,7 +718,12 @@ function MenuListButton::onHighlighted(%this, %state) //if we're not centering, then left-justify %parentContainer.buttonPointerCtrl.setGlobalCenter(%buttonCenter.x - %this.extent.x / 2, %buttonCenter.y); } + echo(" - pointer position:" @ %parentContainer.buttonPointerCtrl.getPosition()); } + /*else + { + %parentContainer.buttonPointerCtrl.setHidden(true); + }*/ } } } \ No newline at end of file diff --git a/Templates/BaseGame/game/data/UI/scripts/menuNavigation.tscript b/Templates/BaseGame/game/data/UI/scripts/menuNavigation.tscript index d10e5847d..80eeb33a6 100644 --- a/Templates/BaseGame/game/data/UI/scripts/menuNavigation.tscript +++ b/Templates/BaseGame/game/data/UI/scripts/menuNavigation.tscript @@ -1,3 +1,12 @@ +//============================================================================== +/// Summary: +/// This function sets the root page for the navigation stack. The root page is 'below' +/// all other normal pages and cannot be popped like a normal page. +/// When we set a new root page, we first check if we have an existing root page. +/// If we do, we run the usual canClose() then onClose() function pair, then we +/// set it and call the onOpen() for the new root page. +/// +/// \param %rootPage (guiControl) The new guiControl that is being set as the root page of the navigation stack function UINavigation::setRootPage(%this, %rootPage) { if(!isObject(%this.pageStack)) @@ -36,6 +45,25 @@ function UINavigation::setRootPage(%this, %rootPage) %rootPage.call("onOpen"); } +//============================================================================== +/// Summary: +/// This function pushes a page onto the given UINavigation-classed GUIContainer's stack +/// The order of operations is thus: +/// 1) check to see if the new page being pushed says it can open via the canOpen() function. +/// If this method is not defined, it defaults to true, as there's no impediment to continuing +/// If this check returns false, the pushPage event cancels. +/// 2) check to see if the current page on the stack says it can close. Similar to +/// the canOpen() check on the new page, we default to true +/// If this check returns false, the pushPage event cancels. +/// 3) Call - if defined - onClose() on the current top page of the stack +/// 4) Add the new page onto the stack +/// 5) Call - if defined - onOpen() on the new page +/// 6) Finally, if we defined a callback, call that. +/// With this all run, the previous top page has done any cleanup work it needed to +/// and the new top page has been opened successfully. +/// +/// \param %newPage (guiControl) The new guiControl that is being added onto the page stack +/// \param %callback[optional]: (Evaluable string) A evalable statement to invoke when the push has been completed function UINavigation::pushPage(%this, %newPage, %callback) { if(!isObject(%this.pageStack)) @@ -80,6 +108,24 @@ function UINavigation::pushPage(%this, %newPage, %callback) eval(%callback); } +//============================================================================== +/// Summary: +/// This function pops the topmost page off the given UINavigation-classed GUIContainer's stack +/// The order of operations is thus: +/// 1) check to see if the top page being popped says it can close via the canClose() function. +/// If this method is not defined, it defaults to true, as there's no impediment to continuing +/// If this check returns false, the popPage event cancels. +/// 2) check to see if the previous page on the stack says it can open. Similar to +/// the canClose() check on the new page, we default to true +/// If this check returns false, the popPage event cancels. +/// 3) Call - if defined - onClose() on the current top page of the stack +/// 4) Remove the top page +/// 5) Call - if defined - onOpen() on the now topmost page +/// 6) Finally, if we defined a callback, call that. +/// With this all run, the previous top page has done any cleanup work it needed to +/// and the new top page has been opened successfully. +/// +/// \param %callback[optional] (Evaluable string) A evalable statement to invoke when the pop has been completed function UINavigation::popPage(%this, %callback) { if(%this.pageStack.count() == 0) @@ -128,6 +174,12 @@ function UINavigation::popPage(%this, %callback) eval(%callback); } +//============================================================================== +/// Summary: +/// In order tops the topmost page in a loop until it has closed the entire stack, +/// leaving only the root page +/// +/// \param %callback[optional] (Evaluable String) A evalable statement to invoke when the pop has been completed function UINavigation::popToRoot(%this, %callback) { %pageChanged = false; @@ -177,6 +229,10 @@ function UINavigation::popToRoot(%this, %callback) eval(%callback); } +//============================================================================== +/// Summary: +/// Gets the current, topmost page on the stack. If no non-root pages are on the stack +/// the root page is returned function UINavigation::getCurrentPage(%this) { if(isObject(%this.pageStack) && %this.pageStack.count() != 0) @@ -192,6 +248,10 @@ function UINavigation::getCurrentPage(%this) return 0; } +//============================================================================== +/// Summary: +/// Gets the page just under the topmost page in the stack. If there is no previous page +/// then the root page is returned function UINavigation::getPreviousPage(%this) { if(isObject(%this.pageStack) && %this.pageStack.count() > 1) @@ -207,6 +267,9 @@ function UINavigation::getPreviousPage(%this) return 0; } +//============================================================================== +/// Summary: +/// Gets the number of pages on the stack. function UINavigation::getPageCount(%this) { %count = 0;