Description
EDIT: The navigation branch has been merged to master. See #1599. Further improvements/fixes will be pushed to mainline.
Opening a new thread because the old one ( #323 ) became overwhelmingly long and unwelcoming. This is a trimmed post with info that are still relevant.
TL;DR; there is a new branch that add supports for gamepad/joystick navigation. The same system can be used for keyboard navigation to some degree, but the initial focus is on gamepad. Typically you can use that to access your tools on PS4/XBone/Wii-U/etc without a synergy/mouse/keyboard setup. Best if you can still have even a virtual mouse around (e.g. on DualShock4 touch pad).
EDIT March 2018
Link to PNG + PSD depicting the controls for DualShock 4 and Joy-Con
https://drive.google.com/open?id=1k4328OV-w20pWZfNcfpH0UoxHHQRkbQi
EDIT: Now in master!
Branch: https://github.com/ocornut/imgui/tree/navigation
(checkout branch navigation
or download from web and overwrite your imgui_xxx files)
I'm calling it beta because:
- This feature required changing lots of code. The branch probably has bugs.
- It is rough and work in progress. The more I add and fix things the more I see new things to do. My initial schedule projection was a joke. Long tail feature.
- But it is pretty useful already!
- I would ideally like to merge this in master but I can only do so with more testing and feedback.
Even if you don't need gamepad navigation, using this branch without wiring the inputs would be useful testing.
What I would like from users:
- See how it fits in your real-world app and what we need to fix/add.
- Any bug report, questions, features request, welcome. Please be critical!
The development of this feature has been partly sponsored by Insomniac Games (thank you!).
My current mapping for DualShock4
D-Pad up/down/left/right: navigate, tweak values
Cross button: press button, hold to tweak/activate widget, enter child, etc.
Circle button: close popup, exit child, clear selection, etc.
Square button(TAP): access menu, collapsing, window options, etc.
Square button(HOLD)+Dpad: resize window
Square button(HOLD)+Analog: move window
Square button(HOLD)+L/R trigger changes window focus, ALT-TAB style
Triangle button: text input (requires user back-end reading back io.WantTextInput, possibly display an OS keyboard display).
L/R Trigger: slow down/speed up tweaking values
Analog stick: manual scroll.
Quick instructions
// Fill ImGuiIO.NavInputs[] float array every frame to feed gamepad/keyboard navigation inputs.
// 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks.
// ImGui uses a simple >0.0f for activation testing, and won't attempt to test for a dead-zone.
// Your code passing analog gamepad values is likely to want to transform your raw inputs, using a dead-zone and maybe a power curve.
enum ImGuiNavInput_
{
ImGuiNavInput_PadActivate, // press button, tweak value // e.g. Circle button
ImGuiNavInput_PadCancel, // close menu/popup/child, lose selection // e.g. Cross button
ImGuiNavInput_PadInput, // text input // e.g. Triangle button
ImGuiNavInput_PadMenu, // access menu, focus, move, resize // e.g. Square button
ImGuiNavInput_PadUp, // move up, resize window (with PadMenu held) // e.g. D-pad up/down/left/right, analog
ImGuiNavInput_PadDown, // move down
ImGuiNavInput_PadLeft, // move left
ImGuiNavInput_PadRight, // move right
ImGuiNavInput_PadScrollUp, // scroll up, move window (with PadMenu held) // e.g. right stick up/down/left/right, analog
ImGuiNavInput_PadScrollDown, // "
ImGuiNavInput_PadScrollLeft, //
ImGuiNavInput_PadScrollRight, //
ImGuiNavInput_PadFocusPrev, // next window (with PadMenu held) // e.g. L-trigger
ImGuiNavInput_PadFocusNext, // prev window (with PadMenu held) // e.g. R-trigger
ImGuiNavInput_PadTweakSlow, // slower tweaks // e.g. L-trigger, analog
ImGuiNavInput_PadTweakFast, // faster tweaks // e.g. R-trigger, analog
ImGuiNavInput_COUNT,
};
Current blurb in imgui.cpp (I know it is cropped by github but please read it)
USING GAMEPAD/KEYBOARD NAVIGATION [BETA]
- Gamepad/keyboard navigation support is available, currently in Beta with some issues. Your feedback and bug reports are welcome.
- See https://github.com/ocornut/imgui/issues/323 discussion thread and ask questions there.
- The current primary focus is to support game controllers.
- Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
- Consider using Synergy host (on your computer) + uSynergy.c (in your console/tablet/phone app) to use PC mouse/keyboard.
- Your inputs are passed to imgui by filling the io.NavInputs[] array. See 'enum ImGuiNavInput_' in imgui.h for a description of available inputs.
- For gamepad use, the easiest approach is to go all-or-nothing, with a buttons combo that toggle your inputs between imgui and your game/application.
Sharing inputs in a more advanced or granular way between imgui and your game/application may be tricky and requires further work on imgui.
For more advanced uses, you may want to use:
- io.NavUsable: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
- io.NavActive: true when the navigation cursor is visible (and usually goes false when mouse is used).
- query focus information with IsWindowFocused(), IsAnyWindowFocused(), IsAnyItemFocused() functions.
The reality is more complex than what those flags can express. Please discuss your issues and usage scenario in the thread above.
As we head toward more keyboard-oriented development this aspect will need to be improved.
- It is recommended that you enable the 'io.NavMovesMouse' option. Enabling it instructs ImGui that it can move your move cursor to track navigated items and ease readability.
When enabled and using directional navigation (with d-pad or arrow keys), the NewFrame() functions may alter 'io.MousePos' and set 'io.WantMoveMouse' to notify you that it did so.
When that happens your back-end NEEDS to move the OS or underlying mouse cursor on the next frame. The examples binding in examples/ do that.
(Important: It you set 'io.NavMovesMouse' to true but don't honor 'io.WantMoveMouse' properly, imgui will misbehave as it will think your mouse is moving back and forth.)
// Application init
io.NavMovesMouse = true;
// Application main loop
if (io.WantMoveMouse)
MyFuncToSetMousePosition(io.MousePos.x, io.MousePos.y);
ImGui::NewFrame();
In a setup when you may not have easy control over the mouse cursor (e.g. uSynergy.c doesn't expose moving remote mouse cursor),
you might want to set a boolean to ignore your other external mouse positions until they move again
-
THE INITIAL FOCUS IS ON USING A GAME CONTROLLER! The inputs are explicitly named using the word "pad" because the current scheme is optimized for game controllers. You can however trivially map keyboard keys on those inputs and it'll work if you use mouse+keyboard combo. Later on I will focus on keyboard and add especially named enums for keyboard (user expectation for keyboard controls are much higher/harsher than with gamepad).
-
Depending on your application using gamepad/keyboard might requires more fine-tuned controls of how inputs are dispatched and shared between your app/game and different parts of your imgui interfaces. We may need better options/tooling for that.
-
It is very probably missing or failing at something that will appear obvious to you. I've nailed many issues and big technical problems but barely started scrapping the surface of all possible usage scenarios. Please report!
-
The actual scoring function for navigating the graph hasn't been given a lot of love yet and is failing to behave as expected in various cases. Failure usually mean that you press a direction and don't end up exactly where you intended. Will keep improving it. Please report issues with screenshots!
-
Addition to the public API are rather minimal. New functions
IsItemFocused()
,IsAnyItemFocused()
,SetItemDefaultFocus()
,GetKeyPressedAmount()
. I agressively madeIsItemHovered()
be aware of current navigation focus to maximize existing code just naturally working with Nav (typically tooltip pattern). New window flagsImGuiWindowFlags_NoNavFocus
ImGuiWindowFlags_NoNavInputs
. New colorsImGuiCol_NavHighlight
(make it same or close to ImGuiCol_HeaderActive)ImGuiCol_NavWindowingHighlight
(white and very transparent), a bunch of keys (read instructions), 1 IO settingNavMovesMouse
, 3 IO outputsWantMoveMouse
NavUsable
NavActive
. -
The option
io.NavMovesMouse
is currently off by default. I recommend enabling it. When enabled. the mouse cursor can be moved byImGui::NewFrame()
when directional navigation is used. It does so by overwritingio.MousePos
and setio.WantMoveMouse=true
. It is up to your backend when that flag is set to apply the new mouse position in your OS. If you enable the option but don't honor those requests, ImGui will be very confused. (this is why I can't have it on by default). -
If you are running this on VR, some suggestions: if you want to display ImGui as a static overlay (not affected by head rotation) you may want to reduce DisplaySize and avoid rendering over your entire framebuffer. You can also increase the style.DisplaySafeAreaPadding value. Popups should stay within this rectangle while you can still partly move regular window outside. It might be just better to display it within the 3D world but I haven't tried.
Following in the next message will be my test code for GLFW binding to map a DualShock 4.
TODO list
- A. Sort-out/finalize all the input bindings correctly.
- B. Menus: Navigating menus is still awkward in multiple ways.
- B. Investigate crossing over the boundaries of child windows, in particular those without scroll. Introduce a window flag to flatten child in term of navigation.
- C. Menubars inside modals windows are acting weird (broken in master as well)
- A. Problem various problem with graph navigation/scoring functions, currently biased toward vertical layouts.
- C.
Using scrolling should activate scrollbar in a way the user can tell programmatically (e.g. Log window). - C. NavHighlight clipping issue within child window.
- C. Merge all the old FocusIdx tabbing stuff into the new system.
- B. Resizing window will currently fail with certain types of resizing constraints/callback applied
- C. Popup: introduce a default validation button e.g.
SetItemDefaultValidation()
activable from anywhere in the window with Enter. Currently can use imgui_internal.h declaredImGui::PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); / ImGui::PopItemFlag()
- C. TreeNode: NavLeft to close, NavRight on closed node to open. How would it behave with buttons/items after a closed treenode, and/or multiple columns?
- B. Can't reliably use Left/Right within menus with regular widgets. Need to figure out a way to only use the Left/Right nav requests for menu open/closure as fallback to a failed moving request.
- C. Drag/Slider: experiment with keeping item active when activated, using cancel to stop editing.
- B.
Popup: add options to disable auto-closing popups when using a MenuItem/Selectable (Menus API work #126)(not part of Nav) - C. Lost of currently focused widget when using buttons that changes labels on click (obvious, but only made apparent with directional navigation- can we automagically work around it?)
- B. Bug with keeping visibility of navigated them within horizontal scrollbar. Stuck nav (visible in Horizontal Scrolling demo corner. Still there?).
Activity