Closed velvitonator closed 7 years ago
Before anything else, have you looked into using a system like Synergy + usynergy.c to have your game console use your PC mouse/keyboard ? I'd wholly recommend that.
If so, is there a way to ask for the rectangle of a given item outside of the update loop?
Not sure what you mean by "the update loop" nor why you would need that if you are spoofing mouse control? If you are going to spoof mouse control just make your controller adjust the MousePos and MouseDown (buttons) values.
You can call GetItemRectMin()
/ GetItemRectMax()
to obtain the bounding rectangle of the last item (which might be clipped and outside of view, testable with IsItemVisible()
) but I don't see how that would help here.
I also couldn't find any way to programmatically set the focus of an item other than input text fields; did I miss something?
No it's not available yet, there's no generalized concept of focus in fact.
Now if you really want controller support. There's different way we can approach this:
I have rename your topic to keyboard/controller because the problems are the same for keyboard controls and many people are likely to come from the angle of desiring keyboard control.
Before anything else, have you looked into using a system like Synergy + usynergy.c to have your game console use your PC mouse/keyboard ? I'd wholly recommend that.
Yeah, we've got this up and running (except for keyboard support, since I've yet to hook up the manual character-input stuff). We want to add controller as a good native option, so users don't have to switch input devices.
Not sure what you mean by "the update loop" nor why you would need that if you are spoofing mouse control? If you are going to spoof mouse control just make your controller adjust the MousePos and MouseDown (buttons) values.
Apologies; I meant that we could do something like move the mouse down "an item" instead, but that starts to get a bit complicated for client code, especially since we'd need this to be something for everyone to use, to make their own debug GUIs. With that approach, we'd need the screen-rect of the options etc. This is all certainly possible for us to do, it's just a matter of the effort we want to expend on it. Hence, I'm hoping to clarify the available options before embarking on something time-consuming. =)
Emulating mouse with game controller (or keyboard) would be easy to implement and functional immediately. Obviously it'd be rather awkward and underwhelming interaction-wise, but for casual use it may be enough.
Yeah, this is looking to be the only option in the timeframe we're looking at. Since we also have Synergy, I'm not convinced adding this is worth it; I know I'd easily switch to mouse + keys as soon as I had to, rather than bear analog-stick mouse cursor!
If you only want this for simple sort of UI like list of items you can emulate the concept of "whats focused" on user side and handle interaction yourself (e.g. Button X pressed while Item N is selected).
Certainly! But getting it to reflect in the UI is the tricky bit =/ Also, we already have a couple of in-game debug UIs using ImGUI, and they have quite a bit of variety. =P
The correct thing would be to fully handle keyboard/controller navigation and per-widget focus. It's not a simple feature, probably a bunch of work to get it right (I'd say 1 week optimistically). It is a very desirable feature but I don't know when / if I'll ever be able to do it.
Hmm, I'll be honest: I'm so accustomed to working with closed-source things that simply adding the feature to ImGUI didn't occur to me. I think it's a little out of scope for the timeframe I have, but it's certainly worth considering in the longer term, should we find that controller support is indispensable for whatever reason.
I have rename your topic to keyboard/controller because the problems are the same for keyboard controls and many people are likely to come from the angle of desiring keyboard control.
Good idea!
Apologies; I meant that we could do something like move the mouse down "an item" instead, but that starts to get a bit complicated for client code, especially since we'd need this to be something for everyone to use,
It's trivial to store the rectangle of the hovered item (we can add that to the library), so you could move the cursor outside of it but you wouldn't know by how much as you don't know where the next item. An hybrid approach to move outward the item by the amount of its width/height and then move smoothly if there's no item under the cursor. It would be easy to implement and may be a little better than a cursor but probably still mediocre. If you are on PS4, I haven't tried it but I suspect the touch pad is precise enough to emulate a mouse.
Hmm, I'll be honest: I'm so accustomed to working with closed-source things that simply adding the feature to ImGUI didn't occur to me. I think it's a little out of scope for the timeframe I have, but it's certainly worth considering in the longer term, should we find that controller support is indispensable for whatever reason.
FYI if your project/company has the fund I'm more than happy to provide custom work on imgui to develop that sort of non-trivial feature (been looking for possible ways to fund its development as I can't really afford it any more myself, at least not at the pace I was working on it a few months ago). Something to consider, may be more optimal than you doing it and it'll benefit the sustainability of the library.
It's one of the those feature that's touching quite a lot of elements in the code - quite a complex feature to do right. If you to want to tackle it yourself I can also guide you.
Sorry for the silence; I forgot the re-mark the mail as unread to get back to it.
Your idea to use the PS4 touchpad was pretty effective--it's somewhat finicky, but much better than a stick, and it wasn't too difficult to hook up the sticks and pad for scrolling, either.
So, we're good for now; thanks for the suggestion!
I don't have an ETA yet but I've been working on this lately. When it starts to be semi usable I will push it to a public branch, right now it is way too prototypey.
Some progress report, been improving support for the popup stack, combos, menus, sliders, visualizing selection, avoiding confusion between keyboard and mouse, etc. There's still a billion things to do, it is a little overwhelming.
This is VERY interesting :grin:
This looks great. We'd like to use it for interaction with debug UI in PSVR. It's exactly the kind of thing we need for this. When are you looking to make it available?
When it is done @MrMarkie :) I've been working on my non-existent spare time until now, but because this feature is now sponsored (a first) I'm aiming before end of July. Will probably start testing and iterating on API and edge cases mid-july.
Hi. Is there any news on this feature? I'd be happy to help test it if you have a preview version
I was thinking about pushing a branch but the IO api is utterly temporary/broken yet and it's still missing important features. I'll work on it this week-end. From Monday if you're happy with experimenting with it (at the cost of minor breakage in the following weeks) it would be indeed quite helpful if you want to try it and submit feedback. You can also e-mail me if you want to move this off-line for back and forth.
Absolutely. Let me know when / where I can get it. Look forwards to trying it out. Markie On 15 Jul 2016 17:35, omar notifications@github.com wrote:I was thinking about pushing a branch but the IO api is utterly temporary/broken yet and it's still missing important features. I'll work on it this week-end. From Monday if you're happy with experimenting with it (at the cost of minor breakage in the following weeks) it would be indeed quite helpful if you want to try it and submit feedback. You can also e-mail me if you want to move this off-line for back and forth.
—You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub, or mute the thread.
Quick update GIF
I'm not finished but I'll be pushing first test version to a branch tonight, along with instructions for those interested.
This looks excellent!! On 19 Jul 2016 22:28, omar notifications@github.com wrote:Quick update GIF
I'm not finished but I'll be pushing first test version to a branch tonight, along with instructions for those interested.
—You are receiving this because you were mentioned.Reply to this email directly, view it on GitHub, or mute the thread.
Hello,
I have started to push gamepad/keyboard controls this into a work-in-progress branch: https://github.com/ocornut/imgui/tree/navigation
Brain dump, some of it may be hard to digest :)
It's been a big chunk of work and it isn't finished (probably 50+ changes left to apply, bug fixes and tweaks), BUT i think it is ready for early evaluation. If you are willing to help and checkout this branch it would be super helpful.
If you don't have time to look it and help, come back in 2-4 weeks and it'll be in a better state.
I will keep working on this mostly in the upcoming week-ends. Even if incomplete it could be merged in master in a few weeks if everything works out nicely, and then I can keep improving/fixing afterwards. Any test with testing would obviously accelerate merging into master.
The development of this feature has been sponsored by Insomniac Games (thank you!).
IsItemFocused()
, IsAnyItemFocused()
, SetItemDefaultFocus()
, 1 window flag ImGuiWindowFlags_NoNav
, a bunch of keys (read instructions below), 1 IO setting NavMovesMouse
, 3 IO outputs WantMoveMouse
NavUsable
NavActive
.Note that I didn't yet add Moving or Collapsing windows yet. The code for those is now trivial to add but the key question is how to decide on an input layout and how to expose it to the end-user so it is decently configurable. Things like ALT-TAB window focus alteration requires by design holding one button while pressing another (because of the MRU logic) so I don't yet know how to suitably expose all those inputs. Currently the L/R triggers are mapped on ImGuiKey_NavTweakSlower
ImGuiKey_NavTweakFaster
but that's also used to change focus. So while for a gamepad ImGuiKey_NavTweakSlower
==ImGuiKey_NavPrevWindow
and ImGuiKey_NavTweakFaster
==ImGuiKey_NavNextWindow
, on a keyboard we are likely to want to use Alt/Shift for Slower/Faster and CTRL+TAB/CTRL+SHIFT+TAB, etc. So there's work to rearrange those.
I expect we can also use analog stick and that will also weight in the design to pass all those inputs.
io.NavMovesMouse
, currently off by default. When on, the mouse cursor can be moved in ImGui::NewFrame()
when directional navigation is used. It does so by overwriting io.MousePos
and setting io.WantMoveMouse=true
. It is up to your backend when that flag is set to apply the new mouse position in your OS. Right now you need to apply it on the next frame (which is awkward, I will rework the code to handle this delay).
Depending on your application/use case this option may be best. io.NavMovesMouse=true
so there's probably issues I have overlooked that mostly appears with it set to false.In ImGui_ImplGlfw_NewFrame()
io.NavMovesMouse = true;
// Setup events/key mapping within the existing keyboard array.
int avail_key = GLFW_KEY_LAST;
io.KeyMap[ImGuiKey_NavActivate] = avail_key++;
io.KeyMap[ImGuiKey_NavCancel] = avail_key++;
io.KeyMap[ImGuiKey_NavWindowing] = avail_key++;
io.KeyMap[ImGuiKey_NavInput] = avail_key++;
io.KeyMap[ImGuiKey_NavLeft] = avail_key++;
io.KeyMap[ImGuiKey_NavRight] = avail_key++;
io.KeyMap[ImGuiKey_NavUp] = avail_key++;
io.KeyMap[ImGuiKey_NavDown] = avail_key++;
io.KeyMap[ImGuiKey_NavTweakFaster] = avail_key++;
io.KeyMap[ImGuiKey_NavTweakSlower] = avail_key++;
// Update Keyboard Nav
const bool TEST_KEYBOARD = false;
io.KeysDown[io.KeyMap[ImGuiKey_NavActivate]] = TEST_KEYBOARD && io.KeysDown[GLFW_KEY_SPACE];
io.KeysDown[io.KeyMap[ImGuiKey_NavCancel]] = TEST_KEYBOARD && io.KeysDown[GLFW_KEY_ESCAPE];
io.KeysDown[io.KeyMap[ImGuiKey_NavWindowing]] = TEST_KEYBOARD && false;
io.KeysDown[io.KeyMap[ImGuiKey_NavInput]] = TEST_KEYBOARD && io.KeysDown[GLFW_KEY_ENTER];
io.KeysDown[io.KeyMap[ImGuiKey_NavLeft]] = TEST_KEYBOARD && io.KeysDown[GLFW_KEY_LEFT];
io.KeysDown[io.KeyMap[ImGuiKey_NavRight]] = TEST_KEYBOARD && io.KeysDown[GLFW_KEY_RIGHT];
io.KeysDown[io.KeyMap[ImGuiKey_NavUp]] = TEST_KEYBOARD && io.KeysDown[GLFW_KEY_UP];
io.KeysDown[io.KeyMap[ImGuiKey_NavDown]] = TEST_KEYBOARD && io.KeysDown[GLFW_KEY_DOWN];
io.KeysDown[io.KeyMap[ImGuiKey_NavTweakFaster]] = TEST_KEYBOARD && io.KeyShift;
io.KeysDown[io.KeyMap[ImGuiKey_NavTweakSlower]] = TEST_KEYBOARD && io.KeyAlt;
// Update Joystick Nav
if (glfwJoystickPresent(GLFW_JOYSTICK_1))
{
int buttons_count = 0;
const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count);
if (buttons_count > 0 && buttons[0] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavActivate]]= true;
if (buttons_count > 1 && buttons[1] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavCancel]] = true;
if (buttons_count > 2 && buttons[2] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavWindowing]] = true;
if (buttons_count > 3 && buttons[3] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavInput]] = true;
if (buttons_count > 10 && buttons[10] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavUp]] = true;
if (buttons_count > 11 && buttons[11] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavRight]] = true;
if (buttons_count > 12 && buttons[12] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavDown]] = true;
if (buttons_count > 13 && buttons[13] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavLeft]] = true;
if (buttons_count > 4 && buttons[4] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavTweakSlower]] = true;
if (buttons_count > 5 && buttons[5] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavTweakFaster]] = true;
}
Also in ImGui_ImplGlfw_NewFrame(), honor io.WantMoveMouse
request:
// Setup inputs
// (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents())
if (glfwGetWindowAttrib(g_Window, GLFW_FOCUSED))
{
if (io.WantMoveMouse)
{
// Requested to move mouse (when using directional navigation with 'NavMovesMouse=true'). Intentionally applied on following frame to compensate for render lag.
glfwSetCursorPos(g_Window, (double)io.MousePos.x, (double)io.MousePos.y);
}
else
{
double mouse_x, mouse_y;
glfwGetCursorPos(g_Window, &mouse_x, &mouse_y);
io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); // Mouse position in screen coordinates (set to -1,-1 if no mouse / on another screen, etc.)
}
}
else
{
io.MousePos = ImVec2(-1,-1);
}
If you can spend an hour setting it up and reporting confusion or issues it'll be helpful!
Thanks!
Extra musing.. Unfortunately some of the code to support that feature has become rather fragile, not in the sense that it is crippled with bugs (hopefully it isn't), but many of those changes requires too much expertise and knowledge of the codebase, which isn't a good thing at all. The testing suite and documentation will need to take more priority in the future and I above everything want to steer this project away from ever being a bus-factor-1 project. I will return to this topic later and looking forward to gather any help I can to make the testing suite happen! (#435).
Some of my todo list.
SetItemDefaultValidation()
activable from anywhere in the window with Enter. > Now can use ImGui::PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true); / ImGui::PopItemFlag()
The big commit already includes fixes/changes for probably a hundred different things, so the left-over may be quite easy :) As long as we focus on gamepad this is fairly scoped and we can see the end of the tunnel (full keyboard friendlyness is another thing).
I haven't integrated any of this into my own code, just looking at your branch directly, but it seems like setup will be very simple.
The only hiccup I had was I had to install DS4Windows to get the controller inputs to be mapped correctly for your setup. I guess Sony hasn't provided any official drivers for the DS4... shame! However, I noticed that the touch pad works and I can click/move windows with it, but it looks like its a DS4Windows feature since I can move the mouse on my entire system and click things with it.
Navigation with the controller produces the expected results for most things, but the one area I've noticed which can yield very poor navigation is switching windows. I suspect it's because you may be cycling through windows somehow in memory? But most of the time I'm trying to cycle through them in some spatial fashion on screen. This may be exacerbated by the fact that I was moving the windows around with the mouse in between.
Will be a little while before I have more detailed feedback, but this is very promising!
Easy to get going, I liked the sub-selection in the style color editor. Something that felt like it was missing was a way to jump to the parent in a tree widget. That's tricky though, since I'd normally expect NavLeft to do that, which doesn't necessarily make sense if the children of that tree node have horizontal elements. I'm not doing very complex things with imgui at the moment, so I'll keep my comment brief.
Here's a quick hack for people wishing to test it out with an Xbox 360 controller on Mac:
if (glfwJoystickPresent(GLFW_JOYSTICK_1))
{
struct GamepadMap
{
enum {
DPAD_UP, DPAD_DOWN, DPAD_LEFT, DPAD_RIGHT,
FACE_UP, FACE_DOWN, FACE_LEFT, FACE_RIGHT,
SHOULDER_LEFT, SHOULDER_RIGHT,
LAST
};
unsigned char map[LAST];
bool checkpress(const unsigned char* buttons, int buttons_count, int btn) { return buttons_count > btn && buttons[map[btn]] == GLFW_PRESS; }
};
static GamepadMap ds4win_map = {{10, 12, 13, 11, 3, 0, 2, 1, 4, 5}};
static GamepadMap x360macos_map = {{0, 1, 2, 3, 14, 11, 13, 12, 8, 9}};
// GamepadMap &padmap = ds4win_map;
GamepadMap &padmap = x360macos_map;
int buttons_count = 0;
const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count);
if (padmap.checkpress(buttons, buttons_count, GamepadMap::FACE_DOWN)) io.KeysDown[io.KeyMap[ImGuiKey_NavActivate]]= true;
if (padmap.checkpress(buttons, buttons_count, GamepadMap::FACE_RIGHT)) io.KeysDown[io.KeyMap[ImGuiKey_NavCancel]] = true;
if (padmap.checkpress(buttons, buttons_count, GamepadMap::FACE_LEFT)) io.KeysDown[io.KeyMap[ImGuiKey_NavWindowing]] = true;
if (padmap.checkpress(buttons, buttons_count, GamepadMap::FACE_UP)) io.KeysDown[io.KeyMap[ImGuiKey_NavInput]] = true;
if (padmap.checkpress(buttons, buttons_count, GamepadMap::DPAD_UP)) io.KeysDown[io.KeyMap[ImGuiKey_NavUp]] = true;
if (padmap.checkpress(buttons, buttons_count, GamepadMap::DPAD_RIGHT)) io.KeysDown[io.KeyMap[ImGuiKey_NavRight]] = true;
if (padmap.checkpress(buttons, buttons_count, GamepadMap::DPAD_DOWN)) io.KeysDown[io.KeyMap[ImGuiKey_NavDown]] = true;
if (padmap.checkpress(buttons, buttons_count, GamepadMap::DPAD_LEFT)) io.KeysDown[io.KeyMap[ImGuiKey_NavLeft]] = true;
if (padmap.checkpress(buttons, buttons_count, GamepadMap::SHOULDER_LEFT)) io.KeysDown[io.KeyMap[ImGuiKey_NavTweakSlower]] = true;
if (padmap.checkpress(buttons, buttons_count, GamepadMap::SHOULDER_RIGHT)) io.KeysDown[io.KeyMap[ImGuiKey_NavTweakFaster]] = true;
}
[EDIT] NavLeft not NavRight >_<
@Roflraging Yeah, DS4window provide mouse emulation. I expect native PS4 developers to implement a similar setup to emulate a fallback mouse, even awkwardly.
Navigation with the controller produces the expected results for most things, but the one area I've noticed which can yield very poor navigation is switching windows. I suspect it's because you may be cycling through windows somehow in memory? But most of the time I'm trying to cycle through them in some spatial fashion on screen. This may be exacerbated by the fact that I was moving the windows around with the mouse in between.
Switching windows is indeed a previous/next thing based on the current z-order (~most recently used window). It currently works this way because it compares to Windows' ALT-TAB and because spatial navigation wouldn't work very well with lots of overlapping windows. It would work better if we could show a preview of the windows arranged in a linear fashion, the same way Mac/Windows does it, but that seems like lots of extra work and the contents of imgui windows aren't really fit for easy identification after resizing down. If you have any idea on how to improve this let me know. I don't think it is a big issue however.
Also thanks @michaelbartnett. Eventually we should provide similar code in the committed example that has some sort of nice remapping table, even if the code itself ends up being commented out.
I've got a bunch of e-mail feedback from @pdoane which he may copy here later.
I did an integration into one of my test projects - mostly just a property grid and a couple of modal dialog boxes. My interest is in keyboard navigation which as you say is going to be more complicated, but even the controller support is already a good foundation.
Integration was easy. I followed your GLFW sample and was up and running in a few minutes. No major issues and a lot of the navigation already felt natural.
Menus:
Modal Dialogs:
Text input:
Multi-column navigation:
Drag controls:
List box:
Tree Nodes:
Navigation override:
It currently works this way because it compares to Windows' ALT-TAB and because spatial navigation wouldn't work very well with lots of overlapping windows.
So I think part of the reason why I have the spatial expectation at all is because of the controller bindings. Having it cycle through with left + right bumpers give me some sort of expectation that I should be cycling left/right on the screen. I'll have to try with different binds to see if it makes it better for me, but I suspect that the preview of the cycling order as in alt+tab makes the most difference.
I do tend to agree that this is not a huge issue, I personally don't have more than 2 or 3 windows at any time, if that.
@pdoane:
Escape closed the dialog box. Good! Enter didn't didn't accept the dialog. I don't remember a flag for it but I'm assuming we'll need to semantically identify the Ok button. I want control over the initial focus. There's a text input field that would be the natural thing to be active on opening of the dialog.
There's a new function SetItemDefaultFocus()
for that. So you could use that on the OK button as well if you are happy with just regularly "activating" it (mapped to "Space" in my example) vs a window "global" Enter to identify OK button. Perhaps doable as an extra function let's say SetItemDefaultValidate()
which would record the ID and that can be activated from anywhere in the window with Enter?
TextInput: After I've moved the highlight over a text input field, I'm expecting that I could start typing. Or at least to activate it and start typing.
Right now you can type in by pressing NavInput (Enter) which works on sliders, drags.. we could make NavActivate function on TextInput as well, can't tell if that would feel more or less consistent?
Drag controls: Having to hold the activate key while pressing left/right feels awkward.
I will experiment with keeping the control activated until pressing NavCancel but I think the current scheme allows for faster interactions.
Tree nodes: I am expecting Left/Right to open/close the node. Not sure how that would interact with a multi-column environment though.
Yes need to look at columns before we can try that on trees. EDIT Problem is also that one can have items next to a closed tree node (I often do that myself).
and then press the down key to transfer into navigating within the list box. Maybe that is the appropriate behavior for single-line text input?
Will try.
@pdoane
Multi-colum navigation Up/Down isn't staying in the same column. This is just a two column setup, pressing up ends up on the > right column and down on the left column.
Please generally provide screenshots or repro because that's a general navigation issue and may not be tied to columns. It works for example in the simple columns part of the demo, but acts weirdly as you described in the "Property Example" sample. In that case it is related to the Selectable() on the left incorrectly sticking out too far and the navigation scoring calculation not applying clipping over the item rectangle.
@pdoane I have pushed some fixes, it would be nice if you could confirm if you columns issues are resolved or improved.
API BREAKING CHANGES
ImGuiKey_NavWindowing
to ImGuiKey_NavMenu
Pushed various changes above. Most notable, the NavMenu button (recommended "Square" on a DualShock4 controller) allows toggling between the scrollable area of a window and the menubar. Holding the NavMenu button still allows resizing and selecting window focus.
I will add moving and collapsing window once I figure out the input scheme for that and the menu/layer system was a necessary step forward. May adopt Windows style NavMenu+left to access a window menu to select among those options, which also would be keyboard compatible (rather than taking advantage of multiple sticks).
Finally got around to checking out these controller changes in a non-trivial imgui setup.
Some thoughts:
I would write more, but it is very late for me, I'll try to get more thoughts tomorrow. Biggest thing I've realized is that having a GUI already set up with the mouse + keyboard expectation doesn't really work with the controller. It's workable, but not ideal! Many of the issues I currently have is much more of a problem with the GUI design fundamentally not matching the input method rather than legitimate issues with your implementation.
@Roflraging
Will there be support for analogous middle mouse and right mouse action for the controller?
You can always map mouse buttons to free buttons of your controller (if there are any?), which may or not cover you enough if you have io.NavMovesMouse
activated and your binding is honoring io.WantMoveMouse
.
Fast scrolling/next item? Some of the scrolling areas we have are very large, and to cycle through the items at the default rate can be tedious. Obviously, a controller based GUI would probably rely on large menus less, but GUIs already set up, this might be very convenient.
"Faster next item" is a little cumbersome to get right, but I agree we need manual scrolling in (there's already support for scrolling but only when there's no item to interact with). The question is how to return back from "scrolling" to "selecting a widget" and how is said widget selected (perhaps based on relative widget position of where we were before scrolling?)
Currently in check-list above as - [ ] B. Explicit manual scrolling (e..g mapped on a analog stick): how to reapply suitable focus? currently only possible when no item is navigable.
Support to pop up to top level child.
There's different issues and possible solution here. If you can post screenshots (or e-mail privately, I have a NDA with your company) I could look at the specific case. If they are childs with no visible scrolling I think we could adopt a scheme where we can cross through parent-child borders. It is also possible we need extra window flags or options to parametrize those behaviors as well.
Still uncertain about how to express/configure inputs, it appears too difficult to treat gamepad and keyboard as a same source and gets in the way of lots of possible improvements
Instead of attempting to expose semantic-only I may bite the bullet and expose separate explicit sets of inputs for gamepad and keyboard, and we can optimize input scheme specifically for those (with first focus on gamepad).
For now I have added 4 new digital inputs currently mapped to left-analog stick on my dual shock:
ImGuiKey_NavScrollLeft, // e.g. Analog left
ImGuiKey_NavScrollRight,// e.g. Analog right
ImGuiKey_NavScrollUp, // e.g. Analog up
ImGuiKey_NavScrollDown, // e.g. Analog down
Used to:
ImGuiKey_NavTweakSlower
/ImGuiKey_NavTweakFaster
, again naming/semantic is making it rather ugly) . Speed currently constant.With those changes, apart from the big questions mark of how to expose inputs, it is starting to be quite usable with a gamepad. There's probably hundreds of upcoming incremental changes obviously, but you can do stuff without a keyboard/mouse around.
Reminder: todo list is here https://github.com/ocornut/imgui/issues/323#issuecomment-233785300
The ugly binding for GLFW+DualShock4 with DS4Window is: We shall make that less ugly, probably using a dedicated input visualization/config window. Currently under "Keyboard,Mouse,etc." in the demo window there's a panel that shows all pressed inputs.
// FIXME-NAVIGATION
// Setup events/key mapping within the existing keyboard array.
int avail_key = GLFW_KEY_LAST;
for (int n = ImGuiKey_NavActivate; n < ImGuiKey_NavLast_; n++)
{
io.KeyMap[n] = avail_key++;
io.KeysDown[io.KeyMap[n]] = false;
}
// Update Keyboard Inputs
if (1)
{
io.KeysDown[io.KeyMap[ImGuiKey_NavActivate]] = io.KeysDown[GLFW_KEY_SPACE];
io.KeysDown[io.KeyMap[ImGuiKey_NavCancel]] = io.KeysDown[GLFW_KEY_ESCAPE];
io.KeysDown[io.KeyMap[ImGuiKey_NavInput]] = io.KeysDown[GLFW_KEY_ENTER];
io.KeysDown[io.KeyMap[ImGuiKey_NavLeft]] = io.KeysDown[GLFW_KEY_LEFT];
io.KeysDown[io.KeyMap[ImGuiKey_NavRight]] = io.KeysDown[GLFW_KEY_RIGHT];
io.KeysDown[io.KeyMap[ImGuiKey_NavUp]] = io.KeysDown[GLFW_KEY_UP];
io.KeysDown[io.KeyMap[ImGuiKey_NavDown]] = io.KeysDown[GLFW_KEY_DOWN];
io.KeysDown[io.KeyMap[ImGuiKey_NavTweakFaster]] = io.KeyShift;
io.KeysDown[io.KeyMap[ImGuiKey_NavTweakSlower]] = io.KeyAlt;
}
// Update Joystick Inputs
int axes_count = 0;
const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count);
if (glfwJoystickPresent(GLFW_JOYSTICK_1))
{
int buttons_count = 0;
const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count);
if (buttons_count > 0 && buttons[0] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavActivate]]= true;
if (buttons_count > 1 && buttons[1] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavCancel]] = true;
if (buttons_count > 2 && buttons[2] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavMenu]] = true;
if (buttons_count > 3 && buttons[3] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavInput]] = true;
if (buttons_count > 10 && buttons[10] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavUp]] = true;
if (buttons_count > 11 && buttons[11] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavRight]] = true;
if (buttons_count > 12 && buttons[12] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavDown]] = true;
if (buttons_count > 13 && buttons[13] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavLeft]] = true;
if (buttons_count > 4 && buttons[4] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavTweakSlower]] = true;
if (buttons_count > 5 && buttons[5] == GLFW_PRESS) io.KeysDown[io.KeyMap[ImGuiKey_NavTweakFaster]] = true;
if (axes_count > 0 && axes[0] < -0.5f) io.KeysDown[io.KeyMap[ImGuiKey_NavScrollLeft]] = true;
if (axes_count > 0 && axes[0] > +0.5f) io.KeysDown[io.KeyMap[ImGuiKey_NavScrollRight]] = true;
if (axes_count > 1 && axes[1] < -0.5f) io.KeysDown[io.KeyMap[ImGuiKey_NavScrollDown]] = true;
if (axes_count > 1 && axes[1] > +0.5f) io.KeysDown[io.KeyMap[ImGuiKey_NavScrollUp]] = true;
}
FYI I have been working on how to pass inputs, including analog inputs, in a way that would work for both gamepad and future keyboard. That'll probably be the last major change before the thing can be marked "usable beta".
Ok I made a first-pass breaking commit. EDIT typos, fixes
ImGuiKey_NavXXX
were removed from the ImGuiKey enum, now using ImGuiNavInput_XXX
(ImGuiNavInput enum).ImGuiNavInput_PadActivate
, so that explicitly include the word PAD. This is because some of the input won't make sense for keyboard and vice-versa, they'll need to be separated. You can still totally map your keyboard keys on the Pad inputs and it'll work but you'll know that you are using an interface that has been tweaked for gamepad.Here's the enum
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
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
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
ImGuiNavInput_PadTweakFast, // faster tweaks // e.g. R-trigger
ImGuiNavInput_COUNT,
};
Here's my current GLFW binding code for DualShock4 with DSWindow:
// Setup directional navigation events/key mapping
bool nav_uses_keyboard = true;
bool nav_uses_gamepad = true;
memset(io.NavInputs, 0, sizeof(io.NavInputs));
// Enable to allow ImGui moving mouse cursor when using keyboard/gamepad navigation
io.NavMovesMouse = false;
// Update Keyboard Inputs
if (nav_uses_keyboard)
{
#define MAP_KEY(NAV_NO, KEY_NO) { if (io.KeysDown[KEY_NO]) io.NavInputs[NAV_NO] = 1.0f; }
MAP_KEY(ImGuiNavInput_PadActivate, GLFW_KEY_SPACE);
MAP_KEY(ImGuiNavInput_PadCancel, GLFW_KEY_ESCAPE);
MAP_KEY(ImGuiNavInput_PadMenu, GLFW_KEY_LEFT_ALT);
MAP_KEY(ImGuiNavInput_PadInput, GLFW_KEY_ENTER);
MAP_KEY(ImGuiNavInput_PadUp, GLFW_KEY_UP);
MAP_KEY(ImGuiNavInput_PadDown, GLFW_KEY_DOWN);
MAP_KEY(ImGuiNavInput_PadLeft, GLFW_KEY_LEFT);
MAP_KEY(ImGuiNavInput_PadRight, GLFW_KEY_RIGHT);
MAP_KEY(ImGuiNavInput_PadTweakSlow, GLFW_KEY_LEFT_ALT);
MAP_KEY(ImGuiNavInput_PadTweakSlow, GLFW_KEY_RIGHT_ALT);
MAP_KEY(ImGuiNavInput_PadTweakFast, GLFW_KEY_LEFT_SHIFT);
MAP_KEY(ImGuiNavInput_PadTweakFast, GLFW_KEY_RIGHT_SHIFT);
#undef MAP_KEY
}
// Update Gamepad Inputs
if (nav_uses_gamepad)
{
#define MAP_BUTTON(NAV_NO, BUTTON_NO) { if (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS) io.NavInputs[NAV_NO] = 1.0f; }
#define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); if (v > 1.0f) v = 1.0f; if (io.NavInputs[NAV_NO] < v) io.NavInputs[NAV_NO] = v; }
int axes_count = 0, buttons_count = 0;
const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count);
const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count);
MAP_BUTTON(ImGuiNavInput_PadActivate, 0);
MAP_BUTTON(ImGuiNavInput_PadCancel, 1);
MAP_BUTTON(ImGuiNavInput_PadMenu, 2);
MAP_BUTTON(ImGuiNavInput_PadInput, 3);
MAP_BUTTON(ImGuiNavInput_PadUp, 10);
MAP_BUTTON(ImGuiNavInput_PadDown, 12);
MAP_BUTTON(ImGuiNavInput_PadLeft, 13);
MAP_BUTTON(ImGuiNavInput_PadRight, 11);
MAP_BUTTON(ImGuiNavInput_PadFocusPrev, 4);
MAP_BUTTON(ImGuiNavInput_PadFocusNext, 5);
MAP_BUTTON(ImGuiNavInput_PadTweakSlow, 4);
MAP_BUTTON(ImGuiNavInput_PadTweakFast, 5);
MAP_ANALOG(ImGuiNavInput_PadScrollUp, 1, +0.3f, +0.9f);
MAP_ANALOG(ImGuiNavInput_PadScrollDown, 1, -0.3f, -0.9f);
MAP_ANALOG(ImGuiNavInput_PadScrollLeft, 0, -0.3f, -0.9f);
MAP_ANALOG(ImGuiNavInput_PadScrollRight,0, +0.3f, +0.9f);
#undef MAP_BUTTON
#undef MAP_ANALOG
}
This is me toying with a macro based version, but I won't keep that. The naive data driven version didn't encourage custom control flow and I'm pretty sure that beginner users will feel restricted to any provided data structure, whereas I want to encourage people to tweak their input. So I'll may just keep something like the above but with a helper function, the question is wether we can provide a similar structure for all the bindings or not. Even if that code isn't part of imgui itself it's nice to figure out something more elegant.
whereas I want to encourage people to tweak their input.
In particular, I am looking at mechanism/idioms to make it easy to selectively transfer input from one part of the app (imgui/tools) to another (game) which may vary per peripheral, per platform, etc. Right now the nav_uses_keyboard
nav_uses_joystick
flags in that demo code are part of the backend but it would be nice to provide a standard interface in imgui to allow app to pass info to the bindings.
I am stuck on this one, so will be thinking aloud:
[ ] Popup: add options to disable auto-closing popups when using a MenuItem/Selectable
@MrMarkie asked for it under those terms:
"Also, when I select a check option from a sub menu, the sub menu closes. I wonder if it should stay open until you press cancel? It's just that setting a bunch of check boxes one after another could be quite laborious"
This is indeed desirable when using MenuItem that are meant to be toggled/checked. But not desirable for other MenuItem that are meant to be activated, such as a typical "Open.." or "Quit" item.
The problem is that the two MenuItem() APIs don't allow to differentiate one from case another.
bool MenuItem(const char* label, const char* shortcut = NULL, bool selected = false, bool enabled = true);
bool MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled = true);
We could fairly assume that the bool*
version is always a checkable when the pointer isn't NULL and not a checkable when NULL.
But in many situations the bool
version can be used (most commonly for code that doesn't use bool for storage or need to access the data via accessors). And then passing 'false' is ambiguous, we can't tell if the item is a checkable or not.
Particularly common as if you just want to create an "Open" item you are likely to just either pass false or NULL inconsistently.
A) If we were to disable auto-closing popup solely based on passing a non-NULL pointer to the bool*
version that would make both overloads behave inconsistently which isn't so great. It would solve 90% use cases for most people but make the whole thing feel inconsistent :(
B) We could introduce new flags to the function, e.g. MenuItemEx()
which could fit the additional information we want (either "can be checked" or "dont close popup"/"close popup"). That seems a little overkill just for this purpose but we can keep this idea brewing. Current API was designed as one of the rare function to take two bools, which is generally not great API design but in this case made a lot of sense as they are very commonly used and we want to make code terse, but that means we can't just add a new flag to an existing flag set. And we are not going to add a third bool while I'm alive. So MenuItemEx(const char* label, const char* label, ImGuiMenuItemFlags flags)
could be a candidate, for again, probably not willing to add that just for this. Even with that added, it would make thing a little more painful to use on the users' side.
C) Third solution, perhaps more approachable, would be to add a new window state flag to enable-disable the behavior of auto-closing popups. The problem is to settle on what's the correct way to name this API. The behavior that's currently used but not exposed publicly is completely arbitrary ("Selectable/MenuItem closes current popup when their parent is a popup, unless ImGuiSelectableFlags_DontClosePopups
flag is set").
If we expose it as an option in the public API it comes with extras expectations: why just Selectable/MenuItem? Can I configure it separately for different types of widget?
D) Fourth solution, if you need this behavior just use Checkbox() and not MenuItem().
@MrMarkie following post above,
I have now added a non-publicly exposed flag, if you include <imgui_internal.h>
you can do in your menu:
ImGui::PushItemFlag(ImGuiItemFlags_SelectableDontClosePopup, true);
/ ImGui::PopItemFlag()
.
To disable closing the menu. You can still call CloseCurrentPopup()
to close a menu explicitly in code.
Putting it in imgui_internal.h for now means I have more flexibility with changing the name/semantic/rules of this later. In particular we might want to split the behavior between mouse and gamepad/keyboard.
MOVING TO #787
(ADMIN EDIT July 2016: see https://github.com/ocornut/imgui/issues/323#issuecomment-233785300 for the current todo list / ongoing tasks)
I've been looking to make our ImGUI accept controller input, for use on consoles. Something like hitting d-pad down to select the next item down, etc.
From what I could tell, however, the only way to do such a thing would be to spoof mouse control. Is this the case? If so, is there a way to ask for the rectangle of a given item outside of the update loop?
I also couldn't find any way to programmatically set the focus of an item other than input text fields; did I miss something?