Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 71 additions & 4 deletions core/input/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,45 @@ void Input::_bind_methods() {
ADD_SIGNAL(MethodInfo("joy_connection_changed", PropertyInfo(Variant::INT, "device"), PropertyInfo(Variant::BOOL, "connected")));
}

void Input::_process(double p_delta) {
if (joy_echo_send_events && joy_echo_count_per_second > 0) {
const float delay_between_sending = 1.0 / joy_echo_count_per_second;
HashMap<uint16_t, JoyEchoInfo>::Iterator iter = joy_echo.begin();
while (iter != joy_echo.end()) {
JoyEchoInfo &echo = iter->value;
JoyEchoId echo_id;
echo_id.value = iter->key;
if (echo_id.is_axis && !joy_echo_send_axis_events) {
continue;
}

int times_to_send = 0;
echo.time += p_delta;

if (echo.waiting && echo.time > joy_echo_wait_time) {
echo.waiting = false;
echo.time -= joy_echo_wait_time;
times_to_send = 1;
}

if (!echo.waiting && echo.time > delay_between_sending) {
times_to_send += (int)Math::floor(echo.time / delay_between_sending);
echo.time = Math::fmod(echo.time, delay_between_sending);
}

for (int i = 0; i < times_to_send; i++) {
if (echo_id.is_axis) {
_axis_event(echo_id.device, (JoyAxis)echo_id.input_id, get_joy_axis(echo_id.device, (JoyAxis)echo_id.input_id), true);
} else {
_button_event(echo_id.device, (JoyButton)echo_id.input_id, true, true);
}
}

++iter;
}
}
}

