Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nav Branch: How to programmatically force nav highlight on item? #1074

Closed
uglycoyote opened this issue Mar 21, 2017 · 8 comments
Closed

Nav Branch: How to programmatically force nav highlight on item? #1074

uglycoyote opened this issue Mar 21, 2017 · 8 comments
Labels
focus nav keyboard/gamepad navigation

Comments

@uglycoyote
Copy link

Hi, I'm playing around with the Nav Branch (issue #787). I'm trying to get something working which is a gamepad driven menu. I am trying to set something up which is basically an interface choosing at item from within a folder structure, but rather than using a tree or a ImGui dropdown menu to explore the folder, I'm making more of a stateful kind of interface which displays buttons for the currently selected level of the tree. It is akin to something like windows Explorer, where the explorer window shows only the contents of the current directory, including subfolder icons and leaf-item icons.

When this folder is active it will be the only ImGui thing on the screen, and I want this to be driven by gamepad, and at any time I always want to have one of the menu items (buttons) highlighted in blue (the "nav highlight"). I think it's confusing to a pad-only user when there is no nav-highlight shown at all.

In order to force the nav-highlight to show immediately when the top-level folder appears, I have added a call to ImGui::SetItemDefaultFocus(); for the first element in the folder. This wasn't quite enough, I also needed to explicitly do GImGui->NavDisableHighlight = false; to get the blue highlight to show up.

This works for the top-level folder, but when I enter a sub-folder nothing is immediately highlighted with the blue rectangle (although it does highlight once you start pressing up and down buttons). If I want something to be highlighted as soon as you enter the sub-folder (like the first item), it's not clear how to explicitly programmatically highlight something. I would have hoped that since the previously highlighted button ceases to exist that the the calls to SetItemDefaultFocus() would again force a new menu item button to be highlighted and that at any time something would always be highlighted. But that doesn't seem to be the case.

