diff --git a/CMakeLists.txt b/CMakeLists.txt index c57ccb5a..b7af96db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,10 @@ project(enduro2d) # build mode # +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif(NOT CMAKE_BUILD_TYPE) + foreach(flags CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG) set(${flags} "${${flags}} -D_DEBUG -DNRELEASE") diff --git a/ROADMAP.md b/ROADMAP.md index 343dc1f7..82cda0dd 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -42,7 +42,7 @@ ``` levels, sinks ``` - - [ ] `basic input system` + - [x] `basic input system` ``` events, listeners, polling devices: mouse, keyboard diff --git a/headers/enduro2d/core/_all.hpp b/headers/enduro2d/core/_all.hpp index 18716f01..fcc494db 100644 --- a/headers/enduro2d/core/_all.hpp +++ b/headers/enduro2d/core/_all.hpp @@ -9,5 +9,6 @@ #include "_core.hpp" #include "debug.hpp" +#include "input.hpp" #include "vfs.hpp" #include "window.hpp" diff --git a/headers/enduro2d/core/_core.hpp b/headers/enduro2d/core/_core.hpp index 36e783da..3267910c 100644 --- a/headers/enduro2d/core/_core.hpp +++ b/headers/enduro2d/core/_core.hpp @@ -13,6 +13,9 @@ namespace e2d { class debug; + class mouse; + class keyboard; + class input; class vfs; class window; } @@ -24,3 +27,71 @@ namespace e2d return modules::instance(); } } + +namespace e2d +{ + enum class mouse_button : u8 { + left, + right, + middle, + x1, + x2, + unknown + }; + + enum class keyboard_key : u16 { + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, + + a, b, c, d, e, f, g, h, i, j, k, l, m, + n, o, p, q, r, s, t, u, v, w, x, y, z, + + f1, f2, f3, f4, f5, + f6, f7, f8, f9, f10, + f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, + f21, f22, f23, f24, f25, + + minus, equal, backspace, section_sign, grave_accent, + + lbracket, rbracket, semicolon, apostrophe, backslash, + + comma, period, slash, + + escape, tab, caps_lock, space, enter, + + lshift, rshift, lcontrol, rcontrol, + lalt, ralt, lsuper, rsuper, menu, + + print_screen, scroll_lock, pause, + + insert, del, home, end, page_up, page_down, + + left, up, right, down, + + kp_0, kp_1, kp_2, kp_3, kp_4, + kp_5, kp_6, kp_7, kp_8, kp_9, + + kp_num_lock, kp_divide, kp_multiply, kp_subtract, + kp_add, kp_enter, kp_equal, kp_decimal, + + unknown + }; + + enum class mouse_button_action : u8 { + press, + release, + unknown + }; + + enum class keyboard_key_action : u8 { + press, + repeat, + release, + unknown + }; + + const char* mouse_button_to_cstr(mouse_button btn) noexcept; + const char* keyboard_key_to_cstr(keyboard_key key) noexcept; + const char* mouse_button_action_to_cstr(mouse_button_action action) noexcept; + const char* keyboard_key_action_to_cstr(keyboard_key_action action) noexcept; +} diff --git a/headers/enduro2d/core/input.hpp b/headers/enduro2d/core/input.hpp new file mode 100644 index 00000000..7c92ce29 --- /dev/null +++ b/headers/enduro2d/core/input.hpp @@ -0,0 +1,126 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#pragma once + +#include "_core.hpp" +#include "window.hpp" + +namespace e2d +{ + class mouse final : private noncopyable { + public: + mouse(); + ~mouse() noexcept; + + v2f cursor_pos() const noexcept; + v2f scroll_delta() const noexcept; + + bool is_any_button_pressed() const noexcept; + bool is_any_button_just_pressed() const noexcept; + bool is_any_button_just_released() const noexcept; + + bool is_button_pressed(mouse_button btn) const noexcept; + bool is_button_just_pressed(mouse_button btn) const noexcept; + bool is_button_just_released(mouse_button btn) const noexcept; + + void extract_pressed_buttons(std::vector& dst) const; + void extract_just_pressed_buttons(std::vector& dst) const; + void extract_just_released_buttons(std::vector& dst) const; + private: + class state; + friend class input; + std::unique_ptr state_; + }; + + class keyboard final : private noncopyable { + public: + keyboard(); + ~keyboard() noexcept; + + str32 input_text() const; + void extract_input_text(str32& dst) const; + + bool is_any_key_pressed() const noexcept; + bool is_any_key_just_pressed() const noexcept; + bool is_any_key_just_released() const noexcept; + + bool is_key_pressed(keyboard_key key) const noexcept; + bool is_key_just_pressed(keyboard_key key) const noexcept; + bool is_key_just_released(keyboard_key key) const noexcept; + + void extract_pressed_keys(std::vector& dst) const; + void extract_just_pressed_keys(std::vector& dst) const; + void extract_just_released_keys(std::vector& dst) const; + private: + class state; + friend class input; + std::unique_ptr state_; + }; + + class input final : public module { + public: + struct input_char_event { + char32_t uchar = 0; + input_char_event(char32_t uchar) + : uchar(uchar) {} + }; + struct move_cursor_event { + v2f pos = v2f::zero(); + move_cursor_event(const v2f& pos) + : pos(pos) {} + }; + struct mouse_scroll_event { + v2f delta = v2f::zero(); + mouse_scroll_event(const v2f& delta) + : delta(delta) {} + }; + struct mouse_button_event { + mouse_button button = mouse_button::unknown; + mouse_button_action action = mouse_button_action::unknown; + mouse_button_event(mouse_button btn, mouse_button_action act) + : button(btn) + , action(act) {} + + }; + struct keyboard_key_event { + keyboard_key key = keyboard_key::unknown; + keyboard_key_action action = keyboard_key_action::unknown; + keyboard_key_event(keyboard_key key, keyboard_key_action act) + : key(key) + , action(act) {} + }; + public: + input(); + ~input() noexcept; + + const class mouse& mouse() const noexcept; + const class keyboard& keyboard() const noexcept; + + void post_event(input_char_event evt) noexcept; + void post_event(move_cursor_event evt) noexcept; + void post_event(mouse_scroll_event evt) noexcept; + void post_event(mouse_button_event evt) noexcept; + void post_event(keyboard_key_event evt) noexcept; + + void frame_tick() noexcept; + private: + class state; + std::unique_ptr state_; + }; + + class window_input_source : public window::event_listener { + public: + window_input_source(input& input) noexcept; + void on_input_char(char32_t uchar) noexcept final; + void on_move_cursor(const v2f& pos) noexcept final; + void on_mouse_scroll(const v2f& delta) noexcept final; + void on_mouse_button(mouse_button btn, mouse_button_action act) noexcept final; + void on_keyboard_key(keyboard_key key, u32 scancode, keyboard_key_action act) noexcept final; + private: + input& input_; + }; +} diff --git a/headers/enduro2d/core/vfs.hpp b/headers/enduro2d/core/vfs.hpp index a6e4c4a3..695a0502 100644 --- a/headers/enduro2d/core/vfs.hpp +++ b/headers/enduro2d/core/vfs.hpp @@ -35,10 +35,10 @@ namespace e2d template < typename T, typename... Args > bool register_scheme(str_view scheme, Args&&... args); bool register_scheme(str_view scheme, file_source_uptr source); - bool unregister_scheme(str_view scheme) noexcept; + bool unregister_scheme(str_view scheme); bool register_scheme_alias(str_view scheme, url alias); - bool unregister_scheme_alias(str_view scheme) noexcept; + bool unregister_scheme_alias(str_view scheme); bool exists(const url& url) const; input_stream_uptr open(const url& url) const; diff --git a/headers/enduro2d/core/window.hpp b/headers/enduro2d/core/window.hpp index 554677e2..a9c00b19 100644 --- a/headers/enduro2d/core/window.hpp +++ b/headers/enduro2d/core/window.hpp @@ -18,6 +18,20 @@ namespace e2d }; class window final : public module { + public: + class event_listener : private e2d::noncopyable { + public: + virtual ~event_listener() noexcept = default; + virtual void on_input_char(char32_t uchar) noexcept; + virtual void on_move_cursor(const v2f& pos) noexcept; + virtual void on_mouse_scroll(const v2f& delta) noexcept; + virtual void on_mouse_button(mouse_button btn, mouse_button_action act) noexcept; + virtual void on_keyboard_key(keyboard_key key, u32 scancode, keyboard_key_action act) noexcept; + virtual void on_window_close() noexcept; + virtual void on_window_focus(bool focused) noexcept; + virtual void on_window_minimize(bool minimized) noexcept; + }; + using event_listener_uptr = std::unique_ptr; public: window(const v2u& size, str_view title, bool vsync, bool fullscreen); ~window() noexcept; @@ -37,6 +51,10 @@ namespace e2d bool toggle_vsync(bool yesno) noexcept; bool toggle_fullscreen(bool yesno) noexcept; + void hide_cursor() noexcept; + void show_cursor() noexcept; + bool is_cursor_hidden() const noexcept; + v2u real_size() const noexcept; v2u virtual_size() const noexcept; v2u framebuffer_size() const noexcept; @@ -48,9 +66,38 @@ namespace e2d void set_should_close(bool yesno) noexcept; void swap_buffers() noexcept; - static bool poll_events() noexcept; + static bool frame_tick() noexcept; + + template < typename T, typename... Args > + T& register_event_listener(Args&&... args); + event_listener& register_event_listener(event_listener_uptr listener); + void unregister_event_listener(const event_listener& listener) noexcept; private: class state; std::unique_ptr state_; }; + + class window_trace_event_listener final : public window::event_listener { + public: + window_trace_event_listener(debug& debug) noexcept; + void on_input_char(char32_t uchar) noexcept final; + void on_move_cursor(const v2f& pos) noexcept final; + void on_mouse_scroll(const v2f& delta) noexcept final; + void on_mouse_button(mouse_button btn, mouse_button_action act) noexcept final; + void on_keyboard_key(keyboard_key key, u32 scancode, keyboard_key_action act) noexcept final; + void on_window_close() noexcept final; + void on_window_focus(bool focused) noexcept final; + void on_window_minimize(bool minimized) noexcept final; + private: + debug& debug_; + }; +} + +namespace e2d +{ + template < typename T, typename... Args > + T& window::register_event_listener(Args&&... args) { + return static_cast( + register_event_listener(std::make_unique(std::forward(args)...))); + } } diff --git a/samples/sources/sample_00/sample_00.cpp b/samples/sources/sample_00/sample_00.cpp index ed56df53..d4df0407 100644 --- a/samples/sources/sample_00/sample_00.cpp +++ b/samples/sources/sample_00/sample_00.cpp @@ -8,20 +8,23 @@ using namespace e2d; int main() { - modules::initialize() - .add_sink(); - + input& i = modules::initialize(); + debug& d = modules::initialize(); window& w = modules::initialize( - v2u{640, 480}, "Enduro2D", false, false); + v2u{640, 480}, "Enduro2D", true, false); - the() - .trace("SAMPLE: window real size: %0", w.real_size()) - .trace("SAMPLE: window virtual size: %0", w.virtual_size()) - .trace("SAMPLE: window framebuffer size: %0", w.framebuffer_size()); + d.add_sink(); + w.register_event_listener(i); + w.register_event_listener(d); - auto closing_time = time::now_s() + make_seconds(5); - while ( !w.should_close() && time::now_s() < closing_time ) { + d.trace("SAMPLE: window real size: %0", w.real_size()) + .trace("SAMPLE: window virtual size: %0", w.virtual_size()) + .trace("SAMPLE: window framebuffer size: %0", w.framebuffer_size()); + + const keyboard& k = i.keyboard(); + while ( !w.should_close() && !k.is_key_just_released(keyboard_key::escape) ) { + i.frame_tick(); w.swap_buffers(); - window::poll_events(); + window::frame_tick(); } } diff --git a/samples/sources/sample_01/sample_01.cpp b/samples/sources/sample_01/sample_01.cpp index 52030adc..d4df0407 100644 --- a/samples/sources/sample_01/sample_01.cpp +++ b/samples/sources/sample_01/sample_01.cpp @@ -1,7 +1,30 @@ /******************************************************************************* * This file is part of the "Enduro2D" - * For conditions of distribution and use, see copyright notice in enduro2d.hpp + * For conditions of distribution and use, see copyright notice in LICENSE.md * Copyright (C) 2018 Matvey Cherevko ******************************************************************************/ #include "../common.hpp" +using namespace e2d; + +int main() { + input& i = modules::initialize(); + debug& d = modules::initialize(); + window& w = modules::initialize( + v2u{640, 480}, "Enduro2D", true, false); + + d.add_sink(); + w.register_event_listener(i); + w.register_event_listener(d); + + d.trace("SAMPLE: window real size: %0", w.real_size()) + .trace("SAMPLE: window virtual size: %0", w.virtual_size()) + .trace("SAMPLE: window framebuffer size: %0", w.framebuffer_size()); + + const keyboard& k = i.keyboard(); + while ( !w.should_close() && !k.is_key_just_released(keyboard_key::escape) ) { + i.frame_tick(); + w.swap_buffers(); + window::frame_tick(); + } +} diff --git a/sources/enduro2d/core/_core.cpp b/sources/enduro2d/core/_core.cpp new file mode 100644 index 00000000..f7737296 --- /dev/null +++ b/sources/enduro2d/core/_core.cpp @@ -0,0 +1,196 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include + +namespace e2d +{ + const char* mouse_button_to_cstr(mouse_button btn) noexcept { + #define DEFINE_CASE(x) case mouse_button::x: return #x + switch ( btn ) { + DEFINE_CASE(left); + DEFINE_CASE(right); + DEFINE_CASE(middle); + DEFINE_CASE(x1); + DEFINE_CASE(x2); + DEFINE_CASE(unknown); + default: + E2D_ASSERT_MSG(false, "unexpected mouse button"); + return ""; + } + #undef DEFINE_CASE + } + + const char* keyboard_key_to_cstr(keyboard_key key) noexcept { + #define DEFINE_CASE(x) case keyboard_key::x: return #x + switch ( key ) { + DEFINE_CASE(_0); + DEFINE_CASE(_1); + DEFINE_CASE(_2); + DEFINE_CASE(_3); + DEFINE_CASE(_4); + DEFINE_CASE(_5); + DEFINE_CASE(_6); + DEFINE_CASE(_7); + DEFINE_CASE(_8); + DEFINE_CASE(_9); + + DEFINE_CASE(a); + DEFINE_CASE(b); + DEFINE_CASE(c); + DEFINE_CASE(d); + DEFINE_CASE(e); + DEFINE_CASE(f); + DEFINE_CASE(g); + DEFINE_CASE(h); + DEFINE_CASE(i); + DEFINE_CASE(j); + DEFINE_CASE(k); + DEFINE_CASE(l); + DEFINE_CASE(m); + DEFINE_CASE(n); + DEFINE_CASE(o); + DEFINE_CASE(p); + DEFINE_CASE(q); + DEFINE_CASE(r); + DEFINE_CASE(s); + DEFINE_CASE(t); + DEFINE_CASE(u); + DEFINE_CASE(v); + DEFINE_CASE(w); + DEFINE_CASE(x); + DEFINE_CASE(y); + DEFINE_CASE(z); + + DEFINE_CASE(f1); + DEFINE_CASE(f2); + DEFINE_CASE(f3); + DEFINE_CASE(f4); + DEFINE_CASE(f5); + DEFINE_CASE(f6); + DEFINE_CASE(f7); + DEFINE_CASE(f8); + DEFINE_CASE(f9); + DEFINE_CASE(f10); + DEFINE_CASE(f11); + DEFINE_CASE(f12); + DEFINE_CASE(f13); + DEFINE_CASE(f14); + DEFINE_CASE(f15); + DEFINE_CASE(f16); + DEFINE_CASE(f17); + DEFINE_CASE(f18); + DEFINE_CASE(f19); + DEFINE_CASE(f20); + DEFINE_CASE(f21); + DEFINE_CASE(f22); + DEFINE_CASE(f23); + DEFINE_CASE(f24); + DEFINE_CASE(f25); + + DEFINE_CASE(minus); + DEFINE_CASE(equal); + DEFINE_CASE(backspace); + DEFINE_CASE(section_sign); + DEFINE_CASE(grave_accent); + + DEFINE_CASE(lbracket); + DEFINE_CASE(rbracket); + DEFINE_CASE(semicolon); + DEFINE_CASE(apostrophe); + DEFINE_CASE(backslash); + + DEFINE_CASE(comma); + DEFINE_CASE(period); + DEFINE_CASE(slash); + + DEFINE_CASE(escape); + DEFINE_CASE(tab); + DEFINE_CASE(caps_lock); + DEFINE_CASE(space); + DEFINE_CASE(enter); + + DEFINE_CASE(lshift); + DEFINE_CASE(rshift); + DEFINE_CASE(lcontrol); + DEFINE_CASE(rcontrol); + DEFINE_CASE(lalt); + DEFINE_CASE(ralt); + DEFINE_CASE(lsuper); + DEFINE_CASE(rsuper); + DEFINE_CASE(menu); + + DEFINE_CASE(print_screen); + DEFINE_CASE(scroll_lock); + DEFINE_CASE(pause); + + DEFINE_CASE(insert); + DEFINE_CASE(del); + DEFINE_CASE(home); + DEFINE_CASE(end); + DEFINE_CASE(page_up); + DEFINE_CASE(page_down); + + DEFINE_CASE(left); + DEFINE_CASE(up); + DEFINE_CASE(right); + DEFINE_CASE(down); + + DEFINE_CASE(kp_0); + DEFINE_CASE(kp_1); + DEFINE_CASE(kp_2); + DEFINE_CASE(kp_3); + DEFINE_CASE(kp_4); + DEFINE_CASE(kp_5); + DEFINE_CASE(kp_6); + DEFINE_CASE(kp_7); + DEFINE_CASE(kp_8); + DEFINE_CASE(kp_9); + + DEFINE_CASE(kp_num_lock); + DEFINE_CASE(kp_divide); + DEFINE_CASE(kp_multiply); + DEFINE_CASE(kp_subtract); + DEFINE_CASE(kp_add); + DEFINE_CASE(kp_enter); + DEFINE_CASE(kp_equal); + DEFINE_CASE(kp_decimal); + + DEFINE_CASE(unknown); + default: + E2D_ASSERT_MSG(false, "unexpected keyboard key"); + return ""; + } + #undef DEFINE_CASE + } + + const char* mouse_button_action_to_cstr(mouse_button_action action) noexcept { + #define DEFINE_CASE(x) case mouse_button_action::x: return #x + switch ( action ) { + DEFINE_CASE(press); + DEFINE_CASE(release); + DEFINE_CASE(unknown); + default: + E2D_ASSERT_MSG(false, "unexpected mouse button action"); + return ""; + } + #undef DEFINE_CASE + } + + const char* keyboard_key_action_to_cstr(keyboard_key_action action) noexcept { + #define DEFINE_CASE(x) case keyboard_key_action::x: return #x + switch ( action ) { + DEFINE_CASE(press); + DEFINE_CASE(repeat); + DEFINE_CASE(release); + DEFINE_CASE(unknown); + default: + E2D_ASSERT_MSG(false, "unexpected keyboard key action"); + return ""; + } + #undef DEFINE_CASE + } +} diff --git a/sources/enduro2d/core/input.cpp b/sources/enduro2d/core/input.cpp new file mode 100644 index 00000000..5a9cd4ad --- /dev/null +++ b/sources/enduro2d/core/input.cpp @@ -0,0 +1,446 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include + +namespace +{ + using namespace e2d; + + enum class button_state : u8 { + pressed, + released, + just_pressed, + just_released + }; + + void update_button_state(button_state& state) noexcept { + if ( state == button_state::just_pressed ) { + state = button_state::pressed; + } else if ( state == button_state::just_released ) { + state = button_state::released; + } + } + + template < typename E > + constexpr std::underlying_type_t enum_to_number(E e) noexcept { + return static_cast>(e); + } +} + +namespace e2d +{ + // + // class mouse::state + // + + class mouse::state final : private e2d::noncopyable { + public: + mutable std::mutex mutex; + v2f cursor_pos; + v2f scroll_delta; + std::array button_states; + char _pad[2]; + public: + state() noexcept { + std::fill( + button_states.begin(), + button_states.end(), + button_state::released); + } + + std::size_t button_index(mouse_button btn) const noexcept { + const auto index = enum_to_number(btn); + E2D_ASSERT(index < button_states.size()); + return math::numeric_cast(index); + } + + void frame_tick() noexcept { + std::lock_guard guard(mutex); + scroll_delta = v2f::zero(); + std::for_each( + button_states.begin(), + button_states.end(), + &update_button_state); + } + + void post_event(input::move_cursor_event evt) noexcept { + std::lock_guard guard(mutex); + cursor_pos = evt.pos; + } + + void post_event(input::mouse_scroll_event evt) noexcept { + std::lock_guard guard(mutex); + scroll_delta += evt.delta; + } + + void post_event(input::mouse_button_event evt) noexcept { + std::lock_guard guard(mutex); + const std::size_t index = button_index(evt.button); + const button_state ms = button_states[index]; + switch ( evt.action ) { + case mouse_button_action::press: + if ( ms == button_state::released || ms == button_state::just_released ) { + button_states[index] = button_state::just_pressed; + } + break; + case mouse_button_action::release: + if ( ms == button_state::just_pressed || ms == button_state::pressed ) { + button_states[index] = button_state::just_released; + } + break; + case mouse_button_action::unknown: + break; + default: + E2D_ASSERT_MSG(false, "unexpected mouse action"); + break; + } + } + }; + + // + // class keyboard::state + // + + class keyboard::state final : private e2d::noncopyable { + public: + mutable std::mutex mutex; + str32 input_text; + std::array key_states; + public: + state() noexcept { + std::fill( + key_states.begin(), + key_states.end(), + button_state::released); + } + + std::size_t key_index(keyboard_key key) const noexcept { + const auto index = enum_to_number(key); + E2D_ASSERT(index < key_states.size()); + return math::numeric_cast(index); + } + + void frame_tick() noexcept { + std::lock_guard guard(mutex); + input_text.clear(); + std::for_each( + key_states.begin(), + key_states.end(), + &update_button_state); + } + + void post_event(input::input_char_event evt) noexcept { + std::lock_guard guard(mutex); + input_text += evt.uchar; + } + + void post_event(input::keyboard_key_event evt) noexcept { + std::lock_guard guard(mutex); + const std::size_t index = key_index(evt.key); + const button_state ks = key_states[index]; + switch ( evt.action ) { + case keyboard_key_action::press: + case keyboard_key_action::repeat: + if ( ks == button_state::released || ks == button_state::just_released ) { + key_states[index] = button_state::just_pressed; + } + break; + case keyboard_key_action::release: + if ( ks == button_state::pressed || ks == button_state::just_pressed ) { + key_states[index] = button_state::just_released; + } + break; + case keyboard_key_action::unknown: + break; + default: + E2D_ASSERT_MSG(false, "unexpected key action"); + break; + } + } + }; + + // + // class mouse + // + + mouse::mouse() + : state_(new state()) {} + mouse::~mouse() noexcept = default; + + v2f mouse::cursor_pos() const noexcept { + std::lock_guard guard(state_->mutex); + return state_->cursor_pos; + } + + v2f mouse::scroll_delta() const noexcept { + std::lock_guard guard(state_->mutex); + return state_->scroll_delta; + } + + bool mouse::is_any_button_pressed() const noexcept { + std::lock_guard guard(state_->mutex); + return std::any_of( + state_->button_states.cbegin(), + state_->button_states.cend(), + [](button_state s) noexcept { + return s == button_state::just_pressed + || s == button_state::pressed; + }); + } + + bool mouse::is_any_button_just_pressed() const noexcept { + std::lock_guard guard(state_->mutex); + return std::any_of( + state_->button_states.cbegin(), + state_->button_states.cend(), + [](button_state s) noexcept { + return s == button_state::just_pressed; + }); + } + + bool mouse::is_any_button_just_released() const noexcept { + std::lock_guard guard(state_->mutex); + return std::any_of( + state_->button_states.cbegin(), + state_->button_states.cend(), + [](button_state s) noexcept { + return s == button_state::just_released; + }); + } + + bool mouse::is_button_pressed(mouse_button btn) const noexcept { + std::lock_guard guard(state_->mutex); + const std::size_t index = state_->button_index(btn); + const button_state ms = state_->button_states[index]; + return ms == button_state::just_pressed + || ms == button_state::pressed; + } + + bool mouse::is_button_just_pressed(mouse_button btn) const noexcept { + std::lock_guard guard(state_->mutex); + const std::size_t index = state_->button_index(btn); + const button_state ms = state_->button_states[index]; + return ms == button_state::just_pressed; + } + + bool mouse::is_button_just_released(mouse_button btn) const noexcept { + std::lock_guard guard(state_->mutex); + const std::size_t index = state_->button_index(btn); + const button_state ms = state_->button_states[index]; + return ms == button_state::just_released; + } + + void mouse::extract_pressed_buttons(std::vector& dst) const { + std::lock_guard guard(state_->mutex); + for ( std::size_t i = 0; i < state_->button_states.size(); ++i ) { + button_state ks = state_->button_states[i]; + if ( ks == button_state::just_pressed || ks == button_state::pressed ) { + dst.push_back(static_cast(i)); + } + } + } + + void mouse::extract_just_pressed_buttons(std::vector& dst) const { + std::lock_guard guard(state_->mutex); + for ( std::size_t i = 0; i < state_->button_states.size(); ++i ) { + button_state ks = state_->button_states[i]; + if ( ks == button_state::just_pressed ) { + dst.push_back(static_cast(i)); + } + } + } + + void mouse::extract_just_released_buttons(std::vector& dst) const { + std::lock_guard guard(state_->mutex); + for ( std::size_t i = 0; i < state_->button_states.size(); ++i ) { + button_state ks = state_->button_states[i]; + if ( ks == button_state::just_released ) { + dst.push_back(static_cast(i)); + } + } + } + + // + // class keyboard + // + + keyboard::keyboard() + : state_(new state()) {} + keyboard::~keyboard() noexcept = default; + + str32 keyboard::input_text() const { + std::lock_guard guard(state_->mutex); + return state_->input_text; + } + + void keyboard::extract_input_text(str32& dst) const { + std::lock_guard guard(state_->mutex); + dst = state_->input_text; + } + + bool keyboard::is_any_key_pressed() const noexcept { + std::lock_guard guard(state_->mutex); + return std::any_of( + state_->key_states.cbegin(), + state_->key_states.cend(), + [](button_state s) noexcept { + return s == button_state::just_pressed + || s == button_state::pressed; + }); + } + + bool keyboard::is_any_key_just_pressed() const noexcept { + std::lock_guard guard(state_->mutex); + return std::any_of( + state_->key_states.cbegin(), + state_->key_states.cend(), + [](button_state s) noexcept { + return s == button_state::just_pressed; + }); + } + + bool keyboard::is_any_key_just_released() const noexcept { + std::lock_guard guard(state_->mutex); + return std::any_of( + state_->key_states.cbegin(), + state_->key_states.cend(), + [](button_state s) noexcept { + return s == button_state::just_released; + }); + } + + bool keyboard::is_key_pressed(keyboard_key key) const noexcept { + std::lock_guard guard(state_->mutex); + const std::size_t index = state_->key_index(key); + const button_state ks = state_->key_states[index]; + return ks == button_state::just_pressed + || ks == button_state::pressed; + } + + bool keyboard::is_key_just_pressed(keyboard_key key) const noexcept { + std::lock_guard guard(state_->mutex); + const std::size_t index = state_->key_index(key); + const button_state ks = state_->key_states[index]; + return ks == button_state::just_pressed; + } + + bool keyboard::is_key_just_released(keyboard_key key) const noexcept { + std::lock_guard guard(state_->mutex); + const std::size_t index = state_->key_index(key); + const button_state ks = state_->key_states[index]; + return ks == button_state::just_released; + } + + void keyboard::extract_pressed_keys(std::vector& dst) const { + std::lock_guard guard(state_->mutex); + for ( std::size_t i = 0; i < state_->key_states.size(); ++i ) { + button_state ks = state_->key_states[i]; + if ( ks == button_state::just_pressed || ks == button_state::pressed ) { + dst.push_back(static_cast(i)); + } + } + } + + void keyboard::extract_just_pressed_keys(std::vector& dst) const { + std::lock_guard guard(state_->mutex); + for ( std::size_t i = 0; i < state_->key_states.size(); ++i ) { + button_state ks = state_->key_states[i]; + if ( ks == button_state::just_pressed ) { + dst.push_back(static_cast(i)); + } + } + } + + void keyboard::extract_just_released_keys(std::vector& dst) const { + std::lock_guard guard(state_->mutex); + for ( std::size_t i = 0; i < state_->key_states.size(); ++i ) { + button_state ks = state_->key_states[i]; + if ( ks == button_state::just_released ) { + dst.push_back(static_cast(i)); + } + } + } + + // + // class input::state + // + + class input::state final : private e2d::noncopyable { + public: + class mouse mouse; + class keyboard keyboard; + }; + + // + // class input + // + + input::input() + : state_(new state()) {} + input::~input() noexcept = default; + + const class mouse& input::mouse() const noexcept { + return state_->mouse; + } + + const class keyboard& input::keyboard() const noexcept { + return state_->keyboard; + } + + void input::post_event(input_char_event evt) noexcept { + state_->keyboard.state_->post_event(evt); + } + + void input::post_event(move_cursor_event evt) noexcept { + state_->mouse.state_->post_event(evt); + } + + void input::post_event(mouse_scroll_event evt) noexcept { + state_->mouse.state_->post_event(evt); + } + + void input::post_event(mouse_button_event evt) noexcept { + state_->mouse.state_->post_event(evt); + } + + void input::post_event(keyboard_key_event evt) noexcept { + state_->keyboard.state_->post_event(evt); + } + + void input::frame_tick() noexcept { + state_->mouse.state_->frame_tick(); + state_->keyboard.state_->frame_tick(); + } + + // + // class window_input_source + // + + window_input_source::window_input_source(input& input) noexcept + : input_(input) {} + + void window_input_source::on_input_char(char32_t uchar) noexcept { + input_.post_event(input::input_char_event{uchar}); + } + + void window_input_source::on_move_cursor(const v2f& pos) noexcept { + input_.post_event(input::move_cursor_event{pos}); + } + + void window_input_source::on_mouse_scroll(const v2f& delta) noexcept { + input_.post_event(input::mouse_scroll_event{delta}); + } + + void window_input_source::on_mouse_button(mouse_button btn, mouse_button_action act) noexcept { + input_.post_event(input::mouse_button_event{btn, act}); + } + + void window_input_source::on_keyboard_key(keyboard_key key, u32 scancode, keyboard_key_action act) noexcept { + E2D_UNUSED(scancode); + input_.post_event(input::keyboard_key_event{key, act}); + } +} diff --git a/sources/enduro2d/core/vfs.cpp b/sources/enduro2d/core/vfs.cpp index e7453c1e..a60db560 100644 --- a/sources/enduro2d/core/vfs.cpp +++ b/sources/enduro2d/core/vfs.cpp @@ -116,7 +116,7 @@ namespace e2d : false; } - bool vfs::unregister_scheme(str_view scheme) noexcept { + bool vfs::unregister_scheme(str_view scheme) { std::lock_guard guard(state_->mutex); return state_->schemes.erase(scheme) > 0; } @@ -127,7 +127,7 @@ namespace e2d std::make_pair(scheme, alias)).second; } - bool vfs::unregister_scheme_alias(str_view scheme) noexcept { + bool vfs::unregister_scheme_alias(str_view scheme) { std::lock_guard guard(state_->mutex); return state_->aliases.erase(scheme) > 0; } diff --git a/sources/enduro2d/core/window.cpp b/sources/enduro2d/core/window.cpp index b08ccb2e..f95b49a1 100644 --- a/sources/enduro2d/core/window.cpp +++ b/sources/enduro2d/core/window.cpp @@ -5,3 +5,86 @@ ******************************************************************************/ #include +#include + +namespace e2d +{ + // + // class window::event_listener + // + + void window::event_listener::on_input_char(char32_t uchar) noexcept { + E2D_UNUSED(uchar); + } + + void window::event_listener::on_move_cursor(const v2f& pos) noexcept { + E2D_UNUSED(pos); + } + + void window::event_listener::on_mouse_scroll(const v2f& delta) noexcept { + E2D_UNUSED(delta); + } + + void window::event_listener::on_mouse_button(mouse_button btn, mouse_button_action act) noexcept { + E2D_UNUSED(btn, act); + } + + void window::event_listener::on_keyboard_key(keyboard_key key, u32 scancode, keyboard_key_action act) noexcept { + E2D_UNUSED(key, scancode, act); + } + + void window::event_listener::on_window_close() noexcept { + } + + void window::event_listener::on_window_focus(bool focused) noexcept { + E2D_UNUSED(focused); + } + + void window::event_listener::on_window_minimize(bool minimized) noexcept { + E2D_UNUSED(minimized); + } + + // + // class trace_window_event_listener + // + + window_trace_event_listener::window_trace_event_listener(debug& debug) noexcept + : debug_(debug) {} + + void window_trace_event_listener::on_input_char(char32_t uchar) noexcept { + debug_.trace("WINDOW: on_input_char(uchar: %0)", str32_view(&uchar, 1)); + } + + void window_trace_event_listener::on_move_cursor(const v2f& pos) noexcept { + debug_.trace("WINDOW: on_move_cursor(pos: %0)", pos); + } + + void window_trace_event_listener::on_mouse_scroll(const v2f& delta) noexcept { + debug_.trace("WINDOW: on_scroll(delta: %0)", delta); + } + + void window_trace_event_listener::on_mouse_button(mouse_button btn, mouse_button_action act) noexcept { + debug_.trace("WINDOW: on_mouse_button(btn: %0 act: %1)", + mouse_button_to_cstr(btn), + mouse_button_action_to_cstr(act)); + } + + void window_trace_event_listener::on_keyboard_key(keyboard_key key, u32 scancode, keyboard_key_action act) noexcept { + debug_.trace("WINDOW: on_keyboard_key(key: %0 scancode: %1 act: %2)", + keyboard_key_to_cstr(key), + scancode, + keyboard_key_action_to_cstr(act)); + } + + void window_trace_event_listener::on_window_close() noexcept { + debug_.trace("WINDOW: on_window_close()"); + } + + void window_trace_event_listener::on_window_focus(bool focused) noexcept { + debug_.trace("WINDOW: on_window_focus(focused: %0)", focused); + } + + void window_trace_event_listener::on_window_minimize(bool minimized) noexcept { + debug_.trace("WINDOW: on_window_minimize(minimized: %0)", minimized); + } +} diff --git a/sources/enduro2d/core/window_impl/window_glfw.cpp b/sources/enduro2d/core/window_impl/window_glfw.cpp index 80564ee9..cd5c395d 100644 --- a/sources/enduro2d/core/window_impl/window_glfw.cpp +++ b/sources/enduro2d/core/window_impl/window_glfw.cpp @@ -64,6 +64,183 @@ namespace std::mutex glfw_state::mutex_; std::shared_ptr glfw_state::shared_state_; + + mouse_button convert_glfw_mouse_button(int m) noexcept { + #define DEFINE_CASE(x,y) case GLFW_MOUSE_BUTTON_##x: return mouse_button::y + switch ( m ) { + DEFINE_CASE(LEFT, left); + DEFINE_CASE(RIGHT, right); + DEFINE_CASE(MIDDLE, middle); + DEFINE_CASE(4, x1); + DEFINE_CASE(5, x2); + default: return mouse_button::unknown; + } + #undef DEFINE_CASE + } + + keyboard_key convert_glfw_keyboard_key(int k) noexcept { + #define DEFINE_CASE(x,y) case GLFW_KEY_##x: return keyboard_key::y + switch ( k ) { + DEFINE_CASE(0, _0); + DEFINE_CASE(1, _1); + DEFINE_CASE(2, _2); + DEFINE_CASE(3, _3); + DEFINE_CASE(4, _4); + DEFINE_CASE(5, _5); + DEFINE_CASE(6, _6); + DEFINE_CASE(7, _7); + DEFINE_CASE(8, _8); + DEFINE_CASE(9, _9); + + DEFINE_CASE(A, a); + DEFINE_CASE(B, b); + DEFINE_CASE(C, c); + DEFINE_CASE(D, d); + DEFINE_CASE(E, e); + DEFINE_CASE(F, f); + DEFINE_CASE(G, g); + DEFINE_CASE(H, h); + DEFINE_CASE(I, i); + DEFINE_CASE(J, j); + DEFINE_CASE(K, k); + DEFINE_CASE(L, l); + DEFINE_CASE(M, m); + DEFINE_CASE(N, n); + DEFINE_CASE(O, o); + DEFINE_CASE(P, p); + DEFINE_CASE(Q, q); + DEFINE_CASE(R, r); + DEFINE_CASE(S, s); + DEFINE_CASE(T, t); + DEFINE_CASE(U, u); + DEFINE_CASE(V, v); + DEFINE_CASE(W, w); + DEFINE_CASE(X, x); + DEFINE_CASE(Y, y); + DEFINE_CASE(Z, z); + + DEFINE_CASE(F1, f1); + DEFINE_CASE(F2, f2); + DEFINE_CASE(F3, f3); + DEFINE_CASE(F4, f4); + DEFINE_CASE(F5, f5); + DEFINE_CASE(F6, f6); + DEFINE_CASE(F7, f7); + DEFINE_CASE(F8, f8); + DEFINE_CASE(F9, f9); + DEFINE_CASE(F10, f10); + DEFINE_CASE(F11, f11); + DEFINE_CASE(F12, f12); + DEFINE_CASE(F13, f13); + DEFINE_CASE(F14, f14); + DEFINE_CASE(F15, f15); + DEFINE_CASE(F16, f16); + DEFINE_CASE(F17, f17); + DEFINE_CASE(F18, f18); + DEFINE_CASE(F19, f19); + DEFINE_CASE(F20, f20); + DEFINE_CASE(F21, f21); + DEFINE_CASE(F22, f22); + DEFINE_CASE(F23, f23); + DEFINE_CASE(F24, f24); + DEFINE_CASE(F25, f25); + + DEFINE_CASE(MINUS, minus); + DEFINE_CASE(EQUAL, equal); + DEFINE_CASE(BACKSPACE, backspace); + DEFINE_CASE(WORLD_1, section_sign); + DEFINE_CASE(GRAVE_ACCENT, grave_accent); + + DEFINE_CASE(LEFT_BRACKET, lbracket); + DEFINE_CASE(RIGHT_BRACKET, rbracket); + DEFINE_CASE(SEMICOLON, semicolon); + DEFINE_CASE(APOSTROPHE, apostrophe); + DEFINE_CASE(BACKSLASH, backslash); + + DEFINE_CASE(COMMA, comma); + DEFINE_CASE(PERIOD, period); + DEFINE_CASE(SLASH, slash); + + DEFINE_CASE(ESCAPE, escape); + DEFINE_CASE(TAB, tab); + DEFINE_CASE(CAPS_LOCK, caps_lock); + DEFINE_CASE(SPACE, space); + DEFINE_CASE(ENTER, enter); + + DEFINE_CASE(LEFT_SHIFT, lshift); + DEFINE_CASE(RIGHT_SHIFT, rshift); + DEFINE_CASE(LEFT_CONTROL, lcontrol); + DEFINE_CASE(RIGHT_CONTROL, rcontrol); + DEFINE_CASE(LEFT_ALT, lalt); + DEFINE_CASE(RIGHT_ALT, ralt); + DEFINE_CASE(LEFT_SUPER, lsuper); + DEFINE_CASE(RIGHT_SUPER, rsuper); + DEFINE_CASE(MENU, menu); + + DEFINE_CASE(PRINT_SCREEN, print_screen); + DEFINE_CASE(SCROLL_LOCK, scroll_lock); + DEFINE_CASE(PAUSE, pause); + + DEFINE_CASE(INSERT, insert); + DEFINE_CASE(DELETE, del); + DEFINE_CASE(HOME, home); + DEFINE_CASE(END, end); + DEFINE_CASE(PAGE_UP, page_up); + DEFINE_CASE(PAGE_DOWN, page_down); + + DEFINE_CASE(LEFT, left); + DEFINE_CASE(UP, up); + DEFINE_CASE(RIGHT, right); + DEFINE_CASE(DOWN, down); + + DEFINE_CASE(KP_0, kp_0); + DEFINE_CASE(KP_1, kp_1); + DEFINE_CASE(KP_2, kp_2); + DEFINE_CASE(KP_3, kp_3); + DEFINE_CASE(KP_4, kp_4); + DEFINE_CASE(KP_5, kp_5); + DEFINE_CASE(KP_6, kp_6); + DEFINE_CASE(KP_7, kp_7); + DEFINE_CASE(KP_8, kp_8); + DEFINE_CASE(KP_9, kp_9); + + DEFINE_CASE(NUM_LOCK, kp_num_lock); + DEFINE_CASE(KP_DIVIDE, kp_divide); + DEFINE_CASE(KP_MULTIPLY, kp_multiply); + DEFINE_CASE(KP_SUBTRACT, kp_subtract); + DEFINE_CASE(KP_ADD, kp_add); + DEFINE_CASE(KP_ENTER, kp_enter); + DEFINE_CASE(KP_EQUAL, kp_equal); + DEFINE_CASE(KP_DECIMAL, kp_decimal); + + default: return keyboard_key::unknown; + } + #undef DEFINE_CASE + } + + mouse_button_action convert_glfw_mouse_button_action(int ma) noexcept { + switch ( ma ) { + case GLFW_PRESS: + return mouse_button_action::press; + case GLFW_RELEASE: + return mouse_button_action::release; + default: + return mouse_button_action::unknown; + } + } + + keyboard_key_action convert_glfw_keyboard_key_action(int ka) noexcept { + switch ( ka ) { + case GLFW_PRESS: + return keyboard_key_action::press; + case GLFW_REPEAT: + return keyboard_key_action::repeat; + case GLFW_RELEASE: + return keyboard_key_action::release; + default: + return keyboard_key_action::unknown; + } + } } namespace e2d @@ -72,13 +249,18 @@ namespace e2d public: using window_uptr = std::unique_ptr< GLFWwindow, void(*)(GLFWwindow*)>; + using listeners_t = std::vector; + public: + listeners_t listeners; + std::recursive_mutex rmutex; glfw_state_ptr shared_state; window_uptr window; v2u virtual_size; str title; bool vsync = false; bool fullscreen = false; - std::mutex mutex; + bool cursor_hidden = false; + char _pad[5]; public: state(const v2u& size, str_view title, bool vsync, bool fullscreen) : shared_state(glfw_state::get_shared_state()) @@ -87,16 +269,44 @@ namespace e2d , title(title) , vsync(vsync) , fullscreen(fullscreen) + , cursor_hidden(false) { if ( !window ) { throw bad_window_operation(); } + glfwSetWindowUserPointer(window.get(), this); + glfwSetCharCallback(window.get(), input_char_callback_); + glfwSetCursorPosCallback(window.get(), move_cursor_callback_); + glfwSetScrollCallback(window.get(), mouse_scroll_callback_); + glfwSetMouseButtonCallback(window.get(), mouse_button_callback_); + glfwSetKeyCallback(window.get(), keyboard_key_callback_); + glfwSetWindowCloseCallback(window.get(), window_close_callback_); + glfwSetWindowFocusCallback(window.get(), window_focus_callback_); + glfwSetWindowIconifyCallback(window.get(), window_minimize_callback_); } ~state() noexcept { // reset window before shared state window.reset(); } + + template < typename F, typename... Args > + void for_all_listeners(const F& f, const Args&... args) noexcept { + std::lock_guard guard(rmutex); + for ( const event_listener_uptr& listener : listeners ) { + if ( listener ) { + stdex::invoke(f, *listener.get(), args...); + } + } + } + + event_listener& on_new_event_listener(event_listener& listener) noexcept { + double cursor_x = 0.0, cursor_y = 0.0; + E2D_ASSERT(window); + glfwGetCursorPos(window.get(), &cursor_x, &cursor_y); + listener.on_move_cursor(make_vec2(cursor_x, cursor_y).cast_to()); + return listener; + } private: static window_uptr open_window_( const v2u& virtual_size, const str& title, bool vsync, bool fullscreen) noexcept @@ -129,66 +339,147 @@ namespace e2d } return {w, glfwDestroyWindow}; } + + static void input_char_callback_(GLFWwindow* window, u32 uchar) noexcept { + state* self = static_cast(glfwGetWindowUserPointer(window)); + if ( self ) { + self->for_all_listeners( + &event_listener::on_input_char, + static_cast(uchar)); + } + } + + static void move_cursor_callback_(GLFWwindow* window, double pos_x, double pos_y) noexcept { + state* self = static_cast(glfwGetWindowUserPointer(window)); + if ( self ) { + self->for_all_listeners( + &event_listener::on_move_cursor, + make_vec2(pos_x, pos_y).cast_to()); + } + } + + static void mouse_scroll_callback_(GLFWwindow* window, double delta_x, double delta_y) noexcept { + state* self = static_cast(glfwGetWindowUserPointer(window)); + if ( self ) { + self->for_all_listeners( + &event_listener::on_mouse_scroll, + make_vec2(delta_x, delta_y).cast_to()); + } + } + + static void mouse_button_callback_(GLFWwindow* window, int button, int action, int mods) noexcept { + E2D_UNUSED(mods); + state* self = static_cast(glfwGetWindowUserPointer(window)); + if ( self ) { + self->for_all_listeners( + &event_listener::on_mouse_button, + convert_glfw_mouse_button(button), + convert_glfw_mouse_button_action(action)); + } + } + + static void keyboard_key_callback_(GLFWwindow* window, int key, int scancode, int action, int mods) noexcept { + E2D_UNUSED(mods); + state* self = static_cast(glfwGetWindowUserPointer(window)); + if ( self ) { + self->for_all_listeners( + &event_listener::on_keyboard_key, + convert_glfw_keyboard_key(key), + math::numeric_cast(scancode), + convert_glfw_keyboard_key_action(action)); + } + } + + static void window_close_callback_(GLFWwindow* window) noexcept { + state* self = static_cast(glfwGetWindowUserPointer(window)); + if ( self ) { + self->for_all_listeners( + &event_listener::on_window_close); + } + } + + static void window_focus_callback_(GLFWwindow* window, int focused) noexcept { + state* self = static_cast(glfwGetWindowUserPointer(window)); + if ( self ) { + self->for_all_listeners( + &event_listener::on_window_focus, + focused); + } + } + + static void window_minimize_callback_(GLFWwindow* window, int minimized) noexcept { + state* self = static_cast(glfwGetWindowUserPointer(window)); + if ( self ) { + self->for_all_listeners( + &event_listener::on_window_minimize, + minimized); + } + } }; + // + // class window + // + window::window(const v2u& size, str_view title, bool vsync, bool fullscreen) : state_(new state(size, title, vsync, fullscreen)) {} window::~window() noexcept = default; void window::hide() noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); glfwHideWindow(state_->window.get()); } void window::show() noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); glfwShowWindow(state_->window.get()); } void window::restore() noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); glfwRestoreWindow(state_->window.get()); } void window::minimize() noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); glfwIconifyWindow(state_->window.get()); } bool window::visible() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); - return glfwGetWindowAttrib(state_->window.get(), GLFW_VISIBLE); + return GLFW_TRUE == glfwGetWindowAttrib(state_->window.get(), GLFW_VISIBLE); } bool window::focused() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); - return glfwGetWindowAttrib(state_->window.get(), GLFW_FOCUSED); + return GLFW_TRUE == glfwGetWindowAttrib(state_->window.get(), GLFW_FOCUSED); } bool window::minimized() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); - return glfwGetWindowAttrib(state_->window.get(), GLFW_ICONIFIED); + return GLFW_TRUE == glfwGetWindowAttrib(state_->window.get(), GLFW_ICONIFIED); } bool window::vsync() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->vsync; } bool window::fullscreen() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->fullscreen; } bool window::toggle_vsync(bool yesno) noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); + E2D_ASSERT(state_->window); glfwMakeContextCurrent(state_->window.get()); glfwSwapInterval(yesno ? 1 : 0); state_->vsync = yesno; @@ -196,7 +487,7 @@ namespace e2d } bool window::toggle_fullscreen(bool yesno) noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); if ( state_->fullscreen == yesno ) { return true; } @@ -211,6 +502,7 @@ namespace e2d v2i real_size = yesno ? make_vec2(video_mode->width, video_mode->height) : state_->virtual_size.cast_to(); + E2D_ASSERT(state_->window); glfwSetWindowMonitor( state_->window.get(), yesno ? monitor : nullptr, @@ -223,62 +515,97 @@ namespace e2d return true; } - v2u window::real_size() const noexcept { - std::lock_guard guard(state_->mutex); + void window::hide_cursor() noexcept { + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); + glfwSetInputMode(state_->window.get(), GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + state_->cursor_hidden = true; + } + + void window::show_cursor() noexcept { + std::lock_guard guard(state_->rmutex); + E2D_ASSERT(state_->window); + glfwSetInputMode(state_->window.get(), GLFW_CURSOR, GLFW_CURSOR_NORMAL); + state_->cursor_hidden = false; + } + + bool window::is_cursor_hidden() const noexcept { + std::lock_guard guard(state_->rmutex); + return state_->cursor_hidden; + } + + v2u window::real_size() const noexcept { + std::lock_guard guard(state_->rmutex); int w = 0, h = 0; + E2D_ASSERT(state_->window); glfwGetWindowSize(state_->window.get(), &w, &h); return make_vec2(w,h).cast_to(); } v2u window::virtual_size() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->virtual_size; } v2u window::framebuffer_size() const noexcept { - std::lock_guard guard(state_->mutex); - E2D_ASSERT(state_->window); + std::lock_guard guard(state_->rmutex); int w = 0, h = 0; + E2D_ASSERT(state_->window); glfwGetFramebufferSize(state_->window.get(), &w, &h); return make_vec2(w,h).cast_to(); } const str& window::title() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->title; } void window::set_title(str_view title) { - std::lock_guard guard(state_->mutex); - E2D_ASSERT(state_->window); + std::lock_guard guard(state_->rmutex); state_->title = make_utf8(title); - glfwSetWindowTitle( - state_->window.get(), state_->title.c_str()); + E2D_ASSERT(state_->window); + glfwSetWindowTitle(state_->window.get(), state_->title.c_str()); } bool window::should_close() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); - return glfwWindowShouldClose(state_->window.get()); + return GLFW_TRUE == glfwWindowShouldClose(state_->window.get()); } void window::set_should_close(bool yesno) noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); - glfwSetWindowShouldClose( - state_->window.get(), yesno ? GLFW_TRUE : GLFW_FALSE); + glfwSetWindowShouldClose(state_->window.get(), yesno ? GLFW_TRUE : GLFW_FALSE); } void window::swap_buffers() noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); E2D_ASSERT(state_->window); glfwSwapBuffers(state_->window.get()); } - bool window::poll_events() noexcept { + bool window::frame_tick() noexcept { return glfw_state::poll_events(); } + + window::event_listener& window::register_event_listener(event_listener_uptr listener) { + E2D_ASSERT(listener); + std::lock_guard guard(state_->rmutex); + state_->listeners.push_back(std::move(listener)); + return state_->on_new_event_listener(*state_->listeners.back()); + } + + void window::unregister_event_listener(const event_listener& listener) noexcept { + std::lock_guard guard(state_->rmutex); + for ( auto iter = state_->listeners.begin(); iter != state_->listeners.end(); ) { + if ( iter->get() == &listener ) { + iter = state_->listeners.erase(iter); + } else { + ++iter; + } + } + } } #endif diff --git a/sources/enduro2d/core/window_impl/window_none.cpp b/sources/enduro2d/core/window_impl/window_none.cpp index 68a2500f..0b025aa6 100644 --- a/sources/enduro2d/core/window_impl/window_none.cpp +++ b/sources/enduro2d/core/window_impl/window_none.cpp @@ -12,15 +12,20 @@ namespace e2d { class window::state final : private e2d::noncopyable { public: + using listeners_t = std::vector; + public: + listeners_t listeners; + std::recursive_mutex rmutex; v2u virtual_size; str title; bool vsync = false; bool fullscreen = false; + bool cursor_hidden = false; bool should_close = false; bool visible = true; bool focused = true; bool minimized = false; - std::mutex mutex; + char _pad[1]; public: state(const v2u& size, str_view title, bool vsync, bool fullscreen) : virtual_size(size) @@ -28,6 +33,16 @@ namespace e2d , vsync(vsync) , fullscreen(fullscreen) {} ~state() noexcept = default; + + template < typename F, typename... Args > + void for_all_listeners(const F& f, const Args&... args) noexcept { + std::lock_guard guard(rmutex); + for ( const event_listener_uptr& listener : listeners ) { + if ( listener ) { + stdex::invoke(f, listener.get(), args...); + } + } + } }; window::window(const v2u& size, str_view title, bool vsync, bool fullscreen) @@ -35,103 +50,150 @@ namespace e2d window::~window() noexcept = default; void window::hide() noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); state_->visible = false; } void window::show() noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); state_->visible = true; } void window::restore() noexcept { - std::lock_guard guard(state_->mutex); - state_->minimized = false; + std::lock_guard guard(state_->rmutex); + if ( !state_->focused ) { + state_->focused = true; + state_->for_all_listeners(&event_listener::on_window_focus, true); + } + if ( state_->minimized ) { + state_->minimized = false; + state_->for_all_listeners(&event_listener::on_window_minimize, false); + } } void window::minimize() noexcept { - std::lock_guard guard(state_->mutex); - state_->minimized = true; + std::lock_guard guard(state_->rmutex); + if ( state_->focused ) { + state_->focused = false; + state_->for_all_listeners(&event_listener::on_window_focus, false); + } + if ( !state_->minimized ) { + state_->minimized = true; + state_->for_all_listeners(&event_listener::on_window_minimize, true); + } } bool window::visible() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->visible; } bool window::focused() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->focused; } bool window::minimized() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->minimized; } bool window::vsync() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->vsync; } bool window::fullscreen() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->fullscreen; } bool window::toggle_vsync(bool yesno) noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); state_->vsync = yesno; return true; } bool window::toggle_fullscreen(bool yesno) noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); state_->fullscreen = yesno; return true; } + void window::hide_cursor() noexcept { + std::lock_guard guard(state_->rmutex); + state_->cursor_hidden = true; + } + + void window::show_cursor() noexcept { + std::lock_guard guard(state_->rmutex); + state_->cursor_hidden = false; + } + + bool window::is_cursor_hidden() const noexcept { + std::lock_guard guard(state_->rmutex); + return state_->cursor_hidden; + } + v2u window::real_size() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->virtual_size; } v2u window::virtual_size() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->virtual_size; } v2u window::framebuffer_size() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->virtual_size; } const str& window::title() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->title; } void window::set_title(str_view title) { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); state_->title = make_utf8(title); } bool window::should_close() const noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); return state_->should_close; } void window::set_should_close(bool yesno) noexcept { - std::lock_guard guard(state_->mutex); + std::lock_guard guard(state_->rmutex); state_->should_close = yesno; } void window::swap_buffers() noexcept { } - bool window::poll_events() noexcept { + bool window::frame_tick() noexcept { return false; } + + window::event_listener& window::register_event_listener(event_listener_uptr listener) { + E2D_ASSERT(listener); + std::lock_guard guard(state_->rmutex); + state_->listeners.push_back(std::move(listener)); + return *state_->listeners.back(); + } + + void window::unregister_event_listener(const event_listener& listener) noexcept { + std::lock_guard guard(state_->rmutex); + for ( auto iter = state_->listeners.begin(); iter != state_->listeners.end(); ) { + if ( iter->get() == &listener ) { + iter = state_->listeners.erase(iter); + } else { + ++iter; + } + } + } } #endif diff --git a/untests/sources/untests_core/input.cpp b/untests/sources/untests_core/input.cpp new file mode 100644 index 00000000..9e878a90 --- /dev/null +++ b/untests/sources/untests_core/input.cpp @@ -0,0 +1,278 @@ +/******************************************************************************* + * This file is part of the "Enduro2D" + * For conditions of distribution and use, see copyright notice in LICENSE.md + * Copyright (C) 2018 Matvey Cherevko + ******************************************************************************/ + +#include "_core.hpp" +using namespace e2d; + +TEST_CASE("input"){ + SECTION("mouse"){ + { + input i; + const mouse& m = i.mouse(); + + REQUIRE(math::approximately(m.cursor_pos(), v2f::zero())); + REQUIRE(math::approximately(m.scroll_delta(), v2f::zero())); + + i.post_event(input::move_cursor_event{v2f(10.f,20.f)}); + i.post_event(input::mouse_scroll_event{v2f(0.1f,0.2f)}); + REQUIRE(math::approximately(m.cursor_pos(), v2f(10.f,20.f))); + REQUIRE(math::approximately(m.scroll_delta(), v2f(0.1f,0.2f))); + + i.frame_tick(); + + REQUIRE(math::approximately(m.cursor_pos(), v2f(10.f,20.f))); + REQUIRE(math::approximately(m.scroll_delta(), v2f::zero())); + + i.post_event(input::move_cursor_event{v2f(12.f,22.f)}); + REQUIRE(math::approximately(m.cursor_pos(), v2f(12.f,22.f))); + } + { + input i; + const mouse& m = i.mouse(); + + REQUIRE_FALSE(m.is_any_button_pressed()); + REQUIRE_FALSE(m.is_any_button_just_pressed()); + REQUIRE_FALSE(m.is_any_button_just_released()); + REQUIRE_FALSE(m.is_button_pressed(mouse_button::left)); + REQUIRE_FALSE(m.is_button_just_pressed(mouse_button::left)); + REQUIRE_FALSE(m.is_button_just_released(mouse_button::left)); + + i.post_event(input::mouse_button_event{mouse_button::left, mouse_button_action::press}); + + REQUIRE(m.is_any_button_pressed()); + REQUIRE(m.is_any_button_just_pressed()); + REQUIRE_FALSE(m.is_any_button_just_released()); + REQUIRE(m.is_button_pressed(mouse_button::left)); + REQUIRE(m.is_button_just_pressed(mouse_button::left)); + REQUIRE_FALSE(m.is_button_just_released(mouse_button::left)); + + i.frame_tick(); + + REQUIRE(m.is_any_button_pressed()); + REQUIRE_FALSE(m.is_any_button_just_pressed()); + REQUIRE_FALSE(m.is_any_button_just_released()); + REQUIRE(m.is_button_pressed(mouse_button::left)); + REQUIRE_FALSE(m.is_button_just_pressed(mouse_button::left)); + REQUIRE_FALSE(m.is_button_just_released(mouse_button::left)); + + i.post_event(input::mouse_button_event{mouse_button::left, mouse_button_action::release}); + + REQUIRE_FALSE(m.is_any_button_pressed()); + REQUIRE_FALSE(m.is_any_button_just_pressed()); + REQUIRE(m.is_any_button_just_released()); + REQUIRE_FALSE(m.is_button_pressed(mouse_button::left)); + REQUIRE_FALSE(m.is_button_just_pressed(mouse_button::left)); + REQUIRE(m.is_button_just_released(mouse_button::left)); + + i.frame_tick(); + + REQUIRE_FALSE(m.is_any_button_pressed()); + REQUIRE_FALSE(m.is_any_button_just_pressed()); + REQUIRE_FALSE(m.is_any_button_just_released()); + REQUIRE_FALSE(m.is_button_pressed(mouse_button::left)); + REQUIRE_FALSE(m.is_button_just_pressed(mouse_button::left)); + REQUIRE_FALSE(m.is_button_just_released(mouse_button::left)); + } + { + input i; + const mouse& m = i.mouse(); + + std::vector buttons; + m.extract_pressed_buttons(buttons); + m.extract_just_pressed_buttons(buttons); + m.extract_just_released_buttons(buttons); + REQUIRE(buttons.size() == 0); + + buttons.clear(); + i.post_event(input::mouse_button_event{mouse_button::right, mouse_button_action::press}); + + m.extract_pressed_buttons(buttons); + REQUIRE(buttons.size() == 1); + REQUIRE(buttons[0] == mouse_button::right); + + m.extract_just_pressed_buttons(buttons); + REQUIRE(buttons.size() == 2); + REQUIRE(buttons[0] == mouse_button::right); + REQUIRE(buttons[1] == mouse_button::right); + + m.extract_just_released_buttons(buttons); + REQUIRE(buttons.size() == 2); + REQUIRE(buttons[0] == mouse_button::right); + REQUIRE(buttons[1] == mouse_button::right); + + buttons.clear(); + i.frame_tick(); + + m.extract_pressed_buttons(buttons); + REQUIRE(buttons.size() == 1); + REQUIRE(buttons[0] == mouse_button::right); + + m.extract_just_pressed_buttons(buttons); + REQUIRE(buttons.size() == 1); + REQUIRE(buttons[0] == mouse_button::right); + + m.extract_just_released_buttons(buttons); + REQUIRE(buttons.size() == 1); + REQUIRE(buttons[0] == mouse_button::right); + + buttons.clear(); + i.post_event(input::mouse_button_event{mouse_button::right, mouse_button_action::release}); + + m.extract_pressed_buttons(buttons); + REQUIRE(buttons.size() == 0); + + m.extract_just_pressed_buttons(buttons); + REQUIRE(buttons.size() == 0); + + m.extract_just_released_buttons(buttons); + REQUIRE(buttons.size() == 1); + REQUIRE(buttons[0] == mouse_button::right); + } + } + SECTION("keyboard"){ + { + input i; + const keyboard& k = i.keyboard(); + + REQUIRE(k.input_text() == make_utf32("")); + + i.post_event(input::input_char_event{'H'}); + i.post_event(input::input_char_event{'e'}); + + REQUIRE(k.input_text() == make_utf32("He")); + + i.frame_tick(); + + REQUIRE(k.input_text() == make_utf32("")); + } + { + input i; + const keyboard& k = i.keyboard(); + + REQUIRE_FALSE(k.is_any_key_pressed()); + REQUIRE_FALSE(k.is_any_key_just_pressed()); + REQUIRE_FALSE(k.is_any_key_just_released()); + REQUIRE_FALSE(k.is_key_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_released(keyboard_key::enter)); + + i.post_event(input::keyboard_key_event{keyboard_key::enter, keyboard_key_action::press}); + + REQUIRE(k.is_any_key_pressed()); + REQUIRE(k.is_any_key_just_pressed()); + REQUIRE_FALSE(k.is_any_key_just_released()); + REQUIRE(k.is_key_pressed(keyboard_key::enter)); + REQUIRE(k.is_key_just_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_released(keyboard_key::enter)); + + i.frame_tick(); + + REQUIRE(k.is_any_key_pressed()); + REQUIRE_FALSE(k.is_any_key_just_pressed()); + REQUIRE_FALSE(k.is_any_key_just_released()); + REQUIRE(k.is_key_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_released(keyboard_key::enter)); + + i.post_event(input::keyboard_key_event{keyboard_key::enter, keyboard_key_action::repeat}); + + REQUIRE(k.is_any_key_pressed()); + REQUIRE_FALSE(k.is_any_key_just_pressed()); + REQUIRE_FALSE(k.is_any_key_just_released()); + REQUIRE(k.is_key_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_released(keyboard_key::enter)); + + i.post_event(input::keyboard_key_event{keyboard_key::enter, keyboard_key_action::release}); + + REQUIRE_FALSE(k.is_any_key_pressed()); + REQUIRE_FALSE(k.is_any_key_just_pressed()); + REQUIRE(k.is_any_key_just_released()); + REQUIRE_FALSE(k.is_key_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_pressed(keyboard_key::enter)); + REQUIRE(k.is_key_just_released(keyboard_key::enter)); + + i.frame_tick(); + + REQUIRE_FALSE(k.is_any_key_pressed()); + REQUIRE_FALSE(k.is_any_key_just_pressed()); + REQUIRE_FALSE(k.is_any_key_just_released()); + REQUIRE_FALSE(k.is_key_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_pressed(keyboard_key::enter)); + REQUIRE_FALSE(k.is_key_just_released(keyboard_key::enter)); + } + { + input i; + const keyboard& k = i.keyboard(); + + std::vector keys; + k.extract_pressed_keys(keys); + k.extract_just_pressed_keys(keys); + k.extract_just_released_keys(keys); + REQUIRE(keys.size() == 0); + + keys.clear(); + i.post_event(input::keyboard_key_event{keyboard_key::escape, keyboard_key_action::press}); + + k.extract_pressed_keys(keys); + REQUIRE(keys.size() == 1); + REQUIRE(keys[0] == keyboard_key::escape); + + k.extract_just_pressed_keys(keys); + REQUIRE(keys.size() == 2); + REQUIRE(keys[0] == keyboard_key::escape); + REQUIRE(keys[1] == keyboard_key::escape); + + k.extract_just_released_keys(keys); + REQUIRE(keys.size() == 2); + REQUIRE(keys[0] == keyboard_key::escape); + REQUIRE(keys[1] == keyboard_key::escape); + + keys.clear(); + i.frame_tick(); + + k.extract_pressed_keys(keys); + REQUIRE(keys.size() == 1); + REQUIRE(keys[0] == keyboard_key::escape); + + k.extract_just_pressed_keys(keys); + REQUIRE(keys.size() == 1); + REQUIRE(keys[0] == keyboard_key::escape); + + k.extract_just_released_keys(keys); + REQUIRE(keys.size() == 1); + REQUIRE(keys[0] == keyboard_key::escape); + + keys.clear(); + i.post_event(input::keyboard_key_event{keyboard_key::escape, keyboard_key_action::repeat}); + + k.extract_pressed_keys(keys); + REQUIRE(keys.size() == 1); + REQUIRE(keys[0] == keyboard_key::escape); + + k.extract_just_pressed_keys(keys); + REQUIRE(keys.size() == 1); + REQUIRE(keys[0] == keyboard_key::escape); + + k.extract_just_released_keys(keys); + REQUIRE(keys.size() == 1); + REQUIRE(keys[0] == keyboard_key::escape); + + keys.clear(); + i.post_event(input::keyboard_key_event{keyboard_key::escape, keyboard_key_action::release}); + + k.extract_pressed_keys(keys); + REQUIRE(keys.size() == 0); + + k.extract_just_pressed_keys(keys); + REQUIRE(keys.size() == 0); + + k.extract_just_released_keys(keys); + REQUIRE(keys.size() == 1); + REQUIRE(keys[0] == keyboard_key::escape); + } + } +}