diff options
Diffstat (limited to 'source/frontend/StarKeybindingsMenu.cpp')
-rw-r--r-- | source/frontend/StarKeybindingsMenu.cpp | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/source/frontend/StarKeybindingsMenu.cpp b/source/frontend/StarKeybindingsMenu.cpp new file mode 100644 index 0000000..85b5b56 --- /dev/null +++ b/source/frontend/StarKeybindingsMenu.cpp @@ -0,0 +1,244 @@ +#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(); +} + +} |