#ifdef TOOLS_ENABLED
void Input::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
const String pf = p_function;
Expand Down Expand Up @@ -734,9 +773,11 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_
for (int i = 0; i < (int)JoyButton::MAX; i++) {
JoyButton c = _combine_device((JoyButton)i, p_idx);
joy_buttons_pressed.erase(c);
joy_echo.erase(JoyEchoId(p_idx, (JoyButton)i).value);
}
for (int i = 0; i < (int)JoyAxis::MAX; i++) {
set_joy_axis(p_idx, (JoyAxis)i, 0.0f);
joy_echo.erase(JoyEchoId(p_idx, (JoyAxis)i).value);
}
MotionInfo *motion = joy_motion.getptr(p_idx);
if (motion != nullptr && motion->gamepad_motion != nullptr) {
Expand Down Expand Up @@ -976,20 +1017,39 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em

Ref<InputEventJoypadButton> jb = p_event;

if (jb.is_valid()) {
if (jb.is_valid() && !jb->is_echo()) {
JoyButton c = _combine_device(jb->get_button_index(), jb->get_device());

if (jb->is_pressed()) {
joy_buttons_pressed.insert(c);
} else {
joy_buttons_pressed.erase(c);
}

if (joy_echo_send_events) {
uint16_t echo_id = JoyEchoId(jb->get_device(), jb->get_button_index()).value;
if (jb->is_pressed()) {
joy_echo.insert(echo_id, JoyEchoInfo());
} else {
joy_echo.erase(echo_id);
}
}
}

Ref<InputEventJoypadMotion> jm = p_event;

if (jm.is_valid()) {
if (jm.is_valid() && !jm->is_echo()) {
set_joy_axis(jm->get_device(), jm->get_axis(), jm->get_axis_value());
if (joy_echo_send_events && joy_echo_send_axis_events) {
uint16_t echo_id = JoyEchoId(jm->get_device(), jm->get_axis()).value;
if (Math::abs(jm->get_axis_value()) >= joy_echo_axis_deadzone) {
if (!joy_echo.has(echo_id)) {
joy_echo.insert(echo_id, JoyEchoInfo());
}
} else {
joy_echo.erase(echo_id);
}
}
}

Ref<InputEventGesture> ge = p_event;
Expand Down Expand Up @@ -1823,22 +1883,24 @@ void Input::joy_motion_sensors(int p_device, const Vector3 &p_accelerometer, con
motion->gamepad_motion->ProcessMotion(gyro_degrees.x, gyro_degrees.y, gyro_degrees.z, accel_g.x, accel_g.y, accel_g.z, delta_time);
}

void Input::_button_event(int p_device, JoyButton p_index, bool p_pressed) {
void Input::_button_event(int p_device, JoyButton p_index, bool p_pressed, bool p_echo) {
Ref<InputEventJoypadButton> ievent;
ievent.instantiate();
ievent->set_device(p_device);
ievent->set_button_index(p_index);
ievent->set_pressed(p_pressed);
ievent->set_echo(p_echo);

parse_input_event(ievent);
}

void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value) {
void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value, bool p_echo) {
Ref<InputEventJoypadMotion> ievent;
ievent.instantiate();
ievent->set_device(p_device);
ievent->set_axis(p_axis);
ievent->set_axis_value(p_value);
ievent->set_echo(p_echo);

parse_input_event(ievent);
}
Expand Down Expand Up @@ -2372,6 +2434,11 @@ Input::Input() {
gyroscope_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_gyroscope", false);
magnetometer_enabled = GLOBAL_DEF_RST_BASIC("input_devices/sensors/enable_magnetometer", false);
ignore_joypad_on_unfocused_application = GLOBAL_DEF_RST_BASIC("input_devices/joypads/ignore_joypad_on_unfocused_application", false);
joy_echo_send_events = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/send", false);
joy_echo_wait_time = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/wait_time", 0.5f);
joy_echo_count_per_second = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/count_per_second", 20);
joy_echo_send_axis_events = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/axis_echo_events/send", true);
joy_echo_axis_deadzone = GLOBAL_DEF_RST_BASIC("input_devices/joypads/joypad_echo_events/axis_echo_events/deadzone", InputMap::DEFAULT_TOGGLE_DEADZONE);
}

Input::~Input() {
Expand Down
43 changes: 41 additions & 2 deletions core/input/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,43 @@ class Input : public Object {

HashMap<int, MotionInfo> joy_motion;

union JoyEchoId {
struct {
uint16_t input_id : 7; // 0-128 to allow both axes and buttons
uint16_t device : 4; // 16 joypad devices
uint16_t is_axis : 1;
};
uint16_t value;

JoyEchoId() {}

JoyEchoId(int p_device, JoyButton p_button) {
device = p_device;
input_id = (uint16_t)p_button;
is_axis = 0;
}

JoyEchoId(int p_device, JoyAxis p_axis) {
device = p_device;
input_id = (uint16_t)p_axis;
is_axis = 1;
}
};

static_assert(sizeof(JoyEchoId) == sizeof(JoyEchoId::value), "Bitpacking doesn't work properly on this platform.");

struct JoyEchoInfo {
bool waiting = true;
float time = 0.0;
};

HashMap<uint16_t, JoyEchoInfo> joy_echo;
bool joy_echo_send_events = false;
float joy_echo_wait_time = 0.0;
int joy_echo_count_per_second = 0;
bool joy_echo_send_axis_events = false;
float joy_echo_axis_deadzone = 0.0;

struct VelocityTrack {
uint64_t last_tick = 0;
Vector2 velocity;
Expand Down Expand Up @@ -297,8 +334,8 @@ class Input : public Object {
void _get_mapped_hat_events(const JoyDeviceMapping &mapping, HatDir p_hat, JoyEvent r_events[(size_t)HatDir::MAX]);
JoyButton _get_output_button(const String &output);
JoyAxis _get_output_axis(const String &output);
void _button_event(int p_device, JoyButton p_index, bool p_pressed);
void _axis_event(int p_device, JoyAxis p_axis, float p_value);
void _button_event(int p_device, JoyButton p_index, bool p_pressed, bool p_echo = false);
void _axis_event(int p_device, JoyAxis p_axis, float p_value, bool p_echo = false);
void _update_action_cache(const StringName &p_action_name, ActionState &r_action_state);
void _update_joypad_features(int p_device);

Expand Down Expand Up @@ -349,6 +386,8 @@ class Input : public Object {
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif

void _process(double p_delta);

static Input *get_singleton();

bool is_anything_pressed() const;
Expand Down
30 changes: 28 additions & 2 deletions core/input/input_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,15 @@ float InputEventJoypadMotion::get_axis_value() const {
return axis_value;
}

void InputEventJoypadMotion::set_echo(bool p_enable) {
echo = p_enable;
emit_changed();
}

bool InputEventJoypadMotion::is_echo() const {
return echo;
}

bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const {
Ref<InputEventJoypadMotion> jm = p_event;
if (jm.is_null()) {
Expand Down Expand Up @@ -1200,7 +1209,8 @@ String InputEventJoypadMotion::as_text() const {
}

String InputEventJoypadMotion::_to_string() {
return vformat("InputEventJoypadMotion: axis=%d, axis_value=%.2f", axis, axis_value);
String e = is_echo() ? "true" : "false";
return vformat("InputEventJoypadMotion: axis=%d, axis_value=%.2f, echo=%s", axis, axis_value, e);
}

Ref<InputEventJoypadMotion> InputEventJoypadMotion::create_reference(JoyAxis p_axis, float p_value, int p_device) {
Expand All @@ -1220,8 +1230,11 @@ void InputEventJoypadMotion::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_axis_value", "axis_value"), &InputEventJoypadMotion::set_axis_value);
ClassDB::bind_method(D_METHOD("get_axis_value"), &InputEventJoypadMotion::get_axis_value);

ClassDB::bind_method(D_METHOD("set_echo", "echo"), &InputEventJoypadMotion::set_echo);

ADD_PROPERTY(PropertyInfo(Variant::INT, "axis"), "set_axis", "get_axis");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "axis_value"), "set_axis_value", "get_axis_value");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "echo"), "set_echo", "is_echo");
}

///////////////////////////////////
Expand All @@ -1247,6 +1260,15 @@ float InputEventJoypadButton::get_pressure() const {
return pressure;
}

void InputEventJoypadButton::set_echo(bool p_enable) {
echo = p_enable;
emit_changed();
}

bool InputEventJoypadButton::is_echo() const {
return echo;
}

bool InputEventJoypadButton::action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const {
Ref<InputEventJoypadButton> jb = p_event;
if (jb.is_null()) {
Expand Down Expand Up @@ -1320,7 +1342,8 @@ String InputEventJoypadButton::as_text() const {

String InputEventJoypadButton::_to_string() {
String p = is_pressed() ? "true" : "false";
return vformat("InputEventJoypadButton: button_index=%d, pressed=%s, pressure=%.2f", button_index, p, pressure);
String e = is_echo() ? "true" : "false";
return vformat("InputEventJoypadButton: button_index=%d, pressed=%s, pressure=%.2f, echo=%s", button_index, p, pressure, e);
}

Ref<InputEventJoypadButton> InputEventJoypadButton::create_reference(JoyButton p_btn_index, int p_device) {
Expand All @@ -1341,9 +1364,12 @@ void InputEventJoypadButton::_bind_methods() {

ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventJoypadButton::set_pressed);

ClassDB::bind_method(D_METHOD("set_echo", "echo"), &InputEventJoypadButton::set_echo);

ADD_PROPERTY(PropertyInfo(Variant::INT, "button_index"), "set_button_index", "get_button_index");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pressure"), "set_pressure", "get_pressure");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "echo"), "set_echo", "is_echo");
}

///////////////////////////////////
Expand Down
9 changes: 9 additions & 0 deletions core/input/input_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ class InputEventJoypadMotion : public InputEvent {
JoyAxis axis = (JoyAxis)0; ///< Joypad axis
float axis_value = 0; ///< -1 to 1

bool echo = false; /// true if this is an echo key
protected:
static void _bind_methods();

Expand All @@ -324,6 +325,9 @@ class InputEventJoypadMotion : public InputEvent {
void set_axis_value(float p_value);
float get_axis_value() const;

void set_echo(bool p_enable);
virtual bool is_echo() const override;

virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override;
virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const override;

Expand All @@ -342,6 +346,8 @@ class InputEventJoypadButton : public InputEvent {

JoyButton button_index = (JoyButton)0;
float pressure = 0; //0 to 1

bool echo = false; /// true if this is an echo key
protected:
static void _bind_methods();

Expand All @@ -354,6 +360,9 @@ class InputEventJoypadButton : public InputEvent {
void set_pressure(float p_pressure);
float get_pressure() const;

void set_echo(bool p_enable);
virtual bool is_echo() const override;

virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override;
virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const override;

Expand Down
6 changes: 4 additions & 2 deletions doc/classes/InputEvent.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@
<method name="is_echo" qualifiers="const" keywords="is_repeat">
<return type="bool" />
<description>
Returns [code]true[/code] if this input event is an echo event (only for events of type [InputEventKey]). An echo event is a repeated key event sent when the user is holding down the key. Any other event type returns [code]false[/code].
[b]Note:[/b] The rate at which echo events are sent is typically around 20 events per second (after holding down the key for roughly half a second). However, the key repeat delay/speed can be changed by the user or disabled entirely in the operating system settings. To ensure your project works correctly on all configurations, do not assume the user has a specific key repeat configuration in your project's behavior.
Returns [code]true[/code] if this input event is an echo event (only for events of type [InputEventKey] and [InputEventJoypadButton]). An echo event is a repeated event sent when the user is holding down the input. Any other event type returns [code]false[/code].
[b]Note:[/b] The rate at which keyboard echo events are sent is typically around 20 events per second (after holding down the key for roughly half a second). However, the key repeat delay/speed can be changed by the user or disabled entirely in the operating system settings. To ensure your project works correctly on all configurations, do not assume the user has a specific key repeat configuration in your project's behavior.
[b]Note:[/b] To allow the engine to send joypad echo events, make sure [member ProjectSettings.input_devices/joypads/joypad_echo_events/send] is set to [code]true[/code].
To control when joypad echo events are sent and how often, see [member ProjectSettings.input_devices/joypads/joypad_echo_events/wait_time] and [member ProjectSettings.input_devices/joypads/joypad_echo_events/count_per_second].
</description>
</method>
<method name="is_match" qualifiers="const">
Expand Down
5 changes: 5 additions & 0 deletions doc/classes/InputEventJoypadButton.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
<member name="button_index" type="int" setter="set_button_index" getter="get_button_index" enum="JoyButton" default="0">
Button identifier. One of the [enum JoyButton] button constants.
</member>
<member name="echo" type="bool" setter="set_echo" getter="is_echo" default="false">
If [code]true[/code], the button was already pressed before this event. An echo event is a repeated button event sent when the user is holding down the button.
[b]Note:[/b] To allow the engine to send joypad echo events, make sure [member ProjectSettings.input_devices/joypads/joypad_echo_events/send] is set to [code]true[/code].
To control when echo events are sent and how often, see [member ProjectSettings.input_devices/joypads/joypad_echo_events/wait_time] and [member ProjectSettings.input_devices/joypads/joypad_echo_events/count_per_second].
</member>
<member name="pressed" type="bool" setter="set_pressed" getter="is_pressed" default="false">
If [code]true[/code], the button's state is pressed. If [code]false[/code], the button's state is released.
</member>
Expand Down
6 changes: 6 additions & 0 deletions doc/classes/InputEventJoypadMotion.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,11 @@
<member name="axis_value" type="float" setter="set_axis_value" getter="get_axis_value" default="0.0">
Current position of the joystick on the given axis. The value ranges from [code]-1.0[/code] to [code]1.0[/code]. A value of [code]0[/code] means the axis is in its resting position.
</member>
<member name="echo" type="bool" setter="set_echo" getter="is_echo" default="false">
If [code]true[/code], the axis was already in the same position before this event. An echo event is a repeated axis event sent when the user is keeping the axis over the deadzone specified by [member ProjectSettings.input_devices/joypads/joypad_echo_events/axis_echo_events/deadzone].
[b]Note:[/b] To allow the engine to send joypad echo events, make sure [member ProjectSettings.input_devices/joypads/joypad_echo_events/send] is set to [code]true[/code].
To control when echo events are sent and how often, see [member ProjectSettings.input_devices/joypads/joypad_echo_events/wait_time] and [member ProjectSettings.input_devices/joypads/joypad_echo_events/count_per_second].
To allow the engine to send joypad echo events for the axes, make sure [member ProjectSettings.input_devices/joypads/joypad_echo_events/axis_echo_events/send] is set to [code]true[/code].
</member>
</members>
</class>
17 changes: 17 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1668,6 +1668,23 @@
<member name="input_devices/joypads/ignore_joypad_on_unfocused_application" type="bool" setter="" getter="" default="false">
If [code]true[/code], joypad input (including motion sensors) and LED light changes will be ignored and joypad vibration will be stopped when the application is not focused.
</member>
<member name="input_devices/joypads/joypad_echo_events/axis_echo_events/deadzone" type="float" setter="" getter="" default="0.5">
The deadzone that is used by the engine to determine if joypad axis echo events should be sent.
</member>
<member name="input_devices/joypads/joypad_echo_events/axis_echo_events/send" type="bool" setter="" getter="" default="true">
If [code]true[/code], the engine will send joypad echo events for axis movement after the user has moved the joypad axes for long enough.
[b]Note:[/b] This setting has no effect if [member input_devices/joypads/joypad_echo_events/count_per_second] is set to [code]0[/code] or [member input_devices/joypads/joypad_echo_events/send] is set to [code]false[/code].
</member>
<member name="input_devices/joypads/joypad_echo_events/count_per_second" type="int" setter="" getter="" default="20">
The number of times per second joypad echo events should be sent by the engine after the user has pressed the buttons or kept the axes over the deadzone for long enough.
</member>
<member name="input_devices/joypads/joypad_echo_events/send" type="bool" setter="" getter="" default="false">
If [code]true[/code], the engine will send joypad echo events after the user has pressed the joypad buttons or moved the joypad axes for long enough.
[b]Note:[/b] This setting has no effect if [member input_devices/joypads/joypad_echo_events/count_per_second] is set to [code]0[/code].
</member>
<member name="input_devices/joypads/joypad_echo_events/wait_time" type="float" setter="" getter="" default="0.5">
The amount of time of pressing the joypad buttons or keeping the joypad axes over the deadzone required for the engine to start sending echo events, in seconds.
</member>
<member name="input_devices/pen_tablet/driver" type="String" setter="" getter="">
Specifies the tablet driver to use. If left empty, the default driver will be used.
[b]Note:[/b] The driver in use can be overridden at runtime via the [code]--tablet-driver[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url].
Expand Down
1 change: 1 addition & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8269,6 +8269,7 @@ HashMap<String, Variant> EditorNode::get_initial_settings() {
HashMap<String, Variant> settings;
settings["physics/3d/physics_engine"] = "Jolt Physics";
settings["rendering/rendering_device/driver.windows"] = "d3d12";
settings["input_devices/joypads/joypad_echo_events/send"] = true;
return settings;
}

Expand Down
1 change: 1 addition & 0 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5002,6 +5002,7 @@ bool Main::iteration() {
if (Input::get_singleton()->is_agile_input_event_flushing()) {
Input::get_singleton()->flush_buffered_events();
}
Input::get_singleton()->_process(process_step * time_scale);

uint64_t process_begin = OS::get_singleton()->get_ticks_usec();

Expand Down
Loading