diff --git a/doomsday/client/include/ui/commandbinding.h b/doomsday/client/include/ui/commandbinding.h index d139a2d08d..663c7a3672 100644 --- a/doomsday/client/include/ui/commandbinding.h +++ b/doomsday/client/include/ui/commandbinding.h @@ -20,10 +20,13 @@ #ifndef CLIENT_INPUTSYSTEM_COMMANDBINDING_H #define CLIENT_INPUTSYSTEM_COMMANDBINDING_H +#include #include #include "Binding" #include "ddevent.h" +class BindContext; + /** * Utility for handling event => command binding records. * @@ -45,6 +48,18 @@ class CommandBinding : public Binding void resetToDefaults(); de::String composeDescriptor(); + + /** + * Evaluate the given binding and event pair and attempt to generate an Action. + * + * @param event Event to match against. + * @param context Context in which the binding exists. + * @param respectHigherContexts Bindings are shadowed by higher active contexts. + * + * @return Action instance (caller gets ownership), or @c nullptr if no matching. + */ + de::Action *makeAction(ddevent_t const &event, BindContext const &context, + bool respectHigherContexts) const; }; #endif // CLIENT_INPUTSYSTEM_COMMANDBINDING_H diff --git a/doomsday/client/include/ui/impulsebinding.h b/doomsday/client/include/ui/impulsebinding.h index 2394e524ed..2d01c2be66 100644 --- a/doomsday/client/include/ui/impulsebinding.h +++ b/doomsday/client/include/ui/impulsebinding.h @@ -40,7 +40,7 @@ enum ibcontroltype_t #define IBDF_TIME_STAGED 0x2 /** - * Utility for handling event => command binding records. + * Utility for handling input-device-control => impulse binding records. * * @ingroup ui */ diff --git a/doomsday/client/src/ui/bindcontext.cpp b/doomsday/client/src/ui/bindcontext.cpp index ac66ef0231..e55091119f 100644 --- a/doomsday/client/src/ui/bindcontext.cpp +++ b/doomsday/client/src/ui/bindcontext.cpp @@ -22,12 +22,9 @@ #include #include #include -#include #include #include "clientapp.h" -#include "world/p_players.h" // P_ConsoleToLocal - #include "CommandAction" #include "CommandBinding" #include "ImpulseBinding" @@ -35,12 +32,6 @@ #include "ui/inputdevice.h" #include "ui/playerimpulse.h" -/// @todo: remove -#include "ui/inputdeviceaxiscontrol.h" -#include "ui/inputdevicebuttoncontrol.h" -#include "ui/inputdevicehatcontrol.h" -// todo ends - using namespace de; static inline InputSystem &inputSys() @@ -330,192 +321,6 @@ bool BindContext::deleteBinding(int id) return false; } -/** - * Substitute placeholders in a command string. Placeholders consist of two characters, - * the first being a %. Use %% to output a plain % character. - * - * - %i: id member of the event - * - %p: (symbolic events only) local player number - * - * @param command Original command string with the placeholders. - * @param event Event data. - * @param out String with placeholders replaced. - */ -static void substituteInCommand(String const &command, ddevent_t const &event, ddstring_t *out) -{ - DENG2_ASSERT(out); - Block const str = command.toUtf8(); - for(char const *ptr = str.constData(); *ptr; ptr++) - { - if(*ptr == '%') - { - // Escape. - ptr++; - - // Must have another character in the placeholder. - if(!*ptr) break; - - if(*ptr == 'i') - { - int id = 0; - switch(event.type) - { - case E_TOGGLE: id = event.toggle.id; break; - case E_AXIS: id = event.axis.id; break; - case E_ANGLE: id = event.angle.id; break; - case E_SYMBOLIC: id = event.symbolic.id; break; - - default: break; - } - Str_Appendf(out, "%i", id); - } - else if(*ptr == 'p') - { - int id = 0; - if(event.type == E_SYMBOLIC) - { - id = P_ConsoleToLocal(event.symbolic.id); - } - Str_Appendf(out, "%i", id); - } - else if(*ptr == '%') - { - Str_AppendChar(out, *ptr); - } - continue; - } - - Str_AppendChar(out, *ptr); - } -} - -/** - * Evaluate the given binding and event pair and attempt to generate an Action. - * - * @param context Context in which @a bind exists. - * @param bind Binding to match against. - * @param event Event to match against. - * @param respectHigherContexts Bindings are shadowed by higher active contexts. - * - * @return Action instance (caller gets ownership), or @c nullptr if no matching. - */ -static Action *commandActionFor(BindContext const &context, CommandBinding const &bind, - ddevent_t const &event, bool respectHigherContexts) -{ - if(bind.geti("type") != event.type) return nullptr; - - InputDevice const *dev = nullptr; - if(event.type != E_SYMBOLIC) - { - if(bind.geti("deviceId") != event.device) return nullptr; - - dev = inputSys().devicePtr(bind.geti("deviceId")); - if(!dev || !dev->isActive()) - { - // The device is not active, there is no way this could get executed. - return nullptr; - } - } - - switch(event.type) - { - case E_TOGGLE: { - if(bind.geti("controlId") != event.toggle.id) - return nullptr; - - DENG2_ASSERT(dev); - InputDeviceButtonControl &button = dev->button(bind.geti("controlId")); - - if(respectHigherContexts) - { - if(button.bindContext() != &context) - return nullptr; // Shadowed by a more important active class. - } - - // We're checking it, so clear the triggered flag. - button.setBindContextAssociation(InputDeviceControl::Triggered, UnsetFlags); - - // Is the state as required? - switch(BindingCondition::ControlTest(bind.geti("test"))) - { - case BindingCondition::ButtonStateAny: - // Passes no matter what. - break; - - case BindingCondition::ButtonStateDown: - if(event.toggle.state != ETOG_DOWN) - return nullptr; - break; - - case BindingCondition::ButtonStateUp: - if(event.toggle.state != ETOG_UP) - return nullptr; - break; - - case BindingCondition::ButtonStateRepeat: - if(event.toggle.state != ETOG_REPEAT) - return nullptr; - break; - - case BindingCondition::ButtonStateDownOrRepeat: - if(event.toggle.state == ETOG_UP) - return nullptr; - break; - - default: return nullptr; - } - break; } - - case E_AXIS: - if(bind.geti("controlId") != event.axis.id) - return nullptr; - - DENG2_ASSERT(dev); - if(dev->axis(bind.geti("controlId")).bindContext() != &context) - return nullptr; // Shadowed by a more important active class. - - // Is the position as required? - if(!B_CheckAxisPosition(BindingCondition::ControlTest(bind.geti("test")), bind.getf("pos"), - inputSys().device(event.device).axis(event.axis.id) - .translateRealPosition(event.axis.pos))) - return nullptr; - break; - - case E_ANGLE: - if(bind.geti("controlId") != event.angle.id) - return nullptr; - - DENG2_ASSERT(dev); - if(dev->hat(bind.geti("controlId")).bindContext() != &context) - return nullptr; // Shadowed by a more important active class. - - // Is the position as required? - if(event.angle.pos != bind.getf("pos")) - return nullptr; - break; - - case E_SYMBOLIC: - if(bind.gets("symbolicName").compareWithCase(event.symbolic.name)) - return nullptr; - break; - - default: return nullptr; - } - - // Any conditions on the current state of the input devices? - for(BindingCondition const &cond : bind.conditions) - { - if(!B_CheckCondition(&cond, 0, nullptr)) - return nullptr; - } - - // Substitute parameters in the command. - AutoStr *command = Str_Reserve(AutoStr_NewStd(), bind.gets("command").length()); - substituteInCommand(bind.gets("command"), event, command); - - return new CommandAction(Str_Text(command), CMDS_BIND); -} - bool BindContext::tryEvent(ddevent_t const &event, bool respectHigherContexts) const { LOG_AS("BindContext"); @@ -530,7 +335,7 @@ bool BindContext::tryEvent(ddevent_t const &event, bool respectHigherContexts) c for(Record const *rec : d->commandBinds) { CommandBinding bind(*rec); - AutoRef act(commandActionFor(*this, bind, event, respectHigherContexts)); + AutoRef act(bind.makeAction(event, *this, respectHigherContexts)); if(act.get()) { act->trigger(); diff --git a/doomsday/client/src/ui/commandbinding.cpp b/doomsday/client/src/ui/commandbinding.cpp index a1d0b328d6..01611eaff9 100644 --- a/doomsday/client/src/ui/commandbinding.cpp +++ b/doomsday/client/src/ui/commandbinding.cpp @@ -19,11 +19,26 @@ #include "ui/commandbinding.h" +#include +#include #include +#include "CommandAction" +#include "clientapp.h" + +#include "world/p_players.h" // P_ConsoleToLocal + #include "ui/b_util.h" +#include "ui/inputdeviceaxiscontrol.h" +#include "ui/inputdevicebuttoncontrol.h" +#include "ui/inputdevicehatcontrol.h" using namespace de; +static InputSystem &inputSys() +{ + return ClientApp::inputSystem(); +} + void CommandBinding::resetToDefaults() { def().addNumber("id", 0); ///< Unique identifier. @@ -62,3 +77,179 @@ String CommandBinding::composeDescriptor() return str; } + +/** + * Substitute placeholders in a command string. Placeholders consist of two characters, + * the first being a %. Use %% to output a plain % character. + * + * - %i: id member of the event + * - %p: (symbolic events only) local player number + * + * @param command Original command string with the placeholders. + * @param event Event data. + * @param out String with placeholders replaced. + */ +static void substituteInCommand(String const &command, ddevent_t const &event, ddstring_t *out) +{ + DENG2_ASSERT(out); + Block const str = command.toUtf8(); + for(char const *ptr = str.constData(); *ptr; ptr++) + { + if(*ptr == '%') + { + // Escape. + ptr++; + + // Must have another character in the placeholder. + if(!*ptr) break; + + if(*ptr == 'i') + { + int id = 0; + switch(event.type) + { + case E_TOGGLE: id = event.toggle.id; break; + case E_AXIS: id = event.axis.id; break; + case E_ANGLE: id = event.angle.id; break; + case E_SYMBOLIC: id = event.symbolic.id; break; + + default: break; + } + Str_Appendf(out, "%i", id); + } + else if(*ptr == 'p') + { + int id = 0; + if(event.type == E_SYMBOLIC) + { + id = P_ConsoleToLocal(event.symbolic.id); + } + Str_Appendf(out, "%i", id); + } + else if(*ptr == '%') + { + Str_AppendChar(out, *ptr); + } + continue; + } + + Str_AppendChar(out, *ptr); + } +} + +Action *CommandBinding::makeAction(ddevent_t const &event, BindContext const &context, + bool respectHigherContexts) const +{ + if(geti("type") != event.type) return nullptr; + + InputDevice const *dev = nullptr; + if(event.type != E_SYMBOLIC) + { + if(geti("deviceId") != event.device) return nullptr; + + dev = inputSys().devicePtr(geti("deviceId")); + if(!dev || !dev->isActive()) + { + // The device is not active, there is no way this could get executed. + return nullptr; + } + } + + switch(event.type) + { + case E_TOGGLE: { + if(geti("controlId") != event.toggle.id) + return nullptr; + + DENG2_ASSERT(dev); + InputDeviceButtonControl &button = dev->button(geti("controlId")); + + if(respectHigherContexts) + { + if(button.bindContext() != &context) + return nullptr; // Shadowed by a more important active class. + } + + // We're checking it, so clear the triggered flag. + button.setBindContextAssociation(InputDeviceControl::Triggered, UnsetFlags); + + // Is the state as required? + switch(Condition::ControlTest(geti("test"))) + { + case Condition::ButtonStateAny: + // Passes no matter what. + break; + + case Condition::ButtonStateDown: + if(event.toggle.state != ETOG_DOWN) + return nullptr; + break; + + case Condition::ButtonStateUp: + if(event.toggle.state != ETOG_UP) + return nullptr; + break; + + case Condition::ButtonStateRepeat: + if(event.toggle.state != ETOG_REPEAT) + return nullptr; + break; + + case Condition::ButtonStateDownOrRepeat: + if(event.toggle.state == ETOG_UP) + return nullptr; + break; + + default: return nullptr; + } + break; } + + case E_AXIS: + if(geti("controlId") != event.axis.id) + return nullptr; + + DENG2_ASSERT(dev); + if(dev->axis(geti("controlId")).bindContext() != &context) + return nullptr; // Shadowed by a more important active class. + + // Is the position as required? + if(!B_CheckAxisPosition(Condition::ControlTest(geti("test")), getf("pos"), + inputSys().device(event.device).axis(event.axis.id) + .translateRealPosition(event.axis.pos))) + return nullptr; + break; + + case E_ANGLE: + if(geti("controlId") != event.angle.id) + return nullptr; + + DENG2_ASSERT(dev); + if(dev->hat(geti("controlId")).bindContext() != &context) + return nullptr; // Shadowed by a more important active class. + + // Is the position as required? + if(event.angle.pos != getf("pos")) + return nullptr; + break; + + case E_SYMBOLIC: + if(gets("symbolicName").compareWithCase(event.symbolic.name)) + return nullptr; + break; + + default: return nullptr; + } + + // Any conditions on the current state of the input devices? + for(BindingCondition const &cond : conditions) + { + if(!B_CheckCondition(&cond, 0, nullptr)) + return nullptr; + } + + // Substitute parameters in the command. + AutoStr *command = Str_Reserve(AutoStr_NewStd(), gets("command").length()); + substituteInCommand(gets("command"), event, command); + + return new CommandAction(Str_Text(command), CMDS_BIND); +}