feat: reimplement input system
This commit is contained in:
		
							parent
							
								
									21e9933a42
								
							
						
					
					
						commit
						d69315c6aa
					
				
					 8 changed files with 432 additions and 283 deletions
				
			
		| 
						 | 
					@ -1,2 +1,4 @@
 | 
				
			||||||
add_library_module(input input.cpp)
 | 
					add_library_module(input system.cpp)
 | 
				
			||||||
target_link_libraries(input PUBLIC surface math imgui::imgui logger)
 | 
					target_link_libraries(input PUBLIC surface math  logger)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_test_module(input system.test.cpp)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,159 +0,0 @@
 | 
				
			||||||
#include <imgui.h>
 | 
					 | 
				
			||||||
#include <input/input.hpp>
 | 
					 | 
				
			||||||
#include <logger/logger.hpp>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace lt {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Input::Input(): m_mouse_position {}, m_mouse_delta {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	restart_input_state();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Input::receive_user_interface_events_impl(bool receive, bool toggle /* = false */)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	m_user_interface_events = toggle ? !m_user_interface_events : receive;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Input::receieve_game_events_impl(bool receive, bool toggle /*= false*/)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	auto prev = m_game_events;
 | 
					 | 
				
			||||||
	m_game_events = toggle ? !m_user_interface_events : receive;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (m_game_events != prev)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		restart_input_state();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Input::restart_input_state()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	m_keyboad_keys.fill(false);
 | 
					 | 
				
			||||||
	m_mouse_buttons.fill(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	m_mouse_position = math::vec2(0.0f);
 | 
					 | 
				
			||||||
	m_mouse_delta = math::vec2(0.0f);
 | 
					 | 
				
			||||||
	m_mouse_wheel_delta = 0.0f;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Input::on_event(const Event &inputEvent)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	auto &io = ImGui::GetIO();
 | 
					 | 
				
			||||||
	switch (inputEvent.get_event_type())
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		//** MOUSE_EVENTS **//
 | 
					 | 
				
			||||||
	case EventType::MouseMoved:
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		const auto &event = dynamic_cast<const MouseMovedEvent &>(inputEvent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_game_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			m_mouse_delta = event.get_position() - m_mouse_position;
 | 
					 | 
				
			||||||
			m_mouse_position = event.get_position();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_user_interface_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			io.MousePos = ImVec2(event.get_x(), event.get_y());
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	case EventType::ButtonPressed:
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		const auto &event = dynamic_cast<const ButtonPressedEvent &>(inputEvent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_game_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			m_mouse_buttons[event.get_button()] = true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_user_interface_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			io.MouseDown[event.get_button()] = true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	case EventType::ButtonReleased:
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		const auto &event = dynamic_cast<const ButtonReleasedEvent &>(inputEvent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_game_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			m_mouse_buttons[event.get_button()] = false;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_user_interface_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			io.MouseDown[event.get_button()] = false;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	case EventType::WheelScrolled:
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		const auto &event = dynamic_cast<const WheelScrolledEvent &>(inputEvent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_game_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			m_mouse_wheel_delta = event.get_offset();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_user_interface_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			io.MouseWheel = event.get_offset();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	//** KEYBOARD_EVENTS **//
 | 
					 | 
				
			||||||
	case EventType::KeyPressed:
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		const auto &event = dynamic_cast<const KeyPressedEvent &>(inputEvent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_game_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			m_keyboad_keys[event.get_key()] = true;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_user_interface_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			// io.AddKeyEvent(event.get_key(), true);
 | 
					 | 
				
			||||||
			// if (event.get_key() == Key::BackSpace)
 | 
					 | 
				
			||||||
			//	io.AddInputCharacter(Key::BackSpace);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	case EventType::KeyReleased:
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		const auto &event = dynamic_cast<const KeyReleasedEvent &>(inputEvent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_game_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			m_keyboad_keys[event.get_key()] = false;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (m_user_interface_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			// io.AddKeyEvent(event.get_key(), false);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	case EventType::SetChar:
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		if (m_user_interface_events)
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			const auto &event = dynamic_cast<const SetCharEvent &>(inputEvent);
 | 
					 | 
				
			||||||
			io.AddInputCharacter(event.get_character());
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	default: log_trc("Dropped event");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
} // namespace lt
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,138 @@
 | 
				
			||||||
 | 
					#include <input/components.hpp>
 | 
				
			||||||
#include <input/system.hpp>
 | 
					#include <input/system.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace lt::input {
 | 
					namespace lt::input {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<class... Ts>
 | 
				
			||||||
 | 
					struct overloads: Ts...
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						using Ts::operator()...;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					System::System(Ref<ecs::Registry> registry): m_registry(std::move(registry))
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						ensure(m_registry, "Failed to initialize input system: null registry");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto System::tick() -> bool
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						m_registry->view<surface::SurfaceComponent>().each([&](const entt::entity,
 | 
				
			||||||
 | 
						                                                       surface::SurfaceComponent &surface) {
 | 
				
			||||||
 | 
							for (const auto &event : surface.peek_events())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								handle_event(event);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m_registry->view<InputComponent>().each([&](const entt::entity, InputComponent &input) {
 | 
				
			||||||
 | 
							// TODO(Light): instead of iterating over all actions each frame,
 | 
				
			||||||
 | 
							// make a list of "dirty" actions to reset
 | 
				
			||||||
 | 
							// and a surface_input->input_action mapping to get to action through input
 | 
				
			||||||
 | 
							// instead of brute-force checking all of them.
 | 
				
			||||||
 | 
							for (auto &action : input.m_actions)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto code = action.trigger.mapped_keycode;
 | 
				
			||||||
 | 
								if (code < m_keys.size() && m_keys[code])
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									if (action.state == InputAction::State::triggered)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										action.state = InputAction::State::active;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									else if (action.state == InputAction::State::inactive)
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										action.state = InputAction::State::triggered;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									action.state = InputAction::State::inactive;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void System::on_register()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void System::on_unregister()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void System::handle_event(const surface::SurfaceComponent::Event &event)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const auto visitor = overloads {
 | 
				
			||||||
 | 
							[this](const surface::ClosedEvent &) { on_surface_lost_focus(); },
 | 
				
			||||||
 | 
							[this](const surface::LostFocusEvent &) { on_surface_lost_focus(); },
 | 
				
			||||||
 | 
							[this](const surface::KeyPressedEvent &event) { on_key_press(event); },
 | 
				
			||||||
 | 
							[this](const surface::KeyReleasedEvent &event) { on_key_release(event); },
 | 
				
			||||||
 | 
							[this](const surface::MouseMovedEvent &event) { on_pointer_move(event); },
 | 
				
			||||||
 | 
							[this](const surface::ButtonPressedEvent &event) { on_button_press(event); },
 | 
				
			||||||
 | 
							[this](const surface::ButtonReleasedEvent &event) { on_button_release(event); },
 | 
				
			||||||
 | 
							[this](auto) {},
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::visit(visitor, event);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void System::on_surface_lost_focus()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						for (auto &key : m_keys)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							key = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto &button : m_buttons)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							button = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void System::on_key_press(const lt::surface::KeyPressedEvent &event)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (event.get_key() > m_keys.size())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							log_dbg(
 | 
				
			||||||
 | 
							    "Key code larger than key container size, implement platform-dependant "
 | 
				
			||||||
 | 
							    "key-code-mapping!"
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m_keys[event.get_key()] = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void System::on_key_release(const lt::surface::KeyReleasedEvent &event)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (event.get_key() > m_keys.size())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							log_dbg(
 | 
				
			||||||
 | 
							    "Key code larger than key container size, implement platform-dependant "
 | 
				
			||||||
 | 
							    "key-code-mapping!"
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m_keys[event.get_key()] = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void System::on_pointer_move(const lt::surface::MouseMovedEvent &event)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						m_pointer_position = event.get_position();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void System::on_button_press(const lt::surface::ButtonPressedEvent &event)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						m_buttons[event.get_button()] = true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void System::on_button_release(const lt::surface::ButtonReleasedEvent &event)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						m_buttons[event.get_button()] = false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace lt::input
 | 
					} // namespace lt::input
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										169
									
								
								modules/input/private/system.test.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								modules/input/private/system.test.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,169 @@
 | 
				
			||||||
 | 
					#include <ecs/entity.hpp>
 | 
				
			||||||
 | 
					#include <input/components.hpp>
 | 
				
			||||||
 | 
					#include <input/system.hpp>
 | 
				
			||||||
 | 
					#include <test/test.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NOLINTBEGIN
 | 
				
			||||||
 | 
					using namespace lt;
 | 
				
			||||||
 | 
					using input::InputComponent;
 | 
				
			||||||
 | 
					using input::System;
 | 
				
			||||||
 | 
					using std::ignore;
 | 
				
			||||||
 | 
					using test::Case;
 | 
				
			||||||
 | 
					using test::expect_eq;
 | 
				
			||||||
 | 
					using test::expect_false;
 | 
				
			||||||
 | 
					using test::expect_ne;
 | 
				
			||||||
 | 
					using test::expect_not_nullptr;
 | 
				
			||||||
 | 
					using test::expect_throw;
 | 
				
			||||||
 | 
					using test::Suite;
 | 
				
			||||||
 | 
					// NOLINTEND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Fixture
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						[[nodiscard]] auto registry() -> Ref<ecs::Registry>
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return m_registry;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto add_input_component() -> ecs::Entity
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto entity = m_registry->create_entity("");
 | 
				
			||||||
 | 
							entity.add_component<InputComponent>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return entity;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto add_surface_component() -> ecs::Entity
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto entity = m_registry->create_entity("");
 | 
				
			||||||
 | 
							entity.add_component<surface::SurfaceComponent>(surface::SurfaceComponent::CreateInfo {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return entity;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						Ref<ecs::Registry> m_registry = create_ref<ecs::Registry>();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Suite raii = [] {
 | 
				
			||||||
 | 
						Case { "happy path won't throw" } = [&] {
 | 
				
			||||||
 | 
							System { Fixture {}.registry() };
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Case { "many won't freeze/throw" } = [&] {
 | 
				
			||||||
 | 
							auto fixture = Fixture {};
 | 
				
			||||||
 | 
							for (auto idx : std::views::iota(0, 10'000))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								ignore = System { fixture.registry() };
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Case { "unhappy path throws" } = [] {
 | 
				
			||||||
 | 
							expect_throw([] { ignore = System { {} }; });
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Suite system_events = [] {
 | 
				
			||||||
 | 
						Case { "on_register won't throw" } = [] {
 | 
				
			||||||
 | 
							auto fixture = Fixture {};
 | 
				
			||||||
 | 
							auto system = System { fixture.registry() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							system.on_register();
 | 
				
			||||||
 | 
							expect_eq(fixture.registry()->view<InputComponent>().size(), 0);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Case { "on_unregister won't throw" } = [] {
 | 
				
			||||||
 | 
							auto fixture = Fixture {};
 | 
				
			||||||
 | 
							auto system = System { fixture.registry() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							system.on_register();
 | 
				
			||||||
 | 
							system.on_unregister();
 | 
				
			||||||
 | 
							expect_eq(fixture.registry()->view<InputComponent>().size(), 0);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Suite registry_events = [] {
 | 
				
			||||||
 | 
						Case { "on_construct<InputComnent>" } = [] {
 | 
				
			||||||
 | 
							auto fixture = Fixture {};
 | 
				
			||||||
 | 
							auto system = System { fixture.registry() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const auto &entity = fixture.add_input_component();
 | 
				
			||||||
 | 
							expect_eq(fixture.registry()->view<InputComponent>().size(), 1);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Case { "on_destrroy<InputComponent>" } = [] {
 | 
				
			||||||
 | 
							auto fixture = Fixture {};
 | 
				
			||||||
 | 
							auto system = create_scope<System>(fixture.registry());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto entity_a = fixture.add_input_component();
 | 
				
			||||||
 | 
							auto entity_b = fixture.add_input_component();
 | 
				
			||||||
 | 
							expect_eq(fixture.registry()->view<InputComponent>().size(), 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							entity_a.remove_component<InputComponent>();
 | 
				
			||||||
 | 
							expect_eq(fixture.registry()->view<InputComponent>().size(), 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							system.reset();
 | 
				
			||||||
 | 
							expect_eq(fixture.registry()->view<InputComponent>().size(), 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							entity_b.remove_component<InputComponent>();
 | 
				
			||||||
 | 
							expect_eq(fixture.registry()->view<InputComponent>().size(), 0);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Suite tick = [] {
 | 
				
			||||||
 | 
						Case { "Empty tick won't throw" } = [] {
 | 
				
			||||||
 | 
							auto fixture = Fixture {};
 | 
				
			||||||
 | 
							auto system = System { fixture.registry() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expect_false(system.tick());
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Case { "Tick triggers input action" } = [] {
 | 
				
			||||||
 | 
							auto fixture = Fixture {};
 | 
				
			||||||
 | 
							auto system = System { fixture.registry() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto &surface = fixture.add_surface_component().get_component<surface::SurfaceComponent>();
 | 
				
			||||||
 | 
							auto &input = fixture.add_input_component().get_component<InputComponent>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto action_key = input.add_action(
 | 
				
			||||||
 | 
							    {
 | 
				
			||||||
 | 
							        .name { "test" },
 | 
				
			||||||
 | 
							        .trigger = { .mapped_keycode = 69 },
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
 | 
				
			||||||
 | 
							system.tick();
 | 
				
			||||||
 | 
							expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							surface.push_event(surface::KeyPressedEvent(69));
 | 
				
			||||||
 | 
							system.tick();
 | 
				
			||||||
 | 
							expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							system.tick();
 | 
				
			||||||
 | 
							expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							system.tick();
 | 
				
			||||||
 | 
							system.tick();
 | 
				
			||||||
 | 
							system.tick();
 | 
				
			||||||
 | 
							expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							surface.push_event(surface::KeyPressedEvent(69));
 | 
				
			||||||
 | 
							system.tick();
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Case { "Tick triggers" } = [] {
 | 
				
			||||||
 | 
							auto fixture = Fixture {};
 | 
				
			||||||
 | 
							auto system = System { fixture.registry() };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto &surface = fixture.add_surface_component().get_component<surface::SurfaceComponent>();
 | 
				
			||||||
 | 
							auto &input = fixture.add_input_component().get_component<InputComponent>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto action_key = input.add_action(
 | 
				
			||||||
 | 
							    {
 | 
				
			||||||
 | 
							        .name { "test" },
 | 
				
			||||||
 | 
							        .trigger = { .mapped_keycode = 69 },
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										57
									
								
								modules/input/public/components.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								modules/input/public/components.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace lt::input {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Trigger
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						uint32_t mapped_keycode;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct InputAction
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						using Key = size_t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						enum class State : uint8_t
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							inactive,
 | 
				
			||||||
 | 
							active,
 | 
				
			||||||
 | 
							triggered,
 | 
				
			||||||
 | 
							cancelled,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::string name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						State state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Trigger trigger;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InputComponent
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						InputComponent() = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto add_action(InputAction action) -> size_t
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							m_actions.emplace_back(std::move(action));
 | 
				
			||||||
 | 
							return m_actions.size() - 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto get_action(auto idx) -> const InputAction &
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return m_actions[idx];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						friend class System;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void push_event()
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::vector<InputAction> m_actions;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace lt::input
 | 
				
			||||||
							
								
								
									
										44
									
								
								modules/input/public/events.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								modules/input/public/events.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <math/vec2.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace lt::input {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AnalogEvent
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						AnalogEvent(uint32_t input_code, math::uvec2 pointer_position)
 | 
				
			||||||
 | 
						    : m_input_code(input_code)
 | 
				
			||||||
 | 
						    , m_pointer_position(pointer_position)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] auto get_code() const -> uint32_t
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return m_input_code;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] auto get_pointer_position() const -> math::uvec2
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return m_pointer_position;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] auto to_string() const -> std::string
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto stream = std::stringstream {};
 | 
				
			||||||
 | 
							const auto &[x, y] = m_pointer_position;
 | 
				
			||||||
 | 
							stream << "input::AnalogEvent: " << m_input_code << " @ " << x << ", " << y;
 | 
				
			||||||
 | 
							return stream.str();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						uint32_t m_input_code;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						math::uvec2 m_pointer_position;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AxisEvent
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace lt::input
 | 
				
			||||||
| 
						 | 
					@ -1,80 +0,0 @@
 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <array>
 | 
					 | 
				
			||||||
#include <math/vec2.hpp>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace lt {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Event;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Input
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
	static auto instance() -> Input &
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		static auto instance = Input {};
 | 
					 | 
				
			||||||
		return instance;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static void receive_user_interface_events(bool receive, bool toggle = false)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		instance().receive_user_interface_events_impl(receive, toggle);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static void receive_game_events(bool receive, bool toggle = false)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		instance().receieve_game_events_impl(receive, toggle);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static auto get_keyboard_key(int code) -> bool
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		return instance().m_keyboad_keys[code];
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static auto get_mouse_button(int code) -> bool
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		return instance().m_mouse_buttons[code];
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static auto get_mouse_position(int /*code*/) -> const math::vec2 &
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		return instance().m_mouse_position;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	void on_event(const Event &inputEvent);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	[[nodiscard]] auto is_receiving_input_events() const -> bool
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		return m_user_interface_events;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	[[nodiscard]] auto is_receiving_game_events() const -> bool
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		return m_game_events;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
	Input();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	void receive_user_interface_events_impl(bool receive, bool toggle = false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	void receieve_game_events_impl(bool receive, bool toggle = false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	void restart_input_state();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	std::array<bool, 348> m_keyboad_keys {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	std::array<bool, 8> m_mouse_buttons {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	math::vec2 m_mouse_position;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	math::vec2 m_mouse_delta;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	float m_mouse_wheel_delta {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	bool m_user_interface_events { true };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	bool m_game_events { true };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
} // namespace lt
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,63 +1,46 @@
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <surface/system.hpp>
 | 
					#include <app/system.hpp>
 | 
				
			||||||
 | 
					#include <ecs/scene.hpp>
 | 
				
			||||||
 | 
					#include <surface/components.hpp>
 | 
				
			||||||
 | 
					#include <surface/events/keyboard.hpp>
 | 
				
			||||||
 | 
					#include <surface/events/mouse.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace lt::input {
 | 
					namespace lt::input {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template<class... Ts>
 | 
					class System: public app::ISystem
 | 
				
			||||||
struct overloads: Ts...
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	using Ts::operator()...;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @note If this system is attached, it will always consume the input events f rom surface.
 | 
					 | 
				
			||||||
 * Therefore if you want any input detection mechanism, callbacks should be setup with this
 | 
					 | 
				
			||||||
 * system and not directly with surface.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class System
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	System(lt::surface::System &surface_system)
 | 
						System(Ref<ecs::Registry> registry);
 | 
				
			||||||
	{
 | 
					
 | 
				
			||||||
		surface_system.add_event_listener([this](auto &&event) {
 | 
						auto tick() -> bool override;
 | 
				
			||||||
			return handle_event(std::forward<decltype(event)>(event));
 | 
					
 | 
				
			||||||
		});
 | 
						void on_register() override;
 | 
				
			||||||
	};
 | 
					
 | 
				
			||||||
 | 
						void on_unregister() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	auto handle_event(const lt::surface::System::Event &event) -> bool
 | 
						void handle_event(const surface::SurfaceComponent::Event &event);
 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		const auto visitor = overloads {
 | 
					 | 
				
			||||||
			[this](const lt::surface::KeyPressedEvent &event) {
 | 
					 | 
				
			||||||
			    m_keys[event.get_key()] = true;
 | 
					 | 
				
			||||||
			    return true;
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			[](const lt::surface::KeyRepeatEvent &) { return false; },
 | 
						void on_surface_lost_focus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			[](const lt::surface::KeyReleasedEvent &) { return false; },
 | 
						void on_key_press(const lt::surface::KeyPressedEvent &event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			[](const lt::surface::KeySetCharEvent &) { return false; },
 | 
						void on_key_release(const lt::surface::KeyReleasedEvent &event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			[](const lt::surface::MouseMovedEvent &) { return false; },
 | 
						void on_pointer_move(const lt::surface::MouseMovedEvent &event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			[](const lt::surface::WheelScrolledEvent &) { return false; },
 | 
						void on_button_press(const lt::surface::ButtonPressedEvent &event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			[](const lt::surface::ButtonPressedEvent &) { return false; },
 | 
						void on_button_release(const lt::surface::ButtonReleasedEvent &event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			[](const lt::surface::ButtonReleasedEvent &) { return false; },
 | 
						Ref<ecs::Registry> m_registry;
 | 
				
			||||||
 | 
					 | 
				
			||||||
			[](const auto &) { return false; },
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return std::visit(visitor, event);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	void setup_callbacks(GLFWwindow *handle);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::array<bool, 512> m_keys {};
 | 
						std::array<bool, 512> m_keys {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						std::array<bool, 512> m_buttons {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						math::vec2 m_pointer_position;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue