mirror of
https://github.com/NoelFB/blah.git
synced 2024-11-25 16:18:57 +08:00
new input binding/mapping implementation
This commit is contained in:
parent
d73241e8fe
commit
3f07c03fa5
|
@ -9,7 +9,7 @@ add_library(blah
|
||||||
|
|
||||||
src/core/app.cpp
|
src/core/app.cpp
|
||||||
src/core/filesystem.cpp
|
src/core/filesystem.cpp
|
||||||
src/core/log.cpp
|
src/core/common.cpp
|
||||||
src/core/time.cpp
|
src/core/time.cpp
|
||||||
|
|
||||||
src/graphics/blend.cpp
|
src/graphics/blend.cpp
|
||||||
|
@ -21,9 +21,8 @@ add_library(blah
|
||||||
src/graphics/texture.cpp
|
src/graphics/texture.cpp
|
||||||
|
|
||||||
src/input/input.cpp
|
src/input/input.cpp
|
||||||
src/input/virtual_stick.cpp
|
src/input/binding.cpp
|
||||||
src/input/virtual_button.cpp
|
src/input/binding_registry.cpp
|
||||||
src/input/virtual_axis.cpp
|
|
||||||
|
|
||||||
src/containers/str.cpp
|
src/containers/str.cpp
|
||||||
|
|
||||||
|
@ -54,7 +53,6 @@ add_library(blah
|
||||||
src/streams/memorystream.cpp
|
src/streams/memorystream.cpp
|
||||||
src/streams/stream.cpp
|
src/streams/stream.cpp
|
||||||
|
|
||||||
|
|
||||||
src/internal/graphics_backend_gl.cpp
|
src/internal/graphics_backend_gl.cpp
|
||||||
src/internal/graphics_backend_d3d11.cpp
|
src/internal/graphics_backend_d3d11.cpp
|
||||||
src/internal/graphics_backend_dummy.cpp
|
src/internal/graphics_backend_dummy.cpp
|
||||||
|
|
|
@ -28,9 +28,8 @@
|
||||||
#include "blah/images/packer.h"
|
#include "blah/images/packer.h"
|
||||||
|
|
||||||
#include "blah/input/input.h"
|
#include "blah/input/input.h"
|
||||||
#include "blah/input/virtual_stick.h"
|
#include "blah/input/binding.h"
|
||||||
#include "blah/input/virtual_button.h"
|
#include "blah/input/binding_registry.h"
|
||||||
#include "blah/input/virtual_axis.h"
|
|
||||||
|
|
||||||
#include "blah/math/calc.h"
|
#include "blah/math/calc.h"
|
||||||
#include "blah/math/circle.h"
|
#include "blah/math/circle.h"
|
||||||
|
|
261
include/blah/input/binding.h
Normal file
261
include/blah/input/binding.h
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
#pragma once
|
||||||
|
#include <blah/input/input.h>
|
||||||
|
#include <blah/containers/stackvector.h>
|
||||||
|
#include <blah/math/point.h>
|
||||||
|
#include <blah/math/vec2.h>
|
||||||
|
|
||||||
|
namespace Blah
|
||||||
|
{
|
||||||
|
// Represents a Controller Trigger or a single direction of a Controller Axis.
|
||||||
|
// Used in the Binding implementation.
|
||||||
|
struct BoundTrigger
|
||||||
|
{
|
||||||
|
// Controller Index we're bound to
|
||||||
|
int controller = 0;
|
||||||
|
|
||||||
|
// The Axis we're bound to
|
||||||
|
Axis axis = Axis::None;
|
||||||
|
|
||||||
|
// Minimum value of the axis
|
||||||
|
float threshold = 0.01f;
|
||||||
|
|
||||||
|
// requires a positive value
|
||||||
|
// otherwise requires a negative value
|
||||||
|
bool positive = true;
|
||||||
|
|
||||||
|
BoundTrigger() = default;
|
||||||
|
BoundTrigger(Axis axis);
|
||||||
|
BoundTrigger(int controller, Axis axis, float threshold, bool positive);
|
||||||
|
|
||||||
|
bool is_down(float axis_value) const;
|
||||||
|
|
||||||
|
// helper functions for frequent use cases
|
||||||
|
static BoundTrigger left_stick_left(int controller, float threshold);
|
||||||
|
static BoundTrigger left_stick_right(int controller, float threshold);
|
||||||
|
static BoundTrigger left_stick_up(int controller, float threshold);
|
||||||
|
static BoundTrigger left_stick_down(int controller, float threshold);
|
||||||
|
static BoundTrigger right_stick_left(int controller, float threshold);
|
||||||
|
static BoundTrigger right_stick_right(int controller, float threshold);
|
||||||
|
static BoundTrigger right_stick_up(int controller, float threshold);
|
||||||
|
static BoundTrigger right_stick_down(int controller, float threshold);
|
||||||
|
static BoundTrigger left_trigger(int controller, float threshold);
|
||||||
|
static BoundTrigger right_trigger(int controller, float threshold);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoundButton
|
||||||
|
{
|
||||||
|
// Controller Index we're bound to
|
||||||
|
int controller = 0;
|
||||||
|
|
||||||
|
// Button we're bound to
|
||||||
|
Button button = Button::None;
|
||||||
|
|
||||||
|
BoundButton() = default;
|
||||||
|
BoundButton(Button button);
|
||||||
|
BoundButton(int controller, Button button);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Single input Binding
|
||||||
|
class Binding
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Input Buffer for press events
|
||||||
|
float press_buffer = 0;
|
||||||
|
|
||||||
|
// Input Buffer for release events
|
||||||
|
float release_buffer = 0;
|
||||||
|
|
||||||
|
// List of bound Keys
|
||||||
|
StackVector<Key, 16> keys;
|
||||||
|
|
||||||
|
// List of bound Buttons
|
||||||
|
StackVector<BoundButton, 16> buttons;
|
||||||
|
|
||||||
|
// List of bound Triggers / Axis
|
||||||
|
StackVector<BoundTrigger, 16> triggers;
|
||||||
|
|
||||||
|
// List of bound Mouse buttons
|
||||||
|
StackVector<MouseButton, 16> mouse;
|
||||||
|
|
||||||
|
// if the binding has been pressed
|
||||||
|
bool pressed() const;
|
||||||
|
|
||||||
|
// if the binding has been released
|
||||||
|
bool released() const;
|
||||||
|
|
||||||
|
// if the binding is currently held
|
||||||
|
bool down() const;
|
||||||
|
|
||||||
|
// returns the binding's value from 0-1
|
||||||
|
float value() const;
|
||||||
|
|
||||||
|
// returns the bindings signed value (0 or 1)
|
||||||
|
int sign() const;
|
||||||
|
|
||||||
|
// returns the timestamp of the last time the binding was pressed
|
||||||
|
double timestamp() const;
|
||||||
|
|
||||||
|
// updates the binding state
|
||||||
|
void update();
|
||||||
|
|
||||||
|
// consumes the current press, and pressed() will return false until the next press
|
||||||
|
void consume_press();
|
||||||
|
|
||||||
|
// consumes the current release, and released() will return false until the next release
|
||||||
|
void consume_release();
|
||||||
|
|
||||||
|
// adds a key to the binding
|
||||||
|
void add(Key key);
|
||||||
|
|
||||||
|
// adds a button to the binding
|
||||||
|
void add(BoundButton button);
|
||||||
|
|
||||||
|
// adds an trigger to the binding
|
||||||
|
void add(BoundTrigger trigger);
|
||||||
|
|
||||||
|
// adds a mouse button to the binding
|
||||||
|
void add(MouseButton mouse);
|
||||||
|
|
||||||
|
// adds an input to the binding
|
||||||
|
template<typename T, typename T2, typename ... Args>
|
||||||
|
void add(T first, T2 second, const Args&... args)
|
||||||
|
{
|
||||||
|
add(first);
|
||||||
|
add(second, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// assigns all the bindings to the specific controller
|
||||||
|
void set_controller(int index);
|
||||||
|
|
||||||
|
// removes all bindings
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
double m_last_timestamp = 0;
|
||||||
|
double m_last_press_time = -1;
|
||||||
|
double m_last_release_time = -1;
|
||||||
|
float m_value = 0.0f;
|
||||||
|
bool m_pressed = false;
|
||||||
|
bool m_released = false;
|
||||||
|
bool m_down = false;
|
||||||
|
bool m_press_consumed = false;
|
||||||
|
bool m_release_consumed = false;
|
||||||
|
|
||||||
|
bool get_pressed() const;
|
||||||
|
bool get_released() const;
|
||||||
|
bool get_down() const;
|
||||||
|
float get_value() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Represents a Bound Axis (ex. Left/Right movement, or a Trigger)
|
||||||
|
class AxisBinding
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
enum class Overlap
|
||||||
|
{
|
||||||
|
Newer,
|
||||||
|
Older,
|
||||||
|
Cancel
|
||||||
|
};
|
||||||
|
|
||||||
|
// Negative Value Binding
|
||||||
|
Binding negative;
|
||||||
|
|
||||||
|
// Positive Value Binding
|
||||||
|
Binding positive;
|
||||||
|
|
||||||
|
// How to handle overlaps (ex. Left and Right are both held)
|
||||||
|
Overlap overlap = Overlap::Newer;
|
||||||
|
|
||||||
|
// Current Value from -1 to 1
|
||||||
|
float value() const;
|
||||||
|
|
||||||
|
// Current value, either -1, 0, or 1
|
||||||
|
int sign() const;
|
||||||
|
|
||||||
|
// updates the Binding
|
||||||
|
void update();
|
||||||
|
|
||||||
|
// consumes the press buffer
|
||||||
|
void consume_press();
|
||||||
|
|
||||||
|
// consumes the release buffer
|
||||||
|
void consume_release();
|
||||||
|
|
||||||
|
// Adds a negative & positive binding pair
|
||||||
|
template<typename NegativeT, typename PositiveT>
|
||||||
|
void add(NegativeT negative, PositiveT positive)
|
||||||
|
{
|
||||||
|
this->negative.add(negative);
|
||||||
|
this->positive.add(positive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a Stick binding
|
||||||
|
void add_left_stick_x(int controller, float threshold);
|
||||||
|
void add_left_stick_y(int controller, float threshold);
|
||||||
|
void add_right_stick_x(int controller, float threshold);
|
||||||
|
void add_right_stick_y(int controller, float threshold);
|
||||||
|
|
||||||
|
// assigns all the bindings to the specific controller
|
||||||
|
void set_controller(int index);
|
||||||
|
|
||||||
|
// Clears all Bindings
|
||||||
|
void clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
class StickBinding
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// An optional threshold for circular thresholds
|
||||||
|
float round_threshold = 0.0f;
|
||||||
|
|
||||||
|
// X Axis Binding
|
||||||
|
AxisBinding x;
|
||||||
|
|
||||||
|
// Y Axis Binding
|
||||||
|
AxisBinding y;
|
||||||
|
|
||||||
|
// Current Value, -1 to 1
|
||||||
|
Vec2 value() const;
|
||||||
|
|
||||||
|
// Current value, either -1, 0, or 1
|
||||||
|
Point sign() const;
|
||||||
|
|
||||||
|
// Updates the Binding
|
||||||
|
void update();
|
||||||
|
|
||||||
|
// Consumes the Press Buffer
|
||||||
|
void consume_press();
|
||||||
|
|
||||||
|
// Consumes the Release Buffer
|
||||||
|
void consume_release();
|
||||||
|
|
||||||
|
// Adds directional bindings
|
||||||
|
template<typename LeftT, typename RightT, typename UpT, typename DownT>
|
||||||
|
void add(LeftT left, RightT right, UpT up, DownT down)
|
||||||
|
{
|
||||||
|
x.negative.add(left);
|
||||||
|
x.positive.add(right);
|
||||||
|
y.negative.add(up);
|
||||||
|
y.positive.add(down);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds the dpad binding
|
||||||
|
void add_dpad(int controller);
|
||||||
|
|
||||||
|
// Adds the left stick binding
|
||||||
|
void add_left_stick(int controller, float threshold);
|
||||||
|
|
||||||
|
// Adds the right stick binding
|
||||||
|
void add_right_stick(int controller, float threshold);
|
||||||
|
|
||||||
|
// assigns all the bindings to the specific controller
|
||||||
|
void set_controller(int index);
|
||||||
|
|
||||||
|
// Clears all the bindings
|
||||||
|
void clear();
|
||||||
|
};
|
||||||
|
}
|
37
include/blah/input/binding_registry.h
Normal file
37
include/blah/input/binding_registry.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
#include <blah/input/binding.h>
|
||||||
|
#include <blah/containers/vector.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Blah
|
||||||
|
{
|
||||||
|
using BindingRef = std::shared_ptr<Binding>;
|
||||||
|
using AxisBindingRef = std::shared_ptr<AxisBinding>;
|
||||||
|
using StickBindingRef = std::shared_ptr<StickBinding>;
|
||||||
|
|
||||||
|
// Optional registry to automatically update bindings.
|
||||||
|
// You can register different types of bindings here and until they are
|
||||||
|
// no longer used, they will be updated without having to explicitely call
|
||||||
|
// their update methods
|
||||||
|
class BindingRegistry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// registers a new binding
|
||||||
|
static BindingRef register_binding();
|
||||||
|
|
||||||
|
// registers a new axis binding
|
||||||
|
static AxisBindingRef register_axis();
|
||||||
|
|
||||||
|
// registers a new stick binding
|
||||||
|
static StickBindingRef register_stick();
|
||||||
|
|
||||||
|
// updates all the bindings. This is called
|
||||||
|
// automatically by the App loop.
|
||||||
|
static void update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Vector<std::weak_ptr<Binding>> bindings;
|
||||||
|
static Vector<std::weak_ptr<AxisBinding>> axes;
|
||||||
|
static Vector<std::weak_ptr<StickBinding>> sticks;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,86 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <blah/input/input.h>
|
|
||||||
|
|
||||||
namespace Blah
|
|
||||||
{
|
|
||||||
// A virtual controller axis, which can be used to map multiple
|
|
||||||
// inputs to an axis. Note that you must call `update` every frame!
|
|
||||||
class VirtualAxis
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
struct KeysNode
|
|
||||||
{
|
|
||||||
Key positive = Key::Unknown;
|
|
||||||
Key negative = Key::Unknown;
|
|
||||||
|
|
||||||
int value = 0;
|
|
||||||
|
|
||||||
void init(Key negative, Key positive);
|
|
||||||
void update();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ButtonsNode
|
|
||||||
{
|
|
||||||
int gamepad_id = 0;
|
|
||||||
Button positive = Button::None;
|
|
||||||
Button negative = Button::None;
|
|
||||||
|
|
||||||
int value = 0;
|
|
||||||
|
|
||||||
void init(int gamepad_id, Button negative, Button positive);
|
|
||||||
void update();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AxisNode
|
|
||||||
{
|
|
||||||
int gamepad_id = 0;
|
|
||||||
Axis axis = Axis::None;
|
|
||||||
float deadzone = 0;
|
|
||||||
|
|
||||||
float value = 0;
|
|
||||||
|
|
||||||
void init(int gamepad_id, Axis axis, float deadzone);
|
|
||||||
void update();
|
|
||||||
};
|
|
||||||
|
|
||||||
KeysNode m_keys[Input::max_virtual_nodes];
|
|
||||||
ButtonsNode m_buttons[Input::max_virtual_nodes];
|
|
||||||
AxisNode m_axes[Input::max_virtual_nodes];
|
|
||||||
int m_keys_len = 0;
|
|
||||||
int m_buttons_len = 0;
|
|
||||||
int m_axes_len = 0;
|
|
||||||
|
|
||||||
float m_press_buffer = 0;
|
|
||||||
float m_release_buffer = 0;
|
|
||||||
float m_repeat_delay = 0;
|
|
||||||
float m_repeat_interval = 0;
|
|
||||||
|
|
||||||
float m_value = 0;
|
|
||||||
int m_value_i = 0;
|
|
||||||
float m_last_value = 0;
|
|
||||||
int m_last_value_i = 0;
|
|
||||||
bool m_pressed = false;
|
|
||||||
bool m_released = false;
|
|
||||||
double m_last_press_time = -1;
|
|
||||||
double m_last_release_time = -1;
|
|
||||||
double m_repeat_press_time = -1;
|
|
||||||
|
|
||||||
public:
|
|
||||||
VirtualAxis& add_keys(Key negative, Key positive);
|
|
||||||
VirtualAxis& add_buttons(int gamepad_id, Button negative, Button positive);
|
|
||||||
VirtualAxis& add_axis(int gamepad_id, Axis axis, float deadzone);
|
|
||||||
VirtualAxis& repeat(float m_repeat_delay, float m_repeat_interval);
|
|
||||||
VirtualAxis& press_buffer(float duration);
|
|
||||||
VirtualAxis& release_buffer(float duration);
|
|
||||||
|
|
||||||
void update();
|
|
||||||
float value() const { return m_value; }
|
|
||||||
int value_i() const { return m_value_i; }
|
|
||||||
float last_value() const { return m_last_value; }
|
|
||||||
int last_value_i() const { return m_last_value_i; }
|
|
||||||
bool pressed() const { return m_pressed; }
|
|
||||||
bool released() const { return m_released; }
|
|
||||||
void clear_press_buffer() { m_last_press_time = -1; m_pressed = false; }
|
|
||||||
void clear_release_buffer() { m_last_release_time = -1; m_released = false; }
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <blah/input/input.h>
|
|
||||||
|
|
||||||
namespace Blah
|
|
||||||
{
|
|
||||||
class VirtualButton
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
struct KeyNode
|
|
||||||
{
|
|
||||||
Key key = Key::Unknown;
|
|
||||||
|
|
||||||
bool down = false;
|
|
||||||
bool pressed = false;
|
|
||||||
bool released = false;
|
|
||||||
|
|
||||||
void init(Key key);
|
|
||||||
void update();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ButtonNode
|
|
||||||
{
|
|
||||||
int gamepad_id = 0;
|
|
||||||
Button button = Button::None;
|
|
||||||
|
|
||||||
bool down = false;
|
|
||||||
bool pressed = false;
|
|
||||||
bool released = false;
|
|
||||||
|
|
||||||
void init(int gamepad_id, Button button);
|
|
||||||
void update();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AxisNode
|
|
||||||
{
|
|
||||||
int gamepad_id = 0;
|
|
||||||
Axis axis = Axis::None;
|
|
||||||
float threshold = 0;
|
|
||||||
bool greater_than = false;
|
|
||||||
|
|
||||||
bool down = false;
|
|
||||||
bool pressed = false;
|
|
||||||
bool released = false;
|
|
||||||
|
|
||||||
void init(int gamepad_id, Axis axis, float threshold, bool greater_than);
|
|
||||||
void update();
|
|
||||||
};
|
|
||||||
|
|
||||||
KeyNode m_keys[Input::max_virtual_nodes];
|
|
||||||
ButtonNode m_buttons[Input::max_virtual_nodes];
|
|
||||||
AxisNode m_axes[Input::max_virtual_nodes];
|
|
||||||
int m_keys_len = 0;
|
|
||||||
int m_buttons_len = 0;
|
|
||||||
int m_axes_len = 0;
|
|
||||||
|
|
||||||
float m_press_buffer = 0;
|
|
||||||
float m_release_buffer = 0;
|
|
||||||
float m_repeat_delay = 0;
|
|
||||||
float m_repeat_interval = 0;
|
|
||||||
|
|
||||||
bool m_down = false;
|
|
||||||
bool m_pressed = false;
|
|
||||||
bool m_released = false;
|
|
||||||
double m_last_press_time = -1;
|
|
||||||
double m_last_release_time = -1;
|
|
||||||
double m_repeat_press_time = -1;
|
|
||||||
|
|
||||||
public:
|
|
||||||
VirtualButton& add_key(Key key);
|
|
||||||
VirtualButton& add_button(int gamepad_id, Button button);
|
|
||||||
VirtualButton& add_axis(int gamepad_id, Axis axis, float threshold, bool greater_than);
|
|
||||||
VirtualButton& repeat(float m_repeat_delay, float m_repeat_interval);
|
|
||||||
VirtualButton& press_buffer(float duration);
|
|
||||||
VirtualButton& release_buffer(float duration);
|
|
||||||
|
|
||||||
void update();
|
|
||||||
bool down() const { return m_down; }
|
|
||||||
bool pressed() const { return m_pressed; }
|
|
||||||
bool released() const { return m_released; }
|
|
||||||
void clear_press_buffer() { m_last_press_time = -1; m_pressed = false; }
|
|
||||||
void clear_release_buffer() { m_last_release_time = -1; m_released = false; }
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <blah/input/input.h>
|
|
||||||
#include <blah/math/vec2.h>
|
|
||||||
#include <blah/math/point.h>
|
|
||||||
|
|
||||||
namespace Blah
|
|
||||||
{
|
|
||||||
// A virtual controller stick, which can be used to map multiple
|
|
||||||
// inputs to a stick. Note that you must call `update` every frame!
|
|
||||||
class VirtualStick
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
struct KeysNode
|
|
||||||
{
|
|
||||||
Key left;
|
|
||||||
Key right;
|
|
||||||
Key up;
|
|
||||||
Key down;
|
|
||||||
|
|
||||||
Point value;
|
|
||||||
|
|
||||||
void init(Key left, Key right, Key up, Key down);
|
|
||||||
void update();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ButtonsNode
|
|
||||||
{
|
|
||||||
int gamepad_id;
|
|
||||||
Button left;
|
|
||||||
Button right;
|
|
||||||
Button up;
|
|
||||||
Button down;
|
|
||||||
|
|
||||||
Point value;
|
|
||||||
|
|
||||||
void init(int gamepad_id, Button left, Button right, Button up, Button down);
|
|
||||||
void update();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AxesNode
|
|
||||||
{
|
|
||||||
int gamepad_id;
|
|
||||||
Axis horizontal;
|
|
||||||
Axis vertical;
|
|
||||||
float deadzone;
|
|
||||||
|
|
||||||
Vec2 value;
|
|
||||||
|
|
||||||
void init(int gamepad_id, Axis horizontal, Axis vertical, float deadzone);
|
|
||||||
void update();
|
|
||||||
};
|
|
||||||
|
|
||||||
KeysNode m_keys[Input::max_virtual_nodes];
|
|
||||||
ButtonsNode m_buttons[Input::max_virtual_nodes];
|
|
||||||
AxesNode m_axes[Input::max_virtual_nodes];
|
|
||||||
int m_keys_len = 0;
|
|
||||||
int m_buttons_len = 0;
|
|
||||||
int m_axes_len = 0;
|
|
||||||
|
|
||||||
float m_press_buffer = 0;
|
|
||||||
float m_release_buffer = 0;
|
|
||||||
float m_repeat_delay = 0;
|
|
||||||
float m_repeat_interval = 0;
|
|
||||||
|
|
||||||
Vec2 m_value = Vec2();
|
|
||||||
Point m_value_i = Point();
|
|
||||||
Vec2 m_last_value = Vec2();
|
|
||||||
Point m_last_value_i = Point();
|
|
||||||
bool m_pressed = false;
|
|
||||||
bool m_released = false;
|
|
||||||
|
|
||||||
float m_i_deadzone;
|
|
||||||
double m_last_press_time = -1;
|
|
||||||
double m_last_release_time = -1;
|
|
||||||
double m_repeat_press_time = -1;
|
|
||||||
|
|
||||||
public:
|
|
||||||
VirtualStick();
|
|
||||||
VirtualStick(float iDeadzone);
|
|
||||||
VirtualStick& add_keys(Key left, Key right, Key up, Key down);
|
|
||||||
VirtualStick& add_buttons(int gamepad_id, Button left, Button right, Button up, Button down);
|
|
||||||
VirtualStick& add_axes(int gamepad_id, Axis horizontal, Axis vertical, float deadzone);
|
|
||||||
VirtualStick& repeat(float m_repeat_delay, float m_repeat_interval);
|
|
||||||
VirtualStick& press_buffer(float duration);
|
|
||||||
VirtualStick& release_buffer(float duration);
|
|
||||||
|
|
||||||
void update();
|
|
||||||
const Vec2& value() const { return m_value; }
|
|
||||||
const Point& value_i() const { return m_value_i; }
|
|
||||||
const Vec2& last_value() const { return m_last_value; }
|
|
||||||
const Point& last_value_i() const { return m_last_value_i; }
|
|
||||||
bool pressed() const { return m_pressed; }
|
|
||||||
bool released() const { return m_released; }
|
|
||||||
void clear_press_buffer() { m_last_press_time = 0; }
|
|
||||||
void clear_release_buffer() { m_last_release_time = 0; }
|
|
||||||
};
|
|
||||||
}
|
|
492
src/input/binding.cpp
Normal file
492
src/input/binding.cpp
Normal file
|
@ -0,0 +1,492 @@
|
||||||
|
#include <blah/input/binding.h>
|
||||||
|
#include <blah/math/calc.h>
|
||||||
|
#include <blah/core/time.h>
|
||||||
|
|
||||||
|
using namespace Blah;
|
||||||
|
|
||||||
|
BoundTrigger::BoundTrigger(Axis axis)
|
||||||
|
: axis(axis)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger::BoundTrigger(int controller, Axis axis, float threshold, bool positive)
|
||||||
|
: controller(controller), axis(axis), threshold(threshold), positive(positive)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BoundTrigger::is_down(float axis_value) const
|
||||||
|
{
|
||||||
|
if ((axis_value > 0 && positive) || (axis_value < 0 && !positive))
|
||||||
|
{
|
||||||
|
if (Calc::abs(axis_value) >= threshold)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::left_stick_left(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::LeftX, threshold, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::left_stick_right(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::LeftX, threshold, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::left_stick_up(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::LeftY, threshold, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::left_stick_down(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::LeftY, threshold, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::right_stick_left(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::RightX, threshold, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::right_stick_right(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::RightX, threshold, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::right_stick_up(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::RightY, threshold, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::right_stick_down(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::RightY, threshold, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::left_trigger(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::LeftTrigger, threshold, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundTrigger BoundTrigger::right_trigger(int controller, float threshold)
|
||||||
|
{
|
||||||
|
return BoundTrigger(controller, Axis::RightTrigger, threshold, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundButton::BoundButton(Button button)
|
||||||
|
: button(button) {}
|
||||||
|
|
||||||
|
BoundButton::BoundButton(int controller, Button button)
|
||||||
|
: controller(controller), button(button) {}
|
||||||
|
|
||||||
|
bool Binding::pressed() const
|
||||||
|
{
|
||||||
|
if (m_press_consumed)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (m_last_press_time >= 0 && (Time::seconds - m_last_press_time) <= press_buffer)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return m_pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Binding::released() const
|
||||||
|
{
|
||||||
|
if (m_release_consumed)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (m_last_release_time >= 0 && (Time::seconds - m_last_release_time) <= release_buffer)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return m_released;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Binding::down() const
|
||||||
|
{
|
||||||
|
return m_down;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Binding::value() const
|
||||||
|
{
|
||||||
|
return m_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Binding::sign() const
|
||||||
|
{
|
||||||
|
return (int)Calc::sign(m_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
double Binding::timestamp() const
|
||||||
|
{
|
||||||
|
return m_last_timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Binding::update()
|
||||||
|
{
|
||||||
|
m_press_consumed = false;
|
||||||
|
m_release_consumed = false;
|
||||||
|
|
||||||
|
if (get_pressed())
|
||||||
|
{
|
||||||
|
m_last_timestamp = Time::seconds;
|
||||||
|
m_last_press_time = Time::seconds;
|
||||||
|
m_pressed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_pressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_released())
|
||||||
|
{
|
||||||
|
m_last_release_time = Time::seconds;
|
||||||
|
m_released = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_released = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_down = get_down();
|
||||||
|
m_value = get_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Binding::consume_press()
|
||||||
|
{
|
||||||
|
m_press_consumed = true;
|
||||||
|
m_last_press_time = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Binding::consume_release()
|
||||||
|
{
|
||||||
|
m_release_consumed = true;
|
||||||
|
m_last_release_time = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Binding::add(Key key)
|
||||||
|
{
|
||||||
|
keys.push_back(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Binding::add(BoundButton button)
|
||||||
|
{
|
||||||
|
buttons.push_back(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Binding::add(BoundTrigger trigger)
|
||||||
|
{
|
||||||
|
triggers.push_back(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Binding::add(MouseButton button)
|
||||||
|
{
|
||||||
|
mouse.push_back(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Binding::set_controller(int index)
|
||||||
|
{
|
||||||
|
for (auto& it : buttons)
|
||||||
|
it.controller = index;
|
||||||
|
for (auto& it : triggers)
|
||||||
|
it.controller = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Binding::clear()
|
||||||
|
{
|
||||||
|
keys.clear();
|
||||||
|
buttons.clear();
|
||||||
|
triggers.clear();
|
||||||
|
mouse.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Binding::get_pressed() const
|
||||||
|
{
|
||||||
|
for (auto& it : keys)
|
||||||
|
if (Input::pressed(it))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& it : mouse)
|
||||||
|
if (Input::pressed(it))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& it : buttons)
|
||||||
|
if (Input::pressed(it.controller, it.button))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& it : triggers)
|
||||||
|
{
|
||||||
|
if (it.controller < 0 || it.controller >= Input::max_controllers)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((int)it.axis < 0 || (int)it.axis >= Input::max_controller_axis)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (it.is_down(Input::state()->controllers[it.controller].axis[(int)it.axis]) &&
|
||||||
|
!it.is_down(Input::last_state()->controllers[it.controller].axis[(int)it.axis]))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Binding::get_released() const
|
||||||
|
{
|
||||||
|
for (auto& it : keys)
|
||||||
|
if (Input::released(it))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& it : mouse)
|
||||||
|
if (Input::released(it))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& it : buttons)
|
||||||
|
if (Input::released(it.controller, it.button))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& it : triggers)
|
||||||
|
{
|
||||||
|
if (it.controller < 0 || it.controller >= Input::max_controllers)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((int)it.axis < 0 || (int)it.axis >= Input::max_controller_axis)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!it.is_down(Input::state()->controllers[it.controller].axis[(int)it.axis]) &&
|
||||||
|
it.is_down(Input::last_state()->controllers[it.controller].axis[(int)it.axis]))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Binding::get_down() const
|
||||||
|
{
|
||||||
|
for (auto& it : keys)
|
||||||
|
if (Input::down(it))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& it : mouse)
|
||||||
|
if (Input::down(it))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& it : buttons)
|
||||||
|
if (Input::down(it.controller, it.button))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto& it : triggers)
|
||||||
|
{
|
||||||
|
if (it.controller < 0 || it.controller >= Input::max_controllers)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((int)it.axis < 0 || (int)it.axis >= Input::max_controller_axis)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (it.is_down(Input::state()->controllers[it.controller].axis[(int)it.axis]))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Binding::get_value() const
|
||||||
|
{
|
||||||
|
for (auto& it : keys)
|
||||||
|
if (Input::down(it))
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
for (auto& it : mouse)
|
||||||
|
if (Input::down(it))
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
for (auto& it : buttons)
|
||||||
|
if (Input::down(it.controller, it.button))
|
||||||
|
return 1.0f;
|
||||||
|
|
||||||
|
float highest = 0;
|
||||||
|
|
||||||
|
for (auto& it : triggers)
|
||||||
|
{
|
||||||
|
if (it.controller < 0 || it.controller >= Input::max_controllers)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((int)it.axis < 0 || (int)it.axis >= Input::max_controller_axis)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float raw_value = Input::state()->controllers[it.controller].axis[(int)it.axis];
|
||||||
|
|
||||||
|
if (it.is_down(raw_value))
|
||||||
|
{
|
||||||
|
float mapped_value = Calc::clamped_map(Calc::abs(raw_value), it.threshold, 1.0f, 0.0f, 1.0f);
|
||||||
|
if (mapped_value > highest)
|
||||||
|
highest = mapped_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return highest;
|
||||||
|
}
|
||||||
|
|
||||||
|
float AxisBinding::value() const
|
||||||
|
{
|
||||||
|
float neg = negative.value();
|
||||||
|
float pos = positive.value();
|
||||||
|
|
||||||
|
// neither are down
|
||||||
|
if (neg <= 0 && pos <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// negative-only is down
|
||||||
|
if (neg > 0 && pos <= 0)
|
||||||
|
return -neg;
|
||||||
|
|
||||||
|
// positive-only is down
|
||||||
|
if (pos > 0 && neg <= 0)
|
||||||
|
return pos;
|
||||||
|
|
||||||
|
// both are down:
|
||||||
|
|
||||||
|
// overlap cancel out
|
||||||
|
if (overlap == Overlap::Cancel)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// overlap takes older
|
||||||
|
if (overlap == Overlap::Older)
|
||||||
|
{
|
||||||
|
if (negative.timestamp() < positive.timestamp())
|
||||||
|
return -neg;
|
||||||
|
else
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
// overlap takes newer
|
||||||
|
if (negative.timestamp() > positive.timestamp())
|
||||||
|
return -neg;
|
||||||
|
else
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AxisBinding::sign() const
|
||||||
|
{
|
||||||
|
return (int)Calc::sign(value());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxisBinding::update()
|
||||||
|
{
|
||||||
|
negative.update();
|
||||||
|
positive.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxisBinding::consume_press()
|
||||||
|
{
|
||||||
|
negative.consume_press();
|
||||||
|
positive.consume_press();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxisBinding::consume_release()
|
||||||
|
{
|
||||||
|
negative.consume_release();
|
||||||
|
positive.consume_release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxisBinding::add_left_stick_x(int controller, float threshold)
|
||||||
|
{
|
||||||
|
negative.add(BoundTrigger::left_stick_left(controller, threshold));
|
||||||
|
positive.add(BoundTrigger::left_stick_right(controller, threshold));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxisBinding::add_left_stick_y(int controller, float threshold)
|
||||||
|
{
|
||||||
|
negative.add(BoundTrigger::left_stick_up(controller, threshold));
|
||||||
|
positive.add(BoundTrigger::left_stick_down(controller, threshold));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxisBinding::add_right_stick_x(int controller, float threshold)
|
||||||
|
{
|
||||||
|
negative.add(BoundTrigger::right_stick_left(controller, threshold));
|
||||||
|
positive.add(BoundTrigger::right_stick_right(controller, threshold));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxisBinding::add_right_stick_y(int controller, float threshold)
|
||||||
|
{
|
||||||
|
negative.add(BoundTrigger::right_stick_up(controller, threshold));
|
||||||
|
positive.add(BoundTrigger::right_stick_down(controller, threshold));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxisBinding::set_controller(int index)
|
||||||
|
{
|
||||||
|
negative.set_controller(index);
|
||||||
|
positive.set_controller(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AxisBinding::clear()
|
||||||
|
{
|
||||||
|
negative.clear();
|
||||||
|
positive.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 StickBinding::value() const
|
||||||
|
{
|
||||||
|
Vec2 result = Vec2(x.value(), y.value());
|
||||||
|
if (round_threshold > 0 && result.length() < round_threshold)
|
||||||
|
return Vec2::zero;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point StickBinding::sign() const
|
||||||
|
{
|
||||||
|
Vec2 result = value();
|
||||||
|
return Point((int)Calc::sign(result.x), (int)Calc::sign(result.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
void StickBinding::update()
|
||||||
|
{
|
||||||
|
x.update();
|
||||||
|
y.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StickBinding::consume_press()
|
||||||
|
{
|
||||||
|
x.consume_press();
|
||||||
|
y.consume_press();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StickBinding::consume_release()
|
||||||
|
{
|
||||||
|
x.consume_release();
|
||||||
|
y.consume_release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void StickBinding::add_dpad(int controller)
|
||||||
|
{
|
||||||
|
x.negative.add(BoundButton(controller, Button::Left));
|
||||||
|
x.positive.add(BoundButton(controller, Button::Right));
|
||||||
|
y.negative.add(BoundButton(controller, Button::Up));
|
||||||
|
y.positive.add(BoundButton(controller, Button::Down));
|
||||||
|
}
|
||||||
|
|
||||||
|
void StickBinding::add_left_stick(int controller, float threshold)
|
||||||
|
{
|
||||||
|
x.add_left_stick_x(controller, threshold);
|
||||||
|
y.add_left_stick_y(controller, threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StickBinding::add_right_stick(int controller, float threshold)
|
||||||
|
{
|
||||||
|
x.add_right_stick_x(controller, threshold);
|
||||||
|
y.add_right_stick_y(controller, threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StickBinding::set_controller(int index)
|
||||||
|
{
|
||||||
|
x.set_controller(index);
|
||||||
|
y.set_controller(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StickBinding::clear()
|
||||||
|
{
|
||||||
|
x.clear();
|
||||||
|
y.clear();
|
||||||
|
}
|
70
src/input/binding_registry.cpp
Normal file
70
src/input/binding_registry.cpp
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#include <blah/input/binding_registry.h>
|
||||||
|
|
||||||
|
using namespace Blah;
|
||||||
|
|
||||||
|
Vector<std::weak_ptr<Binding>> BindingRegistry::bindings;
|
||||||
|
Vector<std::weak_ptr<AxisBinding>> BindingRegistry::axes;
|
||||||
|
Vector<std::weak_ptr<StickBinding>> BindingRegistry::sticks;
|
||||||
|
|
||||||
|
BindingRef BindingRegistry::register_binding()
|
||||||
|
{
|
||||||
|
auto binding = std::make_shared<Binding>();
|
||||||
|
bindings.push_back(std::weak_ptr<Binding>(binding));
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxisBindingRef BindingRegistry::register_axis()
|
||||||
|
{
|
||||||
|
auto binding = std::make_shared<AxisBinding>();
|
||||||
|
axes.push_back(std::weak_ptr<AxisBinding>(binding));
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
StickBindingRef BindingRegistry::register_stick()
|
||||||
|
{
|
||||||
|
auto binding = std::make_shared<StickBinding>();
|
||||||
|
sticks.push_back(std::weak_ptr<StickBinding>(binding));
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingRegistry::update()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < bindings.size(); i++)
|
||||||
|
{
|
||||||
|
if (bindings[i].use_count() <= 0)
|
||||||
|
{
|
||||||
|
bindings.erase(i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
else if (auto binding = bindings[i].lock())
|
||||||
|
{
|
||||||
|
binding->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < axes.size(); i++)
|
||||||
|
{
|
||||||
|
if (axes[i].use_count() <= 0)
|
||||||
|
{
|
||||||
|
axes.erase(i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
else if (auto binding = axes[i].lock())
|
||||||
|
{
|
||||||
|
binding->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < sticks.size(); i++)
|
||||||
|
{
|
||||||
|
if (sticks[i].use_count() <= 0)
|
||||||
|
{
|
||||||
|
sticks.erase(i);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
else if (auto binding = sticks[i].lock())
|
||||||
|
{
|
||||||
|
binding->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
#include <blah/input/input.h>
|
#include <blah/input/input.h>
|
||||||
|
#include <blah/input/binding_registry.h>
|
||||||
#include <blah/core/app.h>
|
#include <blah/core/app.h>
|
||||||
#include <blah/core/time.h>
|
#include <blah/core/time.h>
|
||||||
#include <blah/core/common.h>
|
#include <blah/core/common.h>
|
||||||
|
@ -66,6 +67,9 @@ void InputBackend::frame()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update bindings
|
||||||
|
BindingRegistry::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputBackend::on_mouse_move(float x, float y)
|
void InputBackend::on_mouse_move(float x, float y)
|
||||||
|
|
|
@ -1,166 +0,0 @@
|
||||||
#include <blah/input/virtual_axis.h>
|
|
||||||
#include <blah/core/time.h>
|
|
||||||
#include <blah/core/common.h>
|
|
||||||
|
|
||||||
using namespace Blah;
|
|
||||||
|
|
||||||
VirtualAxis& VirtualAxis::add_keys(Key negative, Key positive)
|
|
||||||
{
|
|
||||||
if (m_axes_len >= Input::max_virtual_nodes)
|
|
||||||
BLAH_ERROR("VirtualAxis Keys out of bounds!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_keys[m_keys_len].init(negative, positive);
|
|
||||||
m_keys_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualAxis& VirtualAxis::add_buttons(int gamepad_id, Button negative, Button positive)
|
|
||||||
{
|
|
||||||
if (m_axes_len >= Input::max_virtual_nodes)
|
|
||||||
BLAH_ERROR("VirtualAxis Buttons out of bounds!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_buttons[m_buttons_len].init(gamepad_id, negative, positive);
|
|
||||||
m_buttons_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualAxis& VirtualAxis::add_axis(int gamepad_id, Axis axis, float deadzone)
|
|
||||||
{
|
|
||||||
if (m_axes_len >= Input::max_virtual_nodes)
|
|
||||||
BLAH_ERROR("VirtualAxis Axes out of bounds!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_axes[m_axes_len].init(gamepad_id, axis, deadzone);
|
|
||||||
m_axes_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualAxis& VirtualAxis::repeat(float m_repeat_delay, float m_repeat_interval)
|
|
||||||
{
|
|
||||||
this->m_repeat_delay = m_repeat_delay;
|
|
||||||
this->m_repeat_interval = m_repeat_interval;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualAxis& VirtualAxis::press_buffer(float duration)
|
|
||||||
{
|
|
||||||
this->m_press_buffer = duration;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualAxis& VirtualAxis::release_buffer(float duration)
|
|
||||||
{
|
|
||||||
this->m_release_buffer = duration;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualAxis::update()
|
|
||||||
{
|
|
||||||
m_last_value = m_value;
|
|
||||||
m_value = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < m_keys_len; i++)
|
|
||||||
{
|
|
||||||
m_keys[i].update();
|
|
||||||
if (m_value == 0)
|
|
||||||
m_value = (float)m_keys[i].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < m_buttons_len; i++)
|
|
||||||
{
|
|
||||||
m_buttons[i].update();
|
|
||||||
if (m_value == 0)
|
|
||||||
m_value = (float)m_buttons[i].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < m_axes_len; i++)
|
|
||||||
{
|
|
||||||
m_axes[i].update();
|
|
||||||
if (m_value == 0)
|
|
||||||
m_value = m_axes[i].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Valuei
|
|
||||||
m_last_value_i = m_value_i;
|
|
||||||
if (m_value > 0)
|
|
||||||
m_value_i = 1;
|
|
||||||
else if (m_value < 0)
|
|
||||||
m_value_i = -1;
|
|
||||||
else
|
|
||||||
m_value_i = 0;
|
|
||||||
|
|
||||||
//pressed?
|
|
||||||
m_pressed = false;
|
|
||||||
if (m_value_i != 0 && m_last_value_i != m_value_i)
|
|
||||||
{
|
|
||||||
m_pressed = true;
|
|
||||||
m_last_press_time = m_repeat_press_time = Time::seconds;
|
|
||||||
}
|
|
||||||
else if (m_value_i == m_last_value_i && m_value_i != 0)
|
|
||||||
{
|
|
||||||
if (Time::seconds - m_last_press_time <= m_press_buffer)
|
|
||||||
m_pressed = true;
|
|
||||||
else if (m_repeat_interval > 0 && Time::seconds >= m_repeat_press_time + m_repeat_delay)
|
|
||||||
{
|
|
||||||
int prev = (int)((Time::previous_seconds - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
|
|
||||||
int cur = (int)((Time::seconds - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
|
|
||||||
m_pressed = prev < cur;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//released?
|
|
||||||
if (m_last_value_i != 0 && m_value_i != m_last_value_i)
|
|
||||||
{
|
|
||||||
m_released = true;
|
|
||||||
m_last_release_time = Time::seconds;
|
|
||||||
}
|
|
||||||
else if (Time::seconds - m_last_release_time <= m_release_buffer)
|
|
||||||
m_released = true;
|
|
||||||
else
|
|
||||||
m_released = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualAxis::KeysNode::init(Key negative, Key positive)
|
|
||||||
{
|
|
||||||
this->negative = negative;
|
|
||||||
this->positive = positive;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualAxis::KeysNode::update()
|
|
||||||
{
|
|
||||||
value = Input::axis_check(value, negative, positive);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualAxis::ButtonsNode::init(int gamepad_id, Button negative, Button positive)
|
|
||||||
{
|
|
||||||
this->gamepad_id = gamepad_id;
|
|
||||||
this->negative = negative;
|
|
||||||
this->positive = positive;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualAxis::ButtonsNode::update()
|
|
||||||
{
|
|
||||||
value = Input::axis_check(value, gamepad_id, negative, positive);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualAxis::AxisNode::init(int gamepad_id, Axis axis, float deadzone)
|
|
||||||
{
|
|
||||||
this->gamepad_id = gamepad_id;
|
|
||||||
this->axis = axis;
|
|
||||||
this->deadzone = deadzone;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualAxis::AxisNode::update()
|
|
||||||
{
|
|
||||||
value = Input::axis_check(gamepad_id, axis);
|
|
||||||
if (value < deadzone && value > -deadzone)
|
|
||||||
value = 0;
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
#include <blah/input/virtual_button.h>
|
|
||||||
#include <blah/core/time.h>
|
|
||||||
#include <blah/core/common.h>
|
|
||||||
|
|
||||||
using namespace Blah;
|
|
||||||
|
|
||||||
VirtualButton& VirtualButton::add_key(Key key)
|
|
||||||
{
|
|
||||||
if (m_keys_len >= Input::max_virtual_nodes)
|
|
||||||
BLAH_ERROR("VirtualButton Keys out of bounds!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_keys[m_keys_len].init(key);
|
|
||||||
m_keys_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualButton& VirtualButton::add_button(int gamepad_id, Button button)
|
|
||||||
{
|
|
||||||
if (m_buttons_len >= Input::max_virtual_nodes)
|
|
||||||
BLAH_ERROR("VirtualButton Buttons out of bounds!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_buttons[m_buttons_len].init(gamepad_id, button);
|
|
||||||
m_buttons_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualButton& VirtualButton::add_axis(int gamepad_id, Axis axis, float threshold, bool greater_than)
|
|
||||||
{
|
|
||||||
if (m_axes_len >= Input::max_virtual_nodes)
|
|
||||||
BLAH_ERROR("VirtualButton Axes out of bounds!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_axes[m_axes_len].init(gamepad_id, axis, threshold, greater_than);
|
|
||||||
m_axes_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualButton& VirtualButton::repeat(float m_repeat_delay, float m_repeat_interval)
|
|
||||||
{
|
|
||||||
this->m_repeat_delay = m_repeat_delay;
|
|
||||||
this->m_repeat_interval = m_repeat_interval;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualButton& VirtualButton::press_buffer(float duration)
|
|
||||||
{
|
|
||||||
this->m_press_buffer = duration;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualButton& VirtualButton::release_buffer(float duration)
|
|
||||||
{
|
|
||||||
this->m_release_buffer = duration;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualButton::update()
|
|
||||||
{
|
|
||||||
m_down = false;
|
|
||||||
m_pressed = false;
|
|
||||||
m_released = false;
|
|
||||||
|
|
||||||
// Keys
|
|
||||||
for (int i = 0; i < m_keys_len; i++)
|
|
||||||
{
|
|
||||||
m_keys[i].update();
|
|
||||||
|
|
||||||
m_down = m_down || m_keys[i].down;
|
|
||||||
m_pressed = m_pressed || m_keys[i].pressed;
|
|
||||||
m_released = m_released || m_keys[i].released;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buttons
|
|
||||||
for (int i = 0; i < m_buttons_len; i++)
|
|
||||||
{
|
|
||||||
m_buttons[i].update();
|
|
||||||
|
|
||||||
m_down = m_down || m_buttons[i].down;
|
|
||||||
m_pressed = m_pressed || m_buttons[i].pressed;
|
|
||||||
m_released = m_released || m_buttons[i].released;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Axes
|
|
||||||
for (int i = 0; i < m_axes_len; i++)
|
|
||||||
{
|
|
||||||
m_axes[i].update();
|
|
||||||
|
|
||||||
m_down = m_down || m_axes[i].down;
|
|
||||||
m_pressed = m_pressed || m_axes[i].pressed;
|
|
||||||
m_released = m_released || m_axes[i].released;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pressed?
|
|
||||||
if (m_pressed)
|
|
||||||
{
|
|
||||||
m_repeat_press_time = m_last_press_time = Time::seconds;
|
|
||||||
}
|
|
||||||
else if (Time::seconds - m_last_press_time <= m_press_buffer)
|
|
||||||
{
|
|
||||||
m_pressed = true;
|
|
||||||
}
|
|
||||||
else if (m_down && m_repeat_interval > 0 && Time::seconds >= m_repeat_press_time + m_repeat_delay)
|
|
||||||
{
|
|
||||||
int prev = (int)((Time::previous_seconds - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
|
|
||||||
int cur = (int)((Time::seconds - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
|
|
||||||
m_pressed = prev < cur;
|
|
||||||
}
|
|
||||||
|
|
||||||
// released?
|
|
||||||
if (m_released)
|
|
||||||
m_last_release_time = Time::seconds;
|
|
||||||
else
|
|
||||||
m_released = Time::seconds - m_last_release_time <= m_release_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualButton::KeyNode::init(Key key)
|
|
||||||
{
|
|
||||||
this->key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualButton::KeyNode::update()
|
|
||||||
{
|
|
||||||
down = Input::down(key);
|
|
||||||
pressed = Input::pressed(key);
|
|
||||||
released = Input::released(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualButton::ButtonNode::init(int gamepad_id, Button button)
|
|
||||||
{
|
|
||||||
this->gamepad_id = gamepad_id;
|
|
||||||
this->button = button;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualButton::ButtonNode::update()
|
|
||||||
{
|
|
||||||
down = Input::down(gamepad_id, button);
|
|
||||||
pressed = Input::pressed(gamepad_id, button);
|
|
||||||
released = Input::released(gamepad_id, button);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualButton::AxisNode::init(int gamepad_id, Axis axis, float threshold, bool greater_than)
|
|
||||||
{
|
|
||||||
this->gamepad_id = gamepad_id;
|
|
||||||
this->axis = axis;
|
|
||||||
this->threshold = threshold;
|
|
||||||
this->greater_than = greater_than;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualButton::AxisNode::update()
|
|
||||||
{
|
|
||||||
float curr = Input::state()->controllers[gamepad_id].axis[(int)axis];
|
|
||||||
float prev = Input::last_state()->controllers[gamepad_id].axis[(int)axis];
|
|
||||||
|
|
||||||
if (greater_than)
|
|
||||||
{
|
|
||||||
down = curr >= threshold;
|
|
||||||
pressed = down && prev < threshold;
|
|
||||||
released = !down && prev >= threshold;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
down = curr <= threshold;
|
|
||||||
pressed = down && prev > threshold;
|
|
||||||
released = !down && prev <= threshold;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,191 +0,0 @@
|
||||||
#include <blah/input/virtual_stick.h>
|
|
||||||
#include <blah/core/time.h>
|
|
||||||
#include <blah/core/common.h>
|
|
||||||
|
|
||||||
using namespace Blah;
|
|
||||||
|
|
||||||
VirtualStick::VirtualStick()
|
|
||||||
{
|
|
||||||
this->m_i_deadzone = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualStick::VirtualStick(float iDeadzone)
|
|
||||||
{
|
|
||||||
this->m_i_deadzone = iDeadzone;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualStick& VirtualStick::add_keys(Key left, Key right, Key up, Key down)
|
|
||||||
{
|
|
||||||
if (m_keys_len >= Input::max_virtual_nodes)
|
|
||||||
BLAH_ERROR("VirtualStick Keys out of bounds!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_keys[m_keys_len].init(left, right, up, down);
|
|
||||||
m_keys_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualStick& VirtualStick::add_buttons(int gamepad_id, Button left, Button right, Button up, Button down)
|
|
||||||
{
|
|
||||||
if (m_buttons_len >= Input::max_virtual_nodes)
|
|
||||||
BLAH_ERROR("VirtualStick Buttons out of bounds!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_buttons[m_buttons_len].init(gamepad_id, left, right, up, down);
|
|
||||||
m_buttons_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualStick& VirtualStick::add_axes(int gamepad_id, Axis horizontal, Axis vertical, float deadzone)
|
|
||||||
{
|
|
||||||
if (m_axes_len >= Input::max_virtual_nodes)
|
|
||||||
BLAH_ERROR("VirtualStick Axes out of bounds!");
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_axes[m_axes_len].init(gamepad_id, horizontal, vertical, deadzone);
|
|
||||||
m_axes_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualStick& VirtualStick::repeat(float repeat_delay, float repeat_interval)
|
|
||||||
{
|
|
||||||
this->m_repeat_delay = repeat_delay;
|
|
||||||
this->m_repeat_interval = repeat_interval;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualStick& VirtualStick::press_buffer(float duration)
|
|
||||||
{
|
|
||||||
m_press_buffer = duration;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
VirtualStick& VirtualStick::release_buffer(float duration)
|
|
||||||
{
|
|
||||||
m_release_buffer = duration;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualStick::update()
|
|
||||||
{
|
|
||||||
m_last_value = m_value;
|
|
||||||
m_value = Vec2::zero;
|
|
||||||
|
|
||||||
for (int i = 0; i < m_keys_len; i++)
|
|
||||||
{
|
|
||||||
m_keys[i].update();
|
|
||||||
if (m_value == Vec2::zero)
|
|
||||||
m_value = m_keys[i].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < m_buttons_len; i++)
|
|
||||||
{
|
|
||||||
m_buttons[i].update();
|
|
||||||
if (m_value == Vec2::zero)
|
|
||||||
m_value = m_buttons[i].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < m_axes_len; i++)
|
|
||||||
{
|
|
||||||
m_axes[i].update();
|
|
||||||
if (m_value == Vec2::zero)
|
|
||||||
m_value = m_axes[i].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Valuei
|
|
||||||
m_last_value_i = m_value_i;
|
|
||||||
if (m_value.x > m_i_deadzone)
|
|
||||||
m_value_i.x = 1;
|
|
||||||
else if (m_value.x < -m_i_deadzone)
|
|
||||||
m_value_i.x = -1;
|
|
||||||
else
|
|
||||||
m_value_i.x = 0;
|
|
||||||
if (m_value.y > m_i_deadzone)
|
|
||||||
m_value_i.y = 1;
|
|
||||||
else if (m_value.y < -m_i_deadzone)
|
|
||||||
m_value_i.y = -1;
|
|
||||||
else
|
|
||||||
m_value_i.y = 0;
|
|
||||||
|
|
||||||
//pressed?
|
|
||||||
m_pressed = false;
|
|
||||||
if (m_value_i != Point::zero && m_last_value_i != m_value_i)
|
|
||||||
{
|
|
||||||
m_pressed = true;
|
|
||||||
m_last_press_time = m_repeat_press_time = Time::seconds;
|
|
||||||
}
|
|
||||||
else if (m_value_i == m_last_value_i && m_value_i != Point::zero)
|
|
||||||
{
|
|
||||||
if (Time::seconds - m_last_press_time <= m_press_buffer)
|
|
||||||
m_pressed = true;
|
|
||||||
else if (m_repeat_interval > 0 && Time::seconds >= m_repeat_press_time + m_repeat_delay)
|
|
||||||
{
|
|
||||||
int prev = (int)((Time::previous_seconds - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
|
|
||||||
int cur = (int)((Time::seconds - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
|
|
||||||
m_pressed = prev < cur;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//released?
|
|
||||||
if (m_last_value_i != Point::zero && m_value_i != m_last_value_i)
|
|
||||||
{
|
|
||||||
m_released = true;
|
|
||||||
m_last_release_time = Time::seconds;
|
|
||||||
}
|
|
||||||
else if (Time::seconds - m_last_release_time <= m_release_buffer)
|
|
||||||
m_released = true;
|
|
||||||
else
|
|
||||||
m_released = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualStick::KeysNode::init(Key left, Key right, Key up, Key down)
|
|
||||||
{
|
|
||||||
this->left = left;
|
|
||||||
this->right = right;
|
|
||||||
this->up = up;
|
|
||||||
this->down = down;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualStick::KeysNode::update()
|
|
||||||
{
|
|
||||||
value.x = Input::axis_check(value.x, left, right);
|
|
||||||
value.y = Input::axis_check(value.y, up, down);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualStick::ButtonsNode::init(int gamepad_id, Button left, Button right, Button up, Button down)
|
|
||||||
{
|
|
||||||
this->gamepad_id = gamepad_id;
|
|
||||||
this->left = left;
|
|
||||||
this->right = right;
|
|
||||||
this->up = up;
|
|
||||||
this->down = down;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualStick::ButtonsNode::update()
|
|
||||||
{
|
|
||||||
value.x = Input::axis_check(value.x, gamepad_id, left, right);
|
|
||||||
value.y = Input::axis_check(value.y, gamepad_id, up, down);
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualStick::AxesNode::init(int gamepad_id, Axis horizontal, Axis vertical, float deadzone)
|
|
||||||
{
|
|
||||||
this->gamepad_id = gamepad_id;
|
|
||||||
this->horizontal = horizontal;
|
|
||||||
this->vertical = vertical;
|
|
||||||
this->deadzone = deadzone;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VirtualStick::AxesNode::update()
|
|
||||||
{
|
|
||||||
value.x = Input::axis_check(gamepad_id, horizontal);
|
|
||||||
value.y = Input::axis_check(gamepad_id, vertical);
|
|
||||||
|
|
||||||
if (value.length() < deadzone)
|
|
||||||
value = Vec2::zero;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user