-
-
Notifications
You must be signed in to change notification settings - Fork 97
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
Allow hard-coded keyboard shortcuts in GUI controls to be customised #1532
Allow hard-coded keyboard shortcuts in GUI controls to be customised #1532
Comments
The main reason for hard-coded shortcuts is that regular shortcuts don't support echo events. godotengine/godot#36493 makes this issue much easier to fix. |
You beat me to it! Was abstracting
|
if (!p_ev->is_pressed()) {
if (get_shortcut_copy()->shortcut_match(p_ev)) {
// < logic for shortcut >
}
} |
Yeah agreed that re-implementing is a pain, though it doesn't feel like much of a burden as it's really only an extra The other problem is it being so heavily weighted, Assuming it does go into void set_shortcut_paste(const InputEvent &p_input_event) {
_set_shortcut("paste", p_input_event);
} Whereas void set_shortcut(Shortcut p_shortcut, const InputEvent &p_input_event) {
switch (p_shortcut) {
case SHORTCUT_PASTE: {
_set_shortcut("paste", p_input_event);
} break;
}
} For the echo support, as you say it's not directly related, but rather we've had to hack some things into |
Yes, to sum up my discussion with @EricEzaM. My opinion is that, if we have not a lot of shortcuts, we should expose getters/setters for each specific shortcuts. This make the API simpler. I don't know how many shortcuts textedit has, but my limit would be 5 for it being worth going for a string or enum based API. However, I'm not against the fact that, internally, it would use strings (I think it's preferable compared to enums), to handle the internal logic. So yeah, what @Paulb23 suggests sounds good to me. |
Personally I prefer consistency - so I am not a huge fan of mixing the styles. At this stage I would probably lean towards using enums over strings and just store them in a Also, if we put the logic in I have done an initial look at /*
* Completion Index Up
* Completion Index Down
* Completion Index Page Up
* Completion Index Page Down
* Completion Index First
* Completion Index Last
* Completion Confirm
*
* New Line Below
* New Line Above
* Escape
* Indent
* Dedent
* Backspace
* Left + Keypad Variant
* Right + Keypad Variant
* Up + Keypad Variant
* Down + Keypad Variant
* Line Start (Home) + Keypad Variant
* Line End (End) + Keypad Variant
* Page Up + Keypad Variant
* Page Down + Keypad Variant
* Delete
* Select All
* Paragraph Start
* Paragraph End (Control + E on Apple?)
*
*
* Cut
* Copy
* Paste
*
* Undo
* Redo
*
* Query Completion (e.g. Ctrl + Space)
* Open Context Menu
*
* Toggle Insert Mode
*
*/ |
How would you deal with actions that have multiple shortcuts? E.g. confirming a completion option has Enter, Keypad Enter and Tab. Same thing with Control+H = Backspace on apple keys. The easiest way would be to just have alternate shortcuts included in the enum...
That is pretty messy though. I don't think storing a Edit: Actually I think you could store a
|
The downside of TextEdit::Shortcut {
SHORTCUT_PASTE,
SHORTCUT_TEXTEDIT_MAX
}
CodeEdit::Shortcut {
SHORTCUT_COMMENT_LINE = TextEdit::Shortcut::SHORTCUT_TEXTEDIT_MAX
} Otherwise internally it will have to be For multiple shortcuts with the same action, was thinking exactly that, multiple entries. There shouldn't be many where this is needed. Not sure I follow with not being able to overwrite them. The Vector<Ref<InputEvent>> shortcut_list;
shortcut_list[TextEdit::Shortcut::SHORTCUT_PASTE]
Yeah I'm looking forward to cleaning up that method, especially with all the remapping. That looks like a solid list, couple are missing that I can think of off the top of my head:
And then |
Unfortunately there are quite a few. A few examples include ENTER and KP_ENTER being interchangeable, Ctrl + C and Ctrl + Insert, as well as Ctrl V and Shift Insert. You also have things like for apple keys, Control + F/B/P/N/D/H for right, left, up, down, delete and backspace, respectively. Then don't forget there is also the Numpad version of those when numlock is off: 6, 4, 8, 2, preiod for R/L/U/D/delete and there is also Home/End and PgUp/PgDn there too! There are more to be found for sure. As a result I think it would be pretty messy to have multiple entries/alternates for so many. And some would only have 2 alts, some would have 3... etc. I will see how it goes and maybe if I can get something nice-looking that uses something like |
Hmm, having to pass in a list of void set_shortcut(Shortcut p_shortcut, const InputEvent &p_input_event, const Vector<InputEvent> &p_alt_input_events = null); Otherwise yeah, not to sure how else to handle it. Even with separate methods that doesn't feel quite right. As for treating Can store them in a |
Current iteration of work-in-progress API is: HashMap<int, Vector<Ref<InputEvent>>> built_in_shortcuts;
// Having the user to construct the vector themselves seemed like a massive pain on the user. This limit's it to 5 alternates, but it's more convenient for the end-user.
void _set_built_in_shortcut(const int &p_idx, const Ref<InputEvent> &p_shortcut, const Ref<InputEvent> &p_alt_shortcut1 = Ref<InputEvent>(), const Ref<InputEvent> &p_alt_shortcut2 = Ref<InputEvent>(), const Ref<InputEvent> &p_alt_shortcut3 = Ref<InputEvent>(), const Ref<InputEvent> &p_alt_shortcut4 = Ref<InputEvent>(), const Ref<InputEvent> &p_alt_shortcut5 = Ref<InputEvent>());
// Loops through shortcuts and returns true if one matches
bool match_builtin_shortcut(const int &p_idx, const Ref<InputEvent> &p_event) const;
void override_builtin_shortcut(const int &p_idx, const Ref<InputEvent> &p_shortcut, const Ref<InputEvent> &p_alt_shortcut1 = Ref<InputEvent>(), const Ref<InputEvent> &p_alt_shortcut2 = Ref<InputEvent>(), const Ref<InputEvent> &p_alt_shortcut3 = Ref<InputEvent>(), const Ref<InputEvent> &p_alt_shortcut4 = Ref<InputEvent>(), const Ref<InputEvent> &p_alt_shortcut5 = Ref<InputEvent>()); I also added a convenience method in InputEventKey for quickly generating // This method is static
Ref<InputEventKey> InputEventKey::create_reference(uint32_t p_keycode, uint32_t p_modifier_masks) {
Ref<InputEventKey> ie;
ie.instance();
ie->set_keycode(p_keycode);
if (p_modifier_masks & KEY_MASK_SHIFT) {
ie->set_shift(true);
}
if (p_modifier_masks & KEY_MASK_ALT) {
ie->set_alt(true);
}
if (p_modifier_masks & KEY_MASK_CTRL) {
ie->set_control(true);
}
if (p_modifier_masks & KEY_MASK_CMD) {
ie->set_command(true);
}
if (p_modifier_masks & KEY_MASK_META) {
ie->set_metakey(true);
}
return ie;
}
//USAGE
ie = InputEventKey::create_reference(KEY_ENTER);
Ref<InputEventKey> ie2 = InputEventKey::create_reference(KEY_KP_ENTER);
Ref<InputEventKey> ie3 = InputEventKey::create_reference(KEY_TAB);
_set_built_in_shortcut(SHORTCUT_COMPLETION_CONFIRM, ie, ie2, ie3);
// What I had to do before... yuck
Ref<InputEventKey> ie;
ie.instance();
ie.set_keycode(KEY_ENTER);
Ref<InputEventKey> ie2;
ie2.instance();
ie2.set_keycode(KEY_KP_ENTER);
Ref<InputEventKey> ie3;
ie3.instance();
ie3.set_keycode(KEY_TAB);
// Also easy to add modifiers:
ie = InputEventKey::create_reference(KEY_ENTER, KEY_MASK_SHIFT | KEY_MASK_CMD); |
That looks good to me, multiple args should be okay. Couple of questions:
|
TextEdit::TextEdit() {
// ...
ie = InputEventKey::create_reference(KEY_ENTER, KEY_MASK_SHIFT | KEY_MASK_CMD);
ie2 = InputEventKey::create_reference(KEY_KP_ENTER, KEY_MASK_SHIFT | KEY_MASK_CMD);
_set_built_in_shortcut(SHORTCUT_NEW_LINE_ABOVE, ie, ie2);
// ...
}
void Control::override_builtin_shortcut(const int &p_idx, const Ref<InputEvent> &p_shortcut, const Ref<InputEvent> &p_alt_shortcut1, ... etc) {
if (!data.built_in_shortcuts.has(p_idx)) {
return;
}
_set_built_in_shortcut(p_idx, p_shortcut, p_alt_shortcut1, ... etc);
}
|
Final list of shortcuts... wow. I'm done with TextEdit almost I think. I'll put up a draft PR soon so you can see what the code looks like. I find TextEdit much easier to follow now, especially after putting a lot of logic which was in _gui_input into their own methods. // All Shortcuts. Default keybinds shown in comment. "SELECT" variants use SHIFT mask.
enum Shortcuts {
// AutoComplete
SHORTCUT_COMPLETION_QUERY, // CMD SPACE (ALT SPACE on Apple)
SHORTCUT_COMPLETION_INDEX_UP, // UP
SHORTCUT_COMPLETION_INDEX_DOWN, // DOWN
SHORTCUT_COMPLETION_INDEX_PAGE_UP, // PGUP
SHORTCUT_COMPLETION_INDEX_PAGE_DOWN, // PGDN
SHORTCUT_COMPLETION_INDEX_FIRST, // HOME
SHORTCUT_COMPLETION_INDEX_LAST, // END
SHORTCUT_COMPLETION_CONFIRM, // ENTER, TAB
SHORTCUT_COMPLETION_CANCEL, // ESC
SHORTCUT_COMPLETION_CLEAR_HINT, // ESC
// Newlines
SHORTCUT_NEW_LINE_BELOW, // CMD ENTER
SHORTCUT_NEW_LINE_BELOW_SPLIT_CURRENT, // ENTER
SHORTCUT_NEW_LINE_ABOVE, // CMD SHIFT ENTER
// Indentation
SHORTCUT_INDENT, // TAB
SHORTCUT_DEDENT, // CMD TAB
// Backspace and Delete
SHORTCUT_BACKSPACE, // BKSPC
SHORTCUT_BACKSPACE_WORD, // CMD BKSPC (ALT BKSPC on Apple)
SHORTCUT_BACKSPACE_ALL_TO_LEFT, // None (CMD BKSPC on Apple)
SHORTCUT_DELETE, // DEL
SHORTCUT_DELETE_WORD, // CMD DEL (ALT DEL on Apple)
SHORTCUT_DELETE_ALL_TO_RIGHT, // None (CMD DEL on Apple)
// Cursor Movement left/right
SHORTCUT_CURSOR_LEFT, // LEFT
SHORTCUT_CURSOR_WORD_LEFT, // CMD LEFT (ALT LEFT on Apple)
SHORTCUT_CURSOR_SELECT_LEFT,
SHORTCUT_CURSOR_SELECT_WORD_LEFT,
SHORTCUT_CURSOR_RIGHT, // RIGHT
SHORTCUT_CURSOR_WORD_RIGHT, // CMD RIGHT (ALT RIGHT on Apple)
SHORTCUT_CURSOR_SELECT_RIGHT,
SHORTCUT_CURSOR_SELECT_WORD_RIGHT,
// Cursor Movement up/down
SHORTCUT_CURSOR_UP, // UP
SHORTCUT_CURSOR_DOWN, // DOWN
SHORTCUT_CURSOR_SELECT_UP,
SHORTCUT_CURSOR_SELECT_DOWN,
// Cursor Movement Line start/end
SHORTCUT_CURSOR_LINE_START, // HOME (CTRL A on Apple, CMD LEFT on Apple)
SHORTCUT_CURSOR_LINE_END, // END (CTRL E on Apple, CMD RIGHT on Apple)
SHORTCUT_CURSOR_SELECT_LINE_START,
SHORTCUT_CURSOR_SELECT_LINE_END,
// Cursor Movement Page up/down
SHORTCUT_CURSOR_PAGE_UP, // PgUp
SHORTCUT_CURSOR_PAGE_DOWN, // PgDn
SHORTCUT_CURSOR_SELECT_PAGE_UP,
SHORTCUT_CURSOR_SELECT_PAGE_DOWN,
// Cursor Movement document start/end
SHORTCUT_CURSOR_DOCUMENT_START, // CMD HOME (CMD UP on Apple)
SHORTCUT_CURSOR_DOCUMENT_END, // CMD END (CMD DOWN on Apple)
SHORTCUT_CURSOR_SELECT_DOCUMENT_START,
SHORTCUT_CURSOR_SELECT_DOCUMENT_END,
// Scrolling
SHORTCUT_SCROLL_LINES_UP, // CMD UP (+ ALT on Apple)
SHORTCUT_SCROLL_LINES_DOWN, // CMD DOWN (+ ALT on Apple)
// Select all, Cut, Copy, Paste
SHORTCUT_SELECT_ALL, // CMD A
SHORTCUT_CUT, // SHIFT DEL, CMD X
SHORTCUT_COPY, // CMD C, CMD INS
SHORTCUT_PASTE, // CMD V, SHIFT INS
// Undo/Redo
SHORTCUT_UNDO, // CMD Z
SHORTCUT_REDO, // CMD SHIFT Z, CMD Y
// Misc
SHORTCUT_OPEN_CONTEXT_MENU, // MENU
SHORTCUT_TOGGLE_INSERT_MODE, // INS
}; |
I have switched from using An issue arose where when using |
How big of a problem is this...? In TextEdit, by default 'paste' has 2 shortcuts. Now, of course this only allows you to set one shortcut in the editor. When you use |
Yeah if
We will end up calling |
Yeah those sound like good changes to make. One thing I am concerned about though: currently TextEdit has 55 shortcuts... this is easy to have visibility of in an Enum, but if we switch to strings it will then be much harder to track what built-in shortcuts exist. One way to get both systems is to use something that maps the enum to a string... like perhaps a Another option which is a bit more 'hackish' would be to use a macro There is also the case of documenting these built in shortcuts... Users should know what the builtin shortcuts are and what strings are used to store them. |
Random thought that I glossed over originally, but is there any reason we can't use
That said, Agreed, |
I think the issue with using InputMap is that there are a lot of one-off shortcuts, like the SHORTCUT_CURSOR_LINE_START, // HOME (CTRL A on Apple, CMD LEFT on Apple)
SHORTCUT_CURSOR_LINE_END, // END (CTRL E on Apple, CMD RIGHT on Apple)
SHORTCUT_CURSOR_SELECT_LINE_START,
SHORTCUT_CURSOR_SELECT_LINE_END, I don't think this would look good on an input map. Now, granted, the above is an example of a shortcut that would very rarely want to be changed. I have included them as such so that all shortcuts follow the same system. This makes the code very simple and easy to understand.
if (_match_builtin_shortcut(SHORTCUT_CURSOR_LINE_START, p_gui_input) && p_gui_input->get_shift()) {
_move_cursor_to_line_start(p_select = true);
} EDIT: Never mind, that won't work... |
Are we just complicating it too much by having the requirement that this functionality should be able to be extended in GDScript? If we assume this should only be able to be used in C++, and not exposed to the user, then it simplifies it a bit. We can use Enums for storage and don't have to worry about storing the shortcut identifiers as strings. Users would still be able to override the shortcuts if they wanted, using
The rest are stock-standard navigation and text manipulation shortcuts which have been assigned shortcuts which maintain consistency with the selected operating system (i.e. for mac, https://support.apple.com/en-au/HT201236#text) |
Here is the diff between current master and my WIP branch
I have tested all shortcuts on Windows and it is all working well for me. Would need someone to test on mac though. |
This is more a UX issue then anything, could solved by allowing you to hide sections in the UI, and / or sub sections. Which is why I'm not too worried about that. We also have some actions already such as Currently it feels like we are re-inventing the same thing, especially with allowing alt shortcuts in the editor etc. I guess the main contention point is how separate text editing should be to the rest of the UI nodes. I don't think we are exposing too much, as some users want to add for example, vim controls. So in this case they would need to be able to remap caret move keys to: Took a very quick glance and it looks amazing, the code is so much easier to follow! |
Ok this is heading towards a much broader discussion of input in general, but it's good to have. This could probably do with a bigger overhaul of the input map editor for both Project and Editor Settings too... Something I have also been considering. Anyway, currently existing Additionally, we could shave off many Ref<InputEventKey> k = p_event;
k = k->duplicate();
bool shift_pressed = k->get_shift();
// Remove shift or else actions will not match
k->set_shift(false);
if (k->is_action_pressed("ui_text_cursor_left")) {
_move_cursor_left(p_select = false);
}
if (k->is_action_pressed("ui_text_cursor_left") && shift_pressed) {
_move_cursor_left(p_select = true);
} As in my example above, I would imagine this entailing adding a number of built-in input maps for the various built-in nodes and things around the editor. I think this would be the exhaustive list. // Shortcuts for text-based nodes only
ui_text_newline
ui_text_newline_blank // (Cmd + Enter, enters a new line without splitting the current one at the cursor)
ui_text_newline_above
ui_text_indent
ui_text_dedent
ui_text_backspace
ui_text_backspace_word
ui_text_backspace_all_to_left
ui_text_delete
ui_text_delete_word
ui_text_delete_all_to_right
ui_text_cursor_left
ui_text_cursor_word_left
ui_text_cursor_right
ui_text_cursor_word_right
ui_text_cursor_up
ui_text_cursor_down
ui_text_cursor_line_start
ui_text_cursor_line_end
ui_text_cursor_page_up
ui_text_cursor_page_down
ui_text_cursor_document_start
ui_text_cursor_document_end
ui_text_scroll_up
ui_text_scroll_down
ui_text_select_all
ui_text_toggle_insert_mode
// Graph Edit
ui_graph_duplicate
ui_graph_delete
// FileDialog
ui_filedialog_up_one_level
ui_filedialog_refresh
ui_filedialog_show_hidden
// And then some shortcuts which I think could be global for ui, for text, graphs, etc
ui_cut
ui_copy
ui_paste
ui_undo
ui_redo I think these would go into a separate section of Editor Shortcuts/Project Input Map where only these built-ins are shown. That would make it very clear to the user that these shortcuts are built-in to the engine. One last thing to handle would be overriding these on a case-by-case basis... for example, in the Editor, the ScriptTextEditor has a shortcut set for |
I updated my branch with a new commit which uses input actions instead. I used the action list from my previous comment as the basis.
I think this is heading in the right direction. However one thing this does make harder is overriding shortcuts on an individual basis. For example, user wants to change copy shortcut from I think to go along with this, changes need to be made in the Input Map editor and Editor Shortcuts editor. Firstly builtin shortcuts should have their own section. I don't think hiding them is that great of an option. I would prefer a separate toggle-able section... Hacked together mockup: |
Yeah 4.0 is a great time to get this sorted. Ahh, I was referring to the project Assuming, For the "customisable" To expand on my earlier point, the downside of the |
How did it work currently for things like TextEdit? The ifdefs for I don't really know how exporting works in Godot... did the Export Template for mac enable these pre-processor variables or something so TextEdit would compile with the right shortcuts? If I was developing on a windows machine and exporting for mac would the shortcuts be correct? |
The "Export Templates" is a compiled version of Godot without |
The input map is just a specialised view for "input" project settings. Normal project settings can be overridden for certain export templates... could the same thing be done for the input map? So you would have only one action, but then underneath that you would have multiple assigned keys, and you could limit them to specific platforms. |
Ok so I just did a quick test in 3.2.2: Make 2 input actions, one called func _process(delta):
if Input.is_action_pressed("test"):
color = Color.red
else:
color = Color.white Play the scene normally - the key mapped to 'test' works normally (I set it to W). Now, Go to Project > Export... and select HTML5. This should be enough to show the preview icon for HTML5. If you preview in HTML5 the normal All we really need to do is add some UI to make overrides for specific input actions. And, of course, add the defaults to the ProjectSettings constructor where the input actions are set. |
Fantastic, I'm not too familiar with how that side works, but if we can do that, problem solved! I think this definitely is the right direction to take this now. |
Ok, I think I have this in a pretty good state right now. A lot of changes had to be made to the Action Editor so I could reuse it for EditorSettings (previously it was coupled to ProjectSettings). This led to a complete overhaul of the UI for editing actions to address a number of other issues too. I think this PR should close 10+ issues. I also had to make sure that the overrides were correctly serialising to and loading from the editor_settings.tres. Which took a little while to get right :P Below is an example of ScriptTextEditor using the builtin shortcut for Here is a longer demo of the same interface reused in ProjectSettings. Latest changes are pushed to my branch (diff here: https://github.com/godotengine/godot/compare/master...EricEzaM:WIP%2Foverridable-builtin-shortcuts?diff=split&expand=1) which can be built and ran if you want to take a look. |
Describe the problem or limitation you are having in your project:
In the Godot engine, a number of GUI controls have keyboard shotcuts 'built-in' to the C++ code. For example, in Text Edit, Copy and Paste are hard-coded to
Ctrl + C
andCtrl + V
respectively. This can cause issues and confusion when trying to rebind input that uses these hard-coded shortcuts.Often these shortcuts give the impression that they can be rebound... Take a look at the Script Text Editor shortcuts in Editor Settings.
However, even if you change this binding,
Ctrl + C
andCtrl + V
still both work for copy and paste, since they are built in to the TextEdit code, which ScriptTextEditor uses under the hood.Related issues:
godotengine/godot#29490
godotengine/godot#42139
godotengine/godot#12164
godotengine/godot#17927
godotengine/godot#26854
godotengine/godot#31739 (this is loosely related - but the comment @akien-mga makes at the end is relevant - "A recent example: in 28561 I added a feature to convert clipboard indentation on paste, but I can't have it overwrite Ctrl+V because that's hardcoded in TextEdit. The new API should allow overriding TextEdit's input management in CodeEdit and EditorCodeEdit."
godotengine/godot#20065 - Ideally this proposals would help make the built-in editor shortcuts more obvious.
Describe the feature / enhancement and how it helps to overcome the problem or limitation:
A method to be able to rebind these shortcuts which is completely separate to EditorSettings to avoid breaching godotengine/godot#29730.
Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:
PLEASE NOTE: The implementation has changed a lot since this was first written. I am now using the InputAction system. Please read the latest comments on this post for a clearer picture of how it will work.
I have a test branch working already. Essentially, each GUI Control node would keep its own mini version of EditorSettings shortcuts which only applies to itself. Here is a mockup:
control.cpp
From here, I think there are 2 options.
_set_built_in_shortcut
andget_builtin_shortcut
internally. This would remove the need foroverride_builtin_shortcut
In this example text edit has quite a few shortcuts so there would be a number of methods:Control
to set the shortcuts. This would rely on strings (fragile) but would not require methods on the GUI control for every shortcut.Either 1) or 2) would work... Built-in shortcuts don't change very often and C++ user's would not be adding them on the fly (like they can with EditorSettings), so this points to 1) being the better option as it does not rely on fragile strings and would be a more consistent and easy to understand API.
Ok, moving on... For brevity, I will use option 1) for the following examples.
Usage in nodes which derive from control, e.g. TextEdit
Usage when you want to override the shortcut... E.g. In ScriptTextEditor
Now you can change the TextEdit shortcuts via an Editor Shortcut if you like, or you can just set a custom shortcut without using EditorSettings.
Note that ED_SHORTCUT is not being checked directly in the TextEdit, so if you make a change to EditorSettings it will no be used until the editor is restarted. I believe you can make the shortcuts update after a change if you handled
NOTIFICATION_EDITOR_SETTINGS_CHANGED
innotification()
and just calledset_shortcut_...(ED_GET_SHORTCUT(...))
again. Assuming the notification is called on shortcut change - that might have to be added, but this is a small detail overall.The text was updated successfully, but these errors were encountered: