mirror of https://github.com/godotengine/godot
Support Input and UI for multiple players
This commit is contained in:
parent
1586c5674b
commit
e0d13f0226
|
|
@ -1546,6 +1546,9 @@ ProjectSettings::ProjectSettings() {
|
|||
GLOBAL_DEF("audio/general/ios/mix_with_others", false);
|
||||
|
||||
_add_builtin_input_map();
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "input/keyboard_player_id_override", PROPERTY_HINT_ENUM, "P1,P2,P3,P4,P5,P6,P7,P8"), (int)PlayerId::P1);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "input/mouse_player_id_override", PROPERTY_HINT_ENUM, "P1,P2,P3,P4,P5,P6,P7,P8"), (int)PlayerId::P1);
|
||||
GLOBAL_DEF(PropertyInfo(Variant::INT, "input/touch_player_id_override", PROPERTY_HINT_ENUM, "P1,P2,P3,P4,P5,P6,P7,P8"), (int)PlayerId::P1);
|
||||
|
||||
// Keep the enum values in sync with the `DisplayServer::ScreenOrientation` enum.
|
||||
custom_prop_info["display/window/handheld/orientation"] = PropertyInfo(Variant::INT, "display/window/handheld/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait,Reverse Landscape,Reverse Portrait,Sensor Landscape,Sensor Portrait,Sensor");
|
||||
|
|
|
|||
|
|
@ -95,6 +95,10 @@ protected:
|
|||
bool project_loaded = false;
|
||||
List<String> input_presets;
|
||||
|
||||
PlayerId keyboard_player_id_override = PlayerId::P1;
|
||||
PlayerId mouse_player_id_override = PlayerId::P1;
|
||||
PlayerId touch_player_id_override = PlayerId::P1;
|
||||
|
||||
HashSet<String> custom_features;
|
||||
HashMap<StringName, LocalVector<Pair<StringName, StringName>>> feature_overrides;
|
||||
|
||||
|
|
|
|||
|
|
@ -527,6 +527,15 @@ void register_global_constants() {
|
|||
BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, MB_XBUTTON1);
|
||||
BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, MB_XBUTTON2);
|
||||
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P1);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P2);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P3);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P4);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P5);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P6);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P7);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P8);
|
||||
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, INVALID);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, A);
|
||||
BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, B);
|
||||
|
|
@ -649,6 +658,7 @@ void register_global_constants() {
|
|||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_PHYSICS);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_NAVIGATION);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_AVOIDANCE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_PLAYER_MASK);
|
||||
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_FILE);
|
||||
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DIR);
|
||||
|
|
|
|||
|
|
@ -122,18 +122,20 @@ void Input::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("is_key_label_pressed", "keycode"), &Input::is_key_label_pressed);
|
||||
ClassDB::bind_method(D_METHOD("is_mouse_button_pressed", "button"), &Input::is_mouse_button_pressed);
|
||||
ClassDB::bind_method(D_METHOD("is_joy_button_pressed", "device", "button"), &Input::is_joy_button_pressed);
|
||||
ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "exact_match"), &Input::is_action_pressed, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("is_action_just_pressed", "action", "exact_match"), &Input::is_action_just_pressed, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("is_action_just_released", "action", "exact_match"), &Input::is_action_just_released, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match"), &Input::get_action_strength, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("get_action_raw_strength", "action", "exact_match"), &Input::get_action_raw_strength, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("get_axis", "negative_action", "positive_action"), &Input::get_axis);
|
||||
ClassDB::bind_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone"), &Input::get_vector, DEFVAL(-1.0f));
|
||||
ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "exact_match", "player_id"), &Input::is_action_pressed, DEFVAL(false), DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("is_action_just_pressed", "action", "exact_match", "player_id"), &Input::is_action_just_pressed, DEFVAL(false), DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("is_action_just_released", "action", "exact_match", "player_id"), &Input::is_action_just_released, DEFVAL(false), DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match", "player_id"), &Input::get_action_strength, DEFVAL(false), DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("get_action_raw_strength", "action", "exact_match", "player_id"), &Input::get_action_raw_strength, DEFVAL(false), DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("get_axis", "negative_action", "positive_action", "player_id"), &Input::get_axis, DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone", "player_id"), &Input::get_vector, DEFVAL(-1.0f), DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("add_joy_mapping", "mapping", "update_existing"), &Input::add_joy_mapping, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("remove_joy_mapping", "guid"), &Input::remove_joy_mapping);
|
||||
ClassDB::bind_method(D_METHOD("is_joy_known", "device"), &Input::is_joy_known);
|
||||
ClassDB::bind_method(D_METHOD("get_joy_axis", "device", "axis"), &Input::get_joy_axis);
|
||||
ClassDB::bind_method(D_METHOD("get_joy_name", "device"), &Input::get_joy_name);
|
||||
ClassDB::bind_method(D_METHOD("get_joy_player_id", "device"), &Input::get_joy_player_id);
|
||||
ClassDB::bind_method(D_METHOD("set_joy_player_id", "device", "player_id"), &Input::set_joy_player_id);
|
||||
ClassDB::bind_method(D_METHOD("get_joy_guid", "device"), &Input::get_joy_guid);
|
||||
ClassDB::bind_method(D_METHOD("get_joy_info", "device"), &Input::get_joy_info);
|
||||
ClassDB::bind_method(D_METHOD("should_ignore_device", "vendor_id", "product_id"), &Input::should_ignore_device);
|
||||
|
|
@ -157,8 +159,8 @@ void Input::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_mouse_mode", "mode"), &Input::set_mouse_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_mouse_mode"), &Input::get_mouse_mode);
|
||||
ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Input::warp_mouse);
|
||||
ClassDB::bind_method(D_METHOD("action_press", "action", "strength"), &Input::action_press, DEFVAL(1.f));
|
||||
ClassDB::bind_method(D_METHOD("action_release", "action"), &Input::action_release);
|
||||
ClassDB::bind_method(D_METHOD("action_press", "action", "strength", "player_id"), &Input::action_press, DEFVAL(1.f), DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("action_release", "action", "player_id"), &Input::action_release, DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("set_default_cursor_shape", "shape"), &Input::set_default_cursor_shape, DEFVAL(CURSOR_ARROW));
|
||||
ClassDB::bind_method(D_METHOD("get_current_cursor_shape"), &Input::get_current_cursor_shape);
|
||||
ClassDB::bind_method(D_METHOD("set_custom_mouse_cursor", "image", "shape", "hotspot"), &Input::set_custom_mouse_cursor, DEFVAL(CURSOR_ARROW), DEFVAL(Vector2()));
|
||||
|
|
@ -201,6 +203,19 @@ void Input::_bind_methods() {
|
|||
BIND_ENUM_CONSTANT(CURSOR_HSPLIT);
|
||||
BIND_ENUM_CONSTANT(CURSOR_HELP);
|
||||
|
||||
BIND_CONSTANT(PLAYERS_MAX);
|
||||
|
||||
BIND_BITFIELD_FLAG(PLAYER_NONE);
|
||||
BIND_BITFIELD_FLAG(PLAYER_1);
|
||||
BIND_BITFIELD_FLAG(PLAYER_2);
|
||||
BIND_BITFIELD_FLAG(PLAYER_3);
|
||||
BIND_BITFIELD_FLAG(PLAYER_4);
|
||||
BIND_BITFIELD_FLAG(PLAYER_5);
|
||||
BIND_BITFIELD_FLAG(PLAYER_6);
|
||||
BIND_BITFIELD_FLAG(PLAYER_7);
|
||||
BIND_BITFIELD_FLAG(PLAYER_8);
|
||||
BIND_BITFIELD_FLAG(PLAYER_ALL);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("joy_connection_changed", PropertyInfo(Variant::INT, "device"), PropertyInfo(Variant::BOOL, "connected")));
|
||||
}
|
||||
|
||||
|
|
@ -282,9 +297,12 @@ bool Input::is_anything_pressed() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
for (const KeyValue<StringName, Input::ActionState> &E : action_states) {
|
||||
if (E.value.cache.pressed) {
|
||||
return true;
|
||||
for (const KeyValue<int, HashMap<StringName, Input::ActionState>> &player_entry : action_states) {
|
||||
const HashMap<StringName, Input::ActionState> &player_action_states = player_entry.value;
|
||||
for (const KeyValue<StringName, Input::ActionState> &action_entry : player_action_states) {
|
||||
if (action_entry.value.cache.pressed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,9 +320,12 @@ bool Input::is_anything_pressed_except_mouse() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
for (const KeyValue<StringName, Input::ActionState> &E : action_states) {
|
||||
if (E.value.cache.pressed) {
|
||||
return true;
|
||||
for (const KeyValue<int, HashMap<StringName, Input::ActionState>> &player_entry : action_states) {
|
||||
const HashMap<StringName, Input::ActionState> &player_action_states = player_entry.value;
|
||||
for (const KeyValue<StringName, Input::ActionState> &action_entry : player_action_states) {
|
||||
if (action_entry.value.cache.pressed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -369,119 +390,144 @@ bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const {
|
|||
return joy_buttons_pressed.has(_combine_device(p_button, p_device));
|
||||
}
|
||||
|
||||
bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
|
||||
bool Input::is_action_pressed(const StringName &p_action, bool p_exact, PlayerId p_player_id) const {
|
||||
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
|
||||
|
||||
if (disable_input) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
|
||||
if (!E) {
|
||||
HashMap<int, HashMap<StringName, ActionState>>::ConstIterator player_entry = action_states.find((int)p_player_id);
|
||||
if (!player_entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return E->value.cache.pressed && (p_exact ? E->value.exact : true);
|
||||
HashMap<StringName, ActionState>::ConstIterator action_entry = player_entry->value.find(p_action);
|
||||
if (!action_entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return action_entry->value.cache.pressed && (p_exact ? action_entry->value.exact : true);
|
||||
}
|
||||
|
||||
bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const {
|
||||
bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact, PlayerId p_player_id) const {
|
||||
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
|
||||
|
||||
if (disable_input) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
|
||||
if (!E) {
|
||||
HashMap<int, HashMap<StringName, ActionState>>::ConstIterator player_entry = action_states.find((int)p_player_id);
|
||||
if (!player_entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_exact && E->value.exact == false) {
|
||||
HashMap<StringName, ActionState>::ConstIterator action_entry = player_entry->value.find(p_action);
|
||||
if (!action_entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_exact && action_entry->value.exact == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Backward compatibility for legacy behavior, only return true if currently pressed.
|
||||
bool pressed_requirement = legacy_just_pressed_behavior ? E->value.cache.pressed : true;
|
||||
bool pressed_requirement = legacy_just_pressed_behavior ? action_entry->value.cache.pressed : true;
|
||||
|
||||
if (Engine::get_singleton()->is_in_physics_frame()) {
|
||||
return pressed_requirement && E->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames();
|
||||
return pressed_requirement && action_entry->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames();
|
||||
} else {
|
||||
return pressed_requirement && E->value.pressed_process_frame == Engine::get_singleton()->get_process_frames();
|
||||
return pressed_requirement && action_entry->value.pressed_process_frame == Engine::get_singleton()->get_process_frames();
|
||||
}
|
||||
}
|
||||
|
||||
bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const {
|
||||
bool Input::is_action_just_released(const StringName &p_action, bool p_exact, PlayerId p_player_id) const {
|
||||
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
|
||||
|
||||
if (disable_input) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
|
||||
if (!E) {
|
||||
HashMap<int, HashMap<StringName, ActionState>>::ConstIterator player_entry = action_states.find((int)p_player_id);
|
||||
if (!player_entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_exact && E->value.exact == false) {
|
||||
HashMap<StringName, ActionState>::ConstIterator action_entry = player_entry->value.find(p_action);
|
||||
if (!action_entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_exact && action_entry->value.exact == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Backward compatibility for legacy behavior, only return true if currently released.
|
||||
bool released_requirement = legacy_just_pressed_behavior ? !E->value.cache.pressed : true;
|
||||
bool released_requirement = legacy_just_pressed_behavior ? !action_entry->value.cache.pressed : true;
|
||||
|
||||
if (Engine::get_singleton()->is_in_physics_frame()) {
|
||||
return released_requirement && E->value.released_physics_frame == Engine::get_singleton()->get_physics_frames();
|
||||
return released_requirement && action_entry->value.released_physics_frame == Engine::get_singleton()->get_physics_frames();
|
||||
} else {
|
||||
return released_requirement && E->value.released_process_frame == Engine::get_singleton()->get_process_frames();
|
||||
return released_requirement && action_entry->value.released_process_frame == Engine::get_singleton()->get_process_frames();
|
||||
}
|
||||
}
|
||||
|
||||
float Input::get_action_strength(const StringName &p_action, bool p_exact) const {
|
||||
float Input::get_action_strength(const StringName &p_action, bool p_exact, PlayerId p_player_id) const {
|
||||
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
|
||||
|
||||
if (disable_input) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
|
||||
if (!E) {
|
||||
HashMap<int, HashMap<StringName, ActionState>>::ConstIterator player_entry = action_states.find((int)p_player_id);
|
||||
if (!player_entry) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (p_exact && E->value.exact == false) {
|
||||
HashMap<StringName, ActionState>::ConstIterator action_entry = player_entry->value.find(p_action);
|
||||
if (!action_entry) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return E->value.cache.strength;
|
||||
if (p_exact && action_entry->value.exact == false) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return action_entry->value.cache.strength;
|
||||
}
|
||||
|
||||
float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) const {
|
||||
float Input::get_action_raw_strength(const StringName &p_action, bool p_exact, PlayerId p_player_id) const {
|
||||
ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
|
||||
|
||||
if (disable_input) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action);
|
||||
if (!E) {
|
||||
HashMap<int, HashMap<StringName, ActionState>>::ConstIterator player_entry = action_states.find((int)p_player_id);
|
||||
if (!player_entry) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (p_exact && E->value.exact == false) {
|
||||
HashMap<StringName, ActionState>::ConstIterator action_entry = player_entry->value.find(p_action);
|
||||
if (!action_entry) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return E->value.cache.raw_strength;
|
||||
if (p_exact && action_entry->value.exact == false) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return action_entry->value.cache.raw_strength;
|
||||
}
|
||||
|
||||
float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const {
|
||||
return get_action_strength(p_positive_action) - get_action_strength(p_negative_action);
|
||||
float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action, PlayerId p_player_id) const {
|
||||
return get_action_strength(p_positive_action, false, p_player_id) - get_action_strength(p_negative_action, false, p_player_id);
|
||||
}
|
||||
|
||||
Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone) const {
|
||||
Vector2 Input::get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone, PlayerId p_player_id) const {
|
||||
Vector2 vector = Vector2(
|
||||
get_action_raw_strength(p_positive_x) - get_action_raw_strength(p_negative_x),
|
||||
get_action_raw_strength(p_positive_y) - get_action_raw_strength(p_negative_y));
|
||||
get_action_raw_strength(p_positive_x, false, p_player_id) - get_action_raw_strength(p_negative_x, false, p_player_id),
|
||||
get_action_raw_strength(p_positive_y, false, p_player_id) - get_action_raw_strength(p_negative_y, false, p_player_id));
|
||||
|
||||
if (p_deadzone < 0.0f) {
|
||||
// If the deadzone isn't specified, get it from the average of the actions.
|
||||
|
|
@ -564,11 +610,14 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_
|
|||
|
||||
// Clear the pressed status if a Joypad gets disconnected.
|
||||
if (!p_connected) {
|
||||
for (KeyValue<StringName, ActionState> &E : action_states) {
|
||||
HashMap<int, ActionState::DeviceState>::Iterator it = E.value.device_states.find(p_idx);
|
||||
if (it) {
|
||||
E.value.device_states.remove(it);
|
||||
_update_action_cache(E.key, E.value);
|
||||
for (KeyValue<int, HashMap<StringName, Input::ActionState>> &player_entry : action_states) {
|
||||
HashMap<StringName, Input::ActionState> &player_action_states = player_entry.value;
|
||||
for (KeyValue<StringName, ActionState> &action_entry : player_action_states) {
|
||||
HashMap<int, ActionState::DeviceState>::Iterator it = action_entry.value.device_states.find(p_idx);
|
||||
if (it) {
|
||||
action_entry.value.device_states.remove(it);
|
||||
_update_action_cache(action_entry.key, action_entry.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -576,6 +625,7 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_
|
|||
Joypad js;
|
||||
js.name = p_connected ? p_name : "";
|
||||
js.uid = p_connected ? p_guid : "";
|
||||
js.player_id = PlayerId(CLAMP(p_idx, 0, PLAYERS_MAX));
|
||||
js.info = p_connected ? p_joypad_info : Dictionary();
|
||||
|
||||
if (p_connected) {
|
||||
|
|
@ -595,6 +645,9 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_
|
|||
}
|
||||
}
|
||||
_set_joypad_mapping(js, mapping);
|
||||
|
||||
// Create player action states.
|
||||
action_states[(int)js.player_id];
|
||||
} else {
|
||||
js.connected = false;
|
||||
for (int i = 0; i < (int)JoyButton::MAX; i++) {
|
||||
|
|
@ -604,6 +657,11 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_
|
|||
for (int i = 0; i < (int)JoyAxis::MAX; i++) {
|
||||
set_joy_axis(p_idx, (JoyAxis)i, 0.0f);
|
||||
}
|
||||
|
||||
// Remove player action states.
|
||||
if (action_states.has((int)js.player_id)) {
|
||||
action_states.erase((int)js.player_id);
|
||||
}
|
||||
}
|
||||
joy_names[p_idx] = js;
|
||||
|
||||
|
|
@ -711,11 +769,14 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
|
|||
if (event_dispatch_function && emulate_touch_from_mouse && !p_is_emulated && mb->get_button_index() == MouseButton::LEFT) {
|
||||
Ref<InputEventScreenTouch> touch_event;
|
||||
touch_event.instantiate();
|
||||
|
||||
touch_event->set_pressed(mb->is_pressed());
|
||||
touch_event->set_canceled(mb->is_canceled());
|
||||
touch_event->set_position(mb->get_position());
|
||||
touch_event->set_double_tap(mb->is_double_click());
|
||||
touch_event->set_device(InputEvent::DEVICE_ID_EMULATION);
|
||||
touch_event->set_player_from_device();
|
||||
|
||||
_THREAD_SAFE_UNLOCK_
|
||||
event_dispatch_function(touch_event);
|
||||
_THREAD_SAFE_LOCK_
|
||||
|
|
@ -746,6 +807,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
|
|||
drag_event->set_velocity(get_last_mouse_velocity());
|
||||
drag_event->set_screen_velocity(get_last_mouse_screen_velocity());
|
||||
drag_event->set_device(InputEvent::DEVICE_ID_EMULATION);
|
||||
drag_event->set_player_from_device();
|
||||
|
||||
_THREAD_SAFE_UNLOCK_
|
||||
event_dispatch_function(drag_event);
|
||||
|
|
@ -784,6 +846,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
|
|||
button_event.instantiate();
|
||||
|
||||
button_event->set_device(InputEvent::DEVICE_ID_EMULATION);
|
||||
button_event->set_player_from_device();
|
||||
button_event->set_position(st->get_position());
|
||||
button_event->set_global_position(st->get_position());
|
||||
button_event->set_pressed(st->is_pressed());
|
||||
|
|
@ -818,6 +881,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
|
|||
motion_event.instantiate();
|
||||
|
||||
motion_event->set_device(InputEvent::DEVICE_ID_EMULATION);
|
||||
motion_event->set_player_from_device();
|
||||
motion_event->set_tilt(sd->get_tilt());
|
||||
motion_event->set_pen_inverted(sd->get_pen_inverted());
|
||||
motion_event->set_pressure(sd->get_pressure());
|
||||
|
|
@ -870,14 +934,17 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
|
|||
ERR_FAIL_COND_MSG(event_index >= (int)MAX_EVENT, vformat("Input singleton does not support more than %d events assigned to an action.", MAX_EVENT));
|
||||
|
||||
int device_id = p_event->get_device();
|
||||
bool is_pressed = p_event->is_action_pressed(E.key, true);
|
||||
ActionState &action_state = action_states[E.key];
|
||||
PlayerId player_id = p_event->get_player();
|
||||
bool is_pressed = p_event->is_action_pressed(E.key, true, false, player_id);
|
||||
|
||||
HashMap<StringName, ActionState> &player_action_states = action_states[(int)player_id];
|
||||
ActionState &action_state = player_action_states[E.key];
|
||||
|
||||
// Update the action's per-device state.
|
||||
ActionState::DeviceState &device_state = action_state.device_states[device_id];
|
||||
device_state.pressed[event_index] = is_pressed;
|
||||
device_state.strength[event_index] = p_event->get_action_strength(E.key);
|
||||
device_state.raw_strength[event_index] = p_event->get_action_raw_strength(E.key);
|
||||
device_state.strength[event_index] = p_event->get_action_strength(E.key, false, player_id);
|
||||
device_state.raw_strength[event_index] = p_event->get_action_raw_strength(E.key, false, player_id);
|
||||
|
||||
// Update the action's global state and cache.
|
||||
if (!is_pressed) {
|
||||
|
|
@ -1014,11 +1081,14 @@ Point2 Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, cons
|
|||
return rel_warped;
|
||||
}
|
||||
|
||||
void Input::action_press(const StringName &p_action, float p_strength) {
|
||||
void Input::action_press(const StringName &p_action, float p_strength, PlayerId p_player_id) {
|
||||
ERR_FAIL_COND_MSG(!InputMap::get_singleton()->has_action(p_action), InputMap::get_singleton()->suggest_actions(p_action));
|
||||
|
||||
// Create or retrieve existing player action states.
|
||||
HashMap<StringName, ActionState> &player_entry = action_states[(int)p_player_id];
|
||||
|
||||
// Create or retrieve existing action.
|
||||
ActionState &action_state = action_states[p_action];
|
||||
ActionState &action_state = player_entry[p_action];
|
||||
|
||||
// As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick.
|
||||
if (!action_state.cache.pressed) {
|
||||
|
|
@ -1031,11 +1101,15 @@ void Input::action_press(const StringName &p_action, float p_strength) {
|
|||
_update_action_cache(p_action, action_state);
|
||||
}
|
||||
|
||||
void Input::action_release(const StringName &p_action) {
|
||||
void Input::action_release(const StringName &p_action, PlayerId p_player_id) {
|
||||
ERR_FAIL_COND_MSG(!InputMap::get_singleton()->has_action(p_action), InputMap::get_singleton()->suggest_actions(p_action));
|
||||
|
||||
// Create or retrieve existing player action states.
|
||||
HashMap<StringName, ActionState> &player_entry = action_states[(int)p_player_id];
|
||||
|
||||
// Create or retrieve existing action.
|
||||
ActionState &action_state = action_states[p_action];
|
||||
ActionState &action_state = player_entry[p_action];
|
||||
|
||||
action_state.cache.pressed = false;
|
||||
action_state.cache.strength = 0.0;
|
||||
action_state.cache.raw_strength = 0.0;
|
||||
|
|
@ -1067,6 +1141,7 @@ void Input::ensure_touch_mouse_raised() {
|
|||
button_event.instantiate();
|
||||
|
||||
button_event->set_device(InputEvent::DEVICE_ID_EMULATION);
|
||||
button_event->set_player_from_device();
|
||||
button_event->set_position(mouse_pos);
|
||||
button_event->set_global_position(mouse_pos);
|
||||
button_event->set_pressed(false);
|
||||
|
|
@ -1104,6 +1179,7 @@ void Input::set_default_cursor_shape(CursorShape p_shape) {
|
|||
mm->set_position(mouse_pos);
|
||||
mm->set_global_position(mouse_pos);
|
||||
mm->set_device(InputEvent::DEVICE_ID_INTERNAL);
|
||||
mm->set_player_from_device();
|
||||
parse_input_event(mm);
|
||||
}
|
||||
|
||||
|
|
@ -1126,6 +1202,9 @@ void Input::parse_input_event(const Ref<InputEvent> &p_event) {
|
|||
|
||||
ERR_FAIL_COND(p_event.is_null());
|
||||
|
||||
// Override player id of the event.
|
||||
p_event->set_player_from_device();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
uint64_t curr_frame = Engine::get_singleton()->get_process_frames();
|
||||
if (curr_frame != last_parsed_frame) {
|
||||
|
|
@ -1211,9 +1290,12 @@ void Input::release_pressed_events() {
|
|||
joy_buttons_pressed.clear();
|
||||
_joy_axis.clear();
|
||||
|
||||
for (KeyValue<StringName, Input::ActionState> &E : action_states) {
|
||||
if (E.value.cache.pressed) {
|
||||
action_release(E.key);
|
||||
for (KeyValue<int, HashMap<StringName, Input::ActionState>> &player_entry : action_states) {
|
||||
HashMap<StringName, Input::ActionState> &player_action_states = player_entry.value;
|
||||
for (KeyValue<StringName, Input::ActionState> &action_entry : player_action_states) {
|
||||
if (action_entry.value.cache.pressed) {
|
||||
action_release(action_entry.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1363,6 +1445,7 @@ void Input::_button_event(int p_device, JoyButton p_index, bool p_pressed) {
|
|||
Ref<InputEventJoypadButton> ievent;
|
||||
ievent.instantiate();
|
||||
ievent->set_device(p_device);
|
||||
ievent->set_player_from_device();
|
||||
ievent->set_button_index(p_index);
|
||||
ievent->set_pressed(p_pressed);
|
||||
|
||||
|
|
@ -1373,6 +1456,7 @@ void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value) {
|
|||
Ref<InputEventJoypadMotion> ievent;
|
||||
ievent.instantiate();
|
||||
ievent->set_device(p_device);
|
||||
ievent->set_player_from_device();
|
||||
ievent->set_axis(p_axis);
|
||||
ievent->set_axis_value(p_value);
|
||||
|
||||
|
|
@ -1798,6 +1882,16 @@ String Input::get_joy_guid(int p_device) const {
|
|||
return joy_names[p_device].uid;
|
||||
}
|
||||
|
||||
PlayerId Input::get_joy_player_id(int p_device) const {
|
||||
ERR_FAIL_COND_V(!joy_names.has(p_device), PlayerId::P1);
|
||||
return joy_names[p_device].player_id;
|
||||
}
|
||||
|
||||
void Input::set_joy_player_id(int p_device, PlayerId p_player_id) {
|
||||
ERR_FAIL_COND(!joy_names.has(p_device));
|
||||
joy_names[p_device].player_id = p_player_id;
|
||||
}
|
||||
|
||||
Dictionary Input::get_joy_info(int p_device) const {
|
||||
ERR_FAIL_COND_V(!joy_names.has(p_device), Dictionary());
|
||||
return joy_names[p_device].info;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,19 @@ public:
|
|||
MOUSE_MODE_MAX,
|
||||
};
|
||||
|
||||
struct Joypad {
|
||||
StringName name;
|
||||
StringName uid;
|
||||
PlayerId player_id = PlayerId::P1;
|
||||
bool connected = false;
|
||||
bool last_buttons[(size_t)JoyButton::MAX] = { false };
|
||||
float last_axis[(size_t)JoyAxis::MAX] = { 0.0f };
|
||||
HatMask last_hat = HatMask::CENTER;
|
||||
int mapping = -1;
|
||||
int hat_current = 0;
|
||||
Dictionary info;
|
||||
};
|
||||
|
||||
#undef CursorShape
|
||||
enum CursorShape {
|
||||
CURSOR_ARROW,
|
||||
|
|
@ -131,7 +144,9 @@ private:
|
|||
} cache;
|
||||
};
|
||||
|
||||
HashMap<StringName, ActionState> action_states;
|
||||
// Key -> player_id.
|
||||
// Value -> All available actions to that player.
|
||||
HashMap<int, HashMap<StringName, ActionState>> action_states;
|
||||
|
||||
bool emulate_touch_from_mouse = false;
|
||||
bool emulate_mouse_from_touch = false;
|
||||
|
|
@ -164,18 +179,6 @@ private:
|
|||
VelocityTrack();
|
||||
};
|
||||
|
||||
struct Joypad {
|
||||
StringName name;
|
||||
StringName uid;
|
||||
bool connected = false;
|
||||
bool last_buttons[(size_t)JoyButton::MAX] = { false };
|
||||
float last_axis[(size_t)JoyAxis::MAX] = { 0.0f };
|
||||
HatMask last_hat = HatMask::CENTER;
|
||||
int mapping = -1;
|
||||
int hat_current = 0;
|
||||
Dictionary info;
|
||||
};
|
||||
|
||||
VelocityTrack mouse_velocity_track;
|
||||
HashMap<int, VelocityTrack> touch_velocity_track;
|
||||
HashMap<int, Joypad> joy_names;
|
||||
|
|
@ -306,14 +309,20 @@ public:
|
|||
bool is_key_label_pressed(Key p_keycode) const;
|
||||
bool is_mouse_button_pressed(MouseButton p_button) const;
|
||||
bool is_joy_button_pressed(int p_device, JoyButton p_button) const;
|
||||
bool is_action_pressed(const StringName &p_action, bool p_exact = false) const;
|
||||
bool is_action_just_pressed(const StringName &p_action, bool p_exact = false) const;
|
||||
bool is_action_just_released(const StringName &p_action, bool p_exact = false) const;
|
||||
float get_action_strength(const StringName &p_action, bool p_exact = false) const;
|
||||
float get_action_raw_strength(const StringName &p_action, bool p_exact = false) const;
|
||||
bool is_action_pressed(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const;
|
||||
bool is_action_just_pressed(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const;
|
||||
bool is_action_just_released(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const;
|
||||
float get_action_strength(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const;
|
||||
float get_action_raw_strength(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const;
|
||||
|
||||
float get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const;
|
||||
Vector2 get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone = -1.0f) const;
|
||||
float get_axis(const StringName &p_negative_action, const StringName &p_positive_action, PlayerId p_player_id = PlayerId::P1) const;
|
||||
Vector2 get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone = -1.0f, PlayerId p_player_id = PlayerId::P1) const;
|
||||
|
||||
static inline bool is_player_id_in_mask(BitField<PlayerMask> p_player_mask, PlayerId p_player_id) {
|
||||
return p_player_mask.has_flag(player_id_to_mask(p_player_id));
|
||||
}
|
||||
|
||||
HashMap<int, Joypad> _get_joy_names() const { return joy_names; }
|
||||
|
||||
float get_joy_axis(int p_device, JoyAxis p_axis) const;
|
||||
String get_joy_name(int p_idx);
|
||||
|
|
@ -350,8 +359,8 @@ public:
|
|||
|
||||
void set_mouse_position(const Point2 &p_posf);
|
||||
|
||||
void action_press(const StringName &p_action, float p_strength = 1.f);
|
||||
void action_release(const StringName &p_action);
|
||||
void action_press(const StringName &p_action, float p_strength = 1.f, PlayerId p_player_id = PlayerId::P1);
|
||||
void action_release(const StringName &p_action, PlayerId p_player_id = PlayerId::P1);
|
||||
|
||||
void set_emulate_touch_from_mouse(bool p_emulate);
|
||||
bool is_emulating_touch_from_mouse() const;
|
||||
|
|
@ -377,6 +386,8 @@ public:
|
|||
|
||||
bool is_joy_known(int p_device);
|
||||
String get_joy_guid(int p_device) const;
|
||||
PlayerId get_joy_player_id(int p_device) const;
|
||||
void set_joy_player_id(int p_device, PlayerId p_player_id);
|
||||
bool should_ignore_device(int p_vendor_id, int p_product_id) const;
|
||||
Dictionary get_joy_info(int p_device) const;
|
||||
void set_fallback_mapping(const String &p_guid);
|
||||
|
|
|
|||
|
|
@ -138,4 +138,36 @@ inline MouseButtonMask mouse_button_to_mask(MouseButton button) {
|
|||
return MouseButtonMask(1 << ((int)button - 1));
|
||||
}
|
||||
|
||||
enum class PlayerId : uint8_t {
|
||||
P1 = 0,
|
||||
P2 = 1,
|
||||
P3 = 2,
|
||||
P4 = 3,
|
||||
P5 = 4,
|
||||
P6 = 5,
|
||||
P7 = 6,
|
||||
P8 = 7,
|
||||
};
|
||||
|
||||
enum {
|
||||
PLAYERS_MAX = 8,
|
||||
};
|
||||
|
||||
enum PlayerMask : uint8_t {
|
||||
PLAYER_NONE = 0U,
|
||||
PLAYER_1 = 1U << 0,
|
||||
PLAYER_2 = 1U << 1,
|
||||
PLAYER_3 = 1U << 2,
|
||||
PLAYER_4 = 1U << 3,
|
||||
PLAYER_5 = 1U << 4,
|
||||
PLAYER_6 = 1U << 5,
|
||||
PLAYER_7 = 1U << 6,
|
||||
PLAYER_8 = 1U << 7,
|
||||
PLAYER_ALL = 0xFFU,
|
||||
};
|
||||
|
||||
inline PlayerMask player_id_to_mask(PlayerId id) {
|
||||
return PlayerMask(1U << (uint8_t)id);
|
||||
}
|
||||
|
||||
#endif // INPUT_ENUMS_H
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@
|
|||
|
||||
#include "input_event.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/input/input.h"
|
||||
#include "core/input/input_map.h"
|
||||
#include "core/input/shortcut.h"
|
||||
#include "core/os/keyboard.h"
|
||||
|
|
@ -47,29 +49,98 @@ int InputEvent::get_device() const {
|
|||
return device;
|
||||
}
|
||||
|
||||
void InputEvent::set_player(PlayerId p_player) {
|
||||
player = p_player;
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
PlayerId InputEvent::get_player() const {
|
||||
return player;
|
||||
}
|
||||
|
||||
void InputEvent::set_player_from_device() {
|
||||
// ProjectSettings *ps = ProjectSettings::get_singleton();
|
||||
Ref<InputEvent> event = Ref<InputEvent>(this);
|
||||
|
||||
// Keyboard events.
|
||||
|
||||
Ref<InputEventKey> k = event;
|
||||
if (k.is_valid()) {
|
||||
player = (PlayerId)(GLOBAL_GET("input/keyboard_player_id_override").operator int());
|
||||
return;
|
||||
}
|
||||
|
||||
// Mouse events.
|
||||
|
||||
Ref<InputEventMouseButton> mb = event;
|
||||
Ref<InputEventMouseMotion> mm = event;
|
||||
if (mb.is_valid() || mm.is_valid()) {
|
||||
player = (PlayerId)(GLOBAL_GET("input/mouse_player_id_override").operator int());
|
||||
return;
|
||||
}
|
||||
|
||||
// Joypad events.
|
||||
|
||||
Ref<InputEventJoypadButton> jb = event;
|
||||
Ref<InputEventJoypadMotion> jm = event;
|
||||
if (jb.is_valid() || jm.is_valid()) {
|
||||
Input *input = Input::get_singleton();
|
||||
HashMap<int, Input::Joypad>::Iterator E = input->_get_joy_names().find(device);
|
||||
|
||||
if (!E) {
|
||||
player = PlayerId::P1;
|
||||
return;
|
||||
}
|
||||
|
||||
player = E->value.player_id;
|
||||
}
|
||||
|
||||
// Touch events.
|
||||
|
||||
Ref<InputEventScreenTouch> st = event;
|
||||
Ref<InputEventScreenDrag> sd = event;
|
||||
Ref<InputEventGesture> ge = event;
|
||||
if (st.is_valid() || sd.is_valid() || ge.is_valid()) {
|
||||
player = (PlayerId)(GLOBAL_GET("input/touch_player_id_override").operator int());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool InputEvent::is_action(const StringName &p_action, bool p_exact_match) const {
|
||||
return InputMap::get_singleton()->event_is_action(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match);
|
||||
}
|
||||
|
||||
bool InputEvent::is_action_pressed(const StringName &p_action, bool p_allow_echo, bool p_exact_match) const {
|
||||
bool InputEvent::is_action_pressed(const StringName &p_action, bool p_allow_echo, bool p_exact_match, PlayerId p_player_id) const {
|
||||
if (player != p_player_id) {
|
||||
return false;
|
||||
}
|
||||
bool pressed_state;
|
||||
bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr);
|
||||
return valid && pressed_state && (p_allow_echo || !is_echo());
|
||||
}
|
||||
|
||||
bool InputEvent::is_action_released(const StringName &p_action, bool p_exact_match) const {
|
||||
bool InputEvent::is_action_released(const StringName &p_action, bool p_exact_match, PlayerId p_player_id) const {
|
||||
if (player != p_player_id) {
|
||||
return false;
|
||||
}
|
||||
bool pressed_state;
|
||||
bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr);
|
||||
return valid && !pressed_state;
|
||||
}
|
||||
|
||||
float InputEvent::get_action_strength(const StringName &p_action, bool p_exact_match) const {
|
||||
float InputEvent::get_action_strength(const StringName &p_action, bool p_exact_match, PlayerId p_player_id) const {
|
||||
if (player != p_player_id) {
|
||||
return 0.0f;
|
||||
}
|
||||
float strength;
|
||||
bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, nullptr, &strength, nullptr);
|
||||
return valid ? strength : 0.0f;
|
||||
}
|
||||
|
||||
float InputEvent::get_action_raw_strength(const StringName &p_action, bool p_exact_match) const {
|
||||
float InputEvent::get_action_raw_strength(const StringName &p_action, bool p_exact_match, PlayerId p_player_id) const {
|
||||
if (player != p_player_id) {
|
||||
return 0.0f;
|
||||
}
|
||||
float raw_strength;
|
||||
bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, nullptr, nullptr, &raw_strength);
|
||||
return valid ? raw_strength : 0.0f;
|
||||
|
|
@ -111,10 +182,14 @@ void InputEvent::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_device", "device"), &InputEvent::set_device);
|
||||
ClassDB::bind_method(D_METHOD("get_device"), &InputEvent::get_device);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_player", "player"), &InputEvent::set_player);
|
||||
ClassDB::bind_method(D_METHOD("get_player"), &InputEvent::get_player);
|
||||
ClassDB::bind_method(D_METHOD("set_player_from_device"), &InputEvent::set_player_from_device);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_action", "action", "exact_match"), &InputEvent::is_action, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "allow_echo", "exact_match"), &InputEvent::is_action_pressed, DEFVAL(false), DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("is_action_released", "action", "exact_match"), &InputEvent::is_action_released, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match"), &InputEvent::get_action_strength, DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "allow_echo", "exact_match", "player_id"), &InputEvent::is_action_pressed, DEFVAL(false), DEFVAL(false), DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("is_action_released", "action", "exact_match", "player_id"), &InputEvent::is_action_released, DEFVAL(false), DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match", "player_id"), &InputEvent::get_action_strength, DEFVAL(false), DEFVAL(PlayerId::P1));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_canceled"), &InputEvent::is_canceled);
|
||||
ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed);
|
||||
|
|
@ -132,6 +207,7 @@ void InputEvent::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("xformed_by", "xform", "local_ofs"), &InputEvent::xformed_by, DEFVAL(Vector2()));
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "device"), "set_device", "get_device");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "player"), "set_player", "get_player");
|
||||
|
||||
BIND_CONSTANT(DEVICE_ID_EMULATION);
|
||||
}
|
||||
|
|
@ -741,6 +817,7 @@ Ref<InputEvent> InputEventMouseButton::xformed_by(const Transform2D &p_xform, co
|
|||
mb.instantiate();
|
||||
|
||||
mb->set_device(get_device());
|
||||
mb->set_player_from_device();
|
||||
mb->set_window_id(get_window_id());
|
||||
mb->set_modifiers_from_event(this);
|
||||
|
||||
|
|
@ -960,6 +1037,7 @@ Ref<InputEvent> InputEventMouseMotion::xformed_by(const Transform2D &p_xform, co
|
|||
mm.instantiate();
|
||||
|
||||
mm->set_device(get_device());
|
||||
mm->set_player_from_device();
|
||||
mm->set_window_id(get_window_id());
|
||||
|
||||
mm->set_modifiers_from_event(this);
|
||||
|
|
@ -1363,6 +1441,7 @@ Ref<InputEvent> InputEventScreenTouch::xformed_by(const Transform2D &p_xform, co
|
|||
Ref<InputEventScreenTouch> st;
|
||||
st.instantiate();
|
||||
st->set_device(get_device());
|
||||
st->set_player_from_device();
|
||||
st->set_window_id(get_window_id());
|
||||
st->set_index(index);
|
||||
st->set_position(p_xform.xform(pos + p_local_ofs));
|
||||
|
|
@ -1488,6 +1567,7 @@ Ref<InputEvent> InputEventScreenDrag::xformed_by(const Transform2D &p_xform, con
|
|||
sd.instantiate();
|
||||
|
||||
sd->set_device(get_device());
|
||||
sd->set_player_from_device();
|
||||
sd->set_window_id(get_window_id());
|
||||
|
||||
sd->set_index(index);
|
||||
|
|
@ -1706,6 +1786,7 @@ Ref<InputEvent> InputEventMagnifyGesture::xformed_by(const Transform2D &p_xform,
|
|||
ev.instantiate();
|
||||
|
||||
ev->set_device(get_device());
|
||||
ev->set_player_from_device();
|
||||
ev->set_window_id(get_window_id());
|
||||
|
||||
ev->set_modifiers_from_event(this);
|
||||
|
|
@ -1748,6 +1829,7 @@ Ref<InputEvent> InputEventPanGesture::xformed_by(const Transform2D &p_xform, con
|
|||
ev.instantiate();
|
||||
|
||||
ev->set_device(get_device());
|
||||
ev->set_player_from_device();
|
||||
ev->set_window_id(get_window_id());
|
||||
|
||||
ev->set_modifiers_from_event(this);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class InputEvent : public Resource {
|
|||
GDCLASS(InputEvent, Resource);
|
||||
|
||||
int device = 0;
|
||||
PlayerId player = PlayerId::P1;
|
||||
|
||||
protected:
|
||||
bool canceled = false;
|
||||
|
|
@ -68,11 +69,15 @@ public:
|
|||
void set_device(int p_device);
|
||||
int get_device() const;
|
||||
|
||||
void set_player(PlayerId p_player);
|
||||
PlayerId get_player() const;
|
||||
void set_player_from_device();
|
||||
|
||||
bool is_action(const StringName &p_action, bool p_exact_match = false) const;
|
||||
bool is_action_pressed(const StringName &p_action, bool p_allow_echo = false, bool p_exact_match = false) const;
|
||||
bool is_action_released(const StringName &p_action, bool p_exact_match = false) const;
|
||||
float get_action_strength(const StringName &p_action, bool p_exact_match = false) const;
|
||||
float get_action_raw_strength(const StringName &p_action, bool p_exact_match = false) const;
|
||||
bool is_action_pressed(const StringName &p_action, bool p_allow_echo = false, bool p_exact_match = false, PlayerId p_player_id = PlayerId::P1) const;
|
||||
bool is_action_released(const StringName &p_action, bool p_exact_match = false, PlayerId p_player_id = PlayerId::P1) const;
|
||||
float get_action_strength(const StringName &p_action, bool p_exact_match = false, PlayerId p_player_id = PlayerId::P1) const;
|
||||
float get_action_raw_strength(const StringName &p_action, bool p_exact_match = false, PlayerId p_player_id = PlayerId::P1) const;
|
||||
|
||||
bool is_canceled() const;
|
||||
bool is_pressed() const;
|
||||
|
|
|
|||
|
|
@ -432,6 +432,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
|
|||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::Y));
|
||||
inputs.back()->get()->set_device(ALL_DEVICES);
|
||||
inputs.push_back(InputEventKey::create_reference(Key::SPACE));
|
||||
default_builtin_cache.insert("ui_select", inputs);
|
||||
|
||||
|
|
@ -450,25 +451,33 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
|
|||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::LEFT));
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_LEFT));
|
||||
inputs.back()->get()->set_device(ALL_DEVICES);
|
||||
inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, -1.0));
|
||||
inputs.back()->get()->set_device(ALL_DEVICES);
|
||||
default_builtin_cache.insert("ui_left", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::RIGHT));
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_RIGHT));
|
||||
inputs.back()->get()->set_device(ALL_DEVICES);
|
||||
inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, 1.0));
|
||||
inputs.back()->get()->set_device(ALL_DEVICES);
|
||||
default_builtin_cache.insert("ui_right", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::UP));
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_UP));
|
||||
inputs.back()->get()->set_device(ALL_DEVICES);
|
||||
inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, -1.0));
|
||||
inputs.back()->get()->set_device(ALL_DEVICES);
|
||||
default_builtin_cache.insert("ui_up", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
inputs.push_back(InputEventKey::create_reference(Key::DOWN));
|
||||
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_DOWN));
|
||||
inputs.back()->get()->set_device(ALL_DEVICES);
|
||||
inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, 1.0));
|
||||
inputs.back()->get()->set_device(ALL_DEVICES);
|
||||
default_builtin_cache.insert("ui_down", inputs);
|
||||
|
||||
inputs = List<Ref<InputEvent>>();
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ enum PropertyHint {
|
|||
PROPERTY_HINT_TOOL_BUTTON,
|
||||
PROPERTY_HINT_ONESHOT, ///< the property will be changed by self after setting, such as AudioStreamPlayer.playing, Particles.emitting.
|
||||
PROPERTY_HINT_NO_NODEPATH, /// < this property will not contain a NodePath, regardless of type (Array, Dictionary, List, etc.). Needed for SceneTreeDock.
|
||||
PROPERTY_HINT_LAYERS_PLAYER_MASK,
|
||||
PROPERTY_HINT_MAX,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ void MIDIDriver::send_event(int p_device_index, uint8_t p_status,
|
|||
Ref<InputEventMIDI> event;
|
||||
event.instantiate();
|
||||
event->set_device(p_device_index);
|
||||
event->set_player_from_device();
|
||||
event->set_channel(Parser::channel(p_status));
|
||||
event->set_message(msg);
|
||||
switch (msg) {
|
||||
|
|
|
|||
|
|
@ -174,6 +174,8 @@ VARIANT_ENUM_CAST(JoyButton);
|
|||
VARIANT_ENUM_CAST(MIDIMessage);
|
||||
VARIANT_ENUM_CAST(MouseButton);
|
||||
VARIANT_BITFIELD_CAST(MouseButtonMask);
|
||||
VARIANT_ENUM_CAST(PlayerId)
|
||||
VARIANT_BITFIELD_CAST(PlayerMask);
|
||||
VARIANT_ENUM_CAST(Orientation);
|
||||
VARIANT_ENUM_CAST(HorizontalAlignment);
|
||||
VARIANT_ENUM_CAST(VerticalAlignment);
|
||||
|
|
|
|||
|
|
@ -561,6 +561,7 @@ public:
|
|||
VARIANT_ENUM_CLASS_CONSTRUCTOR(KeyLocation)
|
||||
VARIANT_ENUM_CLASS_CONSTRUCTOR(MIDIMessage)
|
||||
VARIANT_ENUM_CLASS_CONSTRUCTOR(MouseButton)
|
||||
VARIANT_ENUM_CLASS_CONSTRUCTOR(PlayerId)
|
||||
|
||||
#undef VARIANT_ENUM_CLASS_CONSTRUCTOR
|
||||
|
||||
|
|
|
|||
|
|
@ -2444,6 +2444,30 @@
|
|||
<constant name="MOUSE_BUTTON_MASK_MB_XBUTTON2" value="256" enum="MouseButtonMask" is_bitfield="true">
|
||||
Extra mouse button 2 mask.
|
||||
</constant>
|
||||
<constant name="PLAYER_ID_P1" value="0" enum="PlayerId">
|
||||
Player 1.
|
||||
</constant>
|
||||
<constant name="PLAYER_ID_P2" value="1" enum="PlayerId">
|
||||
Player 1.
|
||||
</constant>
|
||||
<constant name="PLAYER_ID_P3" value="2" enum="PlayerId">
|
||||
Player 1.
|
||||
</constant>
|
||||
<constant name="PLAYER_ID_P4" value="3" enum="PlayerId">
|
||||
Player 1.
|
||||
</constant>
|
||||
<constant name="PLAYER_ID_P5" value="4" enum="PlayerId">
|
||||
Player 1.
|
||||
</constant>
|
||||
<constant name="PLAYER_ID_P6" value="5" enum="PlayerId">
|
||||
Player 1.
|
||||
</constant>
|
||||
<constant name="PLAYER_ID_P7" value="6" enum="PlayerId">
|
||||
Player 1.
|
||||
</constant>
|
||||
<constant name="PLAYER_ID_P8" value="7" enum="PlayerId">
|
||||
Player 1.
|
||||
</constant>
|
||||
<constant name="JOY_BUTTON_INVALID" value="-1" enum="JoyButton">
|
||||
An invalid game controller button.
|
||||
</constant>
|
||||
|
|
@ -2816,6 +2840,9 @@
|
|||
<constant name="PROPERTY_HINT_LAYERS_AVOIDANCE" value="37" enum="PropertyHint">
|
||||
Hints that an integer property is a bitmask using the optionally named avoidance layers.
|
||||
</constant>
|
||||
<constant name="PROPERTY_HINT_LAYERS_PLAYER_MASK" value="38" enum="PropertyHint">
|
||||
Hints that an integer property is a bitmask using the optionally named player mask layers.
|
||||
</constant>
|
||||
<constant name="PROPERTY_HINT_FILE" value="13" enum="PropertyHint">
|
||||
Hints that a [String] property is a path to a file. Editing it will show a file dialog for picking the path. The hint string can be a set of filters with wildcards like [code]"*.png,*.jpg"[/code].
|
||||
</constant>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
Godot propagates input events via viewports. Each [Viewport] is responsible for propagating [InputEvent]s to their child nodes. As the [member SceneTree.root] is a [Window], this already happens automatically for all UI elements in your game.
|
||||
Input events are propagated through the [SceneTree] from the root node to all child nodes by calling [method Node._input]. For UI elements specifically, it makes more sense to override the virtual method [method _gui_input], which filters out unrelated input events, such as by checking z-order, [member mouse_filter], focus, or if the event was inside of the control's bounding box.
|
||||
Call [method accept_event] so no other node receives the event. Once you accept an input, it becomes handled so [method Node._unhandled_input] will not process it.
|
||||
Only one [Control] node can be in focus. Only the node in focus will receive events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus.
|
||||
Up to [constant Input.PLAYERS_MAX] nodes can be in focus. Only focused nodes receive events. To assign focus, call [method grab_focus] specifying the player with the [enum PlayerId] argument. By default, [constant PLAYER_ID_P1] grabs focus of the node. [Control] nodes lose focus when another node grabs it or if you hide the nodes in focus.
|
||||
Multiplayer logic still applies: if another player grabs focus of a node (or even the same one), the original player's focus remains valid and separate.
|
||||
Sets [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] to tell a [Control] node to ignore mouse or touch events. You'll need it if you place an icon on top of a button.
|
||||
[Theme] resources change the control's appearance. The [member theme] of a [Control] node affects all of its direct and indirect children (as long as a chain of controls is uninterrupted). To override some of the theme items, call one of the [code]add_theme_*_override[/code] methods, like [method add_theme_font_override]. You can also override theme items in the Inspector.
|
||||
[b]Note:[/b] Theme items are [i]not[/i] [Object] properties. This means you can't access their values using [method Object.get] and [method Object.set]. Instead, use the [code]get_theme_*[/code] and [code]add_theme_*_override[/code] methods provided by this class.
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
<return type="void" />
|
||||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="strength" type="float" default="1.0" />
|
||||
<param index="2" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
This will simulate pressing the specified action.
|
||||
The strength can be used for non-boolean actions, it's ranged between 0 and 1 representing the intensity of the given action.
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
<method name="action_release">
|
||||
<return type="void" />
|
||||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
If the specified action is already pressed, this will release it.
|
||||
</description>
|
||||
|
|
@ -57,6 +59,7 @@
|
|||
<return type="float" />
|
||||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="exact_match" type="bool" default="false" />
|
||||
<param index="2" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Returns a value between 0 and 1 representing the raw intensity of the given action, ignoring the action's deadzone. In most cases, you should use [method get_action_strength] instead.
|
||||
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
|
||||
|
|
@ -66,6 +69,7 @@
|
|||
<return type="float" />
|
||||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="exact_match" type="bool" default="false" />
|
||||
<param index="2" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Returns a value between 0 and 1 representing the intensity of the given action. In a joypad, for example, the further away the axis (analog sticks or L2, R2 triggers) is from the dead zone, the closer the value will be to 1. If the action is mapped to a control that has no axis such as the keyboard, the value returned will be 0 or 1.
|
||||
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
|
||||
|
|
@ -75,6 +79,7 @@
|
|||
<return type="float" />
|
||||
<param index="0" name="negative_action" type="StringName" />
|
||||
<param index="1" name="positive_action" type="StringName" />
|
||||
<param index="2" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Get axis input by specifying two actions, one negative and one positive.
|
||||
This is a shorthand for writing [code]Input.get_action_strength("positive_action") - Input.get_action_strength("negative_action")[/code].
|
||||
|
|
@ -192,6 +197,7 @@
|
|||
<param index="2" name="negative_y" type="StringName" />
|
||||
<param index="3" name="positive_y" type="StringName" />
|
||||
<param index="4" name="deadzone" type="float" default="-1.0" />
|
||||
<param index="5" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Gets an input vector by specifying four actions for the positive and negative X and Y axes.
|
||||
This method is useful when getting vector input, such as from a joystick, directional pad, arrows, or WASD. The vector has its length limited to 1 and has a circular deadzone, which is useful for using vector input as movement.
|
||||
|
|
@ -202,6 +208,7 @@
|
|||
<return type="bool" />
|
||||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="exact_match" type="bool" default="false" />
|
||||
<param index="2" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Returns [code]true[/code] when the user has [i]started[/i] pressing the action event in the current frame or physics tick. It will only return [code]true[/code] on the frame or tick that the user pressed down the button.
|
||||
This is useful for code that needs to run only once when an action is pressed, instead of every frame while it's pressed.
|
||||
|
|
@ -215,6 +222,7 @@
|
|||
<return type="bool" />
|
||||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="exact_match" type="bool" default="false" />
|
||||
<param index="2" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Returns [code]true[/code] when the user [i]stops[/i] pressing the action event in the current frame or physics tick. It will only return [code]true[/code] on the frame or tick that the user releases the button.
|
||||
[b]Note:[/b] Returning [code]true[/code] does not imply that the action is [i]still[/i] not pressed. An action can be released and pressed again rapidly, and [code]true[/code] will still be returned so as not to miss input.
|
||||
|
|
@ -226,6 +234,7 @@
|
|||
<return type="bool" />
|
||||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="exact_match" type="bool" default="false" />
|
||||
<param index="2" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Returns [code]true[/code] if you are pressing the action event.
|
||||
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
|
||||
|
|
@ -519,5 +528,39 @@
|
|||
<constant name="CURSOR_HELP" value="16" enum="CursorShape">
|
||||
Help cursor. Usually a question mark.
|
||||
</constant>
|
||||
<constant name="PLAYERS_MAX" value="8">
|
||||
Max number of allowed players by the engine.
|
||||
</constant>
|
||||
<constant name="PLAYER_NONE" value="0" enum="PlayerMask">
|
||||
Player none, meaning no player allowed.
|
||||
</constant>
|
||||
<constant name="PLAYER_1" value="1" enum="PlayerMask">
|
||||
Mask with only player 1 activated.
|
||||
</constant>
|
||||
<constant name="PLAYER_2" value="2" enum="PlayerMask">
|
||||
Mask with only player 2 activated.
|
||||
</constant>
|
||||
<constant name="PLAYER_3" value="4" enum="PlayerMask">
|
||||
Mask with only player 3 activated.
|
||||
</constant>
|
||||
<constant name="PLAYER_4" value="8" enum="PlayerMask">
|
||||
Mask with only player 4 activated.
|
||||
</constant>
|
||||
<constant name="PLAYER_5" value="16" enum="PlayerMask">
|
||||
Mask with only player 5 activated.
|
||||
</constant>
|
||||
<constant name="PLAYER_6" value="32" enum="PlayerMask">
|
||||
Mask with only player 6 activated.
|
||||
</constant>
|
||||
<constant name="PLAYER_7" value="64" enum="PlayerMask">
|
||||
Mask with only player 7 activated.
|
||||
</constant>
|
||||
<constant name="PLAYER_8" value="128" enum="PlayerMask">
|
||||
Mask with only player 8 activated.
|
||||
</constant>
|
||||
<constant name="PLAYER_ALL" value="256" enum="PlayerMask">
|
||||
Mask with all players activated.
|
||||
</constant>
|
||||
|
||||
</constants>
|
||||
</class>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
<return type="float" />
|
||||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="exact_match" type="bool" default="false" />
|
||||
<param index="2" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Returns a value between 0.0 and 1.0 depending on the given actions' state. Useful for getting the value of events of type [InputEventJoypadMotion].
|
||||
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
|
||||
|
|
@ -50,6 +51,7 @@
|
|||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="allow_echo" type="bool" default="false" />
|
||||
<param index="2" name="exact_match" type="bool" default="false" />
|
||||
<param index="3" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the given action is being pressed (and is not an echo event for [InputEventKey] events, unless [param allow_echo] is [code]true[/code]). Not relevant for events of type [InputEventMouseMotion] or [InputEventScreenDrag].
|
||||
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
|
||||
|
|
@ -60,6 +62,7 @@
|
|||
<return type="bool" />
|
||||
<param index="0" name="action" type="StringName" />
|
||||
<param index="1" name="exact_match" type="bool" default="false" />
|
||||
<param index="2" name="player_id" type="int" enum="PlayerId" default="PLAYER_ID_P1" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the given action is released (i.e. not pressed). Not relevant for events of type [InputEventMouseMotion] or [InputEventScreenDrag].
|
||||
If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events.
|
||||
|
|
@ -107,6 +110,12 @@
|
|||
Returns [code]true[/code] if this input event is released. Not relevant for events of type [InputEventMouseMotion] or [InputEventScreenDrag].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_player_from_device">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Sets the player ID corresponding to the device ID internal mapping.
|
||||
</description>
|
||||
</method>
|
||||
<method name="xformed_by" qualifiers="const">
|
||||
<return type="InputEvent" />
|
||||
<param index="0" name="xform" type="Transform2D" />
|
||||
|
|
@ -121,6 +130,9 @@
|
|||
The event's device ID.
|
||||
[b]Note:[/b] [member device] can be negative for special use cases that don't refer to devices physically present on the system. See [constant DEVICE_ID_EMULATION].
|
||||
</member>
|
||||
<member name="player" type="int" enum="PlayerId" setter="set_player" getter="get_player" default="0">
|
||||
The event's player ID.
|
||||
</member>
|
||||
</members>
|
||||
<constants>
|
||||
<constant name="DEVICE_ID_EMULATION" value="-1">
|
||||
|
|
|
|||
|
|
@ -2113,6 +2113,27 @@
|
|||
<member name="layer_names/avoidance/layer_32" type="String" setter="" getter="" default="""">
|
||||
Optional name for the navigation avoidance layer 32. If left empty, the layer will display as "Layer 32".
|
||||
</member>
|
||||
<member name="layer_names/player_mask/layer_1" type="String" setter="" getter="" default="""">
|
||||
Optional name for the player mask layer 1. If left empty, the layer will display as "Layer 1".
|
||||
</member>
|
||||
<member name="layer_names/player_mask/layer_2" type="String" setter="" getter="" default="""">
|
||||
Optional name for the player mask layer 2. If left empty, the layer will display as "Layer 2".
|
||||
</member>
|
||||
<member name="layer_names/player_mask/layer_3" type="String" setter="" getter="" default="""">
|
||||
Optional name for the player mask layer 3. If left empty, the layer will display as "Layer 3".
|
||||
</member>
|
||||
<member name="layer_names/player_mask/layer_4" type="String" setter="" getter="" default="""">
|
||||
Optional name for the player mask layer 4. If left empty, the layer will display as "Layer 4".
|
||||
</member>
|
||||
<member name="layer_names/player_mask/layer_5" type="String" setter="" getter="" default="""">
|
||||
Optional name for the player mask layer 5. If left empty, the layer will display as "Layer 5".
|
||||
</member>
|
||||
<member name="layer_names/player_mask/layer_6" type="String" setter="" getter="" default="""">
|
||||
Optional name for the player mask layer 6. If left empty, the layer will display as "Layer 6".
|
||||
</member>
|
||||
<member name="layer_names/player_mask/layer_7" type="String" setter="" getter="" default="""">
|
||||
Optional name for the player mask layer 7. If left empty, the layer will display as "Layer 7".
|
||||
</member>
|
||||
<member name="memory/limits/message_queue/max_size_mb" type="int" setter="" getter="" default="32">
|
||||
Godot uses a message queue to defer some function calls. If you run out of space on it (you will see an error), you can increase the size here.
|
||||
</member>
|
||||
|
|
|
|||
|
|
@ -594,10 +594,10 @@ void FindReplaceBar::_show_search(bool p_with_replace, bool p_show_only) {
|
|||
|
||||
if (focus_replace) {
|
||||
search_text->deselect();
|
||||
callable_mp((Control *)replace_text, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)replace_text, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} else {
|
||||
replace_text->deselect();
|
||||
callable_mp((Control *)search_text, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)search_text, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
}
|
||||
|
||||
if (on_one_line) {
|
||||
|
|
|
|||
|
|
@ -470,7 +470,7 @@ void ConnectDialog::_update_warning_label() {
|
|||
}
|
||||
|
||||
void ConnectDialog::_post_popup() {
|
||||
callable_mp((Control *)dst_method, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)dst_method, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
callable_mp(dst_method, &LineEdit::select_all).call_deferred();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ void CreateDialog::_notification(int p_what) {
|
|||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
if (is_visible()) {
|
||||
callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(); // Still not visible.
|
||||
callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(PlayerId::P1); // Still not visible.
|
||||
search_box->select_all();
|
||||
} else {
|
||||
EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "create_new_node", Rect2(get_position(), get_size()));
|
||||
|
|
|
|||
|
|
@ -1195,6 +1195,12 @@ void EditorPropertyLayers::setup(LayerType p_layer_type) {
|
|||
layer_group_size = 4;
|
||||
layer_count = 32;
|
||||
} break;
|
||||
|
||||
case LAYER_PLAYER_MASK: {
|
||||
basename = "layer_names/player_mask";
|
||||
layer_group_size = 4;
|
||||
layer_count = 8;
|
||||
} break;
|
||||
}
|
||||
|
||||
Vector<String> names;
|
||||
|
|
@ -2757,7 +2763,7 @@ void EditorPropertyNodePath::_menu_option(int p_idx) {
|
|||
const NodePath &np = _get_node_path();
|
||||
edit->set_text(np);
|
||||
edit->show();
|
||||
callable_mp((Control *)edit, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)edit, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
|
||||
case ACTION_SELECT: {
|
||||
|
|
@ -3535,7 +3541,8 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
|
|||
p_hint == PROPERTY_HINT_LAYERS_3D_PHYSICS ||
|
||||
p_hint == PROPERTY_HINT_LAYERS_3D_RENDER ||
|
||||
p_hint == PROPERTY_HINT_LAYERS_3D_NAVIGATION ||
|
||||
p_hint == PROPERTY_HINT_LAYERS_AVOIDANCE) {
|
||||
p_hint == PROPERTY_HINT_LAYERS_AVOIDANCE ||
|
||||
p_hint == PROPERTY_HINT_LAYERS_PLAYER_MASK) {
|
||||
EditorPropertyLayers::LayerType lt = EditorPropertyLayers::LAYER_RENDER_2D;
|
||||
switch (p_hint) {
|
||||
case PROPERTY_HINT_LAYERS_2D_RENDER:
|
||||
|
|
@ -3559,6 +3566,9 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
|
|||
case PROPERTY_HINT_LAYERS_AVOIDANCE:
|
||||
lt = EditorPropertyLayers::LAYER_AVOIDANCE;
|
||||
break;
|
||||
case PROPERTY_HINT_LAYERS_PLAYER_MASK:
|
||||
lt = EditorPropertyLayers::LAYER_PLAYER_MASK;
|
||||
break;
|
||||
default: {
|
||||
} //compiler could be smarter here and realize this can't happen
|
||||
}
|
||||
|
|
|
|||
|
|
@ -303,6 +303,7 @@ public:
|
|||
LAYER_RENDER_3D,
|
||||
LAYER_NAVIGATION_3D,
|
||||
LAYER_AVOIDANCE,
|
||||
LAYER_PLAYER_MASK,
|
||||
};
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -517,7 +517,7 @@ void EditorPropertyArray::update_property() {
|
|||
slot.set_index(idx);
|
||||
}
|
||||
if (slot.index == changing_type_index) {
|
||||
callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);
|
||||
callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(PlayerId::P1);
|
||||
changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE;
|
||||
}
|
||||
slot.prop->update_property();
|
||||
|
|
@ -1337,7 +1337,7 @@ void EditorPropertyDictionary::update_property() {
|
|||
// We need to grab the focus of the property that is being changed, even if the type didn't actually changed.
|
||||
// Otherwise, focus will stay on the change type button, which is not very user friendly.
|
||||
if (changing_type_index == slot.index) {
|
||||
callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0);
|
||||
callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(PlayerId::P1);
|
||||
changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE; // Reset to avoid grabbing focus again.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -404,16 +404,16 @@ void FindInFilesDialog::set_search_text(const String &text) {
|
|||
_search_text_line_edit->set_text(text);
|
||||
_on_search_text_modified(text);
|
||||
}
|
||||
callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
_search_text_line_edit->select_all();
|
||||
} else if (_mode == REPLACE_MODE) {
|
||||
if (!text.is_empty()) {
|
||||
_search_text_line_edit->set_text(text);
|
||||
callable_mp((Control *)_replace_text_line_edit, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)_replace_text_line_edit, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
_replace_text_line_edit->select_all();
|
||||
_on_search_text_modified(text);
|
||||
} else {
|
||||
callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
_search_text_line_edit->select_all();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2236,7 +2236,7 @@ void EditorFileDialog::set_show_search_filter(bool p_show) {
|
|||
search_string.clear();
|
||||
filter_box->clear();
|
||||
if (filter_box->has_focus()) {
|
||||
item_list->call_deferred("grab_focus");
|
||||
item_list->call_deferred("grab_focus", PlayerId::P1);
|
||||
}
|
||||
}
|
||||
show_search_filter = p_show;
|
||||
|
|
|
|||
|
|
@ -681,7 +681,7 @@ void EditorSpinSlider::_focus_entered() {
|
|||
value_input->set_focus_next(find_next_valid_focus()->get_path());
|
||||
value_input->set_focus_previous(find_prev_valid_focus()->get_path());
|
||||
callable_mp((CanvasItem *)value_input_popup, &CanvasItem::show).call_deferred();
|
||||
callable_mp((Control *)value_input, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)value_input, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
callable_mp(value_input, &LineEdit ::select_all).call_deferred();
|
||||
emit_signal("value_focus_entered");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2227,7 +2227,7 @@ void SceneTreeDialog::_notification(int p_what) {
|
|||
tree->update_tree();
|
||||
|
||||
// Select the search bar by default.
|
||||
callable_mp((Control *)filter, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)filter, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
}
|
||||
} break;
|
||||
|
||||
|
|
|
|||
|
|
@ -259,6 +259,7 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven
|
|||
|
||||
// Maintain device selection.
|
||||
received_event->set_device(_get_current_device());
|
||||
received_event->set_player_from_device();
|
||||
|
||||
_set_event(received_event, received_original_event);
|
||||
}
|
||||
|
|
@ -521,6 +522,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
|
|||
|
||||
// Maintain selected device
|
||||
mb->set_device(_get_current_device());
|
||||
mb->set_player_from_device();
|
||||
|
||||
_set_event(mb, mb, false);
|
||||
} break;
|
||||
|
|
@ -530,6 +532,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
|
|||
|
||||
// Maintain selected device
|
||||
jb->set_device(_get_current_device());
|
||||
jb->set_player_from_device();
|
||||
|
||||
_set_event(jb, jb, false);
|
||||
} break;
|
||||
|
|
@ -544,6 +547,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() {
|
|||
|
||||
// Maintain selected device
|
||||
jm->set_device(_get_current_device());
|
||||
jm->set_player_from_device();
|
||||
|
||||
_set_event(jm, jm, false);
|
||||
} break;
|
||||
|
|
@ -554,6 +558,7 @@ void InputEventConfigurationDialog::_device_selection_changed(int p_option_butto
|
|||
// Subtract 1 as option index 0 corresponds to "All Devices" (value of -1)
|
||||
// and option index 1 corresponds to device 0, etc...
|
||||
event->set_device(p_option_button_index - 1);
|
||||
event->set_player_from_device();
|
||||
event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2779,7 +2779,7 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
|
|||
|
||||
// Grab focus
|
||||
if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {
|
||||
callable_mp((Control *)viewport, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)viewport, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1447,27 +1447,27 @@ void ScriptTextEditor::_edit_option(int p_op) {
|
|||
switch (p_op) {
|
||||
case EDIT_UNDO: {
|
||||
tx->undo();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_REDO: {
|
||||
tx->redo();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_CUT: {
|
||||
tx->cut();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_COPY: {
|
||||
tx->copy();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_PASTE: {
|
||||
tx->paste();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_SELECT_ALL: {
|
||||
tx->select_all();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_MOVE_LINE_UP: {
|
||||
code_editor->get_text_editor()->move_lines_up();
|
||||
|
|
|
|||
|
|
@ -362,27 +362,27 @@ void TextEditor::_edit_option(int p_op) {
|
|||
switch (p_op) {
|
||||
case EDIT_UNDO: {
|
||||
tx->undo();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_REDO: {
|
||||
tx->redo();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_CUT: {
|
||||
tx->cut();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_COPY: {
|
||||
tx->copy();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_PASTE: {
|
||||
tx->paste();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_SELECT_ALL: {
|
||||
tx->select_all();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
} break;
|
||||
case EDIT_MOVE_LINE_UP: {
|
||||
code_editor->get_text_editor()->move_lines_up();
|
||||
|
|
|
|||
|
|
@ -734,7 +734,7 @@ void TextShaderEditor::_menu_option(int p_option) {
|
|||
} break;
|
||||
}
|
||||
if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) {
|
||||
callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -989,7 +989,7 @@ void TileSourceInspectorPlugin::_show_id_edit_dialog(Object *p_for_source) {
|
|||
edited_source = p_for_source;
|
||||
id_input->set_value(p_for_source->get("id"));
|
||||
id_edit_dialog->popup_centered(Vector2i(400, 0) * EDSCALE);
|
||||
callable_mp((Control *)id_input->get_line_edit(), &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)id_input->get_line_edit(), &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
}
|
||||
|
||||
void TileSourceInspectorPlugin::_confirm_change_id() {
|
||||
|
|
|
|||
|
|
@ -774,7 +774,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
|
|||
renderer_container->hide();
|
||||
default_files_container->hide();
|
||||
|
||||
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
callable_mp(project_name, &LineEdit::select_all).call_deferred();
|
||||
} else {
|
||||
if (p_reset_name) {
|
||||
|
|
@ -819,7 +819,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
|
|||
renderer_container->show();
|
||||
default_files_container->show();
|
||||
|
||||
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
callable_mp(project_name, &LineEdit::select_all).call_deferred();
|
||||
} else if (mode == MODE_INSTALL) {
|
||||
set_title(TTR("Install Project:") + " " + zip_title);
|
||||
|
|
@ -832,7 +832,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) {
|
|||
renderer_container->hide();
|
||||
default_files_container->hide();
|
||||
|
||||
callable_mp((Control *)project_path, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)project_path, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
}
|
||||
|
||||
auto_dir = "";
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ void SceneCreateDialog::config(const String &p_dir) {
|
|||
directory = p_dir;
|
||||
root_name_edit->set_text("");
|
||||
scene_name_edit->set_text("");
|
||||
callable_mp((Control *)scene_name_edit, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)scene_name_edit, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
validation_panel->update();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -530,6 +530,17 @@
|
|||
[/codeblock]
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@export_flags_player_mask">
|
||||
<return type="void" />
|
||||
<description>
|
||||
Export an integer property as a bit flag field for player mask layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/player_mask/layer_1].
|
||||
See also [constant PROPERTY_HINT_LAYERS_PLAYER_MASK].
|
||||
[codeblock]
|
||||
@export_flags_player_mask var player_mask_layers: int
|
||||
@export_flags_player_mask var player_mask_layers_array: Array[int]
|
||||
[/codeblock]
|
||||
</description>
|
||||
</annotation>
|
||||
<annotation name="@export_global_dir">
|
||||
<return type="void" />
|
||||
<description>
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ GDScriptParser::GDScriptParser() {
|
|||
register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>);
|
||||
register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>);
|
||||
register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_AVOIDANCE, Variant::INT>);
|
||||
register_annotation(MethodInfo("@export_flags_player_mask"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_PLAYER_MASK, Variant::INT>);
|
||||
register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation);
|
||||
register_annotation(MethodInfo("@export_custom", PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "PropertyHint"), PropertyInfo(Variant::STRING, "hint_string"), PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_BITFIELD, "PropertyUsageFlags")), AnnotationInfo::VARIABLE, &GDScriptParser::export_custom_annotation, varray(PROPERTY_USAGE_DEFAULT));
|
||||
register_annotation(MethodInfo("@export_tool_button", PropertyInfo(Variant::STRING, "text"), PropertyInfo(Variant::STRING, "icon")), AnnotationInfo::VARIABLE, &GDScriptParser::export_tool_button_annotation, varray(""));
|
||||
|
|
|
|||
|
|
@ -226,7 +226,15 @@ void Button::_notification(int p_what) {
|
|||
style->draw(ci, Rect2(Point2(), size));
|
||||
}
|
||||
|
||||
if (has_focus()) {
|
||||
bool has_any_focus = false;
|
||||
for (int i = 0; i < PLAYERS_MAX; i++) {
|
||||
if (has_focus((PlayerId)i)) {
|
||||
has_any_focus = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_any_focus) {
|
||||
theme_cache.focus->draw(ci, Rect2(Point2(), size));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ void ColorPicker::finish_shaders() {
|
|||
}
|
||||
|
||||
void ColorPicker::set_focus_on_line_edit() {
|
||||
callable_mp((Control *)c_text, &Control::grab_focus).call_deferred();
|
||||
callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(PlayerId::P1);
|
||||
}
|
||||
|
||||
void ColorPicker::_update_controls() {
|
||||
|
|
|
|||
|
|
@ -2014,21 +2014,41 @@ Control::FocusMode Control::get_focus_mode() const {
|
|||
return data.focus_mode;
|
||||
}
|
||||
|
||||
bool Control::has_focus() const {
|
||||
ERR_READ_THREAD_GUARD_V(false);
|
||||
return is_inside_tree() && get_viewport()->_gui_control_has_focus(this);
|
||||
TypedArray<int> Control::get_focused_players_id() const {
|
||||
ERR_READ_THREAD_GUARD_V(TypedArray<int>());
|
||||
|
||||
const Control *const *key_focus = get_viewport()->gui.key_focus;
|
||||
TypedArray<int> ret;
|
||||
|
||||
for (int i = 0; i < PLAYERS_MAX; i++) {
|
||||
if (key_focus[i] == this) {
|
||||
ret.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Control::grab_focus() {
|
||||
bool Control::has_focus(PlayerId p_player_id) const {
|
||||
ERR_READ_THREAD_GUARD_V(false);
|
||||
return is_inside_tree() && get_viewport()->_gui_control_has_focus(this, p_player_id);
|
||||
}
|
||||
|
||||
void Control::grab_focus(PlayerId p_player_id) {
|
||||
ERR_MAIN_THREAD_GUARD;
|
||||
ERR_FAIL_COND(!is_inside_tree());
|
||||
|
||||
if (!Input::is_player_id_in_mask(calculate_ancestral_player_mask(), p_player_id)) {
|
||||
// Can't grab focus if that player is not allowed.
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.focus_mode == FOCUS_NONE) {
|
||||
WARN_PRINT("This control can't grab focus. Use set_focus_mode() to allow a control to get focus.");
|
||||
return;
|
||||
}
|
||||
|
||||
get_viewport()->_gui_control_grab_focus(this);
|
||||
get_viewport()->_gui_control_grab_focus(this, p_player_id);
|
||||
}
|
||||
|
||||
void Control::grab_click_focus() {
|
||||
|
|
@ -2038,18 +2058,18 @@ void Control::grab_click_focus() {
|
|||
get_viewport()->_gui_grab_click_focus(this);
|
||||
}
|
||||
|
||||
void Control::release_focus() {
|
||||
void Control::release_focus(PlayerId p_player_id) {
|
||||
ERR_MAIN_THREAD_GUARD;
|
||||
ERR_FAIL_COND(!is_inside_tree());
|
||||
|
||||
if (!has_focus()) {
|
||||
if (!has_focus(p_player_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
get_viewport()->gui_release_focus();
|
||||
get_viewport()->gui_release_focus(p_player_id);
|
||||
}
|
||||
|
||||
static Control *_next_control(Control *p_from) {
|
||||
static Control *_next_control(Control *p_from, const PlayerId p_player_id = PlayerId::P1) {
|
||||
if (p_from->is_set_as_top_level()) {
|
||||
return nullptr; // Can't go above.
|
||||
}
|
||||
|
|
@ -2064,7 +2084,8 @@ static Control *_next_control(Control *p_from) {
|
|||
ERR_FAIL_INDEX_V(next, parent->get_child_count(), nullptr);
|
||||
for (int i = (next + 1); i < parent->get_child_count(); i++) {
|
||||
Control *c = Object::cast_to<Control>(parent->get_child(i));
|
||||
if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) {
|
||||
bool is_player_mask_compatible = Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id);
|
||||
if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level() || !is_player_mask_compatible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -2072,10 +2093,10 @@ static Control *_next_control(Control *p_from) {
|
|||
}
|
||||
|
||||
// No next in parent, try the same in parent.
|
||||
return _next_control(parent);
|
||||
return _next_control(parent, p_player_id);
|
||||
}
|
||||
|
||||
Control *Control::find_next_valid_focus() const {
|
||||
Control *Control::find_next_valid_focus(PlayerId p_player_id) const {
|
||||
ERR_READ_THREAD_GUARD_V(nullptr);
|
||||
Control *from = const_cast<Control *>(this);
|
||||
|
||||
|
|
@ -2087,7 +2108,17 @@ Control *Control::find_next_valid_focus() const {
|
|||
ERR_FAIL_NULL_V_MSG(n, nullptr, "Next focus node path is invalid: '" + data.focus_next + "'.");
|
||||
Control *c = Object::cast_to<Control>(n);
|
||||
ERR_FAIL_NULL_V_MSG(c, nullptr, "Next focus node is not a control: '" + n->get_name() + "'.");
|
||||
if (c->is_visible() && c->get_focus_mode() != FOCUS_NONE) {
|
||||
bool valid = true;
|
||||
if (!c->is_visible()) {
|
||||
valid = false;
|
||||
}
|
||||
if (c->get_focus_mode() == FOCUS_NONE) {
|
||||
valid = false;
|
||||
}
|
||||
if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) {
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
|
@ -2102,12 +2133,16 @@ Control *Control::find_next_valid_focus() const {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
next_child = c;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!next_child) {
|
||||
next_child = _next_control(from);
|
||||
next_child = _next_control(from, p_player_id);
|
||||
if (!next_child) { // Nothing else. Go up and find either window or subwindow.
|
||||
next_child = const_cast<Control *>(this);
|
||||
while (next_child && !next_child->is_set_as_top_level()) {
|
||||
|
|
@ -2142,11 +2177,12 @@ Control *Control::find_next_valid_focus() const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
static Control *_prev_control(Control *p_from) {
|
||||
static Control *_prev_control(Control *p_from, const PlayerId p_player_id = PlayerId::P1) {
|
||||
Control *child = nullptr;
|
||||
for (int i = p_from->get_child_count() - 1; i >= 0; i--) {
|
||||
Control *c = Object::cast_to<Control>(p_from->get_child(i));
|
||||
if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) {
|
||||
bool is_player_mask_compatible = Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id);
|
||||
if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level() || !is_player_mask_compatible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -2159,13 +2195,12 @@ static Control *_prev_control(Control *p_from) {
|
|||
}
|
||||
|
||||
// No prev in parent, try the same in parent.
|
||||
return _prev_control(child);
|
||||
return _prev_control(child, p_player_id);
|
||||
}
|
||||
|
||||
Control *Control::find_prev_valid_focus() const {
|
||||
Control *Control::find_prev_valid_focus(PlayerId p_player_id) const {
|
||||
ERR_READ_THREAD_GUARD_V(nullptr);
|
||||
Control *from = const_cast<Control *>(this);
|
||||
|
||||
while (true) {
|
||||
// If the focus property is manually overwritten, attempt to use it.
|
||||
|
||||
|
|
@ -2174,7 +2209,17 @@ Control *Control::find_prev_valid_focus() const {
|
|||
ERR_FAIL_NULL_V_MSG(n, nullptr, "Previous focus node path is invalid: '" + data.focus_prev + "'.");
|
||||
Control *c = Object::cast_to<Control>(n);
|
||||
ERR_FAIL_NULL_V_MSG(c, nullptr, "Previous focus node is not a control: '" + n->get_name() + "'.");
|
||||
if (c->is_visible() && c->get_focus_mode() != FOCUS_NONE) {
|
||||
bool valid = true;
|
||||
if (!c->is_visible()) {
|
||||
valid = false;
|
||||
}
|
||||
if (c->get_focus_mode() == FOCUS_NONE) {
|
||||
valid = false;
|
||||
}
|
||||
if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) {
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
|
@ -2186,7 +2231,7 @@ Control *Control::find_prev_valid_focus() const {
|
|||
if (from->is_set_as_top_level() || !Object::cast_to<Control>(from->get_parent())) {
|
||||
// Find last of the children.
|
||||
|
||||
prev_child = _prev_control(from);
|
||||
prev_child = _prev_control(from, p_player_id);
|
||||
|
||||
} else {
|
||||
for (int i = (from->get_index() - 1); i >= 0; i--) {
|
||||
|
|
@ -2196,6 +2241,10 @@ Control *Control::find_prev_valid_focus() const {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
prev_child = c;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2203,7 +2252,7 @@ Control *Control::find_prev_valid_focus() const {
|
|||
if (!prev_child) {
|
||||
prev_child = Object::cast_to<Control>(from->get_parent());
|
||||
} else {
|
||||
prev_child = _prev_control(prev_child);
|
||||
prev_child = _prev_control(prev_child, p_player_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2253,9 +2302,39 @@ NodePath Control::get_focus_previous() const {
|
|||
return data.focus_prev;
|
||||
}
|
||||
|
||||
void Control::set_player_mask(BitField<PlayerMask> p_player_mask) {
|
||||
ERR_MAIN_THREAD_GUARD;
|
||||
data.player_mask = p_player_mask;
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
BitField<PlayerMask> Control::get_player_mask() const {
|
||||
ERR_READ_THREAD_GUARD_V(BitField<PlayerMask>(PLAYER_ALL));
|
||||
if (data.initialized) {
|
||||
return data.player_mask;
|
||||
} else {
|
||||
return BitField<PlayerMask>(PLAYER_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
BitField<PlayerMask> Control::calculate_ancestral_player_mask() const {
|
||||
// Editor doesn't need multiplayer UI features (also prevents a crash).
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
return BitField<PlayerMask>(PLAYER_ALL);
|
||||
}
|
||||
|
||||
BitField<PlayerMask> mask = get_player_mask();
|
||||
Control *parent = Object::cast_to<Control>(get_parent());
|
||||
while (parent) {
|
||||
mask = mask & parent->get_player_mask();
|
||||
parent = Object::cast_to<Control>(parent->get_parent());
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
#define MAX_NEIGHBOR_SEARCH_COUNT 512
|
||||
|
||||
Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
|
||||
Control *Control::_get_focus_neighbor(Side p_side, int p_count, PlayerId p_player_id) {
|
||||
ERR_FAIL_INDEX_V((int)p_side, 4, nullptr);
|
||||
|
||||
if (p_count >= MAX_NEIGHBOR_SEARCH_COUNT) {
|
||||
|
|
@ -2273,11 +2352,14 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
|
|||
if (c->get_focus_mode() == FOCUS_NONE) {
|
||||
valid = false;
|
||||
}
|
||||
if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) {
|
||||
valid = false;
|
||||
}
|
||||
if (valid) {
|
||||
return c;
|
||||
}
|
||||
|
||||
c = c->_get_focus_neighbor(p_side, p_count + 1);
|
||||
c = c->_get_focus_neighbor(p_side, p_count + 1, p_player_id);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
|
@ -2382,8 +2464,8 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) {
|
|||
return result;
|
||||
}
|
||||
|
||||
Control *Control::find_valid_focus_neighbor(Side p_side) const {
|
||||
return const_cast<Control *>(this)->_get_focus_neighbor(p_side);
|
||||
Control *Control::find_valid_focus_neighbor(Side p_side, PlayerId p_player_id) const {
|
||||
return const_cast<Control *>(this)->_get_focus_neighbor(p_side, 0, p_player_id);
|
||||
}
|
||||
|
||||
void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Rect2 &p_rect, const Rect2 &p_clamp, real_t p_min, real_t &r_closest_dist_squared, Control **r_closest) {
|
||||
|
|
@ -2395,6 +2477,11 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons
|
|||
Container *container = Object::cast_to<Container>(p_at);
|
||||
bool in_container = container ? container->is_ancestor_of(this) : false;
|
||||
|
||||
bool is_player_mask_compatible = c->calculate_ancestral_player_mask() & calculate_ancestral_player_mask();
|
||||
if (c && !is_player_mask_compatible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (c && c != this && c->get_focus_mode() == FOCUS_ALL && !in_container && p_clamp.intersects(c->get_global_rect())) {
|
||||
Rect2 r_c = c->get_global_rect();
|
||||
r_c = r_c.intersection(p_clamp);
|
||||
|
|
@ -3518,12 +3605,13 @@ void Control::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("get_global_rect"), &Control::get_global_rect);
|
||||
ClassDB::bind_method(D_METHOD("set_focus_mode", "mode"), &Control::set_focus_mode);
|
||||
ClassDB::bind_method(D_METHOD("get_focus_mode"), &Control::get_focus_mode);
|
||||
ClassDB::bind_method(D_METHOD("has_focus"), &Control::has_focus);
|
||||
ClassDB::bind_method(D_METHOD("grab_focus"), &Control::grab_focus);
|
||||
ClassDB::bind_method(D_METHOD("release_focus"), &Control::release_focus);
|
||||
ClassDB::bind_method(D_METHOD("find_prev_valid_focus"), &Control::find_prev_valid_focus);
|
||||
ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_valid_focus);
|
||||
ClassDB::bind_method(D_METHOD("find_valid_focus_neighbor", "side"), &Control::find_valid_focus_neighbor);
|
||||
ClassDB::bind_method(D_METHOD("get_focused_players_id"), &Control::get_focused_players_id);
|
||||
ClassDB::bind_method(D_METHOD("has_focus", "player_id"), &Control::has_focus, DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("grab_focus", "player"), &Control::grab_focus, DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("release_focus", "player"), &Control::release_focus, DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("find_prev_valid_focus", "player_id"), &Control::find_prev_valid_focus, DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("find_next_valid_focus", "player_id"), &Control::find_next_valid_focus, DEFVAL(PlayerId::P1));
|
||||
ClassDB::bind_method(D_METHOD("find_valid_focus_neighbor", "side", "player_id"), &Control::find_valid_focus_neighbor, DEFVAL(PlayerId::P1));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_h_size_flags", "flags"), &Control::set_h_size_flags);
|
||||
ClassDB::bind_method(D_METHOD("get_h_size_flags"), &Control::get_h_size_flags);
|
||||
|
|
@ -3609,6 +3697,10 @@ void Control::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_focus_previous", "previous"), &Control::set_focus_previous);
|
||||
ClassDB::bind_method(D_METHOD("get_focus_previous"), &Control::get_focus_previous);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_player_mask", "player"), &Control::set_player_mask);
|
||||
ClassDB::bind_method(D_METHOD("get_player_mask"), &Control::get_player_mask);
|
||||
ClassDB::bind_method(D_METHOD("calculate_ancestral_player_mask"), &Control::calculate_ancestral_player_mask);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("force_drag", "data", "preview"), &Control::force_drag);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_mouse_filter", "filter"), &Control::set_mouse_filter);
|
||||
|
|
@ -3709,6 +3801,7 @@ void Control::_bind_methods() {
|
|||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_next", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_next", "get_focus_next");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_previous", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_previous", "get_focus_previous");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "player_mask", PROPERTY_HINT_LAYERS_PLAYER_MASK), "set_player_mask", "get_player_mask");
|
||||
|
||||
ADD_GROUP("Mouse", "mouse_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass (Propagate Up),Ignore"), "set_mouse_filter", "get_mouse_filter");
|
||||
|
|
|
|||
|
|
@ -232,6 +232,9 @@ private:
|
|||
NodePath focus_next;
|
||||
NodePath focus_prev;
|
||||
|
||||
// Accept inputs from all players by default.
|
||||
BitField<PlayerMask> player_mask = PLAYER_ALL;
|
||||
|
||||
ObjectID shortcut_context;
|
||||
|
||||
// Theming.
|
||||
|
|
@ -315,7 +318,7 @@ private:
|
|||
// Focus.
|
||||
|
||||
void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Rect2 &p_rect, const Rect2 &p_clamp, real_t p_min, real_t &r_closest_dist_squared, Control **r_closest);
|
||||
Control *_get_focus_neighbor(Side p_side, int p_count = 0);
|
||||
Control *_get_focus_neighbor(Side p_side, int p_count = 0, PlayerId p_player_id = PlayerId::P1);
|
||||
|
||||
// Theming.
|
||||
|
||||
|
|
@ -540,14 +543,15 @@ public:
|
|||
|
||||
void set_focus_mode(FocusMode p_focus_mode);
|
||||
FocusMode get_focus_mode() const;
|
||||
bool has_focus() const;
|
||||
void grab_focus();
|
||||
TypedArray<int> get_focused_players_id() const;
|
||||
bool has_focus(PlayerId p_player_id = PlayerId::P1) const;
|
||||
void grab_focus(PlayerId p_player_id = PlayerId::P1);
|
||||
void grab_click_focus();
|
||||
void release_focus();
|
||||
void release_focus(PlayerId p_player_id = PlayerId::P1);
|
||||
|
||||
Control *find_next_valid_focus() const;
|
||||
Control *find_prev_valid_focus() const;
|
||||
Control *find_valid_focus_neighbor(Side p_size) const;
|
||||
Control *find_next_valid_focus(PlayerId p_player_id = PlayerId::P1) const;
|
||||
Control *find_prev_valid_focus(PlayerId p_player_id = PlayerId::P1) const;
|
||||
Control *find_valid_focus_neighbor(Side p_size, PlayerId p_player_id = PlayerId::P1) const;
|
||||
|
||||
void set_focus_neighbor(Side p_side, const NodePath &p_neighbor);
|
||||
NodePath get_focus_neighbor(Side p_side) const;
|
||||
|
|
@ -557,6 +561,10 @@ public:
|
|||
void set_focus_previous(const NodePath &p_prev);
|
||||
NodePath get_focus_previous() const;
|
||||
|
||||
void set_player_mask(BitField<PlayerMask> p_player_mask);
|
||||
BitField<PlayerMask> get_player_mask() const;
|
||||
BitField<PlayerMask> calculate_ancestral_player_mask() const;
|
||||
|
||||
// Rendering.
|
||||
|
||||
void set_default_cursor_shape(CursorShape p_shape);
|
||||
|
|
|
|||
|
|
@ -1637,7 +1637,7 @@ void FileDialog::set_show_filename_filter(bool p_show) {
|
|||
filename_filter->grab_focus();
|
||||
} else {
|
||||
if (filename_filter->has_focus()) {
|
||||
tree->call_deferred("grab_focus");
|
||||
tree->call_deferred("grab_focus", PlayerId::P1);
|
||||
}
|
||||
}
|
||||
show_filename_filter = p_show;
|
||||
|
|
|
|||
|
|
@ -751,6 +751,7 @@ void Viewport::_process_picking() {
|
|||
mm.instantiate();
|
||||
|
||||
mm->set_device(InputEvent::DEVICE_ID_INTERNAL);
|
||||
mm->set_player_from_device();
|
||||
mm->set_position(get_mouse_position());
|
||||
mm->set_global_position(mm->get_position());
|
||||
mm->set_alt_pressed(Input::get_singleton()->is_key_pressed(Key::ALT));
|
||||
|
|
@ -1786,6 +1787,9 @@ bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_che
|
|||
void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
|
||||
ERR_FAIL_COND(p_event.is_null());
|
||||
|
||||
const PlayerId player_id = p_event->get_player();
|
||||
const int p_id = (int)player_id;
|
||||
|
||||
Ref<InputEventMouseButton> mb = p_event;
|
||||
if (mb.is_valid()) {
|
||||
Point2 mpos = mb->get_position();
|
||||
|
|
@ -1837,8 +1841,8 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
|
|||
if (control->get_focus_mode() != Control::FOCUS_NONE) {
|
||||
// Grabbing unhovered focus can cause issues when mouse is dragged
|
||||
// with another button held down.
|
||||
if (control != gui.key_focus && gui.mouse_over_hierarchy.has(control)) {
|
||||
control->grab_focus();
|
||||
if (control != gui.key_focus[(int)player_id] && gui.mouse_over_hierarchy.has(control)) {
|
||||
control->grab_focus(player_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -2163,13 +2167,13 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
|
|||
}
|
||||
}
|
||||
|
||||
if (gui.key_focus && !gui.key_focus->is_visible_in_tree()) {
|
||||
gui.key_focus->release_focus();
|
||||
if (gui.key_focus[p_id] && !gui.key_focus[p_id]->is_visible_in_tree()) {
|
||||
gui.key_focus[p_id]->release_focus();
|
||||
}
|
||||
|
||||
if (gui.key_focus) {
|
||||
if (gui.key_focus->can_process()) {
|
||||
gui.key_focus->_call_gui_input(p_event);
|
||||
if (gui.key_focus[p_id]) {
|
||||
if (gui.key_focus[p_id]->can_process()) {
|
||||
gui.key_focus[p_id]->_call_gui_input(p_event);
|
||||
}
|
||||
|
||||
if (is_input_handled()) {
|
||||
|
|
@ -2177,7 +2181,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
|
|||
}
|
||||
}
|
||||
|
||||
Control *from = gui.key_focus ? gui.key_focus : nullptr;
|
||||
Control *from = gui.key_focus[p_id] ? gui.key_focus[p_id] : nullptr;
|
||||
|
||||
if (from && p_event->is_pressed()) {
|
||||
Control *next = nullptr;
|
||||
|
|
@ -2186,56 +2190,56 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
|
|||
if (joypadmotion_event.is_valid()) {
|
||||
Input *input = Input::get_singleton();
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_focus_next")) && input->is_action_just_pressed(SNAME("ui_focus_next"))) {
|
||||
next = from->find_next_valid_focus();
|
||||
if (p_event->is_action_pressed(SNAME("ui_focus_next"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_focus_next"), false, player_id)) {
|
||||
next = from->find_next_valid_focus(player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_focus_prev")) && input->is_action_just_pressed(SNAME("ui_focus_prev"))) {
|
||||
next = from->find_prev_valid_focus();
|
||||
if (p_event->is_action_pressed(SNAME("ui_focus_prev"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_focus_prev"), false, player_id)) {
|
||||
next = from->find_prev_valid_focus(player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_up")) && input->is_action_just_pressed(SNAME("ui_up"))) {
|
||||
next = from->_get_focus_neighbor(SIDE_TOP);
|
||||
if (p_event->is_action_pressed(SNAME("ui_up"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_up"), false, player_id)) {
|
||||
next = from->_get_focus_neighbor(SIDE_TOP, 0, player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_left")) && input->is_action_just_pressed(SNAME("ui_left"))) {
|
||||
next = from->_get_focus_neighbor(SIDE_LEFT);
|
||||
if (p_event->is_action_pressed(SNAME("ui_left"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_left"), false, player_id)) {
|
||||
next = from->_get_focus_neighbor(SIDE_LEFT, 0, player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_right")) && input->is_action_just_pressed(SNAME("ui_right"))) {
|
||||
next = from->_get_focus_neighbor(SIDE_RIGHT);
|
||||
if (p_event->is_action_pressed(SNAME("ui_right"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_right"), false, player_id)) {
|
||||
next = from->_get_focus_neighbor(SIDE_RIGHT, 0, player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_down")) && input->is_action_just_pressed(SNAME("ui_down"))) {
|
||||
next = from->_get_focus_neighbor(SIDE_BOTTOM);
|
||||
if (p_event->is_action_pressed(SNAME("ui_down"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_down"), false, player_id)) {
|
||||
next = from->_get_focus_neighbor(SIDE_BOTTOM, 0, player_id);
|
||||
}
|
||||
} else {
|
||||
if (p_event->is_action_pressed(SNAME("ui_focus_next"), true, true)) {
|
||||
next = from->find_next_valid_focus();
|
||||
if (p_event->is_action_pressed(SNAME("ui_focus_next"), true, true, player_id)) {
|
||||
next = from->find_next_valid_focus(player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_focus_prev"), true, true)) {
|
||||
next = from->find_prev_valid_focus();
|
||||
if (p_event->is_action_pressed(SNAME("ui_focus_prev"), true, true, player_id)) {
|
||||
next = from->find_prev_valid_focus(player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_up"), true, true)) {
|
||||
next = from->_get_focus_neighbor(SIDE_TOP);
|
||||
if (p_event->is_action_pressed(SNAME("ui_up"), true, true, player_id)) {
|
||||
next = from->_get_focus_neighbor(SIDE_TOP, 0, player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_left"), true, true)) {
|
||||
next = from->_get_focus_neighbor(SIDE_LEFT);
|
||||
if (p_event->is_action_pressed(SNAME("ui_left"), true, true, player_id)) {
|
||||
next = from->_get_focus_neighbor(SIDE_LEFT, 0, player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_right"), true, true)) {
|
||||
next = from->_get_focus_neighbor(SIDE_RIGHT);
|
||||
if (p_event->is_action_pressed(SNAME("ui_right"), true, true, player_id)) {
|
||||
next = from->_get_focus_neighbor(SIDE_RIGHT, 0, player_id);
|
||||
}
|
||||
|
||||
if (p_event->is_action_pressed(SNAME("ui_down"), true, true)) {
|
||||
next = from->_get_focus_neighbor(SIDE_BOTTOM);
|
||||
if (p_event->is_action_pressed(SNAME("ui_down"), true, true, player_id)) {
|
||||
next = from->_get_focus_neighbor(SIDE_BOTTOM, 0, player_id);
|
||||
}
|
||||
}
|
||||
if (next) {
|
||||
next->grab_focus();
|
||||
next->grab_focus(player_id);
|
||||
set_input_as_handled();
|
||||
}
|
||||
}
|
||||
|
|
@ -2337,18 +2341,20 @@ void Viewport::_gui_remove_root_control(List<Control *>::Element *RI) {
|
|||
gui.roots.erase(RI);
|
||||
}
|
||||
|
||||
void Viewport::_gui_unfocus_control(Control *p_control) {
|
||||
if (gui.key_focus == p_control) {
|
||||
gui.key_focus->release_focus();
|
||||
void Viewport::_gui_unfocus_control(Control *p_control, PlayerId p_player_id) {
|
||||
const int p_id = (int)p_player_id;
|
||||
if (gui.key_focus[p_id] == p_control) {
|
||||
gui.key_focus[p_id]->release_focus();
|
||||
}
|
||||
}
|
||||
|
||||
void Viewport::_gui_hide_control(Control *p_control) {
|
||||
void Viewport::_gui_hide_control(Control *p_control, PlayerId p_player_id) {
|
||||
if (gui.mouse_focus == p_control) {
|
||||
_drop_mouse_focus();
|
||||
}
|
||||
|
||||
if (gui.key_focus == p_control) {
|
||||
const int p_id = (int)p_player_id;
|
||||
if (gui.key_focus[p_id] == p_control) {
|
||||
gui_release_focus();
|
||||
}
|
||||
if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) {
|
||||
|
|
@ -2362,13 +2368,15 @@ void Viewport::_gui_hide_control(Control *p_control) {
|
|||
}
|
||||
}
|
||||
|
||||
void Viewport::_gui_remove_control(Control *p_control) {
|
||||
void Viewport::_gui_remove_control(Control *p_control, PlayerId p_player_id) {
|
||||
if (gui.mouse_focus == p_control) {
|
||||
gui.mouse_focus = nullptr;
|
||||
gui.mouse_focus_mask.clear();
|
||||
}
|
||||
if (gui.key_focus == p_control) {
|
||||
gui.key_focus = nullptr;
|
||||
|
||||
const int p_id = (int)p_player_id;
|
||||
if (gui.key_focus[p_id] == p_control) {
|
||||
gui.key_focus[p_id] = nullptr;
|
||||
}
|
||||
if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) {
|
||||
_drop_mouse_over(p_control->get_parent_control());
|
||||
|
|
@ -2495,24 +2503,27 @@ Window *Viewport::get_base_window() {
|
|||
return w;
|
||||
}
|
||||
|
||||
void Viewport::_gui_remove_focus_for_window(Node *p_window) {
|
||||
void Viewport::_gui_remove_focus_for_window(Node *p_window, PlayerId p_player_id) {
|
||||
if (get_base_window() == p_window) {
|
||||
gui_release_focus();
|
||||
gui_release_focus(p_player_id);
|
||||
}
|
||||
}
|
||||
|
||||
bool Viewport::_gui_control_has_focus(const Control *p_control) {
|
||||
return gui.key_focus == p_control;
|
||||
bool Viewport::_gui_control_has_focus(const Control *p_control, PlayerId p_player_id) {
|
||||
const int p_id = (int)p_player_id;
|
||||
return gui.key_focus[p_id] == p_control;
|
||||
}
|
||||
|
||||
void Viewport::_gui_control_grab_focus(Control *p_control) {
|
||||
if (gui.key_focus && gui.key_focus == p_control) {
|
||||
void Viewport::_gui_control_grab_focus(Control *p_control, PlayerId p_player_id) {
|
||||
const int p_id = (int)p_player_id;
|
||||
if (gui.key_focus[p_id] && gui.key_focus[p_id] == p_control) {
|
||||
// No need for change.
|
||||
return;
|
||||
}
|
||||
get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", get_base_window());
|
||||
get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", get_base_window(), p_id);
|
||||
if (p_control->is_inside_tree() && p_control->get_viewport() == this) {
|
||||
gui.key_focus = p_control;
|
||||
gui.key_focus[p_id] = p_control;
|
||||
// TODO: Anything to do here with this signal?
|
||||
emit_signal(SNAME("gui_focus_changed"), p_control);
|
||||
p_control->notification(Control::NOTIFICATION_FOCUS_ENTER);
|
||||
p_control->queue_redraw();
|
||||
|
|
@ -2544,6 +2555,7 @@ void Viewport::_drop_mouse_focus() {
|
|||
mb->set_button_index(MouseButton(i + 1));
|
||||
mb->set_pressed(false);
|
||||
mb->set_device(InputEvent::DEVICE_ID_INTERNAL);
|
||||
mb->set_player_from_device();
|
||||
c->_call_gui_input(mb);
|
||||
}
|
||||
}
|
||||
|
|
@ -2601,6 +2613,7 @@ void Viewport::_post_gui_grab_click_focus() {
|
|||
mb->set_button_index(MouseButton(i + 1));
|
||||
mb->set_pressed(false);
|
||||
mb->set_device(InputEvent::DEVICE_ID_INTERNAL);
|
||||
mb->set_player_from_device();
|
||||
gui.mouse_focus->_call_gui_input(mb);
|
||||
}
|
||||
}
|
||||
|
|
@ -2619,6 +2632,7 @@ void Viewport::_post_gui_grab_click_focus() {
|
|||
mb->set_button_index(MouseButton(i + 1));
|
||||
mb->set_pressed(true);
|
||||
mb->set_device(InputEvent::DEVICE_ID_INTERNAL);
|
||||
mb->set_player_from_device();
|
||||
callable_mp(gui.mouse_focus, &Control::_call_gui_input).call_deferred(mb);
|
||||
}
|
||||
}
|
||||
|
|
@ -2627,15 +2641,15 @@ void Viewport::_post_gui_grab_click_focus() {
|
|||
|
||||
///////////////////////////////
|
||||
|
||||
void Viewport::push_text_input(const String &p_text) {
|
||||
void Viewport::push_text_input(const String &p_text, PlayerId p_player_id) {
|
||||
ERR_MAIN_THREAD_GUARD;
|
||||
if (gui.subwindow_focused) {
|
||||
gui.subwindow_focused->push_text_input(p_text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gui.key_focus) {
|
||||
gui.key_focus->call("set_text", p_text);
|
||||
if (gui.key_focus[(int)p_player_id]) {
|
||||
gui.key_focus[(int)p_player_id]->call("set_text", p_text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3455,19 +3469,21 @@ int Viewport::gui_get_canvas_sort_index() {
|
|||
return gui.canvas_sort_index++;
|
||||
}
|
||||
|
||||
void Viewport::gui_release_focus() {
|
||||
void Viewport::gui_release_focus(PlayerId p_player_id) {
|
||||
ERR_MAIN_THREAD_GUARD;
|
||||
if (gui.key_focus) {
|
||||
Control *f = gui.key_focus;
|
||||
gui.key_focus = nullptr;
|
||||
const int p_id = (int)p_player_id;
|
||||
if (gui.key_focus[p_id]) {
|
||||
Control *f = gui.key_focus[p_id];
|
||||
gui.key_focus[p_id] = nullptr;
|
||||
f->notification(Control::NOTIFICATION_FOCUS_EXIT, true);
|
||||
f->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
Control *Viewport::gui_get_focus_owner() const {
|
||||
Control *Viewport::gui_get_focus_owner(PlayerId p_player_id) const {
|
||||
ERR_READ_THREAD_GUARD_V(nullptr);
|
||||
return gui.key_focus;
|
||||
const int p_id = (int)p_player_id;
|
||||
return gui.key_focus[p_id];
|
||||
}
|
||||
|
||||
Control *Viewport::gui_get_hovered_control() const {
|
||||
|
|
@ -4794,7 +4810,7 @@ void Viewport::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("get_physics_object_picking_first_only"), &Viewport::get_physics_object_picking_first_only);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_viewport_rid"), &Viewport::get_viewport_rid);
|
||||
ClassDB::bind_method(D_METHOD("push_text_input", "text"), &Viewport::push_text_input);
|
||||
ClassDB::bind_method(D_METHOD("push_text_input", "text", "player_id"), &Viewport::push_text_input);
|
||||
ClassDB::bind_method(D_METHOD("push_input", "event", "in_local_coords"), &Viewport::push_input, DEFVAL(false));
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
ClassDB::bind_method(D_METHOD("push_unhandled_input", "event", "in_local_coords"), &Viewport::push_unhandled_input, DEFVAL(false));
|
||||
|
|
@ -4809,14 +4825,14 @@ void Viewport::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("gui_is_dragging"), &Viewport::gui_is_dragging);
|
||||
ClassDB::bind_method(D_METHOD("gui_is_drag_successful"), &Viewport::gui_is_drag_successful);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("gui_release_focus"), &Viewport::gui_release_focus);
|
||||
ClassDB::bind_method(D_METHOD("gui_get_focus_owner"), &Viewport::gui_get_focus_owner);
|
||||
ClassDB::bind_method(D_METHOD("gui_release_focus", "player_id"), &Viewport::gui_release_focus);
|
||||
ClassDB::bind_method(D_METHOD("gui_get_focus_owner", "player_id"), &Viewport::gui_get_focus_owner);
|
||||
ClassDB::bind_method(D_METHOD("gui_get_hovered_control"), &Viewport::gui_get_hovered_control);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_disable_input", "disable"), &Viewport::set_disable_input);
|
||||
ClassDB::bind_method(D_METHOD("is_input_disabled"), &Viewport::is_input_disabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_gui_remove_focus_for_window"), &Viewport::_gui_remove_focus_for_window);
|
||||
ClassDB::bind_method(D_METHOD("_gui_remove_focus_for_window", "player"), &Viewport::_gui_remove_focus_for_window, DEFVAL(PlayerId::P1));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_positional_shadow_atlas_size", "size"), &Viewport::set_positional_shadow_atlas_size);
|
||||
ClassDB::bind_method(D_METHOD("get_positional_shadow_atlas_size"), &Viewport::get_positional_shadow_atlas_size);
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ private:
|
|||
Control *mouse_focus = nullptr;
|
||||
Control *mouse_click_grabber = nullptr;
|
||||
BitField<MouseButtonMask> mouse_focus_mask;
|
||||
Control *key_focus = nullptr;
|
||||
Control *key_focus[PLAYERS_MAX] = { nullptr };
|
||||
Control *mouse_over = nullptr;
|
||||
LocalVector<Control *> mouse_over_hierarchy;
|
||||
bool sending_mouse_enter_exit_notifications = false;
|
||||
|
|
@ -439,18 +439,18 @@ private:
|
|||
void _gui_cancel_tooltip();
|
||||
void _gui_show_tooltip();
|
||||
|
||||
void _gui_remove_control(Control *p_control);
|
||||
void _gui_hide_control(Control *p_control);
|
||||
void _gui_remove_control(Control *p_control, PlayerId p_player_id = PlayerId::P1);
|
||||
void _gui_hide_control(Control *p_control, PlayerId p_player_id = PlayerId::P1);
|
||||
void _gui_update_mouse_over();
|
||||
|
||||
void _gui_force_drag(Control *p_base, const Variant &p_data, Control *p_control);
|
||||
void _gui_set_drag_preview(Control *p_base, Control *p_control);
|
||||
Control *_gui_get_drag_preview();
|
||||
|
||||
void _gui_remove_focus_for_window(Node *p_window);
|
||||
void _gui_unfocus_control(Control *p_control);
|
||||
bool _gui_control_has_focus(const Control *p_control);
|
||||
void _gui_control_grab_focus(Control *p_control);
|
||||
void _gui_remove_focus_for_window(Node *p_window, PlayerId p_player_id = PlayerId::P1);
|
||||
void _gui_unfocus_control(Control *p_control, PlayerId p_player_id = PlayerId::P1);
|
||||
bool _gui_control_has_focus(const Control *p_control, PlayerId p_player_id = PlayerId::P1);
|
||||
void _gui_control_grab_focus(Control *p_control, PlayerId p_player_id = PlayerId::P1);
|
||||
void _gui_grab_click_focus(Control *p_control);
|
||||
void _post_gui_grab_click_focus();
|
||||
void _gui_accept_event();
|
||||
|
|
@ -591,7 +591,7 @@ public:
|
|||
Vector2 get_camera_coords(const Vector2 &p_viewport_coords) const;
|
||||
Vector2 get_camera_rect_size() const;
|
||||
|
||||
void push_text_input(const String &p_text);
|
||||
void push_text_input(const String &p_text, PlayerId p_player_id = PlayerId::P1);
|
||||
void push_input(const Ref<InputEvent> &p_event, bool p_local_coords = false);
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords = false);
|
||||
|
|
@ -619,8 +619,8 @@ public:
|
|||
void gui_reset_canvas_sort_index();
|
||||
int gui_get_canvas_sort_index();
|
||||
|
||||
void gui_release_focus();
|
||||
Control *gui_get_focus_owner() const;
|
||||
void gui_release_focus(PlayerId p_player_id = PlayerId::P1);
|
||||
Control *gui_get_focus_owner(PlayerId p_player_id = PlayerId::P1) const;
|
||||
Control *gui_get_hovered_control() const;
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
|
|
|||
|
|
@ -853,6 +853,7 @@ void Window::update_mouse_cursor_state() {
|
|||
mm->set_position(pos);
|
||||
mm->set_global_position(xform.xform(pos));
|
||||
mm->set_device(InputEvent::DEVICE_ID_INTERNAL);
|
||||
mm->set_player_from_device();
|
||||
push_input(mm, true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ TEST_CASE("[InputEvent] Signal is emitted when device is changed") {
|
|||
empty_args.push_back(args1);
|
||||
|
||||
input_event->set_device(1);
|
||||
input_event->set_player_from_device();
|
||||
|
||||
SIGNAL_CHECK("changed", empty_args);
|
||||
CHECK(input_event->get_device() == 1);
|
||||
|
|
|
|||
Loading…
Reference in New Issue