245 lines
7.1 KiB
C++
245 lines
7.1 KiB
C++
|
#include "StarKeybindingsMenu.hpp"
|
||
|
#include "StarRoot.hpp"
|
||
|
#include "StarAssets.hpp"
|
||
|
#include "StarConfiguration.hpp"
|
||
|
#include "StarGuiReader.hpp"
|
||
|
#include "StarListWidget.hpp"
|
||
|
#include "StarLabelWidget.hpp"
|
||
|
#include "StarButtonWidget.hpp"
|
||
|
#include "StarOrderedSet.hpp"
|
||
|
#include "StarJsonExtra.hpp"
|
||
|
|
||
|
namespace Star {
|
||
|
|
||
|
KeybindingsMenu::KeybindingsMenu() : m_activeKeybinding(nullptr) {
|
||
|
GuiReader reader;
|
||
|
reader.registerCallback("cancel",
|
||
|
[&](Widget*) {
|
||
|
revert();
|
||
|
dismiss();
|
||
|
});
|
||
|
reader.registerCallback("accept",
|
||
|
[&](Widget*) {
|
||
|
apply();
|
||
|
dismiss();
|
||
|
});
|
||
|
reader.registerCallback("setDefault", [&](Widget*) { resetDefaults(); });
|
||
|
|
||
|
auto assets = Root::singleton().assets();
|
||
|
|
||
|
m_maxBindings = assets->json("/interface/windowconfig/keybindingsmenu.config:maxBindings").toUInt();
|
||
|
|
||
|
Json paneLayout = assets->json("/interface/windowconfig/keybindingsmenu.config:paneLayout");
|
||
|
reader.construct(paneLayout, this);
|
||
|
|
||
|
buildListsFromConfig();
|
||
|
|
||
|
m_currentMods = KeyMod::NoMod;
|
||
|
}
|
||
|
|
||
|
KeyboardCaptureMode KeybindingsMenu::keyboardCaptured() const {
|
||
|
return m_activeKeybinding ? KeyboardCaptureMode::KeyEvents : KeyboardCaptureMode::None;
|
||
|
}
|
||
|
|
||
|
bool KeybindingsMenu::sendEvent(InputEvent const& event) {
|
||
|
if (!m_visible)
|
||
|
return false;
|
||
|
|
||
|
if (m_activeKeybinding) {
|
||
|
if (m_context->actions(event).contains(InterfaceAction::KeybindingClear)) {
|
||
|
clearActive();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (m_context->actions(event).contains(InterfaceAction::KeybindingCancel)) {
|
||
|
exitActiveMode();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (m_activeKeybinding) {
|
||
|
// HACK: I need to pass events only to the trash button first.
|
||
|
if (m_activeKeybinding->parent()->fetchChild<ButtonWidget>("deleteBinding")->sendEvent(event))
|
||
|
return true;
|
||
|
|
||
|
if (auto keyUp = event.ptr<KeyUpEvent>()) {
|
||
|
if (Maybe<KeyMod> modKey = KeyChordMods.maybe(keyUp->key)) {
|
||
|
m_currentMods &= ~*modKey;
|
||
|
setKeybinding(KeyChord{keyUp->key, m_currentMods});
|
||
|
return true;
|
||
|
}
|
||
|
} else if (auto keyDown = event.ptr<KeyDownEvent>()) {
|
||
|
Maybe<KeyMod> modKey = KeyModNames.maybeLeft(KeyNames.getRight(keyDown->key));
|
||
|
|
||
|
if (modKey) {
|
||
|
m_currentMods |= *modKey;
|
||
|
return true;
|
||
|
} else {
|
||
|
setKeybinding(KeyChord{keyDown->key, m_currentMods});
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (m_context->actions(event).contains(InterfaceAction::GuiClose)) {
|
||
|
dismiss();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (Pane::sendEvent(event))
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void KeybindingsMenu::show() {
|
||
|
m_origConfiguration = Root::singleton().configuration()->get("bindings");
|
||
|
Pane::show();
|
||
|
}
|
||
|
|
||
|
void KeybindingsMenu::dismissed() {
|
||
|
exitActiveMode();
|
||
|
Pane::dismissed();
|
||
|
}
|
||
|
|
||
|
void KeybindingsMenu::buildListsFromConfig() {
|
||
|
m_playerList = fetchChild<ListWidget>("categories.tabs.player.scrollArea.keyList");
|
||
|
m_toolBarList = fetchChild<ListWidget>("categories.tabs.toolbar.scrollArea.keyList");
|
||
|
m_gameList = fetchChild<ListWidget>("categories.tabs.game.scrollArea.keyList");
|
||
|
|
||
|
m_childToAction.clear();
|
||
|
|
||
|
auto doKeybindingsFor = [&](ListWidgetPtr const& list, Json const& keybinds) {
|
||
|
list->clear();
|
||
|
|
||
|
list->registerMemberCallback("activateBinding", [this](Widget* widget) { activateBinding(widget); });
|
||
|
|
||
|
list->registerMemberCallback("deleteBinding", [this](Widget*) { clearActive(); });
|
||
|
|
||
|
auto config = Root::singleton().configuration();
|
||
|
auto bindings = config->get("bindings");
|
||
|
|
||
|
for (auto const& keybind : keybinds.iterateArray()) {
|
||
|
auto newListMember = list->addItem();
|
||
|
auto actionString = keybind.get("action").toString();
|
||
|
auto action = InterfaceActionNames.getLeft(actionString);
|
||
|
List<KeyChord> inputDesc;
|
||
|
try {
|
||
|
for (auto const& bindingEntry : bindings.get(actionString).iterateArray())
|
||
|
inputDesc.append(inputDescriptorFromJson(bindingEntry));
|
||
|
} catch (StarException const& e) {
|
||
|
Logger::warn("Could not load keybinding for %s. %s\n", actionString, e.what());
|
||
|
}
|
||
|
|
||
|
m_childToAction.insert({newListMember->fetchChild<ButtonWidget>("boundKeys").get(), action});
|
||
|
newListMember->fetchChild<LabelWidget>("actionName")->setText(keybind.getString("label"));
|
||
|
newListMember->fetchChild<ButtonWidget>("boundKeys")->setText(StringList(inputDesc.transformed(printInputDescriptor)).join(", "));
|
||
|
newListMember->fetchChild<ButtonWidget>("deleteBinding")->hide();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
auto assets = Root::singleton().assets();
|
||
|
doKeybindingsFor(m_playerList, assets->json("/interface/windowconfig/keybindingsmenu.config:keyActions.player"));
|
||
|
doKeybindingsFor(m_toolBarList, assets->json("/interface/windowconfig/keybindingsmenu.config:keyActions.toolbar"));
|
||
|
doKeybindingsFor(m_gameList, assets->json("/interface/windowconfig/keybindingsmenu.config:keyActions.game"));
|
||
|
}
|
||
|
|
||
|
bool KeybindingsMenu::activateBinding(Widget* widget) {
|
||
|
exitActiveMode();
|
||
|
|
||
|
m_activeKeybinding = widget;
|
||
|
m_activeKeybinding->parent()->fetchChild<ButtonWidget>("deleteBinding")->show();
|
||
|
convert<ButtonWidget>(m_activeKeybinding)->setHighlighted(true);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void KeybindingsMenu::setKeybinding(KeyChord desc) {
|
||
|
if (!m_activeKeybinding)
|
||
|
return;
|
||
|
|
||
|
auto out = inputDescriptorToJson(desc);
|
||
|
|
||
|
auto config = Root::singleton().configuration();
|
||
|
auto base = config->get("bindings");
|
||
|
|
||
|
auto action = m_childToAction.get(m_activeKeybinding);
|
||
|
auto key = InterfaceActionNames.getRight(action);
|
||
|
|
||
|
auto bindings = OrderedHashSet<Json>::from(base.get(key).toArray());
|
||
|
|
||
|
if (bindings.contains(out))
|
||
|
bindings.clear();
|
||
|
|
||
|
bindings.add(out);
|
||
|
|
||
|
if (bindings.size() > m_maxBindings)
|
||
|
bindings.removeFirst();
|
||
|
|
||
|
base = base.set(key, JsonArray::from(bindings));
|
||
|
|
||
|
config->set("bindings", base);
|
||
|
|
||
|
String buttonText;
|
||
|
|
||
|
for (auto const& entry : base.get(key).iterateArray()) {
|
||
|
auto stored = inputDescriptorFromJson(entry);
|
||
|
buttonText = String::joinWith(", ", buttonText, printInputDescriptor(stored));
|
||
|
}
|
||
|
|
||
|
convert<ButtonWidget>(m_activeKeybinding)->setText(buttonText);
|
||
|
|
||
|
apply();
|
||
|
exitActiveMode();
|
||
|
}
|
||
|
|
||
|
void KeybindingsMenu::clearActive() {
|
||
|
if (!m_activeKeybinding)
|
||
|
return;
|
||
|
|
||
|
auto config = Root::singleton().configuration();
|
||
|
auto base = config->get("bindings").toObject();
|
||
|
|
||
|
auto action = m_childToAction.get(m_activeKeybinding);
|
||
|
auto key = InterfaceActionNames.getRight(action);
|
||
|
|
||
|
base[key] = JsonArray{};
|
||
|
config->set("bindings", base);
|
||
|
|
||
|
convert<ButtonWidget>(m_activeKeybinding)->setText("<Unbound>");
|
||
|
|
||
|
apply();
|
||
|
exitActiveMode();
|
||
|
}
|
||
|
|
||
|
void KeybindingsMenu::exitActiveMode() {
|
||
|
if (!m_activeKeybinding)
|
||
|
return;
|
||
|
|
||
|
m_activeKeybinding->parent()->fetchChild<ButtonWidget>("deleteBinding")->hide();
|
||
|
convert<ButtonWidget>(m_activeKeybinding)->setHighlighted(false);
|
||
|
m_activeKeybinding = nullptr;
|
||
|
m_currentMods = KeyMod::NoMod;
|
||
|
}
|
||
|
|
||
|
void KeybindingsMenu::apply() {
|
||
|
m_context->refreshKeybindings();
|
||
|
}
|
||
|
|
||
|
void KeybindingsMenu::revert() {
|
||
|
Root::singleton().configuration()->set("bindings", m_origConfiguration);
|
||
|
apply();
|
||
|
|
||
|
buildListsFromConfig();
|
||
|
}
|
||
|
|
||
|
void KeybindingsMenu::resetDefaults() {
|
||
|
auto config = Root::singleton().configuration();
|
||
|
config->set("bindings", config->getDefault("bindings"));
|
||
|
apply();
|
||
|
|
||
|
buildListsFromConfig();
|
||
|
}
|
||
|
|
||
|
}
|