Skip to content

Commit c841fe5

Browse files
committed
Refactor HotkeyBinder
- Allow binder to limit scope to mouse, keyboard, or joystick/ gamecontroller types. - Allow binding left/right click without immediately canceling or rebinding. - Update right click to pop last bind instead of clearing all binds. - Prevent premature clearing of binder field before input occurs.
1 parent 620ab4d commit c841fe5

File tree

2 files changed

+121
-21
lines changed

2 files changed

+121
-21
lines changed

src/gui/hotkeyBinder.cpp

Lines changed: 106 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,130 @@
1+
#include "hotkeyBinder.h"
12
#include <i18n.h>
23
#include "engine.h"
34
#include "hotkeyConfig.h"
4-
#include "hotkeyBinder.h"
55
#include "theme.h"
66

7+
// Track which binder and which key are actively performing a rebind.
8+
static GuiHotkeyBinder* active_rebinder = nullptr;
9+
static sp::io::Keybinding* active_key = nullptr;
710

8-
GuiHotkeyBinder::GuiHotkeyBinder(GuiContainer* owner, string id, sp::io::Keybinding* key)
9-
: GuiElement(owner, id), has_focus(false), key(key)
11+
GuiHotkeyBinder::GuiHotkeyBinder(GuiContainer* owner, string id, sp::io::Keybinding* key,
12+
sp::io::Keybinding::Type display_filter, sp::io::Keybinding::Type capture_filter)
13+
: GuiElement(owner, id), key(key), display_filter(display_filter), capture_filter(capture_filter)
1014
{
15+
// Use textentry theme styles for binder inputs.
16+
// Someday, this should allow for icon representations instead of relying
17+
// on text.
1118
front_style = theme->getStyle("textentry.front");
1219
back_style = theme->getStyle("textentry.back");
1320
}
1421

22+
bool GuiHotkeyBinder::isAnyRebinding()
23+
{
24+
return active_rebinder != nullptr;
25+
}
26+
27+
void GuiHotkeyBinder::clearFilteredKeys()
28+
{
29+
// Filter binds for this control by their type.
30+
int count = 0;
31+
while (key->getKeyType(count) != sp::io::Keybinding::Type::None) count++;
32+
for (int i = count - 1; i >= 0; --i)
33+
if (key->getKeyType(i) & display_filter) key->removeKey(i);
34+
}
35+
1536
bool GuiHotkeyBinder::onMouseDown(sp::io::Pointer::Button button, glm::vec2 position, sp::io::Pointer::ID id)
1637
{
17-
if (button != sp::io::Pointer::Button::Middle)
18-
key->clearKeys();
19-
if (button != sp::io::Pointer::Button::Right)
20-
key->startUserRebind(sp::io::Keybinding::Type::Keyboard | sp::io::Keybinding::Type::Joystick | sp::io::Keybinding::Type::Controller | sp::io::Keybinding::Type::Virtual);
38+
// If this binder is already rebinding, just take the input and skip this.
39+
// This should allow binding left/middle/right-click without also changing
40+
// the binder's state at the same time.
41+
if (active_rebinder == this) return true;
42+
43+
// Left click: Assign input. Middle click: Add input.
44+
// Right click: Remove last input. Ignore all other mouse buttons.
45+
if (button == sp::io::Pointer::Button::Left)
46+
clearFilteredKeys();
47+
if (button == sp::io::Pointer::Button::Right)
48+
{
49+
int count = 0;
50+
while (key->getKeyType(count) != sp::io::Keybinding::Type::None) count++;
51+
for (int i = count - 1; i >= 0; --i)
52+
{
53+
if (key->getKeyType(i) & display_filter)
54+
{
55+
key->removeKey(i);
56+
break;
57+
}
58+
}
59+
}
60+
61+
if (button == sp::io::Pointer::Button::Left || button == sp::io::Pointer::Button::Middle)
62+
{
63+
const sp::io::Keybinding::Type mouse_types = sp::io::Keybinding::Type::Pointer | sp::io::Keybinding::Type::MouseMovement | sp::io::Keybinding::Type::MouseWheel;
64+
if (capture_filter & mouse_types)
65+
{
66+
// Delay startUserRebind until onMouseUp so that the triggering
67+
// mouse click is not immediately captured as the new binding.
68+
pending_rebind = true;
69+
}
70+
else
71+
{
72+
active_rebinder = this;
73+
active_key = key;
74+
key->startUserRebind(capture_filter);
75+
}
76+
}
77+
2178
return true;
2279
}
2380