Furthermore, upon pressing the pad direction within the sub-folder for the first time, the nav highlight often appears in the middle of the menu, implying that it is remembering its position within the previous menu and starting at the same "button index" (it's unclear how this is actually working under the hood). I would like to be able to reset it to the top of the folder each time you enter/leave (or perhaps remember the position of the nav highlight if you leave a folder and come back again)

How can I get it so that when I'm adding and removing buttons like this there's always a nav highlight around some button that's currently being shown? How can i force the nav highlight to jump to specific button?

Here's my source code for a test case

(note, when adding this to one of the demo apps, it was necessary to comment out the code for all other ImGui windows in order for the nav highlighting to work in this window. The SetItemDefaultFocus calls seemed to be getting ignored when my test window was not the only window in the application)

Please let me know if there's a much cleaner way of doing this. It seems like I'm working against the ImGui paradigm here a bit by maintaining menu state in my application like this. Implementing this as an ImGui tree would be much cleaner for instance, but it is not the user experience I want. Perhaps there's a way to "re-skin" the trees so that the user experience is similar to this "windows explorer" style of exploration?

--thanks


#include "imgui.h"
#include <list>
#include <iostream>
#include <sstream>
#include "imgui_internal.h"

// Very simple class representing a menu item; either a folder or a leaf-level item.
class MenuItem
{
public:
	MenuItem(std::string text)
	{
		this->text = text;
		parent = NULL;
	}

	void AddChild(MenuItem* pChild)
	{
		children.push_back( pChild );
		pChild->parent = this;
	}

	MenuItem* parent;
	std::list<MenuItem*> children;
	std::string text;
};

// Root-level menu
static MenuItem* mainMenu = NULL;

// Make a bunch of menus and menu items.
void MBCInitializeMenu()
{
	mainMenu = new MenuItem("Main Menu");

	int folderIndex=0;
	for (folderIndex =0; folderIndex<4; folderIndex ++)
	{
		std::string folderName = "folder ";
		std::ostringstream oss;
		oss << folderIndex;
		folderName += oss.str();
		MenuItem* folderMenuItem = new MenuItem(folderName);
		mainMenu->AddChild( folderMenuItem );

		for (int subItemIndex =0; subItemIndex<10; subItemIndex ++)
		{
			std::string subItemName = folderName + " subItem ";
			std::ostringstream oss;
			oss << subItemIndex;
			subItemName += oss.str();
			MenuItem* subItem = new MenuItem(subItemName);
			folderMenuItem->AddChild( subItem );
		}
	}

}

// This pointer gets updated when you enter different menus
static MenuItem* pCurrentMenu = NULL;

// Menu items are displayed simply as buttons.  If the menu item has children it is treated as a folder
//  and pressing it willl change the "current menu" pointer.  
void DisplayMenuItem(MenuItem* menuItem)
{
	bool pressed = ImGui::Button(menuItem->text.c_str());
    if (ImGui::BeginPopupModal(menuItem->text.c_str(), NULL, ImGuiWindowFlags_AlwaysAutoResize))
    {
        ImGui::Text("You pressed %s.\n\n", menuItem->text.c_str());
        if (ImGui::Button("OK", ImVec2(120,0))) { ImGui::CloseCurrentPopup(); }
        ImGui::EndPopup();

    }
	if ( pressed )
	{
		if ( menuItem->children.size() == 0 )
		{
            ImGui::OpenPopup(menuItem->text.c_str());
		}
		else
		{
			pCurrentMenu = menuItem;	
		}
	}
}

// Display all menu items within the current menu.
void DisplayMenu(MenuItem* menuItem)
{
	ImGui::Text( "Current Menu is %s", menuItem->text.c_str()  );

	bool first = true;

	if ( menuItem->parent )
	{
		bool pressed = ImGui::Button( "Up to parent" );
		if ( first )
		{
			ImGui::SetItemDefaultFocus();
			first = false;
		}
		if ( pressed )
		{
			pCurrentMenu = menuItem->parent;
		}
	}

	for ( MenuItem* child : menuItem->children )
	{
		DisplayMenuItem(child);
		if ( first )
		{
			ImGui::SetItemDefaultFocus();
			first = false;
		}
	}
}

void MBCTest()
{
	if ( mainMenu == NULL )
	{
		MBCInitializeMenu();
		pCurrentMenu = mainMenu;
	}

	bool showMBCTest;
    ImGui::SetNextWindowSize(ImVec2(200,100), ImGuiSetCond_FirstUseEver);
    ImGui::Begin("MBCTest", &showMBCTest);

	DisplayMenu(pCurrentMenu);

	// awkward, but needed to make an item be highlighted off the start
	GImGui->NavDisableHighlight = false;


	ImGui::End();
}
@ocornut
Copy link
Owner

ocornut commented Mar 21, 2017

Thanks for posting this. This type of request is an example why the NavBranch hasn't been merged yet.

The issue seems to lies on the fact that you are repopulating the same window with entirely new widgets, and you expect the behavior of a newly open window to happen (select first widget, etc.). So maybe we would need a way to express this with the new API.

You can probably work it around with SetKeyboardFocusHere() ?
(It's an old call that may end up being renamed/changed with the new branch hence the big FIXME next to its declaration in that branch, but it should work and be useful).

@uglycoyote
Copy link
Author

Thanks for your reply! SetKeyboardFocusHere sounds like it forcibly changes the focus, which is what I said I want, but the next question is how to test when it needs to be done. If something already has focus I don't want to accidentally take focus away.

IsAnyItemFocused() seemed like the thing that I would want to test, but I could not get this to work.

In my DisplayMenu function listed above I tried adding the following code in the places where I was doing SetItemDefaultFocus():

if ( !ImGui::IsAnyItemFocused() )
{
	ImGui::SetKeyboardFocusHere(  );
}

But this had no effect. Putting a breakpoint on it, it was never hitting the SetKeyboardFocusHere() except on application startup (a case that was already working correctly with just using SetItemDefaultFocus). Going from one folder to another it was not calling SetKeyboardFocusHere() meaning that even though it was not highlighting any item with the blue rectangle it still thought there was IsAnyItemFocused.

I also tried forcibly setting GImGui->NavId=0; upon clicking on a folder-button to work around this. This forced it to call SetKeyboardFocusHere on a new button upon entering a folder, however this still did not result in the blue rectangle being visible in the new menu item. (even with adding another call to GImGui->NavDisableHighlight = false; after resetting NavId to zero)

@uglycoyote
Copy link
Author

uglycoyote commented Mar 21, 2017

Update... rather than using SetKeyboardFocusHere I found that the following works:

if ( !ImGui::IsAnyItemFocused() )
{
	GImGui->NavId = GImGui->CurrentWindow->DC.LastItemId;
}

along with explicitly setting GImGui->NavId = 0 when entering a new folder.

Perhaps these operations should be exposed as functions, such as

ImGui::ClearNavFocus() // set NavId to zero
ImGui::SetNavFocusHere() // set NavId to LastItemId

but since there is already a "SetKeyboardFocusHere", it's not clear to me how the concepts of "keyboard focus" vs "nav focus" should interplay. It's just seems right now that SetKeyboardFocusHere doesn't currently affect NavId.

@ocornut
Copy link
Owner

ocornut commented Mar 23, 2017

Err, why closing? Isn't that a totally open question that imgui would want to solve?

@uglycoyote uglycoyote reopened this Mar 23, 2017
@uglycoyote
Copy link
Author

Yes, sorry I just didn't want to burden you when I had found a workable solution. Though my solution is not ideal in that ImGui isn't exposing an obvious way to do what I want and I'm having to delve in to the guts of things to make it work. I'll keep it open for you then :)

@ocornut
Copy link
Owner

ocornut commented Mar 23, 2017

I'm happy with keeping it open as a reminder that this is a legit (and maybe common) request that I would like to solve eventually :). Thanks!

@ocornut
Copy link
Owner

ocornut commented Oct 14, 2024

For the record, linking to #2048 as they haven't been linked and are essentially the same thing.

ocornut added a commit that referenced this issue Oct 18, 2024
ocornut added a commit that referenced this issue Oct 18, 2024
…(), ImGuiNavHighlightFlags to ImGuiNavRenderCursorFlags. (#1074, #2048, #7237, #8059, #1712, #7370, #787)

+ referenced in #8057, #3882, #3411, #2155, #3351, #4722, #1658, #4050.
ocornut added a commit that referenced this issue Oct 18, 2024
+ Further internal renaming for consistency.
ocornut added a commit that referenced this issue Oct 18, 2024
…ways. (#1074, #2048, #7237, #8059, #3200, #787)

Note: the NavCursorHideFrames addition is to support 88a3545 even though ConfigNavCursorVisibleAlways is set.
@ocornut
Copy link
Owner

ocornut commented Oct 18, 2024

I have added a few things related to those requests:

  • Nav: added io.ConfigNavCursorVisibleAuto and io.ConfigNavCursorVisibleAlways config options (ab9ce2a).
  • Nav: added SetNavCursorVisible() to override current state (634a7ed).

Other tangentially related options:

  • Nav: added io.ConfigNavEscapeClearFocusItem (7a56b41).
  • Nav: added io.ConfigNavEscapeClearFocusWindow (b001038).
  • And a couple of related fixes for all the above.

(
Also note, if you have mods using internals, that those two fields have been renamed internally:

  • Terminology has been changed to call this the "nav cursor" more consistently.
  • bool NavDisableHighlight -> became bool NavCursorVisible (with opposite value).
  • bool NavDisableMouseHover -> became bool NavDisableMouseHover
    )

Technically this # also pertain to use SetKeyboardFocusHere() and other functions which are still be redesigned/improved.
My expectation is that the future replacement SetKeyboardFocusHere() will have options to configure how nav cursor visibility is made visible or not.

I'm closing this and other related issues, but feel free to comment or open new issues if you have question or problem related to those.

@ocornut ocornut closed this as completed Oct 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
focus nav keyboard/gamepad navigation
Projects
None yet
Development

No branches or pull requests

2 participants