81+
void GuiHotkeyBinder::onMouseUp(glm::vec2 position, sp::io::Pointer::ID id)
82+
{
83+
// Complete a pending rebind action.
84+
if (pending_rebind)
85+
{
86+
pending_rebind = false;
87+
active_rebinder = this;
88+
active_key = key;
89+
key->startUserRebind(capture_filter);
90+
}
91+
}
92+
2493
void GuiHotkeyBinder::onDraw(sp::RenderTarget& renderer)
2594
{
26-
focus = key->isUserRebinding();
95+
// Clear the active rebind indicator only when the tracked key's rebind
96+
// completes.
97+
if (active_key != nullptr && !active_key->isUserRebinding())
98+
{
99+
active_rebinder = nullptr;
100+
active_key = nullptr;
101+
}
102+
103+
bool is_my_rebind = (active_rebinder == this);
104+
focus = is_my_rebind;
105+
27106
const auto& back = back_style->get(getState());
28107
const auto& front = front_style->get(getState());
29108

30109
renderer.drawStretched(rect, back.texture, back.color);
31110

32-
string text = key->getHumanReadableKeyName(0);
33-
for(int n=1; key->getKeyType(n) != sp::io::Keybinding::Type::None; n++)
34-
text += "," + key->getHumanReadableKeyName(n);
35-
if (key->isUserRebinding())
36-
text = tr("[New input]");
37-
renderer.drawText(sp::Rect(rect.position.x + 16, rect.position.y, rect.size.x, rect.size.y), text, sp::Alignment::CenterLeft, front.size, front.font, front.color);
111+
string text;
112+
113+
// If this is the active rebinder, update its state to indicate that it's
114+
// ready for input. Otherwise, list the associated binds.
115+
// TODO: This list can get quite long. What should it do on overflow?
116+
if (is_my_rebind) text = tr("[New input]");
117+
else
118+
{
119+
for (int n = 0; key->getKeyType(n) != sp::io::Keybinding::Type::None; n++)
120+
{
121+
if (key->getKeyType(n) & display_filter)
122+
{
123+
if (!text.empty()) text += ",";
124+
text += key->getHumanReadableKeyName(n);
125+
}
126+
}
127+
}
128+
129+
renderer.drawText(sp::Rect(rect.position.x + 16.0f, rect.position.y, rect.size.x, rect.size.y), text, sp::Alignment::CenterLeft, front.size, front.font, front.color);
38130
}

src/gui/hotkeyBinder.h

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
1-
#ifndef HOTKEYBINDER_H
2-
#define HOTKEYBINDER_H
1+
#pragma once
32

43
#include "gui2_element.h"
5-
4+
#include "io/keybinding.h"
65

76
class GuiThemeStyle;
7+
88
class GuiHotkeyBinder : public GuiElement
99
{
1010
private:
11-
bool has_focus;
1211
sp::io::Keybinding* key;
12+
sp::io::Keybinding::Type display_filter;
13+
sp::io::Keybinding::Type capture_filter;
14+
bool pending_rebind = false;
1315

1416
const GuiThemeStyle* front_style;
1517
const GuiThemeStyle* back_style;
18+
19+
void clearFilteredKeys();
1620
public:
17-
GuiHotkeyBinder(GuiContainer* owner, string id, sp::io::Keybinding* key);
21+
GuiHotkeyBinder(GuiContainer* owner, string id, sp::io::Keybinding* key, sp::io::Keybinding::Type display_filter = sp::io::Keybinding::Type::Default, sp::io::Keybinding::Type capture_filter = sp::io::Keybinding::Type::Default);
22+
23+
// Returns true if any binder is actively rebinding. Used to prevent
24+
// game-wide binds like escape from being handled while binding a key.
25+
// The escape control can't be rebound otherwise.
26+
static bool isAnyRebinding();
1827

1928
virtual bool onMouseDown(sp::io::Pointer::Button button, glm::vec2 position, sp::io::Pointer::ID id) override;
29+
virtual void onMouseUp(glm::vec2 position, sp::io::Pointer::ID id) override;
2030
virtual void onDraw(sp::RenderTarget& renderer) override;
2131
};
22-
23-
#endif //HOTKEYBINDER_H

0 commit comments

Comments
 (0)