diff --git a/.clang-tidy b/.clang-tidy index 2fd2120e..f7a36a5d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,188 +1,168 @@ # taken from https://github.com/cpp-linter/cpp-linter-action/blob/main/demo/.clang-tidy --- -Checks: "clang-diagnostic-*,clang-analyzer-*,bugprone-*,misc-*,performance-*,readability-*,portability-*,modernize-*,cppcoreguidelines-*,-modernize-use-trailing-return-type,-readability-named-parameter,-readability-identifier-length,-misc-include-cleaner,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-misc-non-private-member-variables-in-classes,-cppcoreguidelines-non-private-member-variables-in-classes,-cppcoreguidelines-special-member-functions" +Checks: "clang-diagnostic-*,clang-analyzer-*,bugprone-*,misc-*,performance-*,readability-*,portability-*,modernize-*,cppcoreguidelines-*,google-*,llvm-*,cert-*,-modernize-use-trailing-return-type,-bugprone-argument-comment,-misc-include-cleaner,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-readability-avoid-nested-conditional-operator,-llvm-namespace-comment,-llvm-header-guard" WarningsAsErrors: "" HeaderFilterRegex: "oopetris/src/.*" -AnalyzeTemporaryDtors: false FormatStyle: "file" CheckOptions: - - key: bugprone-argument-comment.CommentBoolLiterals - value: "0" - - key: bugprone-argument-comment.CommentCharacterLiterals - value: "0" - - key: bugprone-argument-comment.CommentFloatLiterals - value: "0" - - key: bugprone-argument-comment.CommentIntegerLiterals - value: "0" - - key: bugprone-argument-comment.CommentNullPtrs - value: "0" - - key: bugprone-argument-comment.CommentStringLiterals - value: "0" - - key: bugprone-argument-comment.CommentUserDefinedLiterals - value: "0" - - key: bugprone-argument-comment.IgnoreSingleArgument - value: "0" - - key: bugprone-argument-comment.StrictMode - value: "0" - - key: bugprone-assert-side-effect.AssertMacros - value: assert - - key: bugprone-assert-side-effect.CheckFunctionCalls - value: "0" - - key: bugprone-dangling-handle.HandleClasses - value: "std::basic_string_view;std::experimental::basic_string_view" - - key: bugprone-dynamic-static-initializers.HeaderFileExtensions - value: ",h,hh,hpp,hxx" - - key: bugprone-exception-escape.FunctionsThatShouldNotThrow - value: "" - - key: bugprone-exception-escape.IgnoredExceptions - value: "" - - key: bugprone-misplaced-widening-cast.CheckImplicitCasts - value: "0" - - key: bugprone-not-null-terminated-result.WantToUseSafeFunctions - value: "1" - - key: bugprone-signed-char-misuse.CharTypdefsToIgnore - value: "" - - key: bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant - value: "1" - - key: bugprone-sizeof-expression.WarnOnSizeOfConstant - value: "1" - - key: bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression - value: "0" - - key: bugprone-sizeof-expression.WarnOnSizeOfThis - value: "1" - - key: bugprone-string-constructor.LargeLengthThreshold - value: "8388608" - - key: bugprone-string-constructor.WarnOnLargeLength - value: "1" - - key: bugprone-suspicious-enum-usage.StrictMode - value: "0" - - key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens - value: "5" - - key: bugprone-suspicious-missing-comma.RatioThreshold - value: "0.200000" - - key: bugprone-suspicious-missing-comma.SizeThreshold - value: "5" - - key: bugprone-suspicious-string-compare.StringCompareLikeFunctions - value: "" - - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison - value: "1" - - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison - value: "0" - - key: bugprone-too-small-loop-variable.MagnitudeBitsUpperLimit - value: "16" - - key: bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField - value: "1" - - key: bugprone-unused-return-value.CheckedFunctions - value: "::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty" - - key: cert-dcl16-c.NewSuffixes - value: "L;LL;LU;LLU" - - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField - value: "0" - - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors - value: "1" - - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic - value: "1" - - key: google-readability-braces-around-statements.ShortStatementLines - value: "1" - - key: google-readability-function-size.StatementThreshold - value: "800" - - key: google-readability-namespace-comments.ShortNamespaceLines - value: "10" - - key: google-readability-namespace-comments.SpacesBeforeComments - value: "2" - - key: misc-definitions-in-headers.HeaderFileExtensions - value: ",h,hh,hpp,hxx" - - key: misc-definitions-in-headers.UseHeaderFileExtension - value: "1" - - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries - value: "1" - - key: misc-unused-parameters.StrictMode - value: "0" - - key: modernize-loop-convert.MaxCopySize - value: "16" - - key: modernize-loop-convert.MinConfidence - value: reasonable - - key: modernize-use-trailing-return-type - value: "false" - - key: modernize-loop-convert.NamingStyle - value: CamelCase - - key: modernize-pass-by-value.IncludeStyle - value: llvm - - key: modernize-replace-auto-ptr.IncludeStyle - value: llvm - - key: modernize-use-nullptr.NullMacros - value: "NULL" - - key: performance-faster-string-find.StringLikeClasses - value: "std::basic_string" - - key: performance-for-range-copy.AllowedTypes - value: "" - - key: performance-for-range-copy.WarnOnAllAutoCopies - value: "0" - - key: performance-inefficient-string-concatenation.StrictMode - value: "0" - - key: performance-inefficient-vector-operation.EnableProto - value: "0" - - key: performance-inefficient-vector-operation.VectorLikeClasses - value: "::std::vector" - - key: performance-move-const-arg.CheckTriviallyCopyableMove - value: "1" - - key: performance-move-constructor-init.IncludeStyle - value: llvm - - key: performance-no-automatic-move.AllowedTypes - value: "" - - key: performance-type-promotion-in-math-fn.IncludeStyle - value: llvm - - key: performance-unnecessary-copy-initialization.AllowedTypes - value: "" - - key: performance-unnecessary-value-param.AllowedTypes - value: "" - - key: performance-unnecessary-value-param.IncludeStyle - value: llvm - - key: readability-braces-around-statements.ShortStatementLines - value: "0" - - key: readability-else-after-return.WarnOnUnfixable - value: "1" - - key: readability-function-size.BranchThreshold - value: "4294967295" - - key: readability-function-size.LineThreshold - value: "4294967295" - - key: readability-function-size.NestingThreshold - value: "4294967295" - - key: readability-function-size.ParameterThreshold - value: "4294967295" - - key: readability-function-size.StatementThreshold - value: "800" - - key: readability-function-size.VariableThreshold - value: "4294967295" - - key: readability-identifier-naming.IgnoreFailedSplit - value: "0" - - key: readability-implicit-bool-conversion.AllowIntegerConditions - value: "0" - - key: readability-implicit-bool-conversion.AllowPointerConditions - value: "0" - - key: readability-inconsistent-declaration-parameter-name.IgnoreMacros - value: "1" - - key: readability-inconsistent-declaration-parameter-name.Strict - value: "0" - - key: readability-magic-numbers.IgnoredFloatingPointValues - value: "1.0;100.0;" - - key: readability-magic-numbers.IgnoredIntegerValues - value: "1;2;3;4;" - - key: readability-redundant-member-init.IgnoreBaseInCopyConstructors - value: "0" - - key: readability-redundant-smartptr-get.IgnoreMacros - value: "1" - - key: readability-redundant-string-init.StringNames - value: "::std::basic_string" - - key: readability-simplify-boolean-expr.ChainedConditionalAssignment - value: "0" - - key: readability-simplify-boolean-expr.ChainedConditionalReturn - value: "0" - - key: readability-simplify-subscript-expr.Types - value: "::std::basic_string;::std::basic_string_view;::std::vector;::std::array" - - key: readability-static-accessed-through-instance.NameSpecifierNestingThreshold - value: "3" - - key: readability-uppercase-literal-suffix.IgnoreMacros - value: "1" - - key: readability-uppercase-literal-suffix.NewSuffixes - value: "" + ## NAMING CONVENTION SECTION + - key: readability-identifier-naming.AbstractClassCase + value: "CamelCase" + - key: readability-identifier-naming.AggressiveDependentMemberLookup + value: true + - key: readability-identifier-naming.CheckAnonFieldInParent + value: false + - key: readability-identifier-naming.ClassCase + value: "CamelCase" + - key: readability-identifier-naming.ClassConstantCase + value: "lower_case" + - key: readability-identifier-naming.ClassConstantPrefix + value: "c_" + - key: readability-identifier-naming.ClassMemberCase + value: "lower_case" + - key: readability-identifier-naming.ClassMemberPrefix + value: "m_" + - key: readability-identifier-naming.ClassMethodCase + value: "lower_case" + - key: readability-identifier-naming.ConceptCase + value: "CamelCase" + - key: readability-identifier-naming.ConstantCase + value: "lower_case" + - key: readability-identifier-naming.ConstantMemberCase + value: "lower_case" + - key: readability-identifier-naming.ConstantParameterCase + value: "lower_case" + - key: readability-identifier-naming.ConstantPointerParameterCase + value: "lower_case" + - key: readability-identifier-naming.ConstexprFunctionCase + value: "lower_case" + - key: readability-identifier-naming.ConstexprMethodCase + value: "lower_case" + - key: readability-identifier-naming.ConstexprVariableCase + value: "lower_case" + - key: readability-identifier-naming.EnumCase + value: "CamelCase" + - key: readability-identifier-naming.EnumConstantCase + value: "CamelCase" + - key: readability-identifier-naming.FunctionCase + value: "lower_case" + - key: readability-identifier-naming.GlobalConstantCase + value: "lower_case" + - key: readability-identifier-naming.GlobalConstantPointerCase + value: "lower_case" + - key: readability-identifier-naming.GlobalFunctionCase + value: "lower_case" + - key: readability-identifier-naming.GlobalFunctionIgnoredRegexp + value: "(PrintTo)" ## for gtest + - key: readability-identifier-naming.GlobalPointerCase + value: "lower_case" + - key: readability-identifier-naming.GlobalVariableCase + value: "lower_case" + - key: readability-identifier-naming.GlobalVariablePrefix + value: "g_" + - key: readability-identifier-naming.LocalConstantCase + value: "lower_case" + - key: readability-identifier-naming.LocalConstantPointerCase + value: "lower_case" + - key: readability-identifier-naming.LocalPointerCase + value: "lower_case" + - key: readability-identifier-naming.LocalVariableCase + value: "lower_case" + - key: readability-identifier-naming.MacroDefinitionCase + value: "UPPER_CASE" + - key: readability-identifier-naming.MemberCase + value: "lower_case" + - key: readability-identifier-naming.MemberPrefix + value: "m_" + - key: readability-identifier-naming.MethodCase + value: "lower_case" + - key: readability-identifier-naming.NamespaceCase + value: "lower_case" + - key: readability-identifier-naming.ParameterCase + value: "lower_case" + - key: readability-identifier-naming.ParameterPackCase + value: "lower_case" + - key: readability-identifier-naming.PointerParameterCase + value: "lower_case" + - key: readability-identifier-naming.PrivateMemberCase + value: "lower_case" + - key: readability-identifier-naming.PrivateMemberPrefix + value: "m_" + - key: readability-identifier-naming.PrivateMethodCase + value: "lower_case" + - key: readability-identifier-naming.ProtectedMemberCase + value: "lower_case" + - key: readability-identifier-naming.ProtectedMemberPrefix + value: "m_" + - key: readability-identifier-naming.ProtectedMethodCase + value: "lower_case" + - key: readability-identifier-naming.PublicMemberCase + value: "lower_case" + - key: readability-identifier-naming.PublicMemberPrefix + value: "" # NO PREFIX + - key: readability-identifier-naming.PublicMethodCase + value: "lower_case" + - key: readability-identifier-naming.ScopedEnumConstantCase + value: "CamelCase" + - key: readability-identifier-naming.StaticConstantCase + value: "lower_case" + - key: readability-identifier-naming.StaticConstantPrefix + value: "s_" + - key: readability-identifier-naming.StaticVariableCase + value: "lower_case" + - key: readability-identifier-naming.StaticVariablePrefix + value: "s_" + - key: readability-identifier-naming.StructCase + value: "CamelCase" + - key: readability-identifier-naming.TemplateParameterCase + value: "CamelCase" + - key: readability-identifier-naming.TemplateTemplateParameterCase + value: "CamelCase" + - key: readability-identifier-naming.TypeAliasCase + value: "CamelCase" + - key: readability-identifier-naming.TypedefCase + value: "CamelCase" + - key: readability-identifier-naming.TypeTemplateParameterCase + value: "CamelCase" + - key: readability-identifier-naming.UnionCase + value: "CamelCase" + - key: readability-identifier-naming.ValueTemplateParameterCase + value: "CamelCase" + - key: readability-identifier-naming.VariableCase + value: "lower_case" + - key: readability-identifier-naming.VirtualMethodCase + value: "lower_case" + + # some needed settings, that are non default + - key: bugprone-misplaced-widening-cast.CheckImplicitCasts + value: true + - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: true + - key: bugprone-suspicious-enum-usage.StrictMode + value: true + - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison + value: true + - key: bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField + value: true + - key: misc-unused-parameters.StrictMode + value: true + - key: performance-inefficient-string-concatenation.StrictMode + value: true + - key: readability-inconsistent-declaration-parameter-name.Strict + value: true + + ## special things, that have special values + - key: readability-identifier-length.IgnoredVariableNames + value: "" + - key: readability-identifier-length.IgnoredParameterNames + value: "^(os)$" ## std::ostream + - key: readability-identifier-length.IgnoredExceptionVariableNames + value: "" + - key: readability-identifier-length.MinimumLoopCounterNameLength + value: 1 + - key: readability-identifier-length.MinimumExceptionNameLength + value: 5 + - key: readability-function-cognitive-complexity.Threshold + value: 50 + - key: bugprone-assert-side-effect.AssertMacros + value: "assert" diff --git a/meson.build b/meson.build index 09c8aea7..cb73f7dc 100644 --- a/meson.build +++ b/meson.build @@ -7,7 +7,7 @@ project( 'buildtype': 'debug', 'optimization': '3', 'strip': true, - 'cpp_std': ['c++23', 'c++latest', 'vc++latest', 'c++20'], + 'cpp_std': ['c++26', 'c++23', 'c++latest', 'vc++latest'], 'b_ndebug': 'if-release', }, diff --git a/platforms/build-3ds.sh b/platforms/build-3ds.sh index 05eed4ef..a01e3734 100755 --- a/platforms/build-3ds.sh +++ b/platforms/build-3ds.sh @@ -31,7 +31,7 @@ export CMAKE="$BIN_DIR/$TOOL_PREFIX-cmake" export PATH="$BIN_DIR:$PATH" export CC="$COMPILER_BIN/$TOOL_PREFIX-gcc" -export CXX="$COMPILER_BIN/$TOOL_PREFIX-g"++ +export CXX="$COMPILER_BIN/$TOOL_PREFIX-g++" export AS="$COMPILER_BIN/$TOOL_PREFIX-as" export AR="$COMPILER_BIN/$TOOL_PREFIX-gcc-ar" export RANLIB="$COMPILER_BIN/$TOOL_PREFIX-gcc-ranlib" @@ -73,6 +73,8 @@ devkitpro = '$DEVKITPRO' [binaries] c = '$CC' cpp = '$CXX' +c_ld = 'bfd' +cpp_ld = 'bfd' ar = '$AR' as = '$AS' ranlib = '$RANLIB' diff --git a/platforms/build-android.sh b/platforms/build-android.sh index 274aedef..de05f724 100755 --- a/platforms/build-android.sh +++ b/platforms/build-android.sh @@ -150,13 +150,15 @@ for INDEX in "${ARCH_KEYS_INDEX[@]}"; do export BUILD_DIR="build-$ARM_TARGET_ARCH" - export CC=$ARM_TOOL_TRIPLE-clang - export CXX=$ARM_TOOL_TRIPLE-clang++ - export LD=llvm-ld - export AS=llvm-as - export AR=llvm-ar - export RANLIB=llvm-ranlib - export STRIP=llvm-strip + export CC="$ARM_TOOL_TRIPLE-clang" + export CXX="$ARM_TOOL_TRIPLE-clang++" + export LD="llvm-ld" + export AS="llvm-as" + export AR="llvm-ar" + export RANLIB="llvm-ranlib" + export STRIP="llvm-strip" + export OBJCOPY="llvm-objcop" + export LLVM_CONFIG="llvm-config" unset PKG_CONFIG ## BUILD dependencies not buildable with meson (to complicated to port) @@ -300,16 +302,17 @@ android_ndk = '$BIN_DIR' toolchain = '$BIN_DIR/$ARM_TRIPLE' [binaries] -c = '$ARM_TOOL_TRIPLE-clang' -cpp = '$ARM_TOOL_TRIPLE-clang++' -ar = 'llvm-ar' -as = 'llvm-as' -ranlib = 'llvm-ranlib' -ld = 'llvm-link' -strip = 'llvm-strip' -objcopy = 'llvm-objcop' +c = '$CC' +cpp = '$CXX' +c_ld = 'lld' +cpp_ld = 'lld' +ar = '$AR' +as = '$AS' +ranlib = '$RANLIB' +strip = '$STRIP' +objcopy = '$OBJCOPY' pkg-config = 'false' -llvm-config = 'llvm-config' +llvm-config = '$LLVM_CONFIG' [built-in options] c_std = 'gnu11' diff --git a/platforms/build-switch.sh b/platforms/build-switch.sh index c9ecd2a5..0f0f9213 100755 --- a/platforms/build-switch.sh +++ b/platforms/build-switch.sh @@ -29,7 +29,7 @@ export CMAKE="$BIN_DIR/$TOOL_PREFIX-cmake" export PATH="$BIN_DIR:$PATH" export CC="$COMPILER_BIN/$TOOL_PREFIX-gcc" -export CXX="$COMPILER_BIN/$TOOL_PREFIX-g"++ +export CXX="$COMPILER_BIN/$TOOL_PREFIX-g++" export AS="$COMPILER_BIN/$TOOL_PREFIX-as" export AR="$COMPILER_BIN/$TOOL_PREFIX-gcc-ar" export RANLIB="$COMPILER_BIN/$TOOL_PREFIX-gcc-ranlib" @@ -71,6 +71,8 @@ devkitpro = '$DEVKITPRO' [binaries] c = '$CC' cpp = '$CXX' +c_ld = 'bfd' +cpp_ld = 'bfd' ar = '$AR' as = '$AS' ranlib = '$RANLIB' diff --git a/settings.json b/settings.json index 772b03e4..2107fe78 100644 --- a/settings.json +++ b/settings.json @@ -1,14 +1,65 @@ { - "platform": "pc", "controls": { - "type": "keyboard", - "drop": "W", - "hold": "E", - "move_down": "S", - "move_left": "A", - "move_right": "D", - "rotate_left": "Left", - "rotate_right": "Right" + "selected": null, + "inputs": [ + { + "type": "keyboard", + "drop": "W", + "hold": "E", + "move_down": "S", + "move_left": "A", + "move_right": "D", + "rotate_left": "Left", + "rotate_right": "Right", + "menu": { + "pause": "Space", + "open_settings": "P" + } + }, + { + "type": "joystick", + "identification": { + "guid": "00:00:10:32:4e:69:6e:74:65:6e:64:6f:20:33:44:00", + "name": "Nintendo 3DS" + }, + "drop": "X", + "hold": "B", + "move_down": "DPAD_DOWN", + "move_left": "DPAD_LEFT", + "move_right": "DPAD_RIGHT", + "rotate_left": "L", + "rotate_right": "R", + "menu": { + "pause": "START", + "open_settings": "SELECT" + } + }, + { + "type": "joystick", + "identification": { + "guid": "00:00:38:f8:53:77:69:74:63:68:20:43:6f:6e:74:00", + "name": "Switch Controller" + }, + "drop": "X", + "hold": "B", + "move_down": "LPAD_DOWN", + "move_left": "LPAD_LEFT", + "move_right": "LPAD_RIGHT", + "rotate_left": "DPAD_LEFT", + "rotate_right": "DPAD_RIGHT", + "menu": { + "pause": "MINUS", + "open_settings": "PLUS" + } + }, + { + "type": "touch", + "move_x_threshold": 0.07, + "move_y_threshold": 0.37, + "rotation_duration_threshold": 500, + "drop_duration_threshold": 200 + } + ] }, "volume": 0.2, "discord": false diff --git a/src/application.cpp b/src/application.cpp index dd36bce3..50962edb 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,11 +1,13 @@ #include "application.hpp" #include "helper/errors.hpp" +#include "helper/magic_enum_wrapper.hpp" #include "helper/message_box.hpp" #include "helper/sleep.hpp" -#include "platform/capabilities.hpp" +#include "input/input.hpp" #include "scenes/scene.hpp" #include +#include #include #include @@ -13,6 +15,10 @@ #include "helper/console_helpers.hpp" #endif +#ifdef DEBUG_BUILD +#include "graphics/text.hpp" +#endif + namespace { [[nodiscard]] helper::MessageBox::Type get_notification_level(helper::error::Severity severity) { @@ -24,14 +30,15 @@ namespace { } // namespace -Application::Application(std::unique_ptr&& window, const std::vector& arguments) try +Application::Application(std::shared_ptr&& window, const std::vector& arguments) try : m_command_line_arguments{ arguments }, m_window{ std::move(window) }, m_renderer{ *m_window, m_command_line_arguments.target_fps.has_value() ? Renderer::VSync::Disabled : Renderer::VSync::Enabled }, m_music_manager{ this, num_audio_channels }, - m_target_framerate{ m_command_line_arguments.target_fps }, - m_event_dispatcher{ m_window.get() } { + m_input_manager{ std::make_shared(m_window) }, + m_settings_manager{ this }, + m_target_framerate{ m_command_line_arguments.target_fps } { initialize(); } catch (const helper::GeneralError& general_error) { const auto severity = general_error.severity(); @@ -81,7 +88,7 @@ void Application::run() { const Uint64 current_time = SDL_GetPerformanceCounter(); if (current_time - start_time >= update_time) { - double elapsed = static_cast(current_time - start_time) / count_per_s; + const double elapsed = static_cast(current_time - start_time) / count_per_s; m_fps_text->set_text(*this, fmt::format("FPS: {:.2f}", static_cast(frame_counter) / elapsed)); start_time = current_time; frame_counter = 0; @@ -93,7 +100,7 @@ void Application::run() { const auto now = std::chrono::steady_clock::now(); const auto runtime = (now - start_execution_time); if (runtime < sleep_time) { - //TODO: use SDL_DelayNS in sdl >= 3.0 + //TODO(totto): use SDL_DelayNS in sdl >= 3.0 helper::sleep_nanoseconds(sleep_time - runtime); start_execution_time = std::chrono::steady_clock::now(); } else { @@ -103,7 +110,7 @@ void Application::run() { } } -void Application::handle_event(const SDL_Event& event, const Window* window) { +void Application::handle_event(const SDL_Event& event) { if (event.type == SDL_QUIT) { m_is_running = false; } @@ -111,7 +118,7 @@ void Application::handle_event(const SDL_Event& event, const Window* window) { auto handled = false; for (const auto& scene : std::ranges::views::reverse(m_scene_stack)) { - if (not handled and scene->handle_event(event, window)) { + if (not handled and scene->handle_event(m_input_manager, event)) { handled = true; } @@ -122,8 +129,8 @@ void Application::handle_event(const SDL_Event& event, const Window* window) { } // if the scene is not covering the whole screen, it should give scenes in the background mouse events, but keyboard events are still only captured by the scene in focus, we also detect unhovers for whole scenes here - if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Any)) { - if (not utils::is_event_in(window, event, scene->get_layout().get_rect())) { + if (const auto result = m_input_manager->get_pointer_event(event); result.has_value()) { + if (not result->is_in(scene->get_layout().get_rect())) { scene->on_unhover(); } } @@ -134,29 +141,22 @@ void Application::handle_event(const SDL_Event& event, const Window* window) { } // handle some special events + const auto is_special_event = m_input_manager->process_special_inputs(event); + if (is_special_event) { - switch (event.type) { - case SDL_WINDOWEVENT: - switch (event.window.event) { - case SDL_WINDOWEVENT_HIDDEN: - case SDL_WINDOWEVENT_MINIMIZED: - case SDL_WINDOWEVENT_LEAVE: { - for (const auto& scene : m_scene_stack) { - scene->on_unhover(); - } - break; + if (const auto special_event = is_special_event.get_additional(); special_event.has_value()) { + if (special_event.value() == input::SpecialRequest::WindowFocusLost) { + for (const auto& scene : m_scene_stack) { + scene->on_unhover(); } - default: - break; } - break; - default: - break; + } } - // this global event handlers (atm only one) are special cases, they receive all inputs if they are not handled by the scenes explicably - if (m_music_manager.handle_event(event)) { + // this global event handlers (atm only one) are special cases, they receive all inputs if they are not handled by the scenes explicitly + + if (m_music_manager.handle_event(m_input_manager, event)) { return; } } @@ -190,9 +190,12 @@ void Application::update() { spdlog::info("pushing back scene {}", raw_push.name); m_scene_stack.push_back(std::move(raw_push.scene)); }, - [this](const scenes::Scene::Switch& switch_) { - spdlog::info("switching to scene {}", magic_enum::enum_name(switch_.target_scene)); - auto scene = scenes::create_scene(*this, switch_.target_scene, switch_.layout); + [this](const scenes::Scene::Switch& scene_switch) { + spdlog::info( + "switching to scene {}", magic_enum::enum_name(scene_switch.target_scene) + ); + auto scene = + scenes::create_scene(*this, scene_switch.target_scene, scene_switch.layout); // only clear, after the construction was successful m_scene_stack.clear(); @@ -246,20 +249,20 @@ void Application::render() const { void Application::initialize() { - try_load_settings(); load_resources(); push_scene(scenes::create_scene(*this, SceneId::MainMenu, ui::FullScreenLayout{ *m_window })); #ifdef DEBUG_BUILD m_fps_text = std::make_unique( - this, "FPS: ?", fonts().get(FontId::Default), Color::white(), std::pair{ 0.95, 0.95 }, + this, "FPS: ?", font_manager().get(FontId::Default), Color::white(), + std::pair{ 0.95, 0.95 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }, ui::RelativeLayout{ window(), 0.0, 0.0, 0.1, 0.05 }, false ); #endif #if defined(_HAVE_DISCORD_SDK) - if (m_settings.discord) { + if (m_settings_manager.settings().discord) { auto discord_instance = DiscordInstance::initialize(); if (not discord_instance.has_value()) { spdlog::warn( @@ -274,27 +277,11 @@ void Application::initialize() { #endif } -void Application::try_load_settings() { - const std::filesystem::path settings_file = utils::get_root_folder() / settings_filename; - - const auto result = json::try_parse_json_file(settings_file); - - if (result.has_value()) { - m_settings = result.value(); - } else { - spdlog::error("unable to load settings from \"{}\": {}", settings_filename, result.error()); - spdlog::warn("applying default settings"); - } - - // apply settings - m_music_manager.set_volume(m_settings.volume); -} - void Application::load_resources() { constexpr auto fonts_size = 128; const std::vector> fonts{ #if defined(__3DS__) - //TODO: debug why the other font crashed, not on loading, but on trying to render text! + //TODO(Totto): debug why the other font crashed, not on loading, but on trying to render text! { FontId::Default, "LeroyLetteringLightBeta01.ttf" }, #else { FontId::Default, "PressStart2P.ttf" }, diff --git a/src/application.hpp b/src/application.hpp index 32eb5d58..34a713b3 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -1,22 +1,19 @@ #pragma once #include "graphics/renderer.hpp" -#include "graphics/sdl_context.hpp" #include "graphics/window.hpp" #include "helper/command_line_arguments.hpp" #include "helper/types.hpp" +#include "input/input.hpp" #include "manager/event_dispatcher.hpp" #include "manager/event_listener.hpp" #include "manager/music_manager.hpp" #include "manager/resource_manager.hpp" #include "manager/service_provider.hpp" +#include "manager/settings_manager.hpp" #include "scenes/scene.hpp" #include "ui/components/label.hpp" -#ifdef DEBUG_BUILD -#include "graphics/text.hpp" -#endif - #include #include @@ -25,12 +22,12 @@ struct Application final : public EventListener, public ServiceProvider { static constexpr auto num_audio_channels = u8{ 2 }; CommandLineArguments m_command_line_arguments; - std::unique_ptr m_window; + std::shared_ptr m_window; Renderer m_renderer; bool m_is_running{ true }; MusicManager m_music_manager; - static constexpr auto settings_filename = "settings.json"; - Settings m_settings; + std::shared_ptr m_input_manager; + SettingsManager m_settings_manager; FontManager m_font_manager; helper::optional m_target_framerate; @@ -50,17 +47,19 @@ struct Application final : public EventListener, public ServiceProvider { std::vector> m_scene_stack; public: - Application(std::unique_ptr&& window, const std::vector& arguments); + Application(std::shared_ptr&& window, const std::vector& arguments); Application(const Application&) = delete; Application& operator=(const Application&) = delete; void run(); - void handle_event(const SDL_Event& event, const Window* window) override; + void handle_event(const SDL_Event& event) override; virtual void update(); virtual void render() const; + //TODO(Totto): move those functions bodies to the cpp + void push_scene(std::unique_ptr scene) { m_scene_stack.push_back(std::move(scene)); } @@ -74,10 +73,10 @@ struct Application final : public EventListener, public ServiceProvider { return m_event_dispatcher; } - FontManager& fonts() override { + FontManager& font_manager() override { return m_font_manager; } - const FontManager& fonts() const override { + const FontManager& font_manager() const override { return m_font_manager; } @@ -87,11 +86,11 @@ struct Application final : public EventListener, public ServiceProvider { const CommandLineArguments& command_line_arguments() const override { return m_command_line_arguments; } - Settings& settings() override { - return m_settings; + SettingsManager& settings_manager() override { + return m_settings_manager; } - const Settings& settings() const override { - return m_settings; + const SettingsManager& settings_manager() const override { + return m_settings_manager; } MusicManager& music_manager() override { return m_music_manager; @@ -112,6 +111,14 @@ struct Application final : public EventListener, public ServiceProvider { return *m_window; } + [[nodiscard]] input::InputManager& input_manager() override { + return *m_input_manager; + } + + [[nodiscard]] const input::InputManager& input_manager() const override { + return *m_input_manager; + } + #if defined(_HAVE_DISCORD_SDK) @@ -124,6 +131,5 @@ struct Application final : public EventListener, public ServiceProvider { private: void initialize(); - void try_load_settings(); void load_resources(); }; diff --git a/src/discord/core.hpp b/src/discord/core.hpp index 272e69d9..ffad0d68 100644 --- a/src/discord/core.hpp +++ b/src/discord/core.hpp @@ -18,7 +18,7 @@ namespace constants::discord { constexpr auto client_id = 1220147916371394650ULL; - //TODO: this isn't correct for all platforms and needs to be tested + //TODO(Totto): this isn't correct for all platforms and needs to be tested #if defined(__ANDROID__) constexpr const char* platform_dependent_launch_arguments = ""; #elif defined(__CONSOLE__) @@ -49,7 +49,7 @@ struct DiscordActivityWrapper { discord::Activity m_activity{}; public: - //TODO: Add support for party and invites / join / invitations / spectate + //TODO(Totto): Add support for party and invites / join / invitations / spectate DiscordActivityWrapper(const std::string& details, discord::ActivityType type); diff --git a/src/game/game.cpp b/src/game/game.cpp index a7a96c25..aa7922ec 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -1,17 +1,19 @@ #include "game.hpp" -#include "platform/replay_input.hpp" +#include "helper/magic_enum_wrapper.hpp" +#include "helper/utils.hpp" +#include "input/replay_input.hpp" Game::Game( ServiceProvider* const service_provider, - std::unique_ptr&& input, + const std::shared_ptr& input, const tetrion::StartingParameters& starting_parameters, const ui::Layout& layout, bool is_top_level ) : ui::Widget{ layout, ui::WidgetType::Component, is_top_level }, m_clock_source{ std::make_unique(starting_parameters.target_fps) }, - m_input{ std::move(input) } { + m_input{ input } { spdlog::info("starting level for tetrion {}", starting_parameters.starting_level); @@ -60,7 +62,7 @@ void Game::render(const ServiceProvider& service_provider) const { } [[nodiscard]] helper::BoolWrapper> -Game::handle_event(const SDL_Event&, const Window*) { +Game::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } @@ -74,10 +76,10 @@ void Game::set_paused(bool paused) { m_clock_source->resume(); } - auto* listener = dynamic_cast(m_input.get()); + auto listener = utils::is_child_class(m_input); - if (listener != nullptr) { - listener->set_paused(paused); + if (listener.has_value()) { + listener.value()->set_paused(paused); } } @@ -91,10 +93,15 @@ void Game::set_paused(bool paused) { return true; }; - auto* const input_as_replay = dynamic_cast(m_input.get()); - if (input_as_replay != nullptr) { - return input_as_replay->is_end_of_recording(); + const auto input_as_replay = utils::is_child_class(m_input); + if (input_as_replay.has_value()) { + return input_as_replay.value()->is_end_of_recording(); } return false; } + + +[[nodiscard]] const std::shared_ptr& Game::game_input() const { + return m_input; +} diff --git a/src/game/game.hpp b/src/game/game.hpp index 4bc7253f..f6633326 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -1,7 +1,7 @@ #pragma once #include "helper/clock_source.hpp" -#include "input_creator.hpp" +#include "input/input_creator.hpp" #include "recordings/recording.hpp" #include "tetrion.hpp" #include "ui/widget.hpp" @@ -13,24 +13,29 @@ struct Game : public ui::Widget { std::unique_ptr m_clock_source; SimulationStep m_simulation_step_index{ 0 }; std::unique_ptr m_tetrion; - std::unique_ptr m_input; + std::shared_ptr m_input; bool m_is_paused{ false }; public: explicit Game( ServiceProvider* service_provider, - std::unique_ptr&& input, + const std::shared_ptr& input, const tetrion::StartingParameters& starting_parameters, const ui::Layout& layout, bool is_top_level ); void update() override; + void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] Widget::EventHandleResult + + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; void set_paused(bool paused); [[nodiscard]] bool is_paused() const; [[nodiscard]] bool is_game_finished() const; + + [[nodiscard]] const std::shared_ptr& game_input() const; }; diff --git a/src/game/grid.cpp b/src/game/grid.cpp index 6fe73485..942f350c 100644 --- a/src/game/grid.cpp +++ b/src/game/grid.cpp @@ -42,7 +42,7 @@ void Grid::render(const ServiceProvider& service_provider) const { } [[nodiscard]] helper::BoolWrapper> -Grid::handle_event(const SDL_Event&, const Window*) { +Grid::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } @@ -51,7 +51,7 @@ void Grid::draw_preview_background(const ServiceProvider& service_provider) cons service_provider, GridRect{ grid::preview_background_position, - grid::preview_background_position + grid::preview_extends - GridPoint{1, 1}, + grid::preview_background_position + grid::preview_extends - GridPoint{ 1, 1 }, } ); } @@ -61,7 +61,7 @@ void Grid::draw_hold_background(const ServiceProvider& service_provider) const { service_provider, GridRect{ grid::hold_background_position, - grid::hold_background_position + grid::hold_background_extends - GridPoint{1, 1}, + grid::hold_background_position + grid::hold_background_extends - GridPoint{ 1, 1 }, } ); } @@ -71,7 +71,7 @@ void Grid::draw_playing_field_background(const ServiceProvider& service_provider service_provider, GridRect{ grid::grid_position, - grid::grid_position + shapes::UPoint{grid::width_in_tiles - 1, grid::height_in_tiles - 1}, + grid::grid_position + shapes::UPoint{ grid::width_in_tiles - 1, grid::height_in_tiles - 1 }, } ); } diff --git a/src/game/grid.hpp b/src/game/grid.hpp index 98169b57..44a59da8 100644 --- a/src/game/grid.hpp +++ b/src/game/grid.hpp @@ -29,7 +29,7 @@ struct Grid final : public ui::Widget { [[nodiscard]] shapes::UPoint to_screen_coords(GridPoint grid_coords) const; void render(const ServiceProvider& service_provider) const override; [[nodiscard]] helper::BoolWrapper> - handle_event(const SDL_Event& event, const Window* window) override; + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; private: void draw_preview_background(const ServiceProvider& service_provider) const; diff --git a/src/game/meson.build b/src/game/meson.build index 78149184..5e360af8 100644 --- a/src/game/meson.build +++ b/src/game/meson.build @@ -15,8 +15,6 @@ graphics_src_files += files( 'grid.cpp', 'grid.hpp', 'grid_properties.hpp', - 'input_creator.cpp', - 'input_creator.hpp', 'rotation.cpp', 'rotation.hpp', 'tetrion.cpp', diff --git a/src/game/mino_stack.cpp b/src/game/mino_stack.cpp index 5d8eab4a..770774d6 100644 --- a/src/game/mino_stack.cpp +++ b/src/game/mino_stack.cpp @@ -1,12 +1,12 @@ #include "mino_stack.hpp" - #include "grid_properties.hpp" #include "helper/magic_enum_wrapper.hpp" + #include void MinoStack::clear_row_and_let_sink(u8 row) { m_minos.erase( - std::remove_if(m_minos.begin(), m_minos.end(), [&](const Mino& mino) { return mino.position().y == row; }), + std::ranges::remove_if(m_minos, [&](const Mino& mino) { return mino.position().y == row; }).begin(), m_minos.end() ); for (Mino& mino : m_minos) { @@ -17,12 +17,9 @@ void MinoStack::clear_row_and_let_sink(u8 row) { } [[nodiscard]] bool MinoStack::is_empty(GridPoint coordinates) const { - for (const Mino& mino : m_minos) { // NOLINT(readability-use-anyofallof) - if (mino.position() == coordinates) { - return false; - } - } - return true; + return not std::ranges::any_of(m_minos, [&coordinates](const Mino& mino) { + return mino.position() == coordinates; + }); } void MinoStack::set(GridPoint coordinates, helper::TetrominoType type) { @@ -48,18 +45,17 @@ void MinoStack::set(GridPoint coordinates, helper::TetrominoType type) { return false; } - const auto all_of_this_in_other = std::all_of(m_minos.cbegin(), m_minos.cend(), [&](const auto& mino) { - return std::find(other.m_minos.cbegin(), other.m_minos.cend(), mino) != end(other.m_minos); + const auto all_of_this_in_other = std::ranges::all_of(m_minos, [&](const auto& mino) { + return std::ranges::find(other.m_minos, mino) != end(other.m_minos); }); if (not all_of_this_in_other) { return false; } - const auto all_of_other_in_this = - std::all_of(other.m_minos.cbegin(), other.m_minos.cend(), [this](const auto& mino) { - return std::find(m_minos.cbegin(), m_minos.cend(), mino) != end(m_minos); - }); + const auto all_of_other_in_this = std::ranges::all_of(other.m_minos, [this](const auto& mino) { + return std::ranges::find(m_minos, mino) != end(m_minos); + }); return all_of_other_in_this; } @@ -73,10 +69,9 @@ std::ostream& operator<<(std::ostream& ostream, const MinoStack& mino_stack) { ostream << "MinoStack(\n"; for (u8 y = 0; y < grid::height_in_tiles; ++y) { for (u8 x = 0; x < grid::width_in_tiles; ++x) { - const auto find_iterator = - std::find_if(mino_stack.minos().cbegin(), mino_stack.minos().cend(), [&](const auto& mino) { - return mino.position() == shapes::AbstractPoint{ x, y }; - }); + const auto find_iterator = std::ranges::find_if(mino_stack.minos(), [&](const auto& mino) { + return mino.position() == shapes::AbstractPoint{ x, y }; + }); const auto found = (find_iterator != mino_stack.minos().cend()); if (found) { ostream << magic_enum::enum_name(find_iterator->type()); diff --git a/src/game/tetrion.cpp b/src/game/tetrion.cpp index ac4e775b..ec6dea8a 100644 --- a/src/game/tetrion.cpp +++ b/src/game/tetrion.cpp @@ -1,7 +1,9 @@ #include "tetrion.hpp" #include "helper/constants.hpp" #include "helper/graphic_utils.hpp" +#include "helper/magic_enum_wrapper.hpp" #include "helper/music_utils.hpp" +#include "helper/platform.hpp" #include "helper/utils.hpp" #include "manager/music_manager.hpp" #include "manager/resource_manager.hpp" @@ -30,7 +32,7 @@ Tetrion::Tetrion( m_level{ starting_level }, m_tetrion_index{ tetrion_index }, m_next_gravity_simulation_step_index{ get_gravity_delay_frames() }, - main_layout{ + m_main_layout{ utils::size_t_identity<2>(), 0, ui::Direction::Vertical, @@ -40,33 +42,33 @@ Tetrion::Tetrion( layout } { - main_layout.add(); + m_main_layout.add(); - main_layout.add( + m_main_layout.add( 1, 3, ui::Direction::Vertical, ui::AbsolutMargin{ 0 }, std::pair{ 0.0, 0.1 } ); auto* text_layout = get_text_layout(); - constexpr auto text_size = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto text_size = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.2, 0.8 } : std::pair{ 0.6, 0.8 }; text_layout->add( - service_provider, "score: 0", service_provider->fonts().get(FontId::Default), Color::white(), text_size, - ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } + service_provider, "score: 0", service_provider->font_manager().get(FontId::Default), Color::white(), + text_size, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); text_layout->add( - service_provider, "lines: 0", service_provider->fonts().get(FontId::Default), Color::white(), text_size, - ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } + service_provider, "lines: 0", service_provider->font_manager().get(FontId::Default), Color::white(), + text_size, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); text_layout->add( - service_provider, "lines: 0", service_provider->fonts().get(FontId::Default), Color::white(), text_size, - ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } + service_provider, "lines: 0", service_provider->font_manager().get(FontId::Default), Color::white(), + text_size, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); refresh_texts(); @@ -103,7 +105,7 @@ void Tetrion::update_step(const SimulationStep simulation_step_index) { void Tetrion::render(const ServiceProvider& service_provider) const { - main_layout.render(service_provider); + m_main_layout.render(service_provider); const auto* grid = get_grid(); const double original_scale = grid->scale_to_original(); @@ -146,38 +148,38 @@ void Tetrion::render(const ServiceProvider& service_provider) const { } [[nodiscard]] helper::BoolWrapper> -Tetrion::handle_event(const SDL_Event&, const Window*) { +Tetrion::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } -bool Tetrion::handle_input_command(const InputCommand command, const SimulationStep simulation_step_index) { +bool Tetrion::handle_input_command(const input::GameInputCommand command, const SimulationStep simulation_step_index) { switch (command) { - case InputCommand::RotateLeft: + case input::GameInputCommand::RotateLeft: if (rotate_tetromino_left()) { reset_lock_delay(simulation_step_index); return true; } return false; - case InputCommand::RotateRight: + case input::GameInputCommand::RotateRight: if (rotate_tetromino_right()) { reset_lock_delay(simulation_step_index); return true; } return false; - case InputCommand::MoveLeft: + case input::GameInputCommand::MoveLeft: if (move_tetromino_left()) { reset_lock_delay(simulation_step_index); return true; } return false; - case InputCommand::MoveRight: + case input::GameInputCommand::MoveRight: if (move_tetromino_right()) { reset_lock_delay(simulation_step_index); return true; } return false; - case InputCommand::MoveDown: - //TODO: use input_type() != InputType:Touch + case input::GameInputCommand::MoveDown: + //TODO(Totto): use input_type() != InputType:Touch #if not defined(__ANDROID__) m_down_key_pressed = true; m_is_accelerated_down_movement = true; @@ -188,14 +190,14 @@ bool Tetrion::handle_input_command(const InputCommand command, const SimulationS return true; } return false; - case InputCommand::Drop: + case input::GameInputCommand::Drop: m_lock_delay_step_index = simulation_step_index; // lock instantly return drop_tetromino(simulation_step_index); - case InputCommand::ReleaseMoveDown: { + case input::GameInputCommand::ReleaseMoveDown: { m_down_key_pressed = false; return false; } - case InputCommand::Hold: + case input::GameInputCommand::Hold: if (m_allowed_to_hold) { hold_tetromino(simulation_step_index); reset_lock_delay(simulation_step_index); @@ -204,7 +206,7 @@ bool Tetrion::handle_input_command(const InputCommand command, const SimulationS } return false; default: - assert(false and "unknown event"); + assert(false and "unknown GameInput"); return false; } } @@ -332,19 +334,19 @@ void Tetrion::hold_tetromino(const SimulationStep simulation_step_index) { } [[nodiscard]] Grid* Tetrion::get_grid() { - return main_layout.get(0); + return m_main_layout.get(0); } [[nodiscard]] const Grid* Tetrion::get_grid() const { - return main_layout.get(0); + return m_main_layout.get(0); } [[nodiscard]] ui::GridLayout* Tetrion::get_text_layout() { - return main_layout.get(1); + return m_main_layout.get(1); } [[nodiscard]] const ui::GridLayout* Tetrion::get_text_layout() const { - return main_layout.get(1); + return m_main_layout.get(1); } [[nodiscard]] u8 Tetrion::tetrion_index() const { @@ -520,12 +522,9 @@ helper::TetrominoType Tetrion::get_next_tetromino_type() { } bool Tetrion::tetromino_can_move_down(const Tetromino& tetromino) const { - for (const Mino& mino : tetromino.minos()) { // NOLINT(readability-use-anyofallof) - if (not mino_can_move_down(mino.position())) { - return false; - } - } - return true; + return not std::ranges::any_of(tetromino.minos(), [this](const Mino& mino) { + return not mino_can_move_down(mino.position()); + }); } @@ -537,41 +536,38 @@ bool Tetrion::tetromino_can_move_down(const Tetromino& tetromino) const { return frames; } -u8 Tetrion::rotation_to_index(const Rotation from, const Rotation to) { - if (from == Rotation::North and to == Rotation::East) { +u8 Tetrion::rotation_to_index(const Rotation from, const Rotation rotation_to) { + if (from == Rotation::North and rotation_to == Rotation::East) { return 0; } - if (from == Rotation::East and to == Rotation::North) { + if (from == Rotation::East and rotation_to == Rotation::North) { return 1; } - if (from == Rotation::East and to == Rotation::South) { + if (from == Rotation::East and rotation_to == Rotation::South) { return 2; } - if (from == Rotation::South and to == Rotation::East) { + if (from == Rotation::South and rotation_to == Rotation::East) { return 3; } - if (from == Rotation::South and to == Rotation::West) { + if (from == Rotation::South and rotation_to == Rotation::West) { return 4; } - if (from == Rotation::West and to == Rotation::South) { + if (from == Rotation::West and rotation_to == Rotation::South) { return 5; } - if (from == Rotation::West and to == Rotation::North) { + if (from == Rotation::West and rotation_to == Rotation::North) { return 6; } - if (from == Rotation::North and to == Rotation::West) { + if (from == Rotation::North and rotation_to == Rotation::West) { return 7; } utils::unreachable(); } bool Tetrion::is_tetromino_position_valid(const Tetromino& tetromino) const { - for (const Mino& mino : tetromino.minos()) { // NOLINT(readability-use-anyofallof) - if (not is_valid_mino_position(mino.position())) { - return false; - } - } - return true; + return not std::ranges::any_of(tetromino.minos(), [this](const Mino& mino) { + return not is_valid_mino_position(mino.position()); + }); } bool Tetrion::rotate(Tetrion::RotationDirection rotation_direction) { diff --git a/src/game/tetrion.hpp b/src/game/tetrion.hpp index 85dd7e38..83f8a3ae 100644 --- a/src/game/tetrion.hpp +++ b/src/game/tetrion.hpp @@ -5,9 +5,9 @@ #include "helper/optional.hpp" #include "helper/random.hpp" #include "helper/types.hpp" +#include "input/game_input.hpp" #include "manager/service_provider.hpp" #include "mino_stack.hpp" -#include "platform/input.hpp" #include "recordings/tetrion_core_information.hpp" #include "tetromino.hpp" #include "ui/layout.hpp" @@ -76,7 +76,7 @@ struct Tetrion final : public ui::Widget { std::array, num_preview_tetrominos> m_preview_tetrominos{}; u8 m_tetrion_index; u64 m_next_gravity_simulation_step_index; - ui::TileLayout main_layout; + ui::TileLayout m_main_layout; public: @@ -89,10 +89,11 @@ struct Tetrion final : public ui::Widget { bool is_top_level); void update_step(SimulationStep simulation_step_index); void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; // returns if the input event lead to a movement - bool handle_input_command(InputCommand command, SimulationStep simulation_step_index); + bool handle_input_command(input::GameInputCommand command, SimulationStep simulation_step_index); void spawn_next_tetromino(SimulationStep simulation_step_index); void spawn_next_tetromino(helper::TetrominoType type, SimulationStep simulation_step_index); bool rotate_tetromino_right(); diff --git a/src/game/tetromino.cpp b/src/game/tetromino.cpp index ba137eec..ca978562 100644 --- a/src/game/tetromino.cpp +++ b/src/game/tetromino.cpp @@ -69,9 +69,9 @@ Tetromino::Pattern Tetromino::get_pattern(helper::TetrominoType type, Rotation r std::array Tetromino::create_minos(GridPoint position, Rotation rotation, helper::TetrominoType type) { return std::array{ - Mino{position + get_pattern(type, rotation).at(0), type}, - Mino{position + get_pattern(type, rotation).at(1), type}, - Mino{position + get_pattern(type, rotation).at(2), type}, - Mino{position + get_pattern(type, rotation).at(3), type}, + Mino{ position + get_pattern(type, rotation).at(0), type }, + Mino{ position + get_pattern(type, rotation).at(1), type }, + Mino{ position + get_pattern(type, rotation).at(2), type }, + Mino{ position + get_pattern(type, rotation).at(3), type }, }; } diff --git a/src/graphics/point.hpp b/src/graphics/point.hpp index c083a96f..b0849901 100644 --- a/src/graphics/point.hpp +++ b/src/graphics/point.hpp @@ -9,6 +9,8 @@ namespace shapes { template struct AbstractPoint final { + using Type = T; + T x; T y; diff --git a/src/graphics/renderer.cpp b/src/graphics/renderer.cpp index fed3b9ae..4e7bc42a 100644 --- a/src/graphics/renderer.cpp +++ b/src/graphics/renderer.cpp @@ -1,20 +1,19 @@ #include "renderer.hpp" #include "helper/errors.hpp" -//TODO: assert return values of all sdl functions +//TODO(Totto): assert return values of all sdl functions -Renderer::Renderer(const Window& window, const VSync v_sync) : m_renderer { - SDL_CreateRenderer( - window.get_sdl_window(), -1, - (v_sync == VSync::Enabled ? SDL_RENDERER_PRESENTVSYNC : 0) | SDL_RENDERER_TARGETTEXTURE +Renderer::Renderer(const Window& window, const VSync v_sync) + : m_renderer{ SDL_CreateRenderer( + window.get_sdl_window(), + -1, + (v_sync == VSync::Enabled ? SDL_RENDERER_PRESENTVSYNC : 0) | SDL_RENDERER_TARGETTEXTURE #if defined(__3DS__) - | SDL_RENDERER_SOFTWARE + | SDL_RENDERER_SOFTWARE #else - | SDL_RENDERER_ACCELERATED + | SDL_RENDERER_ACCELERATED #endif - ) -} -{ + ) } { if (m_renderer == nullptr) { throw helper::InitializationError{ fmt::format("Failed creating a SDL Renderer: {}", SDL_GetError()) }; diff --git a/src/graphics/sdl_context.cpp b/src/graphics/sdl_context.cpp index e86ab4dc..24ed8b03 100644 --- a/src/graphics/sdl_context.cpp +++ b/src/graphics/sdl_context.cpp @@ -26,17 +26,6 @@ SdlContext::SdlContext() { #if defined(__CONSOLE__) console::platform_init(); - - //TODO: factor out - - // init joystick and other nintendo switch / 3ds specific things - SDL_InitSubSystem(SDL_INIT_JOYSTICK); - SDL_JoystickEventState(SDL_ENABLE); - // only use the first joystick! - - //TODO, since local multiplayer on a switch / 3ds is possible, test here if there are more then one joysticks available (e.g. left and right controller) then ask the user if he wants to play local multiplayer, implement that in JoystickInput (in the SDL event the joystick index is present) - SDL_JoystickOpen(0); - #endif #if defined(_HAVE_FILE_DIALOGS) diff --git a/src/graphics/window.cpp b/src/graphics/window.cpp index 4de48104..036875a2 100644 --- a/src/graphics/window.cpp +++ b/src/graphics/window.cpp @@ -8,13 +8,13 @@ Window::Window(const std::string& title, WindowPosition position, u32 width, u32 Window::Window(const std::string& title, u32 x, u32 y, u32 width, u32 height) : m_window{ SDL_CreateWindow( - title.c_str(), - static_cast(x), - static_cast(y), - static_cast(width), - static_cast(height), - 0 - ) } { } + title.c_str(), + static_cast(x), + static_cast(y), + static_cast(width), + static_cast(height), + 0 + ) } { } Window::Window(const std::string& title, WindowPosition position) : Window{ title, static_cast(position), static_cast(position) } { } diff --git a/src/helper/color.cpp b/src/helper/color.cpp index b45a696c..46e1d3ea 100644 --- a/src/helper/color.cpp +++ b/src/helper/color.cpp @@ -81,7 +81,7 @@ namespace { if (value < static_cast(0.0)) { // see https://math.stackexchange.com/questions/2179579/how-can-i-find-a-mod-with-negative-number T result = value; - //TODO: maybe this is possible faster? + //TODO(Totto): maybe this is possible faster? while (result < static_cast(0.0)) { result += divisor; } diff --git a/src/helper/color.hpp b/src/helper/color.hpp index 954ad3e6..2b0a2a6a 100644 --- a/src/helper/color.hpp +++ b/src/helper/color.hpp @@ -1,5 +1,6 @@ #pragma once +#include "const_utils.hpp" #include "helper/expected.hpp" #include "helper/types.hpp" #include "helper/utils.hpp" @@ -23,17 +24,23 @@ struct HSVColor { double v; u8 a; - constexpr HSVColor(double h, double s, double v, u8 a) // NOLINT(bugprone-easily-swappable-parameters) - : h{ h }, - s{ s }, - v{ v }, - a{ a } { + + constexpr HSVColor( + double hue, // NOLINT(bugprone-easily-swappable-parameters) + double saturation, + double value, + u8 alpha + ) + : h{ hue }, + s{ saturation }, + v{ value }, + a{ alpha } { if (utils::is_constant_evaluated()) { - CONSTEVAL_STATIC_ASSERT(h >= 0.0 && h <= 360.0, "h has to be in range 0.0 - 360.0"); - CONSTEVAL_STATIC_ASSERT(s >= 0.0 && s <= 1.0, "s has to be in range 0.0 - 1.0"); - CONSTEVAL_STATIC_ASSERT(v >= 0.0 && v <= 1.0, "v has to be in range 0.0 - 1.0"); + CONSTEVAL_ONLY_STATIC_ASSERT(h >= 0.0 && h <= 360.0, "h has to be in range 0.0 - 360.0"); + CONSTEVAL_ONLY_STATIC_ASSERT(s >= 0.0 && s <= 1.0, "s has to be in range 0.0 - 1.0"); + CONSTEVAL_ONLY_STATIC_ASSERT(v >= 0.0 && v <= 1.0, "v has to be in range 0.0 - 1.0"); } else { @@ -50,9 +57,9 @@ struct HSVColor { } } - constexpr HSVColor() : HSVColor{ 0.0, 0.0, 0.0, 0 } { } + constexpr HSVColor(double hue, double saturation, double value) : HSVColor{ hue, saturation, value, 0xFF } { } - constexpr HSVColor(double h, double s, double v) : HSVColor{ h, s, v, 0xFF } { } + constexpr HSVColor() : HSVColor{ 0.0, 0.0, 0.0, 0 } { } [[nodiscard]] static helper::expected from_string(const std::string& value); @@ -67,8 +74,8 @@ struct HSVColor { std::ostream& operator<<(std::ostream& os) const; }; -namespace { - //TODO: if everything (also libc++ ) supports c++23 , the std functions are constexpr, so we can use them +namespace { //NOLINT(cert-dcl59-cpp,google-build-namespaces) + //TODO(Totto): if everything (also libc++ ) supports c++23 , the std functions are constexpr, so we can use them template constexpr T fmod_constexpr(T value, T divisor) { if (not utils::is_constant_evaluated()) { @@ -118,15 +125,15 @@ struct Color { u8 a; - constexpr Color(u8 r, u8 g, u8 b, u8 a) // NOLINT(bugprone-easily-swappable-parameters) - : r{ r }, - g{ g }, - b{ b }, - a{ a } { } + constexpr Color(u8 red, u8 green, u8 blue, u8 alpha) // NOLINT(bugprone-easily-swappable-parameters) + : r{ red }, + g{ green }, + b{ blue }, + a{ alpha } { } constexpr Color() : Color{ 0, 0, 0, 0 } { } - constexpr Color(u8 r, u8 g, u8 b) : Color{ r, g, b, 0xFF } { } + constexpr Color(u8 red, u8 green, u8 blue) : Color{ red, green, blue, 0xFF } { } [[nodiscard]] static helper::expected from_string(const std::string& value); @@ -137,30 +144,31 @@ struct Color { [[nodiscard]] HSVColor to_hsv_color() const; - constexpr Color(const HSVColor& color) { + constexpr Color(const HSVColor& color) { //NOLINT(google-explicit-constructor) using FloatType = double; //for more precision use "long double" here // taken from https://scratch.mit.edu/discuss/topic/694772/ - const auto h = static_cast(color.h); - const auto s = static_cast(color.s); - const auto v = static_cast(color.v); + auto hue = static_cast(color.h); + const auto saturation = static_cast(color.s); + const auto value = static_cast(color.v); + + const FloatType chroma = value * saturation; - const FloatType chroma = v * s; - FloatType hue = h; if (hue >= static_cast(360.0)) { hue = static_cast(0.0); } - const FloatType x = chroma - * (static_cast(1.0) - - fabs_constexpr( - fmod_constexpr(hue / static_cast(60.0), static_cast(2.0)) - - static_cast(1.0) - )); + const FloatType x_offset = + chroma + * (static_cast(1.0) + - fabs_constexpr( + fmod_constexpr(hue / static_cast(60.0), static_cast(2.0)) + - static_cast(1.0) + )); const u64 index = static_cast(hue / static_cast(60.0)); @@ -171,44 +179,44 @@ struct Color { switch (index) { case 0: d_r = chroma; - d_g = x; + d_g = x_offset; d_b = static_cast(0.0); break; case 1: - d_r = x; + d_r = x_offset; d_g = chroma; d_b = static_cast(0.0); break; case 2: d_r = static_cast(0.0); d_g = chroma; - d_b = x; + d_b = x_offset; break; case 3: d_r = static_cast(0.0); - d_g = x; + d_g = x_offset; d_b = chroma; break; case 4: - d_r = x; + d_r = x_offset; d_g = static_cast(0.0); d_b = chroma; break; case 5: d_r = chroma; d_g = static_cast(0.0); - d_b = x; + d_b = x_offset; break; default: utils::unreachable(); } - const FloatType m = v - chroma; + const FloatType offset = value - chroma; - const auto finish_value = [m](FloatType value) -> u8 { + const auto finish_value = [offset](FloatType value) -> u8 { const auto result = - std::clamp(value + m, static_cast(0.0), static_cast(1.0)) + std::clamp(value + offset, static_cast(0.0), static_cast(1.0)) * static_cast(0xFF); return static_cast(round_constexpr(result)); diff --git a/src/helper/color_literals.hpp b/src/helper/color_literals.hpp index ac728f9c..8d0f5cee 100644 --- a/src/helper/color_literals.hpp +++ b/src/helper/color_literals.hpp @@ -1,15 +1,15 @@ #pragma once #include "color.hpp" - +#include "const_utils.hpp" #include "helper/types.hpp" -#include "helper/utils.hpp" -#include "manager/service_provider.hpp" + #include -#include +#include -namespace { +namespace { //NOLINT(cert-dcl59-cpp,google-build-namespaces) + namespace const_constants { // offsets in C strings for hex rgb and rgba @@ -22,96 +22,47 @@ namespace { } // namespace const_constants - namespace const_utils { - - // represents a sort of constexpr std::expected - -#define PROPAGATE(val, V) /*NOLINT(cppcoreguidelines-macro-usage)*/ \ - do { /*NOLINT(cppcoreguidelines-avoid-do-while)*/ \ - if (not((val).has_value())) { \ - return const_utils::expected::error_result((val).error()); \ - } \ - } while (false) - - - template - requires std::is_default_constructible_v - struct expected { - private: - V m_value; - std::string m_error; - - constexpr expected(V value, const std::string& error) //NOLINT(modernize-pass-by-value) - : m_value{ value }, - m_error{ error } { } - - public: - [[nodiscard]] constexpr static expected good_result(V type) { - return { type, "" }; - } - - [[nodiscard]] constexpr static expected error_result(const std::string& error) { - return { V{}, error }; - } - - [[nodiscard]] constexpr bool has_value() const { - return m_error.empty(); - } - - [[nodiscard]] constexpr V value() const { - CONSTEVAL_STATIC_ASSERT((has_value()), "value() call on expected without value"); - return m_value; - } - - [[nodiscard]] constexpr std::string error() const { - CONSTEVAL_STATIC_ASSERT((not has_value()), "error() call on expected without error"); - return m_error; - } - }; - - } // namespace const_utils - // decode a decimal number - [[nodiscard]] constexpr const_utils::expected single_decimal_number(char n) { - if (n >= '0' && n <= '9') { - return const_utils::expected::good_result(static_cast(n - '0')); + [[nodiscard]] constexpr const_utils::Expected single_decimal_number(char input) { + if (input >= '0' && input <= '9') { + return const_utils::Expected::good_result(static_cast(input - '0')); } - return const_utils::expected::error_result("the input must be a valid decimal character"); + return const_utils::Expected::error_result("the input must be a valid decimal character"); } // decode a single_hex_number - [[nodiscard]] constexpr const_utils::expected single_hex_number(char n) { - if (n >= '0' && n <= '9') { - return const_utils::expected::good_result(static_cast(n - '0')); + [[nodiscard]] constexpr const_utils::Expected single_hex_number(char input) { + if (input >= '0' && input <= '9') { + return const_utils::Expected::good_result(static_cast(input - '0')); } - if (n >= 'A' && n <= 'F') { - return const_utils::expected::good_result(static_cast(n - 'A' + 10)); + if (input >= 'A' && input <= 'F') { + return const_utils::Expected::good_result(static_cast(input - 'A' + 10)); } - if (n >= 'a' && n <= 'f') { - return const_utils::expected::good_result(static_cast(n - 'a' + 10)); + if (input >= 'a' && input <= 'f') { + return const_utils::Expected::good_result(static_cast(input - 'a' + 10)); } - return const_utils::expected::error_result("the input must be a valid hex character"); + return const_utils::Expected::error_result("the input must be a valid hex character"); } //NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) // decode a single 2 digit color value in hex - [[nodiscard]] constexpr const_utils::expected single_hex_color_value(const char* input) { + [[nodiscard]] constexpr const_utils::Expected single_hex_color_value(const char* input) { const auto first = single_hex_number(input[0]); - PROPAGATE(first, u8); + PROPAGATE(first, u8, std::string); const auto second = single_hex_number(input[1]); - PROPAGATE(second, u8); + PROPAGATE(second, u8, std::string); - return const_utils::expected::good_result((first.value() << 4) | second.value()); + return const_utils::Expected::good_result((first.value() << 4) | second.value()); } template @@ -120,7 +71,9 @@ namespace { using DoubleReturnValue = CharIteratorResult; // decode a single color value as double - [[nodiscard]] constexpr const_utils::expected single_double_color_value(const char* value) { + [[nodiscard]] constexpr const_utils::Expected single_double_color_value( + const char* value + ) { double result{ 0.0 }; bool after_comma = false; @@ -136,20 +89,22 @@ namespace { break; case '.': if (after_comma) { - return const_utils::expected::error_result("only one comma allowed"); + return const_utils::Expected::error_result( + "only one comma allowed" + ); } after_comma = true; break; case ',': case ')': - return const_utils::expected::good_result({ result, value + i }); + return const_utils::Expected::good_result({ result, value + i }); case '\0': - return const_utils::expected::error_result("input ended too early"); + return const_utils::Expected::error_result("input ended too early"); default: { const auto char_result = single_decimal_number(current_char); - PROPAGATE(char_result, DoubleReturnValue); + PROPAGATE(char_result, DoubleReturnValue, std::string); const auto value_of_char = char_result.value(); @@ -164,7 +119,7 @@ namespace { } } - return const_utils::expected::error_result("unreachable"); + return const_utils::Expected::error_result("unreachable"); } using AnySizeType = u32; @@ -172,7 +127,9 @@ namespace { using AnyColorReturnValue = CharIteratorResult; // decode a single_hex_number - [[nodiscard]] constexpr const_utils::expected single_color_value_any(const char* value) { + [[nodiscard]] constexpr const_utils::Expected single_color_value_any( + const char* value + ) { bool accept_hex = false; AnySizeType start = 0; @@ -207,25 +164,28 @@ namespace { break; case ',': case ')': - return const_utils::expected::good_result({ result, value + i }); + return const_utils::Expected::good_result({ result, value + i }); case '\0': - return const_utils::expected::error_result("input ended too early"); + return const_utils::Expected::error_result("input ended too early" + ); default: { const auto char_result = accept_hex ? single_hex_number(current_char) : single_decimal_number(current_char); - PROPAGATE(char_result, AnyColorReturnValue); + PROPAGATE(char_result, AnyColorReturnValue, std::string); const auto value_of_char = char_result.value(); if (result == max_value_before_multiplication && value_of_char > max_value_before_multiplication_rest) { - return const_utils::expected::error_result("overflow detected"); + return const_utils::Expected::error_result("overflow detected" + ); } if (result > max_value_before_multiplication) { - return const_utils::expected::error_result("overflow detected"); + return const_utils::Expected::error_result("overflow detected" + ); } result *= mul_unit; @@ -235,120 +195,129 @@ namespace { } } - return const_utils::expected::error_result("unreachable"); + return const_utils::Expected::error_result("unreachable"); } using ColorFromHexStringReturnType = std::pair; - [[nodiscard]] constexpr const_utils::expected - get_color_from_hex_string(const char* input, std::size_t size) { //NOLINT(readability-function-cognitive-complexity + [[nodiscard]] constexpr const_utils::Expected + get_color_from_hex_string(const char* input, std::size_t size) { if (size == const_constants::hex_rgb_size) { - const auto r = single_hex_color_value(input + const_constants::red_offset); - PROPAGATE(r, ColorFromHexStringReturnType); + const auto red = single_hex_color_value(input + const_constants::red_offset); + PROPAGATE(red, ColorFromHexStringReturnType, std::string); - const auto g = single_hex_color_value(input + const_constants::green_offset); - PROPAGATE(g, ColorFromHexStringReturnType); + const auto green = single_hex_color_value(input + const_constants::green_offset); + PROPAGATE(green, ColorFromHexStringReturnType, std::string); - const auto b = single_hex_color_value(input + const_constants::blue_offset); - PROPAGATE(b, ColorFromHexStringReturnType); + const auto blue = single_hex_color_value(input + const_constants::blue_offset); + PROPAGATE(blue, ColorFromHexStringReturnType, std::string); - return const_utils::expected::good_result({ - Color{r.value(), g.value(), b.value()}, + return const_utils::Expected::good_result({ + Color{ red.value(), green.value(), blue.value() }, false }); } if (size == const_constants::hex_rgba_size) { - const auto r = single_hex_color_value(input + const_constants::red_offset); - PROPAGATE(r, ColorFromHexStringReturnType); + const auto red = single_hex_color_value(input + const_constants::red_offset); + PROPAGATE(red, ColorFromHexStringReturnType, std::string); - const auto g = single_hex_color_value(input + const_constants::green_offset); - PROPAGATE(g, ColorFromHexStringReturnType); + const auto green = single_hex_color_value(input + const_constants::green_offset); + PROPAGATE(green, ColorFromHexStringReturnType, std::string); - const auto b = single_hex_color_value(input + const_constants::blue_offset); - PROPAGATE(b, ColorFromHexStringReturnType); + const auto blue = single_hex_color_value(input + const_constants::blue_offset); + PROPAGATE(blue, ColorFromHexStringReturnType, std::string); - const auto a = single_hex_color_value(input + const_constants::alpha_offset); - PROPAGATE(a, ColorFromHexStringReturnType); + const auto alpha = single_hex_color_value(input + const_constants::alpha_offset); + PROPAGATE(alpha, ColorFromHexStringReturnType, std::string); - return const_utils::expected::good_result({ - Color{r.value(), g.value(), b.value(), a.value()}, + return const_utils::Expected::good_result({ + Color{ red.value(), green.value(), blue.value(), alpha.value() }, true }); } - return const_utils::expected::error_result("Unrecognized HEX literal"); + return const_utils::Expected::error_result("Unrecognized HEX literal" + ); } using ColorFromRGBStringReturnType = std::pair; - [[nodiscard]] constexpr const_utils::expected - get_color_from_rgb_string(const char* input, std::size_t) { //NOLINT(readability-function-cognitive-complexity + [[nodiscard]] constexpr const_utils::Expected + get_color_from_rgb_string( //NOLINT(readability-function-cognitive-complexity) + const char* input, + std::size_t /*unused*/ + ) { if (input[0] == 'r' && input[1] == 'g' && input[2] == 'b') { if (input[3] == '(') { const auto r_result = single_color_value_any(input + 4); - PROPAGATE(r_result, ColorFromRGBStringReturnType); + PROPAGATE(r_result, ColorFromRGBStringReturnType, std::string); const auto [r, next_g] = r_result.value(); if (r > std::numeric_limits::max()) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "r has to be in range 0 - 255" ); } if (*next_g != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto g_result = single_color_value_any(next_g + 1); - PROPAGATE(g_result, ColorFromRGBStringReturnType); + PROPAGATE(g_result, ColorFromRGBStringReturnType, std::string); const auto [g, next_b] = g_result.value(); if (g > std::numeric_limits::max()) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "g has to be in range 0 - 255" ); } if (*next_b != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto b_result = single_color_value_any(next_b + 1); - PROPAGATE(b_result, ColorFromRGBStringReturnType); + PROPAGATE(b_result, ColorFromRGBStringReturnType, std::string); const auto [b, end] = b_result.value(); if (b > std::numeric_limits::max()) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "b has to be in range 0 - 255" ); } if (*end != ')') { - return const_utils::expected::error_result("expected ')'"); + return const_utils::Expected::error_result("expected ')'" + ); } if (*(end + 1) != '\0') { - return const_utils::expected::error_result("expected end of string"); + return const_utils::Expected::error_result( + "expected end of string" + ); } - return const_utils::expected::good_result({ - Color{static_cast(r), static_cast(g), static_cast(b)}, + return const_utils::Expected::good_result({ + Color{ static_cast(r), static_cast(g), static_cast(b) }, false }); } @@ -358,78 +327,84 @@ namespace { const auto r_result = single_color_value_any(input + 5); - PROPAGATE(r_result, ColorFromRGBStringReturnType); + PROPAGATE(r_result, ColorFromRGBStringReturnType, std::string); const auto [r, next_g] = r_result.value(); if (r > std::numeric_limits::max()) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "r has to be in range 0 - 255" ); } if (*next_g != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto g_result = single_color_value_any(next_g + 1); - PROPAGATE(g_result, ColorFromRGBStringReturnType); + PROPAGATE(g_result, ColorFromRGBStringReturnType, std::string); const auto [g, next_b] = g_result.value(); if (g > std::numeric_limits::max()) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "g has to be in range 0 - 255" ); } if (*next_b != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto b_result = single_color_value_any(next_b + 1); - PROPAGATE(b_result, ColorFromRGBStringReturnType); + PROPAGATE(b_result, ColorFromRGBStringReturnType, std::string); const auto [b, next_a] = b_result.value(); if (b > std::numeric_limits::max()) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "b has to be in range 0 - 255" ); } if (*next_a != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto a_result = single_color_value_any(next_a + 1); - PROPAGATE(a_result, ColorFromRGBStringReturnType); + PROPAGATE(a_result, ColorFromRGBStringReturnType, std::string); const auto [a, end] = a_result.value(); if (a > std::numeric_limits::max()) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "a has to be in range 0 - 255" ); } if (*end != ')') { - return const_utils::expected::error_result("expected ')'"); + return const_utils::Expected::error_result("expected ')'" + ); } if (*(end + 1) != '\0') { - return const_utils::expected::error_result("expected end of string"); + return const_utils::Expected::error_result( + "expected end of string" + ); } - return const_utils::expected::good_result({ - Color{static_cast(r), static_cast(g), static_cast(b), static_cast(a)}, + return const_utils::Expected::good_result({ + Color{ static_cast(r), static_cast(g), static_cast(b), static_cast(a) }, true } ); @@ -437,74 +412,83 @@ namespace { } - return const_utils::expected::error_result("Unrecognized RGB literal"); + return const_utils::Expected::error_result("Unrecognized RGB literal" + ); } using ColorFromHSVStringReturnType = std::pair; - [[nodiscard]] constexpr const_utils::expected - get_color_from_hsv_string(const char* input, std::size_t) { //NOLINT(readability-function-cognitive-complexity + [[nodiscard]] constexpr const_utils::Expected + get_color_from_hsv_string( //NOLINT(readability-function-cognitive-complexity) + const char* input, + std::size_t /*unused*/ + ) { if (input[0] == 'h' && input[1] == 's' && input[2] == 'v') { if (input[3] == '(') { const auto h_result = single_double_color_value(input + 4); - PROPAGATE(h_result, ColorFromHSVStringReturnType); + PROPAGATE(h_result, ColorFromHSVStringReturnType, std::string); const auto [h, next_s] = h_result.value(); if (h < 0.0 || h > 360.0) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "h has to be in range 0.0 - 360.0" ); } if (*next_s != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto s_result = single_double_color_value(next_s + 1); - PROPAGATE(s_result, ColorFromHSVStringReturnType); + PROPAGATE(s_result, ColorFromHSVStringReturnType, std::string); const auto [s, next_v] = s_result.value(); if (s < 0.0 || s > 1.0) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "s has to be in range 0.0 - 1.0" ); } if (*next_v != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto v_result = single_double_color_value(next_v + 1); - PROPAGATE(v_result, ColorFromHSVStringReturnType); + PROPAGATE(v_result, ColorFromHSVStringReturnType, std::string); const auto [v, end] = v_result.value(); if (v < 0.0 || v > 1.0) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "v has to be in range 0.0 - 1.0" ); } if (*end != ')') { - return const_utils::expected::error_result("expected ')'"); + return const_utils::Expected::error_result("expected ')'" + ); } if (*(end + 1) != '\0') { - return const_utils::expected::error_result("expected end of string"); + return const_utils::Expected::error_result( + "expected end of string" + ); } - return const_utils::expected::good_result({ - HSVColor{h, s, v}, + return const_utils::Expected::good_result({ + HSVColor{ h, s, v }, false }); } @@ -514,94 +498,101 @@ namespace { const auto h_result = single_double_color_value(input + 5); - PROPAGATE(h_result, ColorFromHSVStringReturnType); + PROPAGATE(h_result, ColorFromHSVStringReturnType, std::string); const auto [h, next_s] = h_result.value(); if (h < 0.0 || h > 360.0) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "h has to be in range 0.0 - 360.0" ); } if (*next_s != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto s_result = single_double_color_value(next_s + 1); - PROPAGATE(s_result, ColorFromHSVStringReturnType); + PROPAGATE(s_result, ColorFromHSVStringReturnType, std::string); const auto [s, next_v] = s_result.value(); if (s < 0.0 || s > 1.0) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "s has to be in range 0.0 - 1.0" ); } if (*next_v != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto v_result = single_double_color_value(next_v + 1); - PROPAGATE(v_result, ColorFromHSVStringReturnType); + PROPAGATE(v_result, ColorFromHSVStringReturnType, std::string); const auto [v, next_a] = v_result.value(); if (v < 0.0 || v > 1.0) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "v has to be in range 0.0 - 1.0" ); } if (*next_a != ',') { - return const_utils::expected::error_result("expected ','"); + return const_utils::Expected::error_result("expected ','" + ); } const auto a_result = single_color_value_any(next_a + 1); - PROPAGATE(a_result, ColorFromHSVStringReturnType); + PROPAGATE(a_result, ColorFromHSVStringReturnType, std::string); const auto [a, end] = a_result.value(); if (a > std::numeric_limits::max()) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "a has to be in range 0 - 255" ); } if (*end != ')') { - return const_utils::expected::error_result("expected ')'"); + return const_utils::Expected::error_result("expected ')'" + ); } if (*(end + 1) != '\0') { - return const_utils::expected::error_result("expected end of string"); + return const_utils::Expected::error_result( + "expected end of string" + ); } - return const_utils::expected::good_result({ - HSVColor{h, s, v, static_cast(a)}, + return const_utils::Expected::good_result({ + HSVColor{ h, s, v, static_cast(a) }, true }); } } - return const_utils::expected::error_result("Unrecognized HSV literal"); + return const_utils::Expected::error_result("Unrecognized HSV literal" + ); } using ColorFromStringReturnType = std::tuple; - [[nodiscard]] constexpr const_utils::expected + [[nodiscard]] constexpr const_utils::Expected get_color_from_string_impl(const char* input, std::size_t size) { if (size == 0) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "not enough data to determine the literal type" ); } @@ -609,55 +600,54 @@ namespace { switch (input[0]) { case '#': { const auto result = get_color_from_hex_string(input, size); - if (not result.has_value()) { - return const_utils::expected::error_result(result.error()); - } + + PROPAGATE(result, ColorFromStringReturnType, std::string); const auto value = result.value(); - return const_utils::expected::good_result( + return const_utils::Expected::good_result( { value.first, color::SerializeMode::Hex, value.second } ); } case 'r': { const auto result = get_color_from_rgb_string(input, size); - if (not result.has_value()) { - return const_utils::expected::error_result(result.error()); - } + + PROPAGATE(result, ColorFromStringReturnType, std::string); const auto value = result.value(); - return const_utils::expected::good_result( + return const_utils::Expected::good_result( { value.first, color::SerializeMode::RGB, value.second } ); } case 'h': { const auto result = get_color_from_hsv_string(input, size); - if (not result.has_value()) { - return const_utils::expected::error_result(result.error()); - } + + PROPAGATE(result, ColorFromStringReturnType, std::string); const auto value = result.value(); - return const_utils::expected::good_result( + return const_utils::Expected::good_result( { Color{ value.first }, color::SerializeMode::HSV, value.second } ); } default: - return const_utils::expected::error_result("Unrecognized color literal"); + return const_utils::Expected::error_result( + "Unrecognized color literal" + ); } } using HSVColorFromStringReturnType = std::tuple; - [[nodiscard]] constexpr const_utils::expected + [[nodiscard]] constexpr const_utils::Expected get_hsv_color_from_string_impl(const char* input, std::size_t size) { if (size == 0) { - return const_utils::expected::error_result( + return const_utils::Expected::error_result( "not enough data to determine the literal type" ); } @@ -665,42 +655,41 @@ namespace { switch (input[0]) { case '#': { const auto result = get_color_from_hex_string(input, size); - if (not result.has_value()) { - return const_utils::expected::error_result(result.error()); - } + + PROPAGATE(result, HSVColorFromStringReturnType, std::string); const auto value = result.value(); - return const_utils::expected::good_result( + return const_utils::Expected::good_result( { value.first.to_hsv_color(), color::SerializeMode::Hex, value.second } ); } case 'r': { const auto result = get_color_from_rgb_string(input, size); - if (not result.has_value()) { - return const_utils::expected::error_result(result.error()); - } + + PROPAGATE(result, HSVColorFromStringReturnType, std::string); const auto value = result.value(); - return const_utils::expected::good_result( + return const_utils::Expected::good_result( { value.first.to_hsv_color(), color::SerializeMode::RGB, value.second } ); } case 'h': { const auto result = get_color_from_hsv_string(input, size); - if (not result.has_value()) { - return const_utils::expected::error_result(result.error()); - } + + PROPAGATE(result, HSVColorFromStringReturnType, std::string); const auto value = result.value(); - return const_utils::expected::good_result( + return const_utils::Expected::good_result( { value.first, color::SerializeMode::HSV, value.second } ); } default: - return const_utils::expected::error_result("Unrecognized color literal"); + return const_utils::Expected::error_result( + "Unrecognized color literal" + ); } } @@ -710,13 +699,13 @@ namespace { namespace detail { - [[nodiscard]] constexpr const_utils::expected get_color_from_string( + [[nodiscard]] constexpr const_utils::Expected get_color_from_string( const std::string& input ) { return get_color_from_string_impl(input.c_str(), input.size()); } - [[nodiscard]] constexpr const_utils::expected get_hsv_color_from_string( + [[nodiscard]] constexpr const_utils::Expected get_hsv_color_from_string( const std::string& input ) { return get_hsv_color_from_string_impl(input.c_str(), input.size()); diff --git a/src/helper/const_utils.hpp b/src/helper/const_utils.hpp new file mode 100644 index 00000000..0dda909b --- /dev/null +++ b/src/helper/const_utils.hpp @@ -0,0 +1,95 @@ + +#pragma once + +#include "helper/utils.hpp" + +#include +#include + +// use C++26 feature, if available: +#if __cpp_static_assert >= 202306L +#define CONSTEVAL_ONLY_STATIC_ASSERT(CHECK, MSG) /*NOLINT(cppcoreguidelines-macro-usage)*/ \ + ((CHECK) ? void(0) : [] { \ + /* If you see this really bad c++ error message, follow the origin of MSG, to see the real error message, c++ error messages suck xD */ \ + throw(MSG); \ + }()) + +//This doesn't work, since CHECK is in most cases not a constant expression so not constant evaluatable inside the if constexpr, and therefore static_assert(false,..) would trigger always +/* +#define CONSTEVAL_ONLY_STATIC_ASSERT(CHECK, MSG) +(if constexpr (!(CHECK)) { static_assert(false, MSG); }()) +s*/ + +#else +#define CONSTEVAL_ONLY_STATIC_ASSERT(CHECK, MSG) /*NOLINT(cppcoreguidelines-macro-usage)*/ \ + ((CHECK) ? void(0) : [] { \ + /* If you see this really bad c++ error message, follow the origin of MSG, to see the real error message, c++ error messages suck xD */ \ + throw(MSG); \ + }()) +#endif + +#define CONSTEVAL_STATIC_ASSERT(CHECK, MSG) /*NOLINT(cppcoreguidelines-macro-usage)*/ \ + do { /*NOLINT(cppcoreguidelines-avoid-do-while)*/ \ + if (utils::is_constant_evaluated()) { \ + CONSTEVAL_ONLY_STATIC_ASSERT(CHECK, MSG); \ + } else { \ + assert((CHECK) && (MSG)); \ + } \ + } while (false) + + +namespace const_utils { + + +#define PROPAGATE(val, V, E) /*NOLINT(cppcoreguidelines-macro-usage)*/ \ + do { /*NOLINT(cppcoreguidelines-avoid-do-while)*/ \ + if (not((val).has_value())) { \ + return const_utils::Expected::error_result((val).error()); \ + } \ + } while (false) + + // represents a sort of constexpr std::Expected + template + requires std::is_default_constructible_v && std::is_default_constructible_v + struct Expected { + private: + bool m_has_value; + V m_value; + E m_error; + + constexpr Expected( + bool has_value, + const V& value, + const E& error + ) //NOLINT(modernize-pass-by-value) + : m_has_value{ has_value }, + m_value{ value }, + m_error{ error } { } + + public: + [[nodiscard]] constexpr static Expected good_result(const V& type) { + return { true, type, E{} }; + } + + [[nodiscard]] constexpr static Expected error_result(const E& error) { + return { false, V{}, error }; + } + + [[nodiscard]] constexpr bool has_value() const { + return m_has_value; + } + + [[nodiscard]] constexpr V value() const { + CONSTEVAL_STATIC_ASSERT((has_value()), "value() call on Expected without value"); + + return m_value; + } + + [[nodiscard]] constexpr E error() const { + CONSTEVAL_STATIC_ASSERT((not has_value()), "error() call on Expected without error"); + + return m_error; + } + }; + +} // namespace const_utils diff --git a/src/helper/errors.cpp b/src/helper/errors.cpp index 88ec58e8..659dc7f0 100644 --- a/src/helper/errors.cpp +++ b/src/helper/errors.cpp @@ -1,13 +1,22 @@ #include "errors.hpp" -helper::GeneralError::GeneralError(const std::string& message, error::Severity severity) +helper::GeneralError::GeneralError(const std::string& message, error::Severity severity) noexcept : m_message{ message }, m_severity{ severity } { } -helper::GeneralError::GeneralError(std::string&& message, error::Severity severity) +helper::GeneralError::GeneralError(std::string&& message, error::Severity severity) noexcept : m_message{ std::move(message) }, m_severity{ severity } { } +helper::GeneralError::~GeneralError() = default; + +helper::GeneralError::GeneralError(const GeneralError& error) noexcept = default; +[[nodiscard]] helper::GeneralError& helper::GeneralError::operator=(const GeneralError& error) noexcept = default; + +helper::GeneralError::GeneralError(GeneralError&& error) noexcept = default; +[[nodiscard]] helper::GeneralError& helper::GeneralError::operator=(GeneralError&& error) noexcept = default; + + [[nodiscard]] const std::string& helper::GeneralError::message() const { return m_message; } @@ -16,14 +25,20 @@ helper::GeneralError::GeneralError(std::string&& message, error::Severity severi return m_severity; } -helper::FatalError::FatalError(const std::string& message) : GeneralError{ message, error::Severity::Fatal } { } +helper::FatalError::FatalError(const std::string& message) noexcept + : GeneralError{ message, error::Severity::Fatal } { } -helper::FatalError::FatalError(std::string&& message) : GeneralError{ std::move(message), error::Severity::Fatal } { } +helper::FatalError::FatalError(std::string&& message) noexcept + : GeneralError{ std::move(message), error::Severity::Fatal } { } -helper::MajorError::MajorError(const std::string& message) : GeneralError{ message, error::Severity::Major } { } +helper::MajorError::MajorError(const std::string& message) noexcept + : GeneralError{ message, error::Severity::Major } { } -helper::MajorError::MajorError(std::string&& message) : GeneralError{ std::move(message), error::Severity::Major } { } +helper::MajorError::MajorError(std::string&& message) noexcept + : GeneralError{ std::move(message), error::Severity::Major } { } -helper::MinorError::MinorError(const std::string& message) : GeneralError{ message, error::Severity::Minor } { } +helper::MinorError::MinorError(const std::string& message) noexcept + : GeneralError{ message, error::Severity::Minor } { } -helper::MinorError::MinorError(std::string&& message) : GeneralError{ std::move(message), error::Severity::Minor } { } +helper::MinorError::MinorError(std::string&& message) noexcept + : GeneralError{ std::move(message), error::Severity::Minor } { } diff --git a/src/helper/errors.hpp b/src/helper/errors.hpp index e6388be3..f1c6550f 100644 --- a/src/helper/errors.hpp +++ b/src/helper/errors.hpp @@ -17,30 +17,38 @@ namespace helper { error::Severity m_severity; public: - GeneralError(const std::string& message, error::Severity severity); + GeneralError(const std::string& message, error::Severity severity) noexcept; - GeneralError(std::string&& message, error::Severity severity); + GeneralError(std::string&& message, error::Severity severity) noexcept; + + ~GeneralError(); + + GeneralError(const GeneralError& error) noexcept; + [[nodiscard]] GeneralError& operator=(const GeneralError& error) noexcept; + + GeneralError(GeneralError&& error) noexcept; + [[nodiscard]] GeneralError& operator=(GeneralError&& error) noexcept; [[nodiscard]] const std::string& message() const; [[nodiscard]] error::Severity severity() const; }; struct FatalError : public GeneralError { - FatalError(const std::string& message); + explicit FatalError(const std::string& message) noexcept; - FatalError(std::string&& message); + explicit FatalError(std::string&& message) noexcept; }; struct MajorError : public GeneralError { - MajorError(const std::string& message); + explicit MajorError(const std::string& message) noexcept; - MajorError(std::string&& message); + explicit MajorError(std::string&& message) noexcept; }; struct MinorError : public GeneralError { - MinorError(const std::string& message); + explicit MinorError(const std::string& message) noexcept; - MinorError(std::string&& message); + explicit MinorError(std::string&& message) noexcept; }; using InitializationError = FatalError; diff --git a/src/helper/git_helper.hpp b/src/helper/git_helper.hpp index 5b2b1836..7a580b43 100644 --- a/src/helper/git_helper.hpp +++ b/src/helper/git_helper.hpp @@ -6,12 +6,9 @@ namespace utils { [[nodiscard]] inline std::string git_commit() { -#if defined(_HAS_GIT_COMMIT_INFORMATION) #include "git_version.hpp" + return GIT_VERSION; -#else - return "Unknown"; -#endif } } // namespace utils diff --git a/src/helper/meson.build b/src/helper/meson.build index 9cff7832..a8956e18 100644 --- a/src/helper/meson.build +++ b/src/helper/meson.build @@ -4,6 +4,7 @@ core_src_files += files( 'color.hpp', 'color_literals.hpp', 'command_line_arguments.hpp', + 'const_utils.hpp', 'constants.hpp', 'date.cpp', 'date.hpp', @@ -20,10 +21,11 @@ core_src_files += files( 'sleep.cpp', 'sleep.hpp', 'static_string.hpp', + 'string_manipulation.cpp', + 'string_manipulation.hpp', 'timer.hpp', 'types.hpp', 'utils.hpp', - ) graphics_src_files += files( @@ -36,29 +38,21 @@ graphics_src_files += files( 'message_box.cpp', 'message_box.hpp', 'music_utils.hpp', + 'platform.cpp', + 'platform.hpp', ) if have_file_dialogs graphics_src_files += files('nfd.cpp', 'nfd_include.hpp') endif -git = find_program('git', required: false) - -if git.found() - - vcs_dep = vcs_tag( - command: ['git', 'describe', '--tags', '--always', '--abbrev=12'], - input: 'git_version.hpp.in', - output: 'git_version.hpp', - replace_string: '@GIT_VERSION@', - ) +git = find_program('git') - graphics_src_files += vcs_dep - graphics_lib += { - 'compile_args': [ - graphics_lib.get('compile_args'), - '-D_HAS_GIT_COMMIT_INFORMATION', - ], - } -endif +vcs_dep = vcs_tag( + command: ['git', 'describe', '--tags', '--always', '--abbrev=12'], + input: 'git_version.hpp.in', + output: 'git_version.hpp', + replace_string: '@GIT_VERSION@', +) +graphics_src_files += vcs_dep diff --git a/src/helper/message_box.hpp b/src/helper/message_box.hpp index c56ca3a5..88a804e7 100644 --- a/src/helper/message_box.hpp +++ b/src/helper/message_box.hpp @@ -25,7 +25,7 @@ namespace helper { */ static void show_simple(Type type, const std::string& title, const std::string& content, SDL_Window* window); - //TODO: add option to use the complicated API, that supports more buttons and also a result which button was pressed! + //TODO(Totto): add option to use the complicated API, that supports more buttons and also a result which button was pressed! }; diff --git a/src/helper/nfd.cpp b/src/helper/nfd.cpp index b5f703dc..a840f498 100644 --- a/src/helper/nfd.cpp +++ b/src/helper/nfd.cpp @@ -20,22 +20,22 @@ namespace { [[nodiscard]] FilterItemType get_filter_items(const std::vector& allowed_files) { const auto size = static_cast(allowed_files.size()); - FilterItemType filterItem{ allowed_files.empty() ? nullptr : new nfdu8filteritem_t[size], - [size](const nfdu8filteritem_t* const value) { - if (value == nullptr) { - return; - } + FilterItemType filter_item{ allowed_files.empty() ? nullptr : new nfdu8filteritem_t[size], + [size](const nfdu8filteritem_t* const value) { + if (value == nullptr) { + return; + } - for (usize i = 0; i < size; ++i) { - const auto& item = - value[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + for (usize i = 0; i < size; ++i) { + const auto& item = + value[i]; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - delete item.name; // NOLINT(cppcoreguidelines-owning-memory) - delete item.spec; // NOLINT(cppcoreguidelines-owning-memory) - } + delete item.name; // NOLINT(cppcoreguidelines-owning-memory) + delete item.spec; // NOLINT(cppcoreguidelines-owning-memory) + } - delete[] value; // NOLINT(cppcoreguidelines-owning-memory) - } }; + delete[] value; // NOLINT(cppcoreguidelines-owning-memory) + } }; if (not allowed_files.empty()) { @@ -55,11 +55,11 @@ namespace { auto* extensions = new nfdu8char_t[extension_list_size]; // NOLINT(cppcoreguidelines-owning-memory) std::memcpy(extensions, extension_list.c_str(), extension_list_size * sizeof(nfdu8char_t)); - filterItem.get()[i] = { name, extensions }; + filter_item.get()[i] = { name, extensions }; } } - return filterItem; + return filter_item; } // namespace @@ -72,8 +72,8 @@ helper::expected helper::openFileDialog( helper::optional default_path ) { - NFD::UniquePathU8 outPath{}; - auto filterItem = get_filter_items(allowed_files); + NFD::UniquePathU8 out_path{}; + auto filter_item = get_filter_items(allowed_files); const auto path_deallocator = [](const nfdu8char_t* const char_value) { if (char_value == nullptr) { @@ -93,10 +93,10 @@ helper::expected helper::openFileDialog( } const nfdresult_t result = NFD::OpenDialog( - outPath, filterItem.get(), static_cast(allowed_files.size()), default_path_value.get() + out_path, filter_item.get(), static_cast(allowed_files.size()), default_path_value.get() ); if (result == NFD_OKAY) { - return std::filesystem::path{ outPath.get() }; + return std::filesystem::path{ out_path.get() }; } if (result == NFD_CANCEL) { @@ -112,8 +112,8 @@ helper::expected helper::openFileDialog( helper::optional default_path ) { - NFD::UniquePathSet outPaths{}; - auto filterItem = get_filter_items(allowed_files); + NFD::UniquePathSet out_paths{}; + auto filter_item = get_filter_items(allowed_files); const auto path_deallocator = [](const nfdu8char_t* const char_value) { if (char_value == nullptr) { @@ -133,20 +133,20 @@ helper::expected helper::openFileDialog( } const nfdresult_t result = NFD::OpenDialogMultiple( - outPaths, filterItem.get(), static_cast(allowed_files.size()), default_path_value.get() + out_paths, filter_item.get(), static_cast(allowed_files.size()), default_path_value.get() ); if (result == NFD_OKAY) { std::vector result_vector{}; nfdpathsetsize_t count_paths{}; - const auto temp_result = NFD::PathSet::Count(outPaths, count_paths); + const auto temp_result = NFD::PathSet::Count(out_paths, count_paths); ASSERT(temp_result == NFD_OKAY && "PathSet get count is successful"); for (nfdpathsetsize_t i = 0; i < count_paths; ++i) { - NFD::UniquePathSetPathU8 outPath{}; - const auto temp_result2 = NFD::PathSet::GetPath(outPaths, i, outPath); + NFD::UniquePathSetPathU8 out_path{}; + const auto temp_result2 = NFD::PathSet::GetPath(out_paths, i, out_path); ASSERT(temp_result2 == NFD_OKAY && "PathSet get path is successful"); - result_vector.emplace_back(outPath.get()); + result_vector.emplace_back(out_path.get()); } return result_vector; @@ -163,7 +163,7 @@ helper::expected helper::openFileDialog( helper::optional default_path ) { - NFD::UniquePathU8 outPath{}; + NFD::UniquePathU8 out_path{}; const auto path_deallocator = [](const nfdu8char_t* const char_value) { if (char_value == nullptr) { @@ -183,9 +183,9 @@ helper::expected helper::openFileDialog( std::memcpy(default_path_value.get(), str.c_str(), str_size * sizeof(nfdu8char_t)); } - const nfdresult_t result = NFD::PickFolder(outPath, default_path_value.get()); + const nfdresult_t result = NFD::PickFolder(out_path, default_path_value.get()); if (result == NFD_OKAY) { - return std::filesystem::path{ outPath.get() }; + return std::filesystem::path{ out_path.get() }; } if (result == NFD_CANCEL) { diff --git a/src/helper/parse_json.cpp b/src/helper/parse_json.cpp index a7434863..76415fc5 100644 --- a/src/helper/parse_json.cpp +++ b/src/helper/parse_json.cpp @@ -34,21 +34,21 @@ std::string json::get_json_type(const nlohmann::json::value_t& type) { } } -void json::check_for_no_additional_keys(const nlohmann::json& j, const std::vector& keys) { +void json::check_for_no_additional_keys(const nlohmann::json& obj, const std::vector& keys) { - if (not j.is_object()) { + if (not obj.is_object()) { throw nlohmann::json::type_error::create( - 302, fmt::format("expected an object, but got type '{}'", get_json_type(j.type())), &j + 302, fmt::format("expected an object, but got type '{}'", get_json_type(obj.type())), &obj ); } - const auto& object = j.get(); + const auto& object = obj.get(); for (const auto& [key, _] : object) { - if (std::find(keys.cbegin(), keys.cend(), key) == keys.cend()) { + if (std::ranges::find(keys, key) == keys.cend()) { throw nlohmann::json::type_error::create( - 302, fmt::format("object may only contain expected keys, but contained '{}'", key), &j + 302, fmt::format("object may only contain expected keys, but contained '{}'", key), &obj ); } } diff --git a/src/helper/parse_json.hpp b/src/helper/parse_json.hpp index 2ebaf5b4..c601a77b 100644 --- a/src/helper/parse_json.hpp +++ b/src/helper/parse_json.hpp @@ -23,21 +23,21 @@ NLOHMANN_JSON_NAMESPACE_BEGIN template struct adl_serializer> { - static void to_json(json& j, const helper::optional& opt) { + static void to_json(json& obj, const helper::optional& opt) { if (not opt) { - j = nullptr; + obj = nullptr; } else { - j = *opt; // this will call adl_serializer::to_json which will - // find the free function to_json in T's namespace! + obj = *opt; // this will call adl_serializer::to_json which will + // find the free function to_json in T's namespace! } } - static void from_json(const json& j, helper::optional& opt) { - if (j.is_null()) { + static void from_json(const json& obj, helper::optional& opt) { + if (obj.is_null()) { opt = helper::nullopt; } else { - opt = j.template get(); // same as above, but with - // adl_serializer::from_json + opt = obj.template get(); // same as above, but with + // adl_serializer::from_json } } }; @@ -111,7 +111,7 @@ namespace json { std::string get_json_type(const nlohmann::json::value_t& type); - void check_for_no_additional_keys(const nlohmann::json& j, const std::vector& keys); + void check_for_no_additional_keys(const nlohmann::json& obj, const std::vector& keys); } // namespace json diff --git a/src/helper/platform.cpp b/src/helper/platform.cpp new file mode 100644 index 00000000..9922cc96 --- /dev/null +++ b/src/helper/platform.cpp @@ -0,0 +1,98 @@ + +#include "platform.hpp" + +#if defined(__CONSOLE__) +#include "helper/console_helpers.hpp" +#include "input/console_buttons.hpp" +#endif + +#include +#include +#include +#include + +namespace { + + inline std::string get_error_from_errno() { + +#if defined(_MSC_VER) + char buffer[256] = { 0 }; + const auto result = strerror_s<256>(buffer, errno); + + if (result == 0) { + return std::string{ buffer }; + + } else { + return std::string{ "Error while getting error!" }; + } + +#else + return std::string{ std::strerror(errno) }; + +#endif + } + + +} // namespace + + +[[nodiscard]] std::string utils::built_for_platform() { +#if defined(__ANDROID__) + return "Android"; +#elif defined(__SWITCH__) + return "Nintendo Switch"; +#elif defined(__3DS__) + return "Nintendo 3DS"; +#elif defined(FLATPAK_BUILD) + return "Linux (Flatpak)"; +#elif defined(__linux__) + return "Linux"; +#elif defined(_WIN32) + return "Windows"; +#elif defined(__APPLE__) + return "MacOS"; +#else +#error "Unsupported platform" +#endif +} + +// partially from: https://stackoverflow.com/questions/17347950/how-do-i-open-a-url-from-c +[[nodiscard]] bool utils::open_url(const std::string& url) { +#if defined(__ANDROID__) + const auto result = SDL_OpenURL(url.c_str()); + if (result < 0) { + spdlog::error("Error in opening url in android: {}", SDL_GetError()); + return false; + } + + return true; + +#elif defined(__CONSOLE__) + auto result = console::open_url(url); + spdlog::info("Returned string from url open was: {}", result); + return true; +#else + //TODO(Totto): this is dangerous, if we supply user input, so use SDL_OpenURL preferably + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + const std::string shell_command = "start " + url; +#elif defined(__APPLE__) + const std::string shell_command = "open " + url; +#elif defined(__linux__) + const std::string shell_command = "xdg-open " + url; +#else +#error "Unsupported platform" +#endif + + const auto result = system(shell_command.c_str()); //NOLINT(cert-env33-c) + if (result < 0) { + spdlog::error("Error in opening url: {}", get_error_from_errno()); + return false; + } + + + return true; + + +#endif +} diff --git a/src/helper/platform.hpp b/src/helper/platform.hpp new file mode 100644 index 00000000..42e7249f --- /dev/null +++ b/src/helper/platform.hpp @@ -0,0 +1,45 @@ + + +#pragma once + +#include "helper/types.hpp" + +#include + +enum class Platform : u8 { PC, Android, Console }; + + +namespace utils { + + constexpr Platform get_platform() { + +#if defined(__ANDROID__) + return Platform::Android; +#elif defined(__CONSOLE__) + return Platform::Console; +#else + return Platform::PC; +#endif + }; + + + enum class Orientation : u8 { + Portrait, // 9x16, e.g. smartphone + Landscape // 16x9 + }; + + + constexpr Orientation get_orientation() { +#if defined(__ANDROID__) + return Orientation::Portrait; +#else + return Orientation::Landscape; +#endif + } + + + [[nodiscard]] std::string built_for_platform(); + + [[nodiscard]] bool open_url(const std::string& url); + +} // namespace utils diff --git a/src/helper/static_string.hpp b/src/helper/static_string.hpp index 878a8cc4..5b4a96e0 100644 --- a/src/helper/static_string.hpp +++ b/src/helper/static_string.hpp @@ -72,8 +72,8 @@ struct StaticString { template> [[nodiscard]] constexpr Result operator+(const StaticString& other) const { auto concatenated = Result{}; - std::copy(cbegin(), cend(), concatenated.begin()); - std::copy(other.cbegin(), other.cend(), concatenated.begin() + size()); + std::ranges::copy(*this, concatenated.begin()); + std::ranges::copy(other, concatenated.begin() + size()); concatenated.back() = '\0'; return concatenated; } diff --git a/src/helper/string_manipulation.cpp b/src/helper/string_manipulation.cpp new file mode 100644 index 00000000..881ae20c --- /dev/null +++ b/src/helper/string_manipulation.cpp @@ -0,0 +1,62 @@ + +#include "string_manipulation.hpp" + +#include +#include + +std::string string::to_lower_case(const std::string& input) { + auto result = input; + for (auto& elem : result) { + elem = static_cast(std::tolower(elem)); + } + + return result; +} + +std::string string::to_upper_case(const std::string& input) { + auto result = input; + for (auto& elem : result) { + elem = static_cast(std::toupper(elem)); + } + + return result; +} + + +// for string delimiter +std::vector string::split_string_by_char(const std::string& start, const std::string& delimiter) { + size_t pos_start = 0; + size_t pos_end = 0; + const auto delim_len = delimiter.length(); + + std::vector res{}; + + while ((pos_end = start.find(delimiter, pos_start)) != std::string::npos) { + auto token = start.substr(pos_start, pos_end - pos_start); + pos_start = pos_end + delim_len; + res.push_back(token); + } + + res.push_back(start.substr(pos_start)); + return res; +} + + +void string::ltrim(std::string& str) { + str.erase(str.begin(), std::ranges::find_if(str, [](unsigned char chr) { return std::isspace(chr) == 0; })); +} + + +void string::rtrim(std::string& str) { + str.erase( + std::ranges::find_if( + std::ranges::reverse_view(str), [](unsigned char chr) { return std::isspace(chr) == 0; } + ).base(), + str.end() + ); +} + +void string::trim(std::string& str) { + ltrim(str); + rtrim(str); +} diff --git a/src/helper/string_manipulation.hpp b/src/helper/string_manipulation.hpp new file mode 100644 index 00000000..9debc4ec --- /dev/null +++ b/src/helper/string_manipulation.hpp @@ -0,0 +1,24 @@ + +#pragma once + +#include +#include + +namespace string { + + std::string to_lower_case(const std::string& input); + + std::string to_upper_case(const std::string& input); + + // for string delimiter + std::vector split_string_by_char(const std::string& start, const std::string& delimiter); + + // trim from start (in place) + void ltrim(std::string& str); + + // trim from end (in place) + void rtrim(std::string& str); + + void trim(std::string& str); + +} // namespace string diff --git a/src/helper/utils.hpp b/src/helper/utils.hpp index 814ce024..3eeebb51 100644 --- a/src/helper/utils.hpp +++ b/src/helper/utils.hpp @@ -7,10 +7,11 @@ #include #include #include -#include -#include +#include +#include +#include #include -#include +#include namespace helper { @@ -58,11 +59,27 @@ namespace utils { return static_cast>(enum_); } +#if __cpp_lib_unreachable >= 202202L [[noreturn]] inline void unreachable() { - assert(false and "unreachable"); - std::terminate(); +#if !defined(NDEBUG) + std::cerr << "UNREACHABLE\n"; +#endif + + std::unreachable(); } +#else + [[noreturn]] inline void unreachable() { +#if !defined(NDEBUG) + std::cerr << "UNREACHABLE\n"; +#endif +#if defined(_MSC_VER) && !defined(__clang__) // MSVC + __assume(false); +#else // GCC, Clang + __builtin_unreachable(); +#endif + } +#endif template struct size_t_identity { //using type = T; @@ -84,6 +101,28 @@ namespace utils { } + template + constexpr helper::optional is_child_class(S* parent) { + auto* result = dynamic_cast(parent); + + if (result == nullptr) { + return helper::nullopt; + } + + return result; + } + + template + constexpr helper::optional is_child_class(const std::unique_ptr& parent) { + return is_child_class(parent.get()); + } + + template + constexpr helper::optional is_child_class(const std::shared_ptr& parent) { + return is_child_class(parent.get()); + } + + } // namespace utils @@ -94,11 +133,3 @@ namespace utils { #else #define ASSERT(x) assert(x) // NOLINT(cppcoreguidelines-macro-usage) #endif - -// define a consteval assert, it isn't a pretty error message, but there's nothing we can do against that atm :( -// this https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2758r2.html tries to fix it -#define CONSTEVAL_STATIC_ASSERT(CHECK, MSG) /*NOLINT(cppcoreguidelines-macro-usage)*/ \ - ((CHECK) ? void(0) : [] { \ - /* If you see this really bad c++ error message, follow the origin of MSG, to see the real error message, c++ error messages suck xD */ \ - throw(MSG); \ - }()) diff --git a/src/platform/console_buttons.hpp b/src/input/console_buttons.hpp similarity index 100% rename from src/platform/console_buttons.hpp rename to src/input/console_buttons.hpp diff --git a/src/platform/input.cpp b/src/input/game_input.cpp similarity index 70% rename from src/platform/input.cpp rename to src/input/game_input.cpp index 5085e28e..18e87ad2 100644 --- a/src/platform/input.cpp +++ b/src/input/game_input.cpp @@ -1,53 +1,54 @@ -#include "input.hpp" #include "game/tetrion.hpp" +#include "input.hpp" #include "manager/event_dispatcher.hpp" -#include "manager/key_codes.hpp" #include -void Input::handle_event(const InputEvent event, const SimulationStep simulation_step_index) { +void input::GameInput::handle_event(const InputEvent event, const SimulationStep simulation_step_index) { if (m_on_event_callback) { m_on_event_callback(event, simulation_step_index); } switch (event) { case InputEvent::RotateLeftPressed: - m_target_tetrion->handle_input_command(InputCommand::RotateLeft, simulation_step_index); + m_target_tetrion->handle_input_command(GameInputCommand::RotateLeft, simulation_step_index); break; case InputEvent::RotateRightPressed: - m_target_tetrion->handle_input_command(InputCommand::RotateRight, simulation_step_index); + m_target_tetrion->handle_input_command(GameInputCommand::RotateRight, simulation_step_index); break; case InputEvent::MoveLeftPressed: if (not supports_das()) { - m_target_tetrion->handle_input_command(InputCommand::MoveLeft, simulation_step_index); + m_target_tetrion->handle_input_command(GameInputCommand::MoveLeft, simulation_step_index); } else { m_keys_hold[HoldableKey::Left] = simulation_step_index + delayed_auto_shift_frames; if (not m_keys_hold.contains(HoldableKey::Right) - and not m_target_tetrion->handle_input_command(InputCommand::MoveLeft, simulation_step_index)) { + and not m_target_tetrion->handle_input_command(GameInputCommand::MoveLeft, simulation_step_index)) { m_keys_hold[HoldableKey::Left] = simulation_step_index; } } break; case InputEvent::MoveRightPressed: if (not supports_das()) { - m_target_tetrion->handle_input_command(InputCommand::MoveRight, simulation_step_index); + m_target_tetrion->handle_input_command(GameInputCommand::MoveRight, simulation_step_index); } else { m_keys_hold[HoldableKey::Right] = simulation_step_index + delayed_auto_shift_frames; if (not m_keys_hold.contains(HoldableKey::Left) - and not m_target_tetrion->handle_input_command(InputCommand::MoveRight, simulation_step_index)) { + and not m_target_tetrion->handle_input_command( + GameInputCommand::MoveRight, simulation_step_index + )) { m_keys_hold[HoldableKey::Right] = simulation_step_index; } } break; case InputEvent::MoveDownPressed: - m_target_tetrion->handle_input_command(InputCommand::MoveDown, simulation_step_index); + m_target_tetrion->handle_input_command(GameInputCommand::MoveDown, simulation_step_index); break; case InputEvent::DropPressed: - m_target_tetrion->handle_input_command(InputCommand::Drop, simulation_step_index); + m_target_tetrion->handle_input_command(GameInputCommand::Drop, simulation_step_index); break; case InputEvent::HoldPressed: - m_target_tetrion->handle_input_command(InputCommand::Hold, simulation_step_index); + m_target_tetrion->handle_input_command(GameInputCommand::Hold, simulation_step_index); break; case InputEvent::MoveLeftReleased: if (supports_das()) { @@ -60,7 +61,7 @@ void Input::handle_event(const InputEvent event, const SimulationStep simulation } break; case InputEvent::MoveDownReleased: - m_target_tetrion->handle_input_command(InputCommand::ReleaseMoveDown, simulation_step_index); + m_target_tetrion->handle_input_command(GameInputCommand::ReleaseMoveDown, simulation_step_index); break; case InputEvent::RotateLeftReleased: case InputEvent::RotateRightReleased: @@ -72,7 +73,7 @@ void Input::handle_event(const InputEvent event, const SimulationStep simulation } } -void Input::update(const SimulationStep simulation_step_index) { +void input::GameInput::update(const SimulationStep simulation_step_index) { const auto current_simulation_step_index = simulation_step_index; const auto is_left_key_down = m_keys_hold.contains(HoldableKey::Left); @@ -87,9 +88,10 @@ void Input::update(const SimulationStep simulation_step_index) { target_simulation_step_index += auto_repeat_rate_frames; } if ((key == HoldableKey::Left - and not m_target_tetrion->handle_input_command(InputCommand::MoveLeft, simulation_step_index)) + and not m_target_tetrion->handle_input_command(GameInputCommand::MoveLeft, simulation_step_index)) or (key == HoldableKey::Right - and not m_target_tetrion->handle_input_command(InputCommand::MoveRight, simulation_step_index))) { + and not m_target_tetrion->handle_input_command(GameInputCommand::MoveRight, simulation_step_index) + )) { target_simulation_step_index = current_simulation_step_index + delayed_auto_shift_frames; } } diff --git a/src/input/game_input.hpp b/src/input/game_input.hpp new file mode 100644 index 00000000..8f8bcd79 --- /dev/null +++ b/src/input/game_input.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "helper/clock_source.hpp" +#include "helper/optional.hpp" +#include "helper/random.hpp" +#include "helper/types.hpp" +#include "manager/event_listener.hpp" +#include "manager/input_event.hpp" + +#include +#include + +struct Tetrion; + +namespace input { + + enum class GameInputCommand : u8 { + MoveLeft, + MoveRight, + MoveDown, + RotateLeft, + RotateRight, + Drop, + Hold, + ReleaseMoveDown, + }; + + enum class GameInputType : u8 { Touch, Keyboard, Controller, Recording }; + + enum class MenuEvent : u8 { OpenSettings, Pause }; + + + struct GameInput { + public: + using OnEventCallback = std::function; + + private: + enum class HoldableKey : u8 { + Left, + Right, + }; + + static constexpr u64 delayed_auto_shift_frames = 10; + static constexpr u64 auto_repeat_rate_frames = 2; + + std::unordered_map m_keys_hold; + GameInputType m_input_type; + Tetrion* m_target_tetrion{}; + OnEventCallback m_on_event_callback; + + protected: + explicit GameInput(GameInputType input_type) : m_input_type{ input_type } { } + + void handle_event(InputEvent event, SimulationStep simulation_step_index); + + [[nodiscard]] const Tetrion* target_tetrion() const { + return m_target_tetrion; + } + + [[nodiscard]] const OnEventCallback& on_event_callback() const { + return m_on_event_callback; + } + + public: + virtual void update(SimulationStep simulation_step_index); + virtual void late_update(SimulationStep /*simulation_step*/){}; + + [[nodiscard]] virtual helper::optional get_menu_event(const SDL_Event& event) const = 0; + + [[nodiscard]] virtual std::string describe_menu_event(MenuEvent event) const = 0; + + [[nodiscard]] GameInputType input_type() const { + return m_input_type; + } + + [[nodiscard]] bool supports_das() const { + // todo support das with hold in touch mode + return m_input_type != GameInputType::Touch; + } + + void set_target_tetrion(Tetrion* target_tetrion) { + m_target_tetrion = target_tetrion; + } + + void set_event_callback(OnEventCallback on_event_callback) { + m_on_event_callback = std::move(on_event_callback); + } + + GameInput(const GameInput&) = delete; + GameInput& operator=(const GameInput&) = delete; + + GameInput(GameInput&&) = default; + GameInput& operator=(GameInput&&) = default; + + virtual ~GameInput() = default; + }; +} // namespace input diff --git a/src/input/guid.cpp b/src/input/guid.cpp new file mode 100644 index 00000000..67014565 --- /dev/null +++ b/src/input/guid.cpp @@ -0,0 +1,39 @@ + +#include "guid.hpp" +#include "helper/utils.hpp" + +#include +#include + +sdl::GUID::GUID(const SDL_GUID& data) : m_guid{} { + std::copy(std::begin(data.data), std::end(data.data), std::begin(m_guid)); +} + +[[nodiscard]] helper::expected sdl::GUID::from_string(const std::string& value) { + + const auto result = detail::get_guid_from_string(value); + + if (result.has_value()) { + return result.value(); + } + + return helper::unexpected{ result.error() }; +} + +[[nodiscard]] bool sdl::GUID::operator==(const GUID& other) const { + return m_guid == other.m_guid; +} + + +[[nodiscard]] std::string sdl::GUID::to_string(FormatType type) const { + switch (type) { + case FormatType::Long: { + return fmt::format("{:02x}", fmt::join(m_guid, ":")); + } + case FormatType::Short: { + return fmt::format("{:02x}", fmt::join(m_guid, "")); + } + default: + utils::unreachable(); + } +} diff --git a/src/input/guid.hpp b/src/input/guid.hpp new file mode 100644 index 00000000..274b9f70 --- /dev/null +++ b/src/input/guid.hpp @@ -0,0 +1,147 @@ +#pragma once + + +#include "helper/const_utils.hpp" +#include "helper/expected.hpp" +#include "helper/types.hpp" + +#include +#include +#include +#include + +extern "C" { +#if SDL_MAJOR_VERSION < 2 || SDL_MINOR_VERSION < 30 || SDL_PATCHLEVEL < 0 +typedef SDL_JoystickGUID SDL_GUID; //NOLINT(modernize-use-using), it's used in extern C, there is no using +#endif +} + + +namespace sdl { + + struct GUID { + public: + using ArrayType = std::array; + + private: + ArrayType m_guid; + + public: + enum class FormatType : u8 { Long, Short }; + + constexpr GUID() : m_guid{} { } + explicit constexpr GUID(const ArrayType& data) : m_guid{ data } { } + + explicit GUID(const SDL_GUID& data); + + [[nodiscard]] static helper::expected from_string(const std::string& value); + + [[nodiscard]] bool operator==(const GUID& other) const; + + [[nodiscard]] std::string to_string(FormatType type = FormatType::Long) const; + }; +} // namespace sdl + + +template<> +struct fmt::formatter : formatter { + auto format(const sdl::GUID& guid, format_context& ctx) { + return formatter::format(guid.to_string(), ctx); + } +}; + +namespace { //NOLINT(cert-dcl59-cpp,google-build-namespaces) + + // decode a single_hex_number + [[nodiscard]] constexpr const_utils::Expected single_hex_number(char input) { + if (input >= '0' && input <= '9') { + return const_utils::Expected::good_result(static_cast(input - '0')); + } + + if (input >= 'A' && input <= 'F') { + return const_utils::Expected::good_result(static_cast(input - 'A' + 10)); + } + + if (input >= 'a' && input <= 'f') { + return const_utils::Expected::good_result(static_cast(input - 'a' + 10)); + } + + return const_utils::Expected::error_result("the input must be a valid hex character"); + } + + // decode a single 2 digit color value in hex + [[nodiscard]] constexpr const_utils::Expected single_hex_color_value(const char* input) { + + const auto first = single_hex_number(input[0]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + PROPAGATE(first, u8, std::string); + + const auto second = single_hex_number(input[1]); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + PROPAGATE(second, u8, std::string); + + return const_utils::Expected::good_result((first.value() << 4) | second.value()); + } + + [[nodiscard]] constexpr const_utils::Expected + get_guid_from_string_impl(const char* input, std::size_t size) { + + if (size == 0) { + return const_utils::Expected::error_result( + "not enough data to determine the literal type" + ); + } + + constexpr std::size_t amount = 16; + + size_t width = 2; + + if (size == amount * 2) { + width = 2; + } else if (size == (amount * 2 + (amount - 1))) { + width = 3; + } else { + + return const_utils::Expected::error_result("Unrecognized guid literal"); + } + + + sdl::GUID::ArrayType result{}; + + for (size_t i = 0; i < amount; ++i) { + const size_t offset = i * width; + + + const auto temp_result = + single_hex_color_value(input + offset); //NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + PROPAGATE(temp_result, sdl::GUID, std::string); + + const auto value = temp_result.value(); + + result.at(i) = value; + } + + return const_utils::Expected::good_result(sdl::GUID{ result }); + } + +} // namespace + + +namespace detail { + + [[nodiscard]] constexpr const_utils::Expected get_guid_from_string(const std::string& input + ) { + return get_guid_from_string_impl(input.c_str(), input.size()); + } + +} // namespace detail + + +consteval sdl::GUID operator""_guid(const char* input, std::size_t size) { + const auto result = get_guid_from_string_impl(input, size); + + CONSTEVAL_STATIC_ASSERT(result.has_value(), "incorrect guid literal"); + + return result.value(); +} diff --git a/src/input/input.cpp b/src/input/input.cpp new file mode 100644 index 00000000..6a18a18b --- /dev/null +++ b/src/input/input.cpp @@ -0,0 +1,364 @@ +#include "input.hpp" +#include "helper/expected.hpp" +#include "helper/optional.hpp" +#include "helper/utils.hpp" +#include "joystick_input.hpp" +#include "keyboard_input.hpp" +#include "manager/settings_manager.hpp" +#include "mouse_input.hpp" +#include "touch_input.hpp" + +#include +#include +#include +#include +#include +#include + + +input::Input::Input(std::string name, InputType type) : m_name{ std::move(name) }, m_type{ type } { } + +input::Input::~Input() = default; + +input::Input::Input(const Input& input) noexcept = default; +[[nodiscard]] input::Input& input::Input::operator=(const Input& input) noexcept = default; + +input::Input::Input(Input&& input) noexcept = default; +[[nodiscard]] input::Input& input::Input::operator=(Input&& input) noexcept = default; + +input::PointerEventHelper::PointerEventHelper(shapes::IPoint pos, PointerEvent event) + : m_pos{ pos }, + m_event{ event } { } + +[[nodiscard]] input::PointerEvent input::PointerEventHelper::event() const { + return m_event; +} + +[[nodiscard]] shapes::IPoint input::PointerEventHelper::position() const { + return m_pos; +} + +[[nodiscard]] bool input::PointerEventHelper::is_in(const shapes::URect& rect) const { + using Type = decltype(m_pos)::Type; + + assert(rect.top_left.x <= static_cast(std::numeric_limits::max())); + assert(rect.top_left.y <= static_cast(std::numeric_limits::max())); + assert(rect.bottom_right.x <= static_cast(std::numeric_limits::max())); + assert(rect.bottom_right.y <= static_cast(std::numeric_limits::max())); + + return is_in(rect.cast()); +} + +[[nodiscard]] bool input::PointerEventHelper::is_in(const shapes::IRect& rect) const { + const auto rect_start_x = rect.top_left.x; + const auto rect_start_y = rect.top_left.y; + const auto rect_end_x = rect.bottom_right.x; + const auto rect_end_y = rect.bottom_right.y; + + + const bool is_in = + (m_pos.x >= rect_start_x and m_pos.x <= rect_end_x and m_pos.y >= rect_start_y and m_pos.y <= rect_end_y); + + return is_in; +} + +[[nodiscard]] bool input::PointerEventHelper::operator==(PointerEvent event) const { + return m_event == event; +} + +input::InputManager::InputManager(const std::shared_ptr& window) { + + //initialize mouse input + m_inputs.push_back(std::make_unique()); + + //initialize keyboard input + m_inputs.push_back(std::make_unique()); + + //initialize touch inputs by using the manager(needs window) + input::TouchInputManager::discover_devices(m_inputs, window); + + //initialize joystick inputs by using the manager + input::JoyStickInputManager::discover_devices(m_inputs); +} + +[[nodiscard]] const std::vector>& input::InputManager::inputs() const { + return m_inputs; +} + + +[[nodiscard]] helper::optional input::InputManager::get_navigation_event(const SDL_Event& event +) const { + for (const auto& input : m_inputs) { + + if (const auto navigation_event = input->get_navigation_event(event); navigation_event.has_value()) { + return navigation_event; + } + } + + return helper::nullopt; +} + +[[nodiscard]] helper::optional input::InputManager::get_pointer_event(const SDL_Event& event +) const { + for (const auto& input : m_inputs) { + if (const auto pointer_input = utils::is_child_class(input); pointer_input.has_value()) { + if (const auto pointer_event = pointer_input.value()->get_pointer_event(event); pointer_event.has_value()) { + return pointer_event; + } + } + } + + return helper::nullopt; +} + + +[[nodiscard]] SDL_Event input::InputManager::offset_pointer_event(const SDL_Event& event, const shapes::IPoint& point) + const { + for (const auto& input : m_inputs) { + if (const auto pointer_input = utils::is_child_class(input); pointer_input.has_value()) { + if (const auto pointer_event = pointer_input.value()->get_pointer_event(event); pointer_event.has_value()) { + return pointer_input.value()->offset_pointer_event(event, point); + } + } + } + + throw std::runtime_error("Tried to offset event, that is no pointer event"); +} + + +[[nodiscard]] helper::BoolWrapper input::InputManager::process_special_inputs( + const SDL_Event& event +) { + switch (event.type) { + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_LEAVE: { + return { true, SpecialRequest::WindowFocusLost }; + } + default: + break; + } + break; + default: + break; + } + + + const auto is_joystick_special_input = input::JoyStickInputManager::process_special_inputs(event, m_inputs); + + if (is_joystick_special_input) { + return { true, SpecialRequest::InputsChanged }; + } + + return false; +} + +namespace { +#if defined(__ANDROID__) + using PrimaryInputType = input::TouchInput; +#elif defined(__CONSOLE__) + using PrimaryInputType = input::JoystickInput; +#else + using PrimaryInputType = input::KeyboardInput; +#endif +} // namespace + + +namespace { + //TODO(Totto): use smart pointer for the event dispatcher + helper::optional> + get_game_input_by_setting(ServiceProvider* service_provider, const Controls& control) { + + using ReturnType = helper::expected, std::string>; + + + auto result = std::visit( + helper::overloaded{ + [service_provider](const input::KeyboardSettings& keyboard_settings) mutable -> ReturnType { + auto* const event_dispatcher = &(service_provider->event_dispatcher()); + return std::make_shared(keyboard_settings, event_dispatcher); + }, + [service_provider](const input::JoystickSettings& joystick_settings) mutable -> ReturnType { + auto* const event_dispatcher = &(service_provider->event_dispatcher()); + auto input = input::JoystickGameInput::get_game_input_by_settings( + service_provider->input_manager(), event_dispatcher, joystick_settings + ); + + if (not input.has_value()) { + return helper::unexpected{ + fmt::format("Not possible to get joystick by settings: {}", input.error()) + }; + } + + + return input.value(); + }, + [service_provider](const input::TouchSettings& touch_settings) mutable -> ReturnType { + //TODO(Totto): make it dynamic, which touch input to use + + for (const auto& input : service_provider->input_manager().inputs()) { + if (const auto pointer_input = utils::is_child_class(input); + pointer_input.has_value()) { + auto* const event_dispatcher = &(service_provider->event_dispatcher()); + return std::make_shared( + touch_settings, event_dispatcher, pointer_input.value() + ); + } + } + + return helper::unexpected{ + "No TouchInput was found, so no TouchGameInput can be created" + }; + } }, + control + ); + + + if (result.has_value()) { + return std::move(result.value()); + } + + return helper::nullopt; + } + + + template + T get_settings_or_default(const std::vector& controls) { + for (const auto& control : controls) { + + const T* retrieved = std::get_if(&control); + + if (retrieved != nullptr) { + return *retrieved; + } + } + + + return T::default_settings(); + } + + + input::JoystickSettings + get_settings_or_default_joystick(const input::JoystickInput* input, const std::vector& controls) { + + for (const auto& control : controls) { + + const auto* retrieved = std::get_if(&control); + + if (retrieved != nullptr) { + if (retrieved->identification.guid == input->guid()) { + return *retrieved; + } + } + } + + + return input->default_settings(); + } + + + helper::optional> + get_game_input_by_input(ServiceProvider* service_provider, const std::unique_ptr& input) { + + const auto& settings = service_provider->settings_manager().settings(); + + + if (const auto keyboard_input = utils::is_child_class(input); + keyboard_input.has_value()) { + + const auto keyboard_settings = get_settings_or_default(settings.controls); + + auto* const event_dispatcher = &(service_provider->event_dispatcher()); + return std::make_shared(keyboard_settings, event_dispatcher); + } + + + if (const auto joystick_input = utils::is_child_class(input); + joystick_input.has_value()) { + + const auto joystick_settings = get_settings_or_default_joystick(joystick_input.value(), settings.controls); + + auto* const event_dispatcher = &(service_provider->event_dispatcher()); + + auto game_input = input::JoystickGameInput::get_game_input_by_settings( + service_provider->input_manager(), event_dispatcher, joystick_settings + ); + + if (not game_input.has_value()) { + spdlog::warn("Not possible to get joystick by settings: {}", game_input.error()); + return helper::nullopt; + } + + + return game_input.value(); + } + + if (const auto touch_input = utils::is_child_class(input); touch_input.has_value()) { + + const auto touch_settings = get_settings_or_default(settings.controls); + + auto* const event_dispatcher = &(service_provider->event_dispatcher()); + + return std::make_shared(touch_settings, event_dispatcher, touch_input.value()); + } + + + return helper::nullopt; + } + + +} // namespace + + +[[nodiscard]] helper::optional> +input::InputManager::get_game_input( //NOLINT(readability-convert-member-functions-to-static) + ServiceProvider* service_provider +) { + + const auto& settings = service_provider->settings_manager().settings(); + + // 1. If we have a fixed index, by settings, we use that, if it fails, we don#t try anything other + + if (settings.selected.has_value()) { + const auto index = settings.selected.value(); + if (settings.controls.size() >= index) { + return helper::nullopt; + } + + return get_game_input_by_setting(service_provider, settings.controls.at(index)); + } + + + // 2. We use the primary input for this platform + + for (const auto& input : service_provider->input_manager().inputs()) { + if (auto primary_input = utils::is_child_class(input); primary_input.has_value()) { + auto result = get_game_input_by_input(service_provider, input); + if (result.has_value()) { + return result; + } + } + } + + + // 3. we fail, since no suitable input could be found + + return helper::nullopt; +} + +[[nodiscard]] const std::unique_ptr& input::InputManager::get_primary_input() { + + for (const auto& input : m_inputs) { + if (const auto pointer_input = utils::is_child_class(input); pointer_input.has_value()) { + return input; + } + } + + // this should always be true, since in the initialization the first one, that is ALWAYS added is the KeyboardInput + assert(not m_inputs.empty() && "at least one input has to be given"); + return m_inputs.at(0); +} + +input::PointerInput::PointerInput(const std::string& name) : Input{ name, input::InputType::Pointer } { } diff --git a/src/input/input.hpp b/src/input/input.hpp new file mode 100644 index 00000000..da8c998f --- /dev/null +++ b/src/input/input.hpp @@ -0,0 +1,137 @@ + + +#pragma once + + +#include "game_input.hpp" +#include "graphics/point.hpp" +#include "graphics/rect.hpp" +#include "graphics/window.hpp" +#include "helper/bool_wrapper.hpp" +#include "helper/expected.hpp" +#include "helper/optional.hpp" +#include "manager/service_provider.hpp" + + +#include +#include +#include + +namespace input { + + enum class InputType : u8 { Keyboard, Pointer, JoyStick }; + + enum class NavigationEvent : u8 { OK, DOWN, UP, LEFT, RIGHT, BACK, TAB }; + + enum class SpecialRequest : u8 { WindowFocusLost, InputsChanged }; + + + struct Input { + private: + std::string m_name; + InputType m_type; + + public: + Input(std::string name, InputType type); + + virtual ~Input(); + + Input(const Input& input) noexcept; + Input& operator=(const Input& input) noexcept; + + Input(Input&& input) noexcept; + Input& operator=(Input&& input) noexcept; + + [[nodiscard]] const std::string& name() const; + [[nodiscard]] InputType type(); + + [[nodiscard]] virtual helper::optional get_navigation_event(const SDL_Event& event) const = 0; + + [[nodiscard]] virtual std::string describe_navigation_event(NavigationEvent event) const = 0; + }; + + enum class PointerEvent : u8 { Motion, PointerDown, PointerUp }; + + struct PointerEventHelper { + private: + shapes::IPoint m_pos; + PointerEvent m_event; + + public: + PointerEventHelper(shapes::IPoint pos, PointerEvent event); + + [[nodiscard]] PointerEvent event() const; + + [[nodiscard]] shapes::IPoint position() const; + + [[nodiscard]] bool is_in(const shapes::URect& rect) const; + + [[nodiscard]] bool is_in(const shapes::IRect& rect) const; + + [[nodiscard]] bool operator==(PointerEvent event) const; + }; + + + struct PointerInput : Input { + explicit PointerInput(const std::string& name); + + [[nodiscard]] virtual helper::optional get_pointer_event(const SDL_Event& event) const = 0; + + [[nodiscard]] virtual SDL_Event offset_pointer_event(const SDL_Event& event, const shapes::IPoint& point) + const = 0; + }; + + struct InputManager { + private: + std::vector> m_inputs; + + public: + explicit InputManager(const std::shared_ptr& window); + + [[nodiscard]] const std::vector>& inputs() const; + + [[nodiscard]] helper::optional get_navigation_event(const SDL_Event& event) const; + + [[nodiscard]] helper::optional get_pointer_event(const SDL_Event& event) const; + + /** + * @brief Offsets a pointer event, only safe to call, if get_pointer_event returns a non null optional + * + * @param event the original SDL Event + * @param point the point to offset it by + * @return SDL_Event which has the correct offset + */ + [[nodiscard]] SDL_Event offset_pointer_event(const SDL_Event& event, const shapes::IPoint& point) const; + + [[nodiscard]] helper::BoolWrapper process_special_inputs(const SDL_Event& event); + + [[nodiscard]] helper::optional> get_game_input( + ServiceProvider* service_provider + ); + + [[nodiscard]] const std::unique_ptr& get_primary_input(); + }; + + + struct InputSettings { + + template + [[nodiscard]] static helper::expected has_unique_members(const std::vector& to_check) { + std::vector already_bound{}; + + + for (const auto& single_check : to_check) { + + if (std::ranges::find(already_bound, single_check) != already_bound.cend()) { + return helper::unexpected{ fmt::format("KeyCode already bound: '{}'", single_check) }; + } + + already_bound.push_back(single_check); + } + + return true; + } + }; + + +} // namespace input diff --git a/src/game/input_creator.cpp b/src/input/input_creator.cpp similarity index 75% rename from src/game/input_creator.cpp rename to src/input/input_creator.cpp index b3192a4b..851be74f 100644 --- a/src/game/input_creator.cpp +++ b/src/input/input_creator.cpp @@ -4,41 +4,16 @@ #include "helper/command_line_arguments.hpp" #include "helper/date.hpp" #include "helper/errors.hpp" -#include "platform/replay_input.hpp" -#include - +#include "helper/optional.hpp" +#include "input.hpp" +#include "input/replay_input.hpp" -#if defined(__ANDROID__) -#include "platform/android_input.hpp" -#elif defined(__CONSOLE__) -#include "platform/console_input.hpp" -#else -#include "platform/keyboard_input.hpp" -#endif #include +#include namespace { - [[nodiscard]] std::unique_ptr create_input(ServiceProvider* service_provider) { - return std::visit( - helper::overloaded{ - [service_provider]([[maybe_unused]] KeyboardControls& keyboard_controls - ) mutable -> std::unique_ptr { - auto* const event_dispatcher = &(service_provider->event_dispatcher()); -#if defined(__ANDROID__) - auto input = std::make_unique(event_dispatcher); -#elif defined(__CONSOLE__) - auto input = std::make_unique(event_dispatcher); -#else - auto input = std::make_unique(keyboard_controls, event_dispatcher); -#endif - return input; - }, - }, - service_provider->settings().controls - ); - } [[nodiscard]] recorder::TetrionHeader create_tetrion_headers_for_one(const input::AdditionalInfo& info) { const auto& needed_info = std::get<1>(info); @@ -96,7 +71,7 @@ namespace { for (u8 tetrion_index = 0; tetrion_index < static_cast(tetrion_headers.size()); ++tetrion_index) { - auto input = std::make_unique(recording_reader); + auto input = std::make_unique(recording_reader); const auto& header = tetrion_headers.at(tetrion_index); @@ -114,13 +89,16 @@ namespace { } -[[nodiscard]] input::AdditionalInfo input::get_single_player_game_parameters( +[[nodiscard]] helper::optional input::get_single_player_game_parameters( ServiceProvider* const service_provider, recorder::AdditionalInformation&& information, const date::ISO8601Date& date ) { - auto input = create_input(service_provider); + auto input = service_provider->input_manager().get_game_input(service_provider); + if (not input.has_value()) { + return helper::nullopt; + } const auto starting_level = service_provider->command_line_arguments().starting_level; @@ -130,7 +108,7 @@ namespace { const tetrion::StartingParameters starting_parameters = { target_fps, seed, starting_level, 0 }; - AdditionalInfo result{ std::move(input), starting_parameters }; + AdditionalInfo result{ input.value(), starting_parameters }; auto tetrion_header = create_tetrion_headers_for_one(result); diff --git a/src/game/input_creator.hpp b/src/input/input_creator.hpp similarity index 85% rename from src/game/input_creator.hpp rename to src/input/input_creator.hpp index 10251833..59f2032e 100644 --- a/src/game/input_creator.hpp +++ b/src/input/input_creator.hpp @@ -3,12 +3,11 @@ #include "helper/date.hpp" #include "helper/optional.hpp" +#include "input/game_input.hpp" #include "manager/service_provider.hpp" -#include "platform/input.hpp" #include "recordings/recording_writer.hpp" #include -#include namespace tetrion { struct StartingParameters { @@ -35,12 +34,12 @@ namespace tetrion { namespace input { - using AdditionalInfo = std::tuple, tetrion::StartingParameters>; + using AdditionalInfo = std::tuple, tetrion::StartingParameters>; [[nodiscard]] std::vector get_game_parameters_for_replay(ServiceProvider* service_provider, const std::filesystem::path& recording_path); - [[nodiscard]] AdditionalInfo get_single_player_game_parameters( + [[nodiscard]] helper::optional get_single_player_game_parameters( ServiceProvider* service_provider, recorder::AdditionalInformation&& information, const date::ISO8601Date& date diff --git a/src/input/joystick_input.cpp b/src/input/joystick_input.cpp new file mode 100644 index 00000000..785e04cb --- /dev/null +++ b/src/input/joystick_input.cpp @@ -0,0 +1,874 @@ + + +#include "joystick_input.hpp" +#include "helper/expected.hpp" +#include "helper/optional.hpp" +#include "helper/utils.hpp" +#include "input/game_input.hpp" +#include "input/input.hpp" + +#include +#include + + +input::JoystickInput::JoystickInput(SDL_Joystick* joystick, SDL_JoystickID instance_id, const std::string& name) + : input::Input{ name, input::InputType::JoyStick }, + m_joystick{ joystick }, + m_instance_id{ instance_id } { } + + +input::JoystickInput::~JoystickInput() { + SDL_JoystickClose(m_joystick); +} + + +input::JoystickInput::JoystickInput(const JoystickInput& input) noexcept = default; +input::JoystickInput& input::JoystickInput::operator=(const JoystickInput& input) noexcept = default; + +input::JoystickInput::JoystickInput(JoystickInput&& input) noexcept = default; +input::JoystickInput& input::JoystickInput::operator=(JoystickInput&& input) noexcept = default; + +[[nodiscard]] helper::optional> input::JoystickInput::get_joystick_by_guid( + const sdl::GUID& guid, + SDL_Joystick* joystick, + SDL_JoystickID instance_id, + const std::string& name + +) { +#if defined(__CONSOLE__) +#if defined(__SWITCH__) + if (guid == SwitchJoystickInput_Type1::guid) { + return std::make_unique(joystick, instance_id, name); + } +#elif defined(__3DS__) + if (guid == _3DSJoystickInput_Type1::guid) { + return std::make_unique<_3DSJoystickInput_Type1>(joystick, instance_id, name); + } + +#endif +#endif + + UNUSED(guid); + UNUSED(joystick); + UNUSED(instance_id); + UNUSED(name); + + return helper::nullopt; +} + + +[[nodiscard]] helper::expected, std::string> +input::JoystickInput::get_by_device_index(int device_index) { + + auto* joystick = SDL_JoystickOpen(device_index); + + if (joystick == nullptr) { + return helper::unexpected{ + fmt::format("Failed to get joystick at device index {}: {}", device_index, SDL_GetError()) + }; + } + + //TODO(Totto): add support for gamecontrollers (SDL_IsGameController) + + const auto instance_id = SDL_JoystickInstanceID(joystick); + + if (instance_id < 0) { + return helper::unexpected{ fmt::format("Failed to get joystick instance id: {}", SDL_GetError()) }; + } + + std::string name = "unknown name"; + const auto* char_name = SDL_JoystickName(joystick); + + if (char_name != nullptr) { + name = char_name; + } + + + const auto guid = sdl::GUID{ SDL_JoystickGetGUID(joystick) }; + + if (guid == sdl::GUID{}) { + return helper::unexpected{ fmt::format("Failed to get joystick GUID: {}", SDL_GetError()) }; + } + + auto joystick_input = JoystickInput::get_joystick_by_guid(guid, joystick, instance_id, name); + + if (joystick_input.has_value()) { + return std::move(joystick_input.value()); + } + + return helper::unexpected{ fmt::format( + "Failed to get joystick model by GUID {} We don't support this joystick yet: the name was {}", guid, name + ) }; +} + +[[nodiscard]] SDL_JoystickID input::JoystickInput::instance_id() const { + return m_instance_id; +} + +[[nodiscard]] sdl::GUID input::JoystickInput::guid() const { + const auto guid = sdl::GUID{ SDL_JoystickGetGUID(m_joystick) }; + + if (guid == sdl::GUID{}) { + throw std::runtime_error{ fmt::format("Failed to get joystick GUID: {}", SDL_GetError()) }; + } + + return guid; +} + +void input::JoyStickInputManager::discover_devices(std::vector>& inputs) { + + //initialize joystick input, this needs to call some sdl things + + const auto result = SDL_InitSubSystem(SDL_INIT_JOYSTICK); + + if (result != 0) { + spdlog::warn("Failed to initialize the joystick system: {}", SDL_GetError()); + return; + } + + + const auto enable_result = SDL_JoystickEventState(SDL_ENABLE); + + if (enable_result != 1) { + const auto* const error = enable_result == 0 ? "it was disabled" : SDL_GetError(); + spdlog::warn("Failed to set JoystickEventState (automatic polling by SDL): {}", error); + + return; + } + + + const auto allow_background_events_result = SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + + if (allow_background_events_result != SDL_TRUE) { + spdlog::warn("Failed to set the JOYSTICK_ALLOW_BACKGROUND_EVENTS hint: {}", SDL_GetError()); + + return; + } + + + const auto num_of_joysticks = SDL_NumJoysticks(); + + if (num_of_joysticks < 0) { + spdlog::warn("Failed to get number of joysticks: {}", SDL_GetError()); + return; + } + + spdlog::debug("Found {} Joysticks", num_of_joysticks); + + for (auto i = 0; i < num_of_joysticks; ++i) { + + auto joystick = JoystickInput::get_by_device_index(i); + if (joystick.has_value()) { + inputs.push_back(std::move(joystick.value())); + } else { + spdlog::warn("Failed to configure joystick: {}", joystick.error()); + } + } +} + + +[[nodiscard]] bool input::JoyStickInputManager::process_special_inputs( + const SDL_Event& event, + std::vector>& inputs +) { + + switch (event.type) { + case SDL_JOYDEVICEADDED: { + const auto device_id = event.jdevice.which; + auto joystick = JoystickInput::get_by_device_index(device_id); + if (joystick.has_value()) { + inputs.push_back(std::move(joystick.value())); + } else { + spdlog::warn("Failed to add newly added joystick: {}", joystick.error()); + } + return true; + } + case SDL_JOYDEVICEREMOVED: { + const auto instance_id = event.jdevice.which; + for (auto it = inputs.cbegin(); it != inputs.cend(); it++) { + + if (const auto joystick_input = utils::is_child_class(*it); + joystick_input.has_value()) { + + if (joystick_input.value()->instance_id() == instance_id) { + //TODO(Totto): if we use this joystick as game input we have to notify the user about it,and pause the game, until he is inserted again + + inputs.erase(it); + return true; + } + } + } + + spdlog::warn("Failed to remove removed joystick from internal joystick vector"); + + return true; + } + default: + return false; + } +} + + +#if defined(__CONSOLE__) + +#if defined(__SWITCH__) + +input::SwitchJoystickInput_Type1::SwitchJoystickInput_Type1( + SDL_Joystick* joystick, + SDL_JoystickID instance_id, + const std::string& name +) + : ConsoleJoystickInput{ + joystick, + instance_id, + name, + //NOTE: this are not all, but atm only those, who can be checked with a SDL_JOYBUTTONDOWN event + { { "A", JOYCON_A }, + { "B", JOYCON_B }, + { "X", JOYCON_X }, + { "Y", JOYCON_Y }, + { "L", JOYCON_L }, + { "R", JOYCON_R }, + { "ZL", JOYCON_ZL }, + { "ZR", JOYCON_ZR }, + { "PLUS", JOYCON_PLUS }, + { "MINUS", JOYCON_MINUS }, + { "DPAD_LEFT", JOYCON_DPAD_LEFT }, + { "DPAD_UP", JOYCON_DPAD_UP }, + { "DPAD_RIGHT", JOYCON_DPAD_RIGHT }, + { "DPAD_DOWN", JOYCON_DPAD_DOWN }, + { "LDPAD_LEFT", JOYCON_LDPAD_LEFT }, + { "LDPAD_UP", JOYCON_LDPAD_UP }, + { "LDPAD_RIGHT", JOYCON_LDPAD_RIGHT }, + { "LDPAD_DOWN", JOYCON_LDPAD_DOWN }, + { "RDPAD_LEFT", JOYCON_RDPAD_LEFT }, + { "RDPAD_UP", JOYCON_RDPAD_UP }, + { "RDPAD_RIGHT", JOYCON_RDPAD_RIGHT }, + { "RDPAD_DOWN", JOYCON_RDPAD_DOWN } } +} { } + + +[[nodiscard]] helper::optional input::SwitchJoystickInput_Type1::get_navigation_event( + const SDL_Event& event +) const { + + if (event.type == SDL_JOYBUTTONDOWN) { + + if (event.jbutton.which != instance_id()) { + return helper::nullopt; + } + + switch (event.jbutton.button) { + case JOYCON_A: + return NavigationEvent::OK; + case JOYCON_DPAD_DOWN: + case JOYCON_LDPAD_DOWN: + case JOYCON_RDPAD_DOWN: + return NavigationEvent::DOWN; + case JOYCON_DPAD_UP: + case JOYCON_LDPAD_UP: + case JOYCON_RDPAD_UP: + return NavigationEvent::UP; + case JOYCON_DPAD_LEFT: + case JOYCON_LDPAD_LEFT: + case JOYCON_RDPAD_LEFT: + return NavigationEvent::LEFT; + case JOYCON_DPAD_RIGHT: + case JOYCON_LDPAD_RIGHT: + case JOYCON_RDPAD_RIGHT: + return NavigationEvent::RIGHT; + case JOYCON_MINUS: + return NavigationEvent::BACK; + default: + return helper::nullopt; + + //note, that NavigationEvent::TAB is not supported + } + } + + return handle_axis_navigation_event(event); +} + +[[nodiscard]] std::string input::SwitchJoystickInput_Type1::describe_navigation_event(NavigationEvent event) const { + switch (event) { + case NavigationEvent::OK: + return "A"; + case NavigationEvent::BACK: + return "Minus"; + case NavigationEvent::DOWN: + return "Down"; + case NavigationEvent::UP: + return "Up"; + case NavigationEvent::LEFT: + return "Left"; + case NavigationEvent::RIGHT: + return "Right"; + case NavigationEvent::TAB: + throw std::runtime_error("Tab is not supported"); + default: + utils::unreachable(); + } +} + + +[[nodiscard]] std::string input::SwitchJoystickInput_Type1::key_to_string(console::SettingsType key) const { + switch (key) { + case JOYCON_A: + return "A"; + case JOYCON_B: + return "B"; + case JOYCON_X: + return "X"; + case JOYCON_Y: + return "Y"; + case JOYCON_L: + return "L"; + case JOYCON_R: + return "R"; + case JOYCON_ZL: + return "ZL"; + case JOYCON_ZR: + return "ZR"; + case JOYCON_PLUS: + return "PLUS"; + case JOYCON_MINUS: + return "MINUS"; + case JOYCON_DPAD_LEFT: + return "DPAD_LEFT"; + case JOYCON_DPAD_UP: + return "DPAD_UP"; + case JOYCON_DPAD_RIGHT: + return "DPAD_RIGHT"; + case JOYCON_DPAD_DOWN: + return "DPAD_DOWN"; + case JOYCON_LDPAD_LEFT: + return "LDPAD_LEFT"; + case JOYCON_LDPAD_UP: + return "LDPAD_UP"; + case JOYCON_LDPAD_RIGHT: + return "LDPAD_RIGHT"; + case JOYCON_LDPAD_DOWN: + return "LDPAD_DOWN"; + case JOYCON_RDPAD_LEFT: + return "RDPAD_LEFT"; + case JOYCON_RDPAD_UP: + return "RDPAD_UP"; + case JOYCON_RDPAD_RIGHT: + return "RDPAD_RIGHT"; + case JOYCON_RDPAD_DOWN: + return "RDPAD_DOWN"; + + + default: + utils::unreachable(); + } +} + +[[nodiscard]] input::JoystickSettings input::SwitchJoystickInput_Type1::to_normal_settings( + const AbstractJoystickSettings& settings +) const { + + JoystickSettings result{}; + +#define X_LIST_MACRO(x) SETTINGS_TO_STRING(settings, result, key_to_string, x); + + X_LIST_OF_SETTINGS_KEYS + +#undef X_LIST_MACRO + + return result; +} + +[[nodiscard]] input::AbstractJoystickSettings +input::SwitchJoystickInput_Type1::default_settings_raw() const { + const AbstractJoystickSettings settings = // + { + .identification = + JoystickIdentification{ .guid = SwitchJoystickInput_Type1::guid, .name = "Switch Controller" }, + .rotate_left = JOYCON_DPAD_LEFT, + .rotate_right = JOYCON_DPAD_RIGHT, + .move_left = JOYCON_LDPAD_LEFT, + .move_right = JOYCON_LDPAD_RIGHT, + .move_down = JOYCON_LDPAD_DOWN, + .drop = JOYCON_X, + .hold = JOYCON_B, + .pause = JOYCON_MINUS, + .open_settings = JOYCON_PLUS + }; + + return settings; +} + + +#elif defined(__3DS__) + +input::_3DSJoystickInput_Type1::_3DSJoystickInput_Type1( + SDL_Joystick* joystick, + SDL_JoystickID instance_id, + const std::string& name +) + : ConsoleJoystickInput{ + joystick, + instance_id, + name, + //NOTE: this are not all, but atm only those, who can be checked with a SDL_JOYBUTTONDOWN event + { { "A", JOYCON_A }, + { "B", JOYCON_B }, + { "SELECT", JOYCON_SELECT }, + { "START", JOYCON_START }, + { "DPAD_RIGHT", JOYCON_DPAD_RIGHT }, + { "DPAD_LEFT", JOYCON_DPAD_LEFT }, + { "DPAD_UP", JOYCON_DPAD_UP }, + { "DPAD_DOWN", JOYCON_DPAD_DOWN }, + { "R", JOYCON_R }, + { "L", JOYCON_L }, + { "X", JOYCON_X }, + { "Y", JOYCON_Y }, + { "ZL", JOYCON_ZL }, + { "ZR", JOYCON_ZR } } +} { } + + +[[nodiscard]] helper::optional input::_3DSJoystickInput_Type1::get_navigation_event( + const SDL_Event& event +) const { + if (event.type == SDL_JOYBUTTONDOWN) { + + if (event.jbutton.which != instance_id()) { + return helper::nullopt; + } + + switch (event.jbutton.button) { + case JOYCON_A: + return NavigationEvent::OK; + case JOYCON_DPAD_DOWN: + case JOYCON_CSTICK_DOWN: + case JOYCON_CPAD_DOWN: + return NavigationEvent::DOWN; + case JOYCON_DPAD_UP: + case JOYCON_CSTICK_UP: + case JOYCON_CPAD_UP: + return NavigationEvent::UP; + case JOYCON_DPAD_LEFT: + case JOYCON_CSTICK_LEFT: + case JOYCON_CPAD_LEFT: + return NavigationEvent::LEFT; + case JOYCON_DPAD_RIGHT: + case JOYCON_CSTICK_RIGHT: + case JOYCON_CPAD_RIGHT: + return NavigationEvent::RIGHT; + case JOYCON_B: + return NavigationEvent::BACK; + default: + return helper::nullopt; + + //note, that NavigationEvent::TAB is not supported + } + } + + return handle_axis_navigation_event(event); +} + +[[nodiscard]] std::string input::_3DSJoystickInput_Type1::describe_navigation_event(NavigationEvent event) const { + switch (event) { + case NavigationEvent::OK: + return "A"; + case NavigationEvent::BACK: + return "B"; + case NavigationEvent::DOWN: + return "Down"; + case NavigationEvent::UP: + return "Up"; + case NavigationEvent::LEFT: + return "Left"; + case NavigationEvent::RIGHT: + return "Right"; + case NavigationEvent::TAB: + throw std::runtime_error("Tab is not supported"); + default: + utils::unreachable(); + } +} + + +[[nodiscard]] std::string input::_3DSJoystickInput_Type1::key_to_string(console::SettingsType key) const { + switch (key) { + case JOYCON_A: + return "A"; + case JOYCON_B: + return "B"; + case JOYCON_SELECT: + return "SELECT"; + case JOYCON_START: + return "START"; + case JOYCON_DPAD_RIGHT: + return "DPAD_RIGHT"; + case JOYCON_DPAD_LEFT: + return "DPAD_LEFT"; + case JOYCON_DPAD_UP: + return "DPAD_UP"; + case JOYCON_DPAD_DOWN: + return "DPAD_DOWN"; + case JOYCON_R: + return "R"; + case JOYCON_L: + return "L"; + case JOYCON_X: + return "X"; + case JOYCON_Y: + return "Y"; + case JOYCON_ZL: + return "ZL"; + case JOYCON_ZR: + return "ZR"; + default: + utils::unreachable(); + } +} + +[[nodiscard]] input::JoystickSettings input::_3DSJoystickInput_Type1::to_normal_settings( + const AbstractJoystickSettings& settings +) const { + + JoystickSettings result{}; + +#define X_LIST_MACRO(x) SETTINGS_TO_STRING(settings, result, key_to_string, x); + + X_LIST_OF_SETTINGS_KEYS + +#undef X_LIST_MACRO + + return result; +} +[[nodiscard]] input::AbstractJoystickSettings +input::_3DSJoystickInput_Type1::default_settings_raw() const { + const AbstractJoystickSettings settings = // + { + .identification = + JoystickIdentification{ .guid = _3DSJoystickInput_Type1::guid, .name = "Nintendo 3DS" }, + .rotate_left = JOYCON_L, + .rotate_right = JOYCON_R, + .move_left = JOYCON_DPAD_LEFT, + .move_right = JOYCON_DPAD_RIGHT, + .move_down = JOYCON_DPAD_DOWN, + .drop = JOYCON_X, + .hold = JOYCON_B, + .pause = JOYCON_START, + .open_settings = JOYCON_SELECT + }; + + return settings; +} + + +#endif +#endif + +input::JoystickGameInput::JoystickGameInput(EventDispatcher* event_dispatcher, JoystickInput* underlying_input) + : GameInput{ GameInputType::Controller }, + m_event_dispatcher{ event_dispatcher }, + m_underlying_input{ underlying_input } { + m_event_dispatcher->register_listener(this); +} + + +input::JoystickGameInput::~JoystickGameInput() { + m_event_dispatcher->unregister_listener(this); +} + +input::JoystickGameInput::JoystickGameInput(JoystickGameInput&& input) noexcept = default; +[[nodiscard]] input::JoystickGameInput& input::JoystickGameInput::operator=(JoystickGameInput&& input +) noexcept = default; + +[[nodiscard]] const input::JoystickInput* input::JoystickGameInput::underlying_input() const { + return m_underlying_input; +} + +void input::JoystickGameInput::handle_event(const SDL_Event& event) { + m_event_buffer.push_back(event); +} + +void input::JoystickGameInput::update(SimulationStep simulation_step_index) { + for (const auto& event : m_event_buffer) { + const auto input_event = sdl_event_to_input_event(event); + if (input_event.has_value()) { + GameInput::handle_event(*input_event, simulation_step_index); + } + } + m_event_buffer.clear(); + + GameInput::update(simulation_step_index); +} + + +namespace { + + [[nodiscard]] helper::optional> get_game_joystick_by_guid( + const sdl::GUID& guid, + const input::JoystickSettings& settings, + EventDispatcher* event_dispatcher, + input::JoystickInput* underlying_input + + ) { +#if defined(__CONSOLE__) +#if defined(__SWITCH__) + if (guid == input::SwitchJoystickInput_Type1::guid) { + return std::make_shared(settings, event_dispatcher, underlying_input); + } +#elif defined(__3DS__) + if (guid == input::_3DSJoystickInput_Type1::guid) { + return std::make_shared(settings, event_dispatcher, underlying_input); + } +#endif +#endif + + UNUSED(guid); + UNUSED(settings); + UNUSED(event_dispatcher); + UNUSED(underlying_input); + + return helper::nullopt; + } + + +} // namespace + +[[nodiscard]] helper::expected, std::string> +input::JoystickGameInput::get_game_input_by_settings( + const input::InputManager& input_manager, + EventDispatcher* event_dispatcher, + const JoystickSettings& settings +) { + + + for (const auto& input : input_manager.inputs()) { + if (const auto joystick_input = utils::is_child_class(input); + joystick_input.has_value()) { + + try { + auto result = get_game_joystick_by_guid( + settings.identification.guid, settings, event_dispatcher, joystick_input.value() + ); + + if (result.has_value()) { + return result.value(); + } + + } catch (const std::exception& exception) { + spdlog::warn("Couldn't construct JoystickGameInput: {}", exception.what()); + } + } + } + + return helper::unexpected{ + fmt::format("No JoystickGameInput candidate for the GUID: {}", settings.identification.guid.to_string()) + }; +} + + +#if defined(__CONSOLE__) + + +input::ConsoleJoystickInput::ConsoleJoystickInput( + SDL_Joystick* joystick, + SDL_JoystickID instance_id, + const std::string& name, + const MappingType& key_mappings +) + : JoystickInput{ joystick, instance_id, name }, + m_key_mappings{ key_mappings } { } + +[[nodiscard]] const input::MappingType& input::ConsoleJoystickInput::key_mappings( +) const { + return m_key_mappings; +} + +[[nodiscard]] input::JoystickSettings input::ConsoleJoystickInput::default_settings() const { + return to_normal_settings(default_settings_raw()); +} + +[[nodiscard]] helper::optional input::ConsoleJoystickInput::handle_axis_navigation_event( + const SDL_Event& event +) const { + if (event.type == SDL_JOYAXISMOTION) { + + //TODO(Totto). maybe make this configurable + // this constant is here, that slight touches aren't counted as inputs ( really slight wiggles might occur unintentinoally) NOTE: that most inputs use all 16 bits for a normal press, so that this value can be that "big"! + constexpr auto axis_threshold = 1000; + + if (event.jaxis.which != instance_id()) { + return helper::nullopt; + } + + // x axis movement + if (event.jaxis.axis == 0) { + if (event.jaxis.value > axis_threshold) { + return NavigationEvent::RIGHT; + } + + if (event.jaxis.value < -axis_threshold) { + return NavigationEvent::LEFT; + } + + return helper::nullopt; + } + + // y axis movement + if (event.jaxis.axis == 1) { + if (event.jaxis.value > axis_threshold) { + return NavigationEvent::DOWN; + } + + if (event.jaxis.value < -axis_threshold) { + return NavigationEvent::UP; + } + + return helper::nullopt; + } + + throw std::runtime_error(fmt::format("Reached unsupported axis for SDL_JOYAXISMOTION {}", event.jaxis.axis)); + } + + return helper::nullopt; +} + + +input::ConsoleJoystickGameInput::ConsoleJoystickGameInput( + JoystickSettings settings, + EventDispatcher* event_dispatcher, + JoystickInput* underlying_input +) + : JoystickGameInput{ event_dispatcher, underlying_input } { + + auto console_input = utils::is_child_class(underlying_input); + + if (not console_input.has_value()) { + throw std::runtime_error("Invalid input received"); + } + + m_underlying_joystick_input = console_input.value(); + + auto validate_settings = + JoystickGameInput::try_resolve_settings(settings, m_underlying_joystick_input->key_mappings()); + if (validate_settings.has_value()) { + m_settings = validate_settings.value(); + } else { + + spdlog::warn("Invalid settings: {}", validate_settings.error()); + spdlog::warn("using default settings"); + + m_settings = m_underlying_joystick_input->default_settings_raw(); + } +} + +input::ConsoleJoystickGameInput::~ConsoleJoystickGameInput() = default; + +// game_input uses Input to handle events, but stores the config settings for the specific button + +helper::optional input::ConsoleJoystickGameInput::sdl_event_to_input_event(const SDL_Event& event) const { + if (event.type == SDL_JOYBUTTONDOWN) { + + if (event.jbutton.which != underlying_input()->instance_id()) { + return helper::nullopt; + } + + //TODO(Totto): use switch case + const auto button = event.jbutton.button; + if (button == m_settings.rotate_left) { + return InputEvent::RotateLeftPressed; + } + if (button == m_settings.rotate_right) { + return InputEvent::RotateRightPressed; + } + if (button == m_settings.move_down) { + return InputEvent::MoveDownPressed; + } + if (button == m_settings.move_left) { + return InputEvent::MoveLeftPressed; + } + if (button == m_settings.move_right) { + return InputEvent::MoveRightPressed; + } + if (button == m_settings.drop) { + return InputEvent::DropPressed; + } + if (button == m_settings.hold) { + return InputEvent::HoldPressed; + } + } else if (event.type == SDL_JOYBUTTONUP) { + + if (event.jbutton.which != underlying_input()->instance_id()) { + return helper::nullopt; + } + + const auto button = event.jbutton.button; + if (button == m_settings.rotate_left) { + return InputEvent::RotateLeftReleased; + } + if (button == m_settings.rotate_right) { + return InputEvent::RotateRightReleased; + } + if (button == m_settings.move_down) { + return InputEvent::MoveDownReleased; + } + if (button == m_settings.move_left) { + return InputEvent::MoveLeftReleased; + } + if (button == m_settings.move_right) { + return InputEvent::MoveRightReleased; + } + if (button == m_settings.drop) { + return InputEvent::DropReleased; + } + if (button == m_settings.hold) { + return InputEvent::HoldReleased; + } + } + return helper::nullopt; +} + +[[nodiscard]] helper::optional input::ConsoleJoystickGameInput::get_menu_event(const SDL_Event& event +) const { + + if (event.type == SDL_JOYBUTTONDOWN) { + + if (event.jbutton.which != underlying_input()->instance_id()) { + return helper::nullopt; + } + + const auto button = event.jbutton.button; + + if (button == m_settings.pause) { + return MenuEvent::Pause; + } + if (button == m_settings.open_settings) { + return MenuEvent::OpenSettings; + } + } + + return helper::nullopt; +} + + +[[nodiscard]] std::string input::ConsoleJoystickGameInput::describe_menu_event(MenuEvent event) const { + switch (event) { + case input::MenuEvent::Pause: + return m_underlying_joystick_input->key_to_string(m_settings.pause); + case input::MenuEvent::OpenSettings: + return m_underlying_joystick_input->key_to_string(m_settings.open_settings); + default: + utils::unreachable(); + } +} + + +#endif + + +std::string json_helper::get_key_from_object(const nlohmann::json& obj, const std::string& name) { + + std::string input; + obj.at(name).get_to(input); + + return input; +} diff --git a/src/input/joystick_input.hpp b/src/input/joystick_input.hpp new file mode 100644 index 00000000..079da857 --- /dev/null +++ b/src/input/joystick_input.hpp @@ -0,0 +1,429 @@ +#pragma once + + +#include "guid.hpp" +#include "helper/expected.hpp" +#include "helper/parse_json.hpp" +#include "input.hpp" +#include "input/console_buttons.hpp" +#include "input/game_input.hpp" +#include "manager/event_dispatcher.hpp" + +#include +#include +#include +#include + +namespace input { + + + // essentially a GUID + struct JoystickIdentification { + sdl::GUID guid; + + std::string name; //optional (can be ""), just for human readable settings + + static helper::expected from_string(const std::string& value); + }; + + //using std::string in here, since we only know, if these are valid joystick button names, after parsing the GUID and than seeing if we support that joystick and than using the string mappings for that specific joystick + + template + struct AbstractJoystickSettings { + JoystickIdentification identification; + + T rotate_left; + T rotate_right; + T move_left; + T move_right; + T move_down; + T drop; + T hold; + + T pause; + T open_settings; + + + [[nodiscard]] helper::expected validate() const { + const std::vector to_use{ rotate_left, rotate_right, move_left, move_right, move_down, + drop, hold, pause, open_settings }; + + return input::InputSettings::has_unique_members(to_use); + } + }; + + using JoystickSettings = AbstractJoystickSettings; + + + /** + * @brief + * + * @note regarding the NOLINT: the destructor just cleans up the SDL_Joystick, it has nothing to do with class members that would need special member functions to be explicitly defined + * + */ + struct JoystickInput : Input { + private: + SDL_Joystick* m_joystick; + SDL_JoystickID m_instance_id; + + [[nodiscard]] static helper::optional> get_joystick_by_guid( + const sdl::GUID& guid, + SDL_Joystick* joystick, + SDL_JoystickID instance_id, + const std::string& name + ); + + public: + JoystickInput(SDL_Joystick* joystick, SDL_JoystickID instance_id, const std::string& name); + + ~JoystickInput() override; + + JoystickInput(const JoystickInput& input) noexcept; + JoystickInput& operator=(const JoystickInput& input) noexcept; + + JoystickInput(JoystickInput&& input) noexcept; + JoystickInput& operator=(JoystickInput&& input) noexcept; + + [[nodiscard]] static helper::expected, std::string> get_by_device_index( + int device_index + ); + + [[nodiscard]] SDL_JoystickID instance_id() const; + + [[nodiscard]] sdl::GUID guid() const; + + [[nodiscard]] virtual JoystickSettings default_settings() const = 0; + + // Add get_game_input method! + }; + + + //TODO(Totto): also support gamecontroller API + // see: https://github.com/mdqinc/SDL_GameControllerDB?tab=readme-ov-file + + struct JoyStickInputManager { + static void discover_devices(std::vector>& inputs); + + [[nodiscard]] static bool + process_special_inputs(const SDL_Event& event, std::vector>& inputs); + }; + + + //TODO(Totto): differntiate different controllers and modes, e.g the switch can have pro controller, the included ones, each of them seperate etc. + + template + using MappingType = std::unordered_map; + +#if defined(__CONSOLE__) + + namespace console { + using SettingsType = enum JOYCON; + } // namespace console + + struct ConsoleJoystickInput : JoystickInput { + private: + MappingType m_key_mappings; + + public: + ConsoleJoystickInput( + SDL_Joystick* joystick, + SDL_JoystickID instance_id, + const std::string& name, + const MappingType& key_mappings + ); + [[nodiscard]] const MappingType& key_mappings() const; + + [[nodiscard]] virtual std::string key_to_string(console::SettingsType key) const = 0; + + [[nodiscard]] virtual JoystickSettings to_normal_settings( + const AbstractJoystickSettings& settings + ) const = 0; + + [[nodiscard]] JoystickSettings default_settings() const override; + + [[nodiscard]] virtual AbstractJoystickSettings default_settings_raw() const = 0; + + [[nodiscard]] helper::optional handle_axis_navigation_event(const SDL_Event& event + ) const; + }; + +#if defined(__SWITCH__) + struct SwitchJoystickInput_Type1 : ConsoleJoystickInput { + static constexpr sdl::GUID guid{ + sdl::GUID::ArrayType{ 0x00, 0x00, 0x38, 0xf8, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x20, 0x43, 0x6f, 0x6e, + 0x74, 0x00 } + }; + SwitchJoystickInput_Type1(SDL_Joystick* joystick, SDL_JoystickID instance_id, const std::string& name); + + [[nodiscard]] helper::optional get_navigation_event(const SDL_Event& event) const override; + + [[nodiscard]] std::string describe_navigation_event(NavigationEvent event) const override; + + [[nodiscard]] std::string key_to_string(console::SettingsType key) const override; + + [[nodiscard]] JoystickSettings to_normal_settings( + const AbstractJoystickSettings& settings + ) const override; + + [[nodiscard]] AbstractJoystickSettings default_settings_raw() const override; + }; + + +#elif defined(__3DS__) + + struct _3DSJoystickInput_Type1 : ConsoleJoystickInput { + + static constexpr sdl::GUID guid{ + sdl::GUID::ArrayType{ 0x00, 0x00, 0x10, 0x32, 0x4e, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x6f, 0x20, 0x33, + 0x44, 0x00 } + }; + _3DSJoystickInput_Type1(SDL_Joystick* joystick, SDL_JoystickID instance_id, const std::string& name); + + [[nodiscard]] helper::optional get_navigation_event(const SDL_Event& event) const override; + + [[nodiscard]] std::string describe_navigation_event(NavigationEvent event) const override; + + [[nodiscard]] std::string key_to_string(console::SettingsType key) const override; + + [[nodiscard]] JoystickSettings to_normal_settings( + const AbstractJoystickSettings& settings + ) const override; + + [[nodiscard]] AbstractJoystickSettings default_settings_raw() const override; + }; + + +#endif + +#endif + + +#define X_LIST_OF_SETTINGS_KEYS \ + X_LIST_MACRO(rotate_left) \ + X_LIST_MACRO(rotate_right) \ + X_LIST_MACRO(move_left) \ + X_LIST_MACRO(move_right) \ + X_LIST_MACRO(move_down) \ + X_LIST_MACRO(drop) \ + X_LIST_MACRO(hold) \ + X_LIST_MACRO(pause) \ + X_LIST_MACRO(open_settings) + + +#define TRY_CONVERT(original, target, map, key) /*NOLINT(cppcoreguidelines-macro-usage)*/ \ + do /*NOLINT(cppcoreguidelines-avoid-do-while)*/ { \ + if ((map).contains((original).key)) { \ + (target).key = (map).at((original).key); \ + } else { \ + return helper::unexpected{ \ + fmt::format("While parsing key '{}': '{}' is not a valid joystick input", #key, (original).key) \ + }; \ + } \ + } while (false) + + +#define SETTINGS_TO_STRING(original, target, fn, key) /*NOLINT(cppcoreguidelines-macro-usage)*/ \ + do /*NOLINT(cppcoreguidelines-avoid-do-while)*/ { \ + (target).key = fn((original).key); \ + } while (false) + + + struct JoystickGameInput : public GameInput, public EventListener { + private: + std::vector m_event_buffer; + EventDispatcher* m_event_dispatcher; + + + JoystickInput* m_underlying_input; + + protected: + [[nodiscard]] const JoystickInput* underlying_input() const; + + public: + JoystickGameInput(EventDispatcher* event_dispatcher, JoystickInput* underlying_input); + + ~JoystickGameInput() override; + + JoystickGameInput(const JoystickGameInput& input) = delete; + [[nodiscard]] JoystickGameInput& operator=(const JoystickGameInput& input) = delete; + + JoystickGameInput(JoystickGameInput&& input) noexcept; + [[nodiscard]] JoystickGameInput& operator=(JoystickGameInput&& input) noexcept; + + void handle_event(const SDL_Event& event) override; + + void update(SimulationStep simulation_step_index) override; + + [[nodiscard]] static helper::expected, std::string> + get_game_input_by_settings( + const input::InputManager& input_manager, + EventDispatcher* event_dispatcher, + const JoystickSettings& settings + ); + + protected: + [[nodiscard]] virtual helper::optional sdl_event_to_input_event(const SDL_Event& event) const = 0; + + template + [[nodiscard]] static helper::expected, std::string> + try_resolve_settings(const JoystickSettings& settings, const MappingType& map) { + + + AbstractJoystickSettings result{}; + + +#define X_LIST_MACRO(x) TRY_CONVERT(settings, result, map, x); //NOLINT(cppcoreguidelines-macro-usage) + + X_LIST_OF_SETTINGS_KEYS + +#undef X_LIST_MACRO + + return result; + } + }; + + +#if defined(__CONSOLE__) + struct ConsoleJoystickGameInput : public JoystickGameInput { + private: + AbstractJoystickSettings m_settings; + ConsoleJoystickInput* m_underlying_joystick_input; + + public: + ConsoleJoystickGameInput( + JoystickSettings settings, + EventDispatcher* event_dispatcher, + JoystickInput* underlying_input + ); + + virtual ~ConsoleJoystickGameInput(); + + [[nodiscard]] helper::optional get_menu_event(const SDL_Event& event) const override; + + [[nodiscard]] std::string describe_menu_event(MenuEvent event) const override; + + protected: + [[nodiscard]] helper::optional sdl_event_to_input_event(const SDL_Event& event) const override; + }; +#if !defined(__SWITCH__) && !defined(__3DS__) +#error "unsupported console" +#endif +#endif +} // namespace input + + +namespace json_helper { + + + [[nodiscard]] std::string get_key_from_object(const nlohmann::json& obj, const std::string& name); + +} // namespace json_helper + + +namespace nlohmann { + + template<> + struct adl_serializer { + static input::JoystickIdentification from_json(const json& obj) { + + ::json::check_for_no_additional_keys(obj, { "guid", "name" }); + + auto context = obj.at("guid"); + + std::string input; + context.get_to(input); + + + const auto& value = sdl::GUID::from_string(input); + + if (not value.has_value()) { + throw nlohmann::json::type_error::create( + 302, fmt::format("Expected a valid GUID but got '{}': {}", input, value.error()), &context + ); + } + + std::string name{}; + + if (obj.contains("name")) { + obj.at("name").get_to(name); + } + + return input::JoystickIdentification{ .guid = value.value(), .name = name }; + } + + static void to_json(json& obj, const input::JoystickIdentification& identification) { + obj = nlohmann::json{ + { "guid", identification.guid.to_string(), { "name", identification.name } }, + }; + } + }; + + template<> + struct adl_serializer { + static input::JoystickSettings from_json(const json& obj) { + + ::json::check_for_no_additional_keys( + obj, { "type", "identification", "rotate_left", "rotate_right", "move_left", "move_right", + "move_down", "drop", "hold", "menu" } + ); + + const input::JoystickIdentification identification = + adl_serializer::from_json(obj.at("identification")); + + const auto rotate_left = json_helper::get_key_from_object(obj, "rotate_left"); + const auto rotate_right = json_helper::get_key_from_object(obj, "rotate_right"); + const auto move_left = json_helper::get_key_from_object(obj, "move_left"); + const auto move_right = json_helper::get_key_from_object(obj, "move_right"); + const auto move_down = json_helper::get_key_from_object(obj, "move_down"); + const auto drop = json_helper::get_key_from_object(obj, "drop"); + const auto hold = json_helper::get_key_from_object(obj, "hold"); + + const auto& menu = obj.at("menu"); + + ::json::check_for_no_additional_keys(menu, { "pause", "open_settings" }); + + const auto pause = json_helper::get_key_from_object(menu, "pause"); + const auto open_settings = json_helper::get_key_from_object(menu, "open_settings"); + + auto settings = input::JoystickSettings{ .identification = identification, + .rotate_left = rotate_left, + .rotate_right = rotate_right, + .move_left = move_left, + .move_right = move_right, + .move_down = move_down, + .drop = drop, + .hold = hold, + .pause = pause, + .open_settings = open_settings }; + + const auto is_valid = settings.validate(); + if (not is_valid.has_value()) { + throw std::runtime_error(is_valid.error()); + } + + return settings; + } + + static void to_json(json& obj, const input::JoystickSettings& settings) { + + auto identification = nlohmann::json{}; + adl_serializer::to_json(identification, settings.identification); + + obj = nlohmann::json{ + { "identification", identification }, + { "rotate_left", settings.rotate_left }, + { "rotate_right", settings.rotate_right }, + { "move_left", settings.move_left }, + { "move_right", settings.move_right }, + { "move_down", settings.move_down }, + { "drop", settings.drop }, + { "hold", settings.hold }, + { + "menu", nlohmann::json{ + { "pause", settings.pause }, + { "open_settings", settings.open_settings }, + }, } + }; + } + }; +} // namespace nlohmann diff --git a/src/input/keyboard_input.cpp b/src/input/keyboard_input.cpp new file mode 100644 index 00000000..6d3e8a4b --- /dev/null +++ b/src/input/keyboard_input.cpp @@ -0,0 +1,218 @@ +#include "keyboard_input.hpp" +#include "helper/optional.hpp" +#include "helper/utils.hpp" +#include "input/game_input.hpp" +#include "input/input.hpp" + + +input::KeyboardInput::KeyboardInput() : input::Input{ "keyboard", InputType::Keyboard } { } + + +[[nodiscard]] helper::optional input::KeyboardInput::get_navigation_event(const SDL_Event& event +) const { + + + if (event.type == SDL_KEYDOWN) { + + const auto key = sdl::Key{ event.key.keysym }; + + if (key == sdl::Key{ SDLK_RETURN } or key == sdl::Key{ SDLK_SPACE }) { + return NavigationEvent::OK; + } + + if (key == sdl::Key{ SDLK_DOWN } or key == sdl::Key{ SDLK_s }) { + return NavigationEvent::DOWN; + } + + + if (key == sdl::Key{ SDLK_UP } or key == sdl::Key{ SDLK_w }) { + return NavigationEvent::UP; + } + + + if (key == sdl::Key{ SDLK_LEFT } or key == sdl::Key{ SDLK_a }) { + return NavigationEvent::LEFT; + } + + + if (key == sdl::Key{ SDLK_RIGHT } or key == sdl::Key{ SDLK_d }) { + return NavigationEvent::RIGHT; + } + + + if (key == sdl::Key{ SDLK_ESCAPE } or key == sdl::Key{ SDLK_BACKSPACE }) { + return NavigationEvent::BACK; + } + + + if (key == sdl::Key{ SDLK_TAB }) { + return NavigationEvent::TAB; + } + } + + return helper::nullopt; +} + + +[[nodiscard]] std::string input::KeyboardInput::describe_navigation_event(NavigationEvent event) const { + + + switch (event) { + + case NavigationEvent::OK: + return fmt::format("{} or {}", sdl::Key{ SDLK_RETURN }, sdl::Key{ SDLK_SPACE }); + case NavigationEvent::DOWN: + return fmt::format("{} or {}", sdl::Key{ SDLK_DOWN }, sdl::Key{ SDLK_s }); + case NavigationEvent::UP: + return fmt::format("{} or {}", sdl::Key{ SDLK_UP }, sdl::Key{ SDLK_w }); + case NavigationEvent::LEFT: + return fmt::format("{} or {}", sdl::Key{ SDLK_LEFT }, sdl::Key{ SDLK_a }); + case NavigationEvent::RIGHT: + return fmt::format("{} or {}", sdl::Key{ SDLK_RIGHT }, sdl::Key{ SDLK_d }); + case NavigationEvent::BACK: + return fmt::format("{} or {}", sdl::Key{ SDLK_ESCAPE }, sdl::Key{ SDLK_BACKSPACE }); + case NavigationEvent::TAB: + return fmt::format("{}", sdl::Key{ SDLK_TAB }); + default: + utils::unreachable(); + } +} + + +void input::KeyboardGameInput::handle_event(const SDL_Event& event) { + m_event_buffer.push_back(event); +} + +void input::KeyboardGameInput::update(SimulationStep simulation_step_index) { + for (const auto& event : m_event_buffer) { + const auto input_event = sdl_event_to_input_event(event); + if (input_event.has_value()) { + GameInput::handle_event(*input_event, simulation_step_index); + } + } + m_event_buffer.clear(); + + GameInput::update(simulation_step_index); +} + +helper::optional input::KeyboardGameInput::sdl_event_to_input_event(const SDL_Event& event) const { + if (event.type == SDL_KEYDOWN and event.key.repeat == 0) { + const auto key = sdl::Key{ event.key.keysym }; + if (key == m_settings.rotate_left) { + return InputEvent::RotateLeftPressed; + } + if (key == m_settings.rotate_right) { + return InputEvent::RotateRightPressed; + } + if (key == m_settings.move_down) { + return InputEvent::MoveDownPressed; + } + if (key == m_settings.move_left) { + return InputEvent::MoveLeftPressed; + } + if (key == m_settings.move_right) { + return InputEvent::MoveRightPressed; + } + if (key == m_settings.drop) { + return InputEvent::DropPressed; + } + if (key == m_settings.hold) { + return InputEvent::HoldPressed; + } + } else if (event.type == SDL_KEYUP) { + const auto key = sdl::Key{ event.key.keysym }; + if (key == m_settings.rotate_left) { + return InputEvent::RotateLeftReleased; + } + if (key == m_settings.rotate_right) { + return InputEvent::RotateRightReleased; + } + if (key == m_settings.move_down) { + return InputEvent::MoveDownReleased; + } + if (key == m_settings.move_left) { + return InputEvent::MoveLeftReleased; + } + if (key == m_settings.move_right) { + return InputEvent::MoveRightReleased; + } + if (key == m_settings.drop) { + return InputEvent::DropReleased; + } + if (key == m_settings.hold) { + return InputEvent::HoldReleased; + } + } + return helper::nullopt; +} + +input::KeyboardGameInput::KeyboardGameInput(const KeyboardSettings& settings, EventDispatcher* event_dispatcher) + : GameInput{ GameInputType::Keyboard }, + m_settings{ settings }, + m_event_dispatcher{ event_dispatcher } { + m_event_dispatcher->register_listener(this); +} + +input::KeyboardGameInput::~KeyboardGameInput() { + m_event_dispatcher->unregister_listener(this); +} + + +input::KeyboardGameInput::KeyboardGameInput(KeyboardGameInput&& input) noexcept = default; +[[nodiscard]] input::KeyboardGameInput& input::KeyboardGameInput::operator=(KeyboardGameInput&& input +) noexcept = default; + + +[[nodiscard]] helper::expected input::KeyboardSettings::validate() const { + + const std::vector to_use{ rotate_left, rotate_right, move_left, move_right, move_down, + drop, hold, pause, open_settings }; + + return input::InputSettings::has_unique_members(to_use); +} + +sdl::Key json_helper::get_key(const nlohmann::json& obj, const std::string& name) { + + auto context = obj.at(name); + + std::string input; + context.get_to(input); + + const auto& value = sdl::Key::from_string(input); + + if (not value.has_value()) { + throw nlohmann::json::type_error::create( + 302, fmt::format("Expected a valid Key for key '{}', but got '{}': {}", name, input, value.error()), + &context + ); + } + return value.value(); +} + + +[[nodiscard]] helper::optional input::KeyboardGameInput::get_menu_event(const SDL_Event& event +) const { + + if (event.type == SDL_KEYDOWN and event.key.repeat == 0) { + const auto key = sdl::Key{ event.key.keysym }; + if (key == m_settings.pause) { + return MenuEvent::Pause; + } + if (key == m_settings.open_settings) { + return MenuEvent::OpenSettings; + } + } + + return helper::nullopt; +} + +[[nodiscard]] std::string input::KeyboardGameInput::describe_menu_event(MenuEvent event) const { + switch (event) { + case input::MenuEvent::Pause: + return m_settings.pause.to_string(); + case input::MenuEvent::OpenSettings: + return m_settings.open_settings.to_string(); + default: + utils::unreachable(); + } +} diff --git a/src/input/keyboard_input.hpp b/src/input/keyboard_input.hpp new file mode 100644 index 00000000..198f7bd0 --- /dev/null +++ b/src/input/keyboard_input.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include "SDL_keycode.h" +#include "game_input.hpp" +#include "helper/expected.hpp" +#include "helper/parse_json.hpp" +#include "input.hpp" +#include "manager/event_dispatcher.hpp" +#include "manager/sdl_key.hpp" + + +#include + + +namespace input { + + + struct KeyboardInput : Input { + + public: + KeyboardInput(); + + [[nodiscard]] helper::optional get_navigation_event(const SDL_Event& event) const override; + + [[nodiscard]] std::string describe_navigation_event(NavigationEvent event) const override; + }; + + + //TODO(Totto): don't default initialize all settings, but rather provide a static default setting, so that everything has to be set explicitly, or you can use the default explicitly + struct KeyboardSettings { + sdl::Key rotate_left; + sdl::Key rotate_right; + sdl::Key move_left; + sdl::Key move_right; + sdl::Key move_down; + sdl::Key drop; + sdl::Key hold; + + sdl::Key pause; + sdl::Key open_settings; + + + [[nodiscard]] helper::expected validate() const; + + [[nodiscard]] static KeyboardSettings default_settings() { + return KeyboardSettings{ .rotate_left = sdl::Key{ SDLK_LEFT }, + .rotate_right = sdl::Key{ SDLK_RIGHT }, + .move_left = sdl::Key{ SDLK_a }, + .move_right = sdl::Key{ SDLK_d }, + .move_down = sdl::Key{ SDLK_s }, + .drop = sdl::Key{ SDLK_w }, + .hold = sdl::Key{ SDLK_TAB }, + .pause = sdl::Key{ SDLK_SPACE }, + .open_settings = sdl::Key{ SDLK_e } }; + } + }; + + + struct KeyboardGameInput : public GameInput, public EventListener { + private: + KeyboardSettings m_settings; + std::vector m_event_buffer; + EventDispatcher* m_event_dispatcher; + + public: + KeyboardGameInput(const KeyboardSettings& settings, EventDispatcher* event_dispatcher); + + ~KeyboardGameInput() override; + + KeyboardGameInput(const KeyboardGameInput& input) = delete; + [[nodiscard]] KeyboardGameInput& operator=(const KeyboardGameInput& input) = delete; + + KeyboardGameInput(KeyboardGameInput&& input) noexcept; + [[nodiscard]] KeyboardGameInput& operator=(KeyboardGameInput&& input) noexcept; + + + void handle_event(const SDL_Event& event) override; + + void update(SimulationStep simulation_step_index) override; + + [[nodiscard]] helper::optional get_menu_event(const SDL_Event& event) const override; + + [[nodiscard]] std::string describe_menu_event(MenuEvent event) const override; + + private: + [[nodiscard]] helper::optional sdl_event_to_input_event(const SDL_Event& event) const; + }; +} // namespace input + + +namespace json_helper { + + + [[nodiscard]] sdl::Key get_key(const nlohmann::json& obj, const std::string& name); + +} // namespace json_helper + + +namespace nlohmann { + template<> + struct adl_serializer { + static input::KeyboardSettings from_json(const json& obj) { + + ::json::check_for_no_additional_keys( + obj, { "type", "rotate_left", "rotate_right", "move_left", "move_right", "move_down", "drop", + "hold", "menu" } + ); + + const auto rotate_left = json_helper::get_key(obj, "rotate_left"); + const auto rotate_right = json_helper::get_key(obj, "rotate_right"); + const auto move_left = json_helper::get_key(obj, "move_left"); + const auto move_right = json_helper::get_key(obj, "move_right"); + const auto move_down = json_helper::get_key(obj, "move_down"); + const auto drop = json_helper::get_key(obj, "drop"); + const auto hold = json_helper::get_key(obj, "hold"); + + const auto& menu = obj.at("menu"); + + ::json::check_for_no_additional_keys(menu, { "pause", "open_settings" }); + + const auto pause = json_helper::get_key(menu, "pause"); + const auto open_settings = json_helper::get_key(menu, "open_settings"); + + auto settings = input::KeyboardSettings{ .rotate_left = rotate_left, + .rotate_right = rotate_right, + .move_left = move_left, + .move_right = move_right, + .move_down = move_down, + .drop = drop, + .hold = hold, + .pause = pause, + .open_settings = open_settings }; + + const auto is_valid = settings.validate(); + if (not is_valid.has_value()) { + throw std::runtime_error(is_valid.error()); + } + + return settings; + } + + static void to_json(json& obj, const input::KeyboardSettings& settings) { + obj = nlohmann::json{ + { "rotate_left", settings.rotate_left.to_string() }, + { "rotate_right", settings.rotate_right.to_string() }, + { "move_left", settings.move_left.to_string() }, + { "move_right", settings.move_right.to_string() }, + { "move_down", settings.move_down.to_string() }, + { "drop", settings.drop.to_string() }, + { "hold", settings.hold.to_string() }, + { + "menu", nlohmann::json{ + { "pause", settings.pause.to_string() }, + { "open_settings", settings.open_settings.to_string() }, + }, } + }; + } + }; +} // namespace nlohmann diff --git a/src/input/meson.build b/src/input/meson.build new file mode 100644 index 00000000..ce2407d1 --- /dev/null +++ b/src/input/meson.build @@ -0,0 +1,21 @@ +graphics_src_files += files( + 'console_buttons.hpp', + 'game_input.cpp', + 'game_input.hpp', + 'guid.cpp', + 'guid.hpp', + 'input.cpp', + 'input.hpp', + 'input_creator.cpp', + 'input_creator.hpp', + 'joystick_input.cpp', + 'joystick_input.hpp', + 'keyboard_input.cpp', + 'keyboard_input.hpp', + 'mouse_input.cpp', + 'mouse_input.hpp', + 'replay_input.cpp', + 'replay_input.hpp', + 'touch_input.cpp', + 'touch_input.hpp', +) diff --git a/src/input/mouse_input.cpp b/src/input/mouse_input.cpp new file mode 100644 index 00000000..b6059abb --- /dev/null +++ b/src/input/mouse_input.cpp @@ -0,0 +1,70 @@ + + +#include "mouse_input.hpp" +#include "graphics/point.hpp" +#include "helper/optional.hpp" +#include "input/input.hpp" + + +input::MouseInput::MouseInput() : PointerInput("mouse") { } + +[[nodiscard]] SDL_Event input::MouseInput::offset_pointer_event(const SDL_Event& event, const shapes::IPoint& point) + const { + + auto new_event = event; + + switch (event.type) { + case SDL_MOUSEMOTION: + new_event.motion.x = event.motion.x + point.x; + new_event.motion.y = event.motion.y + point.y; + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + new_event.button.x = event.button.x + point.x; + new_event.button.y = event.button.y + point.y; + break; + default: + throw std::runtime_error("Tried to offset event, that is no pointer event: in Mouse Input"); + } + + return new_event; +} + + +[[nodiscard]] helper::optional +input::MouseInput::get_navigation_event(const SDL_Event& /*event*/) const { + return helper::nullopt; +} + +[[nodiscard]] std::string input::MouseInput::describe_navigation_event(NavigationEvent /*event*/) const { + throw std::runtime_error("not supported"); +} + +[[nodiscard]] helper::optional input::MouseInput::get_pointer_event(const SDL_Event& event +) const { + + auto pointer_event = input::PointerEvent::PointerUp; + + switch (event.type) { + case SDL_MOUSEMOTION: + return input::PointerEventHelper{ + shapes::IPoint{ event.motion.x, event.motion.y }, + input::PointerEvent::Motion + }; + case SDL_MOUSEBUTTONDOWN: + pointer_event = input::PointerEvent::PointerDown; + break; + case SDL_MOUSEBUTTONUP: + break; + default: + return helper::nullopt; + } + + if (event.button.button != SDL_BUTTON_LEFT) { + return helper::nullopt; + } + + const shapes::IPoint pos{ event.button.x, event.button.y }; + + return input::PointerEventHelper{ pos, pointer_event }; +} diff --git a/src/input/mouse_input.hpp b/src/input/mouse_input.hpp new file mode 100644 index 00000000..4ac37c82 --- /dev/null +++ b/src/input/mouse_input.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "input.hpp" + + +namespace input { + + struct MouseInput : public PointerInput { + public: + MouseInput(); + + [[nodiscard]] helper::optional get_navigation_event(const SDL_Event& event) const override; + + [[nodiscard]] std::string describe_navigation_event(NavigationEvent event) const override; + + [[nodiscard]] helper::optional get_pointer_event(const SDL_Event& event) const override; + + [[nodiscard]] SDL_Event offset_pointer_event(const SDL_Event& event, const shapes::IPoint& point) + const override; + }; + + +} // namespace input diff --git a/src/platform/replay_input.cpp b/src/input/replay_input.cpp similarity index 60% rename from src/platform/replay_input.cpp rename to src/input/replay_input.cpp index 99ab1b1c..61cc6fda 100644 --- a/src/platform/replay_input.cpp +++ b/src/input/replay_input.cpp @@ -1,12 +1,13 @@ #include "replay_input.hpp" #include "game/tetrion.hpp" +#include "helper/magic_enum_wrapper.hpp" +#include "helper/optional.hpp" - -ReplayInput::ReplayInput(std::shared_ptr recording_reader) - : Input{ InputType::Recording }, +input::ReplayGameInput::ReplayGameInput(std::shared_ptr recording_reader) + : GameInput{ GameInputType::Recording }, m_recording_reader{ std::move(recording_reader) } { } -void ReplayInput::update(const SimulationStep simulation_step_index) { +void input::ReplayGameInput::update(const SimulationStep simulation_step_index) { while (true) { if (is_end_of_recording()) { break; @@ -14,7 +15,7 @@ void ReplayInput::update(const SimulationStep simulation_step_index) { const auto& record = m_recording_reader->at(m_next_record_index); - if (record.tetrion_index != m_target_tetrion->tetrion_index()) { + if (record.tetrion_index != target_tetrion()->tetrion_index()) { // the current record is not for this tetrion => discard record and keep reading ++m_next_record_index; continue; @@ -28,16 +29,16 @@ void ReplayInput::update(const SimulationStep simulation_step_index) { spdlog::debug("replaying event {} at step {}", magic_enum::enum_name(record.event), simulation_step_index); - Input::handle_event(record.event, simulation_step_index); + GameInput::handle_event(record.event, simulation_step_index); ++m_next_record_index; } - Input::update(simulation_step_index); + GameInput::update(simulation_step_index); } -void ReplayInput::late_update(const SimulationStep simulation_step_index) { - Input::late_update(simulation_step_index); +void input::ReplayGameInput::late_update(const SimulationStep simulation_step_index) { + GameInput::late_update(simulation_step_index); while (true) { if (m_next_snapshot_index >= m_recording_reader->snapshots().size()) { @@ -45,20 +46,20 @@ void ReplayInput::late_update(const SimulationStep simulation_step_index) { } const auto& snapshot = m_recording_reader->snapshots().at(m_next_snapshot_index); - if (snapshot.tetrion_index() != m_target_tetrion->tetrion_index()) { + if (snapshot.tetrion_index() != target_tetrion()->tetrion_index()) { ++m_next_snapshot_index; continue; } // the snapshot corresponds to this tetrion - assert(snapshot.tetrion_index() == m_target_tetrion->tetrion_index()); + assert(snapshot.tetrion_index() == target_tetrion()->tetrion_index()); if (snapshot.simulation_step_index() != simulation_step_index) { break; } // create a snapshot from the current state of the tetrion and compare it to the loaded snapshot - const auto current_snapshot = TetrionSnapshot{ m_target_tetrion->core_information(), simulation_step_index }; + const auto current_snapshot = TetrionSnapshot{ target_tetrion()->core_information(), simulation_step_index }; spdlog::info("comparing tetrion snapshots at simulation_step {}", simulation_step_index); @@ -75,6 +76,16 @@ void ReplayInput::late_update(const SimulationStep simulation_step_index) { } } -[[nodiscard]] bool ReplayInput::is_end_of_recording() const { + +[[nodiscard]] helper::optional input::ReplayGameInput::get_menu_event(const SDL_Event& /*event*/) + const { + return helper::nullopt; +} + +[[nodiscard]] std::string input::ReplayGameInput::describe_menu_event(MenuEvent /*event*/) const { + throw std::runtime_error("not supported"); +} + +[[nodiscard]] bool input::ReplayGameInput::is_end_of_recording() const { return m_next_record_index >= m_recording_reader->num_records(); } diff --git a/src/input/replay_input.hpp b/src/input/replay_input.hpp new file mode 100644 index 00000000..5e5bb3a9 --- /dev/null +++ b/src/input/replay_input.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "game_input.hpp" +#include "recordings/recording_reader.hpp" + +#include + +namespace input { + + + struct ReplayGameInput : public GameInput { + private: + std::shared_ptr m_recording_reader; + usize m_next_record_index{ 0 }; + usize m_next_snapshot_index{ 0 }; + + public: + explicit ReplayGameInput(std::shared_ptr recording_reader); + + void update(SimulationStep simulation_step_index) override; + void late_update(SimulationStep simulation_step_index) override; + + [[nodiscard]] helper::optional get_menu_event(const SDL_Event& event) const override; + + [[nodiscard]] std::string describe_menu_event(MenuEvent event) const override; + + [[nodiscard]] bool is_end_of_recording() const; + }; + +} // namespace input diff --git a/src/input/touch_input.cpp b/src/input/touch_input.cpp new file mode 100644 index 00000000..18310af2 --- /dev/null +++ b/src/input/touch_input.cpp @@ -0,0 +1,340 @@ +#include "touch_input.hpp" +#include "graphics/point.hpp" +#include "helper/optional.hpp" +#include "helper/utils.hpp" +#include "input/game_input.hpp" +#include "input/input.hpp" + +#include +#include +#include +#include + +void input::TouchGameInput::handle_event(const SDL_Event& event) { + m_event_buffer.push_back(event); +} + +void input::TouchGameInput::update(SimulationStep simulation_step_index) { + for (const auto& event : m_event_buffer) { + const auto input_event = sdl_event_to_input_event(event); + if (input_event.has_value()) { + GameInput::handle_event(*input_event, simulation_step_index); + } + } + m_event_buffer.clear(); + + GameInput::update(simulation_step_index); +} + +helper::optional input::TouchGameInput::sdl_event_to_input_event(const SDL_Event& event) { + + + if (event.tfinger.touchId != m_underlying_input->m_id) { + return helper::nullopt; + } + + //TODO(Totto): to handle those things better, holding has to be supported + + + if (event.type == SDL_FINGERDOWN) { + + // also take into accounts fingerId, since there may be multiple fingers, each finger has it's own saved state + const SDL_FingerID finger_id = event.tfinger.fingerId; + + + if (m_finger_state.contains(finger_id) and m_finger_state.at(finger_id).has_value()) { + // there are some valid reasons, this can occur now + return helper::nullopt; + } + + const auto x_pos = event.tfinger.x; + const auto y_pos = event.tfinger.y; + const auto timestamp = event.tfinger.timestamp; + + m_finger_state.insert_or_assign( + finger_id, + helper::optional{ + PressedState{ timestamp, x_pos, y_pos } + } + ); + } + + + if (event.type == SDL_FINGERUP) { + + // also take into accounts fingerId, since there may be multiple fingers, each finger has it's own saved state + const SDL_FingerID finger_id = event.tfinger.fingerId; + + + if (!m_finger_state.contains(finger_id)) { + // there are some valid reasons, this can occur now + return helper::nullopt; + } + + const auto& finger_state = m_finger_state.at(finger_id); + + if (!finger_state.has_value()) { + return helper::nullopt; + } + + const auto& pressed_state = finger_state.value(); + + + const auto x_pos = event.tfinger.x; + const auto y_pos = event.tfinger.y; + const auto timestamp = event.tfinger.timestamp; + + + const auto delta_x = x_pos - pressed_state.x; + const auto delta_y = y_pos - pressed_state.y; + const auto duration = timestamp - pressed_state.timestamp; + + const auto dx_abs = std::fabs(delta_x); + const auto dy_abs = std::fabs(delta_y); + + const auto threshold_x = m_settings.move_x_threshold; + const auto threshold_y = m_settings.move_y_threshold; + + m_finger_state.insert_or_assign(finger_id, helper::nullopt); + if (duration < m_settings.rotation_duration_threshold) { + if (dx_abs < threshold_x and dy_abs < threshold_y) { + // tap on the right side of the screen + if (x_pos > 0.5) { + return InputEvent::RotateRightPressed; + } + // tap on the left side of the screen + if (x_pos <= 0.5) { + return InputEvent::RotateLeftPressed; + } + } + } + + // swipe right + if (delta_x > threshold_x and dy_abs < threshold_y) { + return InputEvent::MoveRightPressed; + } + // swipe left + if (delta_x < -threshold_x and dy_abs < threshold_y) { + return InputEvent::MoveLeftPressed; + } + // swipe down + if (delta_y > threshold_y and dx_abs < threshold_x) { + // swipe down to drop + if (duration < m_settings.drop_duration_threshold) { + return InputEvent::DropPressed; + } + return InputEvent::MoveDownPressed; + } + + // swipe up + if (delta_y < -threshold_y and dx_abs < threshold_x) { + return InputEvent::HoldPressed; + } + } + + + if (event.type == SDL_FINGERMOTION) { + //TODO(Totto): support hold + } + + + return helper::nullopt; +} + + +input::TouchGameInput::TouchGameInput( + const TouchSettings& settings, + EventDispatcher* event_dispatcher, + TouchInput* underlying_input +) + : GameInput{ GameInputType::Touch }, + m_settings{ settings }, + m_event_dispatcher{ event_dispatcher }, + m_underlying_input{ underlying_input } { + m_event_dispatcher->register_listener(this); +} + +input::TouchGameInput::~TouchGameInput() { + m_event_dispatcher->unregister_listener(this); +} + + +input::TouchGameInput::TouchGameInput(TouchGameInput&& input) noexcept = default; +[[nodiscard]] input::TouchGameInput& input::TouchGameInput::operator=(TouchGameInput&& input) noexcept = default; + +[[nodiscard]] helper::optional input::TouchGameInput::get_menu_event(const SDL_Event& event) const { + + if (event.type == SDL_KEYDOWN and event.key.keysym.sym == SDLK_AC_BACK) { + return MenuEvent::Pause; + } + + return helper::nullopt; +} + +[[nodiscard]] std::string input::TouchGameInput::describe_menu_event(MenuEvent event) const { + switch (event) { + case input::MenuEvent::Pause: + return "Back"; + case input::MenuEvent::OpenSettings: + throw std::runtime_error("Open Settings is not supported"); + default: + utils::unreachable(); + } +} + +input::TouchInput::TouchInput(const std::shared_ptr& window, SDL_TouchID touch_id, const std::string& name) + : PointerInput{ name }, + m_window{ window }, + m_id{ touch_id } { } + +[[nodiscard]] helper::expected, std::string> +input::TouchInput::get_by_device_index(const std::shared_ptr& window, int device_index) { + + const auto touch_id = SDL_GetTouchDevice(device_index); + + if (touch_id <= 0) { + return helper::unexpected{ + fmt::format("Failed to get touch id at device index {}: {}", device_index, SDL_GetError()) + }; + } + + std::string name = "unknown name"; + const auto* char_name = SDL_GetTouchName(device_index); + + if (char_name != nullptr) { + name = char_name; + } + + return std::make_unique(window, touch_id, name); +} + + +[[nodiscard]] helper::optional input::TouchInput::get_navigation_event(const SDL_Event& event +) const { + //technically no touch event, but it's a navigation event, and by APi design it can also handle those + if (event.type == SDL_KEYDOWN and event.key.keysym.sym == SDLK_AC_BACK) { + return input::NavigationEvent::BACK; + } + + return helper::nullopt; +} + +[[nodiscard]] std::string input::TouchInput::describe_navigation_event(NavigationEvent event) const { + switch (event) { + case NavigationEvent::BACK: + return "Back"; + case NavigationEvent::OK: + case NavigationEvent::DOWN: + case NavigationEvent::UP: + case NavigationEvent::LEFT: + case NavigationEvent::RIGHT: + case NavigationEvent::TAB: + return "Unsupported"; + default: + utils::unreachable(); + } +} + +[[nodiscard]] helper::optional input::TouchInput::get_pointer_event(const SDL_Event& event +) const { + + auto pointer_event = input::PointerEvent::PointerUp; + + switch (event.type) { + case SDL_FINGERMOTION: + pointer_event = input::PointerEvent::Motion; + break; + case SDL_FINGERDOWN: + pointer_event = input::PointerEvent::PointerDown; + break; + case SDL_FINGERUP: + break; + default: + return helper::nullopt; + } + + if (event.tfinger.touchId != m_id) { + return helper::nullopt; + } + + // These are doubles, from 0-1 (or if using virtual layouts > 0) in percent, the have to be casted to absolut x coordinates! + const double x_percent = event.tfinger.x; + const double y_percent = event.tfinger.y; + const auto window_size = m_window->size(); + const auto x_pos = static_cast(std::round(x_percent * window_size.x)); + const auto y_pos = static_cast(std::round(y_percent * window_size.y)); + + + return input::PointerEventHelper{ + shapes::IPoint{ x_pos, y_pos }, + pointer_event + }; +} + + +[[nodiscard]] SDL_Event input::TouchInput::offset_pointer_event(const SDL_Event& event, const shapes::IPoint& point) + const { + + + auto new_event = event; + + if (event.type != SDL_FINGERMOTION and event.type != SDL_FINGERDOWN and event.type != SDL_FINGERUP) { + throw std::runtime_error("Tried to offset event, that is no pointer event: in Touch Input"); + } + + using FloatType = decltype(event.tfinger.x); + + const FloatType x_percent = event.tfinger.x; + const FloatType y_percent = event.tfinger.y; + + const auto window_size = m_window->size(); + + new_event.tfinger.x = x_percent + static_cast(point.x) / static_cast(window_size.x); + new_event.tfinger.y = y_percent + static_cast(point.y) / static_cast(window_size.y); + + return new_event; +} + +[[nodiscard]] helper::expected input::TouchSettings::validate() const { + + if (move_x_threshold > 1.0 || move_x_threshold < 0.0) { + return helper::unexpected{ + fmt::format("move_x_threshold has to be in range [0,1] but was {}", move_x_threshold) + }; + } + + if (move_y_threshold > 1.0 || move_y_threshold < 0.0) { + return helper::unexpected{ + fmt::format("move_y_threshold has to be in range [0,1] but was {}", move_y_threshold) + }; + } + + return true; +} + + +void input::TouchInputManager::discover_devices( + std::vector>& inputs, + const std::shared_ptr& window +) { + + + const auto num_of_touch_devices = SDL_GetNumTouchDevices(); + + if (num_of_touch_devices < 0) { + spdlog::warn("Failed to get number of touch devices: {}", SDL_GetError()); + return; + } + + spdlog::debug("Found {} Touch Devices", num_of_touch_devices); + + for (auto i = 0; i < num_of_touch_devices; ++i) { + + auto touch_input = TouchInput::get_by_device_index(window, i); + if (touch_input.has_value()) { + inputs.push_back(std::move(touch_input.value())); + } else { + spdlog::warn("Failed to get TouchInput: {}", touch_input.error()); + } + } +} diff --git a/src/input/touch_input.hpp b/src/input/touch_input.hpp new file mode 100644 index 00000000..6ceb740f --- /dev/null +++ b/src/input/touch_input.hpp @@ -0,0 +1,214 @@ +#pragma once + + +#include "helper/expected.hpp" +#include "helper/parse_json.hpp" +#include "input.hpp" +#include "input/game_input.hpp" +#include "manager/event_dispatcher.hpp" +#include + +namespace input { + + struct TouchInput : PointerInput { + std::shared_ptr m_window; + SDL_TouchID m_id; + + public: + TouchInput(const std::shared_ptr& window, SDL_TouchID touch_id, const std::string& name); + + [[nodiscard]] static helper::expected, std::string> + get_by_device_index(const std::shared_ptr& window, int device_index); + + [[nodiscard]] helper::optional get_navigation_event(const SDL_Event& event) const override; + + [[nodiscard]] std::string describe_navigation_event(NavigationEvent event) const override; + + [[nodiscard]] helper::optional get_pointer_event(const SDL_Event& event) const override; + + [[nodiscard]] SDL_Event offset_pointer_event(const SDL_Event& event, const shapes::IPoint& point) + const override; + }; + + + struct TouchInputManager { + static void + discover_devices(std::vector>& inputs, const std::shared_ptr& window); + }; + + + struct TouchSettings { + double move_x_threshold; + double move_y_threshold; + + // in ms + u32 rotation_duration_threshold; + u32 drop_duration_threshold; + + [[nodiscard]] helper::expected validate() const; + + [[nodiscard]] static TouchSettings default_settings() { + return TouchSettings{ .move_x_threshold = 150.0 / 2160.0, + .move_y_threshold = 400.0 / 1080.0, + .rotation_duration_threshold = 500, + .drop_duration_threshold = 200 }; + } + }; + + struct PressedState { + Uint32 timestamp; + float x; + float y; + explicit PressedState(Uint32 timestamp, float x_pos, float y_pos) //NOLINT(bugprone-easily-swappable-parameters) + : timestamp{ timestamp }, + x{ x_pos }, + y{ y_pos } { } + }; + + struct TouchGameInput final : public GameInput, public EventListener { + + private: + TouchSettings m_settings; + + std::unordered_map> m_finger_state; + std::vector m_event_buffer; + EventDispatcher* m_event_dispatcher; + TouchInput* m_underlying_input; + + public: + explicit TouchGameInput( + const TouchSettings& settings, + EventDispatcher* event_dispatcher, + TouchInput* underlying_input + ); + + ~TouchGameInput() override; + + + TouchGameInput(const TouchGameInput& input) = delete; + [[nodiscard]] TouchGameInput& operator=(const TouchGameInput& input) = delete; + + TouchGameInput(TouchGameInput&& input) noexcept; + [[nodiscard]] TouchGameInput& operator=(TouchGameInput&& input) noexcept; + + void handle_event(const SDL_Event& event) override; + void update(SimulationStep simulation_step_index) override; + + [[nodiscard]] helper::optional get_menu_event(const SDL_Event& event) const override; + + [[nodiscard]] std::string describe_menu_event(MenuEvent event) const override; + + private: + [[nodiscard]] helper::optional sdl_event_to_input_event(const SDL_Event& event); + }; + +} // namespace input + + +namespace json_helper { + + template + concept IsNumeric = std::is_arithmetic_v; + + + template + [[nodiscard]] T get_number(const nlohmann::json& obj, const std::string& name) { + + helper::expected error = true; + + auto context = obj.at(name); + + + if (not context.is_number()) { + error = helper::unexpected{ fmt::format("Not a number but: {}", context.type_name()) }; + } + + if (error.has_value()) { + + + // integers are checked explicitly, floating points are just retrieved vai get_to, which may convert numbers, but converting from int to floating point is always fine + + if constexpr (std::numeric_limits::is_integer) { + + if (not context.is_number_integer()) { + error = helper::unexpected{ "Not an integer" }; + } else { + if constexpr (not std::numeric_limits::is_signed) { + if (not context.is_number_unsigned()) { + error = helper::unexpected{ "Not an unsigned integer" }; + } + } + } + } + } + + if (not error.has_value()) { + + throw nlohmann::json::type_error::create( + 302, + fmt::format( + "Expected a valid number of type '{}' for key '{}' but an error occurred: {}", + typeid(T).name(), name, error.error() + ), + &context + ); + } + + T input; + context.get_to(input); + + + return input; + } + +} // namespace json_helper + + +namespace nlohmann { + template<> + struct adl_serializer { + static input::TouchSettings from_json(const json& obj) { + + + ::json::check_for_no_additional_keys( + obj, + { + "type", + "move_x_threshold", + "move_y_threshold", + "rotation_duration_threshold", + "drop_duration_threshold", + } + ); + + const auto move_x_threshold = json_helper::get_number(obj, "move_x_threshold"); + const auto move_y_threshold = json_helper::get_number(obj, "move_y_threshold"); + + const auto rotation_duration_threshold = json_helper::get_number(obj, "rotation_duration_threshold"); + const auto drop_duration_threshold = json_helper::get_number(obj, "drop_duration_threshold"); + + auto settings = input::TouchSettings{ .move_x_threshold = move_x_threshold, + .move_y_threshold = move_y_threshold, + .rotation_duration_threshold = rotation_duration_threshold, + .drop_duration_threshold = drop_duration_threshold }; + + + const auto is_valid = settings.validate(); + if (not is_valid.has_value()) { + throw std::runtime_error(is_valid.error()); + } + + return settings; + } + + static void to_json(json& obj, const input::TouchSettings& settings) { + + obj = nlohmann::json{ + { "move_x_threshold", settings.move_x_threshold }, + { "move_y_threshold", settings.move_y_threshold }, + { "rotation_duration_threshold", settings.rotation_duration_threshold }, + { "drop_duration_threshold", settings.drop_duration_threshold }, + }; + } + }; +} // namespace nlohmann diff --git a/src/lobby/api.hpp b/src/lobby/api.hpp index 8a4ace1d..2e04aa52 100644 --- a/src/lobby/api.hpp +++ b/src/lobby/api.hpp @@ -157,7 +157,7 @@ namespace lobby { const auto& version = server_version.value(); - //TODO: if version is semver, support semver comparison + //TODO(Totto): if version is semver, support semver comparison if (Client::supported_version.string() != version.version) { return helper::unexpected{ fmt::format( "Connecting to unsupported server, version is {}, but we support only {}", @@ -183,28 +183,24 @@ namespace lobby { explicit Client(const std::string& api_url) : m_client{ api_url } { - + // clang-format off m_client.set_default_headers({ #if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT) - { - "Accept-Encoding", + { "Accept-Encoding", #if defined(CPPHTTPLIB_ZLIB_SUPPORT) - "gzip, deflate" + "gzip, deflate" #endif #if defined(CPPHTTPLIB_ZLIB_SUPPORT) && defined(CPPHTTPLIB_BROTLI_SUPPORT) - ", " + ", " #endif #if defined(CPPHTTPLIB_BROTLI_SUPPORT) - "br" + "br" #endif - } - , + }, #endif - { - "Accept", constants::json_content_type - } - }); + // clang-format on + { "Accept", constants::json_content_type } }); #if defined(CPPHTTPLIB_ZLIB_SUPPORT) || defined(CPPHTTPLIB_BROTLI_SUPPORT) m_client.set_compress(true); @@ -233,7 +229,7 @@ namespace lobby { return helper::unexpected{ reachable.error() }; } - //TODO: once version is standard, check here if the version is supported + //TODO(Totto): once version is standard, check here if the version is supported return client; } diff --git a/src/main.cpp b/src/main.cpp index 948de8e2..d23a52a4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,16 +59,16 @@ int main(int argc, char** argv) { constexpr auto window_name = constants::program_name.c_str(); - std::unique_ptr window{ nullptr }; + std::shared_ptr window{ nullptr }; try { #if defined(__ANDROID__) or defined(__CONSOLE__) - window = std::make_unique(window_name, WindowPosition::Centered); + window = std::make_shared(window_name, WindowPosition::Centered); #else static constexpr int width = 1280; static constexpr int height = 720; - window = std::make_unique(window_name, WindowPosition::Centered, width, height); + window = std::make_shared(window_name, WindowPosition::Centered, width, height); #endif } catch (const helper::GeneralError& general_error) { spdlog::error("{}", general_error.message()); diff --git a/src/manager/controls.hpp b/src/manager/controls.hpp deleted file mode 100644 index 0f672482..00000000 --- a/src/manager/controls.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include "helper/magic_enum_wrapper.hpp" -#include "key_codes.hpp" -#include "platform/capabilities.hpp" - -struct KeyboardControls final { - KeyCode rotate_left = KeyCode::Left; - KeyCode rotate_right = KeyCode::Right; - KeyCode move_left = KeyCode::A; - KeyCode move_right = KeyCode::D; - KeyCode move_down = KeyCode::S; - KeyCode drop = KeyCode::W; - KeyCode hold = KeyCode::Tab; - - void validate() const { - std::vector already_bound_keycodes{}; - if (utils::device_supports_keys()) { - for (const auto& key : utils::get_bound_keys({ utils::CrossPlatformAction::PAUSE, - utils::CrossPlatformAction::OPEN_SETTINGS })) { - const auto key_code = from_sdl_keycode(static_cast(key)); - if (key_code == KeyCode::Unknown) { - throw std::runtime_error("Couldn't map bound key '" + std::to_string(key) + "'"); - } - - already_bound_keycodes.push_back(key_code); - } - } - - - const std::vector to_use{ rotate_left, rotate_right, move_left, move_right, move_down, drop, hold }; - - - for (const auto key_to_use : to_use) { - - if (std::find(already_bound_keycodes.cbegin(), already_bound_keycodes.cend(), key_to_use) - != already_bound_keycodes.cend()) { - std::string error_code = "KeyCode already bound: '"; - error_code += magic_enum::enum_name(key_to_use); - error_code += "'"; - - throw std::runtime_error(error_code); - } - - already_bound_keycodes.push_back(key_to_use); - } - } -}; diff --git a/src/manager/event_dispatcher.hpp b/src/manager/event_dispatcher.hpp index 9e3683a1..66b245ad 100644 --- a/src/manager/event_dispatcher.hpp +++ b/src/manager/event_dispatcher.hpp @@ -3,8 +3,8 @@ #include "graphics/rect.hpp" #include "helper/optional.hpp" #include "manager/event_listener.hpp" +#include "sdl_key.hpp" -#include #include #include #include @@ -12,21 +12,33 @@ struct EventDispatcher final { private: std::vector m_listeners; - Window* m_window; bool m_input_activated{ false }; bool m_enabled{ true }; - std::vector allowed_input_keys{ SDLK_RETURN, SDLK_BACKSPACE, SDLK_DOWN, SDLK_UP, - SDLK_LEFT, SDLK_RIGHT, SDLK_ESCAPE, SDLK_TAB }; + + //TODO(Totto): factor out to some other place! + std::vector m_allowed_input_keys{ + sdl::Key{ SDLK_RETURN }, + sdl::Key{ SDLK_BACKSPACE }, + sdl::Key{ SDLK_BACKSPACE, { sdl::Modifier::CTRL } }, + sdl::Key{ SDLK_DOWN }, + sdl::Key{ SDLK_UP }, + sdl::Key{ SDLK_LEFT }, + sdl::Key{ SDLK_RIGHT }, + sdl::Key{ SDLK_ESCAPE }, + sdl::Key{ SDLK_TAB }, + sdl::Key{ SDLK_c, { sdl::Modifier::CTRL } }, + sdl::Key{ SDLK_v, { sdl::Modifier::CTRL } } + }; public: - EventDispatcher(Window* window) : m_window{ window } {}; + explicit EventDispatcher() = default; void register_listener(EventListener* listener) { m_listeners.push_back(listener); } void unregister_listener(const EventListener* listener) { - const auto end = std::remove(m_listeners.begin(), m_listeners.end(), listener); + const auto end = std::ranges::remove(m_listeners, listener).begin(); assert(end != m_listeners.end() and "listener to delete could not be found"); m_listeners.erase(end, m_listeners.end()); } @@ -42,11 +54,8 @@ struct EventDispatcher final { switch (event.type) { case SDL_KEYDOWN: case SDL_KEYUP: { - if (event.key.keysym.sym == SDLK_v and (event.key.keysym.mod & KMOD_CTRL) != 0) { - break; - } - if (std::find(allowed_input_keys.cbegin(), allowed_input_keys.cend(), event.key.keysym.sym) - == allowed_input_keys.cend()) { + if (std::ranges::find(m_allowed_input_keys, sdl::Key{ event.key.keysym }) + == m_allowed_input_keys.cend()) { return; } @@ -62,7 +71,7 @@ struct EventDispatcher final { continue; } - listener->handle_event(event, m_window); + listener->handle_event(event); } } } diff --git a/src/manager/event_listener.hpp b/src/manager/event_listener.hpp index 05db635f..59415071 100644 --- a/src/manager/event_listener.hpp +++ b/src/manager/event_listener.hpp @@ -1,15 +1,14 @@ #pragma once #include - -#include "graphics/window.hpp" +#include struct EventListener { bool m_is_paused{ false }; virtual ~EventListener() = default; - virtual void handle_event(const SDL_Event& event, const Window* window) = 0; + virtual void handle_event(const SDL_Event& event) = 0; [[nodiscard]] bool is_paused() const { return m_is_paused; diff --git a/src/manager/key_codes.hpp b/src/manager/key_codes.hpp deleted file mode 100644 index 8203415a..00000000 --- a/src/manager/key_codes.hpp +++ /dev/null @@ -1,589 +0,0 @@ -#pragma once - -#include - -//TODO: remove this, SDL has it's own conversion function: -// https://wiki.libsdl.org/SDL2/SDL_GetKeyFromName -// https://wiki.libsdl.org/SDL2/SDL_GetKeyName - -enum class KeyCode { - Unknown, - Return, - Escape, - Backspace, - Tab, - Space, - ExclamationMark, - QuoteDouble, - Hash, - Percent, - Dollar, - Ampersand, - Quote, - LeftParenthesis, - RightParenthesis, - Asterisk, - Plus, - Comma, - Minus, - Period, - Slash, - Key0, - Key1, - Key2, - Key3, - Key4, - Key5, - Key6, - Key7, - Key8, - Key9, - Colon, - Semicolon, - Less, - Equals, - Greater, - QuestionMark, - At, - LeftBracket, - Backslash, - RightBracket, - Caret, - Underscore, - Backquote, - 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, - CapsLock, - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - PrintScreen, - ScrollLock, - Pause, - Insert, - Home, - PageUp, - Delete, - End, - PageDown, - Right, - Left, - Down, - Up, - NumLockClear, - NumPadDivide, - NumPadMultiply, - NumPadMinus, - NumPadPlus, - NumPadEnter, - NumPad1, - NumPad2, - NumPad3, - NumPad4, - NumPad5, - NumPad6, - NumPad7, - NumPad8, - NumPad9, - NumPad0, - NumPadPeriod, -}; - -inline SDL_KeyCode to_sdl_keycode(KeyCode code) { - switch (code) { - case KeyCode::Unknown: - return SDLK_UNKNOWN; - case KeyCode::Return: - return SDLK_RETURN; - case KeyCode::Escape: - return SDLK_ESCAPE; - case KeyCode::Backspace: - return SDLK_BACKSPACE; - case KeyCode::Tab: - return SDLK_TAB; - case KeyCode::Space: - return SDLK_SPACE; - case KeyCode::ExclamationMark: - return SDLK_EXCLAIM; - case KeyCode::QuoteDouble: - return SDLK_QUOTEDBL; - case KeyCode::Hash: - return SDLK_HASH; - case KeyCode::Percent: - return SDLK_PERCENT; - case KeyCode::Dollar: - return SDLK_DOLLAR; - case KeyCode::Ampersand: - return SDLK_AMPERSAND; - case KeyCode::Quote: - return SDLK_QUOTE; - case KeyCode::LeftParenthesis: - return SDLK_LEFTPAREN; - case KeyCode::RightParenthesis: - return SDLK_RIGHTPAREN; - case KeyCode::Asterisk: - return SDLK_ASTERISK; - case KeyCode::Plus: - return SDLK_PLUS; - case KeyCode::Comma: - return SDLK_COMMA; - case KeyCode::Minus: - return SDLK_MINUS; - case KeyCode::Period: - return SDLK_PERIOD; - case KeyCode::Slash: - return SDLK_SLASH; - case KeyCode::Key0: - return SDLK_0; - case KeyCode::Key1: - return SDLK_1; - case KeyCode::Key2: - return SDLK_2; - case KeyCode::Key3: - return SDLK_3; - case KeyCode::Key4: - return SDLK_4; - case KeyCode::Key5: - return SDLK_5; - case KeyCode::Key6: - return SDLK_6; - case KeyCode::Key7: - return SDLK_7; - case KeyCode::Key8: - return SDLK_8; - case KeyCode::Key9: - return SDLK_9; - case KeyCode::Colon: - return SDLK_COLON; - case KeyCode::Semicolon: - return SDLK_SEMICOLON; - case KeyCode::Less: - return SDLK_LESS; - case KeyCode::Equals: - return SDLK_EQUALS; - case KeyCode::Greater: - return SDLK_GREATER; - case KeyCode::QuestionMark: - return SDLK_QUESTION; - case KeyCode::At: - return SDLK_AT; - case KeyCode::LeftBracket: - return SDLK_LEFTBRACKET; - case KeyCode::Backslash: - return SDLK_BACKSLASH; - case KeyCode::RightBracket: - return SDLK_RIGHTBRACKET; - case KeyCode::Caret: - return SDLK_CARET; - case KeyCode::Underscore: - return SDLK_UNDERSCORE; - case KeyCode::Backquote: - return SDLK_BACKQUOTE; - case KeyCode::A: - return SDLK_a; - case KeyCode::B: - return SDLK_b; - case KeyCode::C: - return SDLK_c; - case KeyCode::D: - return SDLK_d; - case KeyCode::E: - return SDLK_e; - case KeyCode::F: - return SDLK_f; - case KeyCode::G: - return SDLK_g; - case KeyCode::H: - return SDLK_h; - case KeyCode::I: - return SDLK_i; - case KeyCode::J: - return SDLK_j; - case KeyCode::K: - return SDLK_k; - case KeyCode::L: - return SDLK_l; - case KeyCode::M: - return SDLK_m; - case KeyCode::N: - return SDLK_n; - case KeyCode::O: - return SDLK_o; - case KeyCode::P: - return SDLK_p; - case KeyCode::Q: - return SDLK_q; - case KeyCode::R: - return SDLK_r; - case KeyCode::S: - return SDLK_s; - case KeyCode::T: - return SDLK_t; - case KeyCode::U: - return SDLK_u; - case KeyCode::V: - return SDLK_v; - case KeyCode::W: - return SDLK_w; - case KeyCode::X: - return SDLK_x; - case KeyCode::Y: - return SDLK_y; - case KeyCode::Z: - return SDLK_z; - case KeyCode::CapsLock: - return SDLK_CAPSLOCK; - case KeyCode::F1: - return SDLK_F1; - case KeyCode::F2: - return SDLK_F2; - case KeyCode::F3: - return SDLK_F3; - case KeyCode::F4: - return SDLK_F4; - case KeyCode::F5: - return SDLK_F5; - case KeyCode::F6: - return SDLK_F6; - case KeyCode::F7: - return SDLK_F7; - case KeyCode::F8: - return SDLK_F8; - case KeyCode::F9: - return SDLK_F9; - case KeyCode::F10: - return SDLK_F10; - case KeyCode::F11: - return SDLK_F11; - case KeyCode::F12: - return SDLK_F12; - case KeyCode::PrintScreen: - return SDLK_PRINTSCREEN; - case KeyCode::ScrollLock: - return SDLK_SCROLLLOCK; - case KeyCode::Pause: - return SDLK_PAUSE; - case KeyCode::Insert: - return SDLK_INSERT; - case KeyCode::Home: - return SDLK_HOME; - case KeyCode::PageUp: - return SDLK_PAGEUP; - case KeyCode::Delete: - return SDLK_DELETE; - case KeyCode::End: - return SDLK_END; - case KeyCode::PageDown: - return SDLK_PAGEDOWN; - case KeyCode::Right: - return SDLK_RIGHT; - case KeyCode::Left: - return SDLK_LEFT; - case KeyCode::Down: - return SDLK_DOWN; - case KeyCode::Up: - return SDLK_UP; - case KeyCode::NumLockClear: - return SDLK_NUMLOCKCLEAR; - case KeyCode::NumPadDivide: - return SDLK_KP_DIVIDE; - case KeyCode::NumPadMultiply: - return SDLK_KP_MULTIPLY; - case KeyCode::NumPadMinus: - return SDLK_KP_MINUS; - case KeyCode::NumPadPlus: - return SDLK_KP_PLUS; - case KeyCode::NumPadEnter: - return SDLK_KP_ENTER; - case KeyCode::NumPad1: - return SDLK_KP_1; - case KeyCode::NumPad2: - return SDLK_KP_2; - case KeyCode::NumPad3: - return SDLK_KP_3; - case KeyCode::NumPad4: - return SDLK_KP_4; - case KeyCode::NumPad5: - return SDLK_KP_5; - case KeyCode::NumPad6: - return SDLK_KP_6; - case KeyCode::NumPad7: - return SDLK_KP_7; - case KeyCode::NumPad8: - return SDLK_KP_8; - case KeyCode::NumPad9: - return SDLK_KP_9; - case KeyCode::NumPad0: - return SDLK_KP_0; - case KeyCode::NumPadPeriod: - return SDLK_KP_PERIOD; - default: - return SDLK_UNKNOWN; - } -} - -inline KeyCode from_sdl_keycode(SDL_KeyCode code) { - switch (code) { - case SDLK_UNKNOWN: - return KeyCode::Unknown; - case SDLK_RETURN: - return KeyCode::Return; - case SDLK_ESCAPE: - return KeyCode::Escape; - case SDLK_BACKSPACE: - return KeyCode::Backspace; - case SDLK_TAB: - return KeyCode::Tab; - case SDLK_SPACE: - return KeyCode::Space; - case SDLK_EXCLAIM: - return KeyCode::ExclamationMark; - case SDLK_QUOTEDBL: - return KeyCode::QuoteDouble; - case SDLK_HASH: - return KeyCode::Hash; - case SDLK_PERCENT: - return KeyCode::Percent; - case SDLK_DOLLAR: - return KeyCode::Dollar; - case SDLK_AMPERSAND: - return KeyCode::Ampersand; - case SDLK_QUOTE: - return KeyCode::Quote; - case SDLK_LEFTPAREN: - return KeyCode::LeftParenthesis; - case SDLK_RIGHTPAREN: - return KeyCode::RightParenthesis; - case SDLK_ASTERISK: - return KeyCode::Asterisk; - case SDLK_PLUS: - return KeyCode::Plus; - case SDLK_COMMA: - return KeyCode::Comma; - case SDLK_MINUS: - return KeyCode::Minus; - case SDLK_PERIOD: - return KeyCode::Period; - case SDLK_SLASH: - return KeyCode::Slash; - case SDLK_0: - return KeyCode::Key0; - case SDLK_1: - return KeyCode::Key1; - case SDLK_2: - return KeyCode::Key2; - case SDLK_3: - return KeyCode::Key3; - case SDLK_4: - return KeyCode::Key4; - case SDLK_5: - return KeyCode::Key5; - case SDLK_6: - return KeyCode::Key6; - case SDLK_7: - return KeyCode::Key7; - case SDLK_8: - return KeyCode::Key8; - case SDLK_9: - return KeyCode::Key9; - case SDLK_COLON: - return KeyCode::Colon; - case SDLK_SEMICOLON: - return KeyCode::Semicolon; - case SDLK_LESS: - return KeyCode::Less; - case SDLK_EQUALS: - return KeyCode::Equals; - case SDLK_GREATER: - return KeyCode::Greater; - case SDLK_QUESTION: - return KeyCode::QuestionMark; - case SDLK_AT: - return KeyCode::At; - case SDLK_LEFTBRACKET: - return KeyCode::LeftBracket; - case SDLK_BACKSLASH: - return KeyCode::Backslash; - case SDLK_RIGHTBRACKET: - return KeyCode::RightBracket; - case SDLK_CARET: - return KeyCode::Caret; - case SDLK_UNDERSCORE: - return KeyCode::Underscore; - case SDLK_BACKQUOTE: - return KeyCode::Backquote; - case SDLK_a: - return KeyCode::A; - case SDLK_b: - return KeyCode::B; - case SDLK_c: - return KeyCode::C; - case SDLK_d: - return KeyCode::D; - case SDLK_e: - return KeyCode::E; - case SDLK_f: - return KeyCode::F; - case SDLK_g: - return KeyCode::G; - case SDLK_h: - return KeyCode::H; - case SDLK_i: - return KeyCode::I; - case SDLK_j: - return KeyCode::J; - case SDLK_k: - return KeyCode::K; - case SDLK_l: - return KeyCode::L; - case SDLK_m: - return KeyCode::M; - case SDLK_n: - return KeyCode::N; - case SDLK_o: - return KeyCode::O; - case SDLK_p: - return KeyCode::P; - case SDLK_q: - return KeyCode::Q; - case SDLK_r: - return KeyCode::R; - case SDLK_s: - return KeyCode::S; - case SDLK_t: - return KeyCode::T; - case SDLK_u: - return KeyCode::U; - case SDLK_v: - return KeyCode::V; - case SDLK_w: - return KeyCode::W; - case SDLK_x: - return KeyCode::X; - case SDLK_y: - return KeyCode::Y; - case SDLK_z: - return KeyCode::Z; - case SDLK_CAPSLOCK: - return KeyCode::CapsLock; - case SDLK_F1: - return KeyCode::F1; - case SDLK_F2: - return KeyCode::F2; - case SDLK_F3: - return KeyCode::F3; - case SDLK_F4: - return KeyCode::F4; - case SDLK_F5: - return KeyCode::F5; - case SDLK_F6: - return KeyCode::F6; - case SDLK_F7: - return KeyCode::F7; - case SDLK_F8: - return KeyCode::F8; - case SDLK_F9: - return KeyCode::F9; - case SDLK_F10: - return KeyCode::F10; - case SDLK_F11: - return KeyCode::F11; - case SDLK_F12: - return KeyCode::F12; - case SDLK_PRINTSCREEN: - return KeyCode::PrintScreen; - case SDLK_SCROLLLOCK: - return KeyCode::ScrollLock; - case SDLK_PAUSE: - return KeyCode::Pause; - case SDLK_INSERT: - return KeyCode::Insert; - case SDLK_HOME: - return KeyCode::Home; - case SDLK_PAGEUP: - return KeyCode::PageUp; - case SDLK_DELETE: - return KeyCode::Delete; - case SDLK_END: - return KeyCode::End; - case SDLK_PAGEDOWN: - return KeyCode::PageDown; - case SDLK_RIGHT: - return KeyCode::Right; - case SDLK_LEFT: - return KeyCode::Left; - case SDLK_DOWN: - return KeyCode::Down; - case SDLK_UP: - return KeyCode::Up; - case SDLK_NUMLOCKCLEAR: - return KeyCode::NumLockClear; - case SDLK_KP_DIVIDE: - return KeyCode::NumPadDivide; - case SDLK_KP_MULTIPLY: - return KeyCode::NumPadMultiply; - case SDLK_KP_MINUS: - return KeyCode::NumPadMinus; - case SDLK_KP_PLUS: - return KeyCode::NumPadPlus; - case SDLK_KP_ENTER: - return KeyCode::NumPadEnter; - case SDLK_KP_1: - return KeyCode::NumPad1; - case SDLK_KP_2: - return KeyCode::NumPad2; - case SDLK_KP_3: - return KeyCode::NumPad3; - case SDLK_KP_4: - return KeyCode::NumPad4; - case SDLK_KP_5: - return KeyCode::NumPad5; - case SDLK_KP_6: - return KeyCode::NumPad6; - case SDLK_KP_7: - return KeyCode::NumPad7; - case SDLK_KP_8: - return KeyCode::NumPad8; - case SDLK_KP_9: - return KeyCode::NumPad9; - case SDLK_KP_0: - return KeyCode::NumPad0; - case SDLK_KP_PERIOD: - return KeyCode::NumPadPeriod; - default: - return KeyCode::Unknown; - } -} diff --git a/src/manager/meson.build b/src/manager/meson.build index b9179d95..886fdb90 100644 --- a/src/manager/meson.build +++ b/src/manager/meson.build @@ -1,12 +1,13 @@ graphics_src_files += files( - 'controls.hpp', 'event_dispatcher.hpp', 'event_listener.hpp', 'font.cpp', 'font.hpp', 'input_event.hpp', - 'key_codes.hpp', 'music_manager.cpp', 'music_manager.hpp', - 'settings.hpp', + 'sdl_key.cpp', + 'sdl_key.hpp', + 'settings_manager.cpp', + 'settings_manager.hpp', ) diff --git a/src/manager/music_manager.cpp b/src/manager/music_manager.cpp index 0d7f2dae..d040ae02 100644 --- a/src/manager/music_manager.cpp +++ b/src/manager/music_manager.cpp @@ -4,7 +4,7 @@ #include "helper/errors.hpp" #include "helper/optional.hpp" #include "helper/types.hpp" -#include "platform/capabilities.hpp" +#include "manager/sdl_key.hpp" #include #include @@ -31,7 +31,7 @@ MusicManager::MusicManager(ServiceProvider* service_provider, u8 channel_size) throw helper::InitializationError{ fmt::format("Failed to initialize the audio system: {}", SDL_GetError()) }; } - //TODO: dynamically handle codecs + //TODO(Totto): dynamically handle codecs const auto initialized_codecs = Mix_Init(MIX_INIT_FLAC | MIX_INIT_MP3); if (initialized_codecs == 0) { throw helper::InitializationError{ fmt::format("Failed to initialize any audio codec: {}", SDL_GetError()) }; @@ -352,18 +352,26 @@ helper::optional MusicManager::change_volume(const std::int8_t steps) { return new_volume; } -bool MusicManager::handle_event(const SDL_Event& event) { +bool MusicManager::handle_event(const std::shared_ptr /*unused*/&, const SDL_Event& event) { + if (event.type == SDL_KEYDOWN) { + const auto key = sdl::Key{ event.key.keysym }; - if (utils::device_supports_keys() && event.type == SDL_KEYDOWN) { - if (event.key.keysym.sym == SDLK_PLUS or event.key.keysym.sym == SDLK_KP_PLUS) { - change_volume(1); + if (key.is_key(sdl::Key{ SDLK_PLUS }) or key.is_key(sdl::Key{ SDLK_KP_PLUS })) { + const i8 steps = key.has_modifier(sdl::Modifier::CTRL) ? static_cast(100) + : key.has_modifier(sdl::Modifier::SHIFT) ? static_cast(10) + : static_cast(1); + + change_volume(steps); return true; } - if (event.key.keysym.sym == SDLK_MINUS or event.key.keysym.sym == SDLK_KP_MINUS) { - change_volume(-1); + if (key.is_key(sdl::Key{ SDLK_MINUS }) or key.is_key(sdl::Key{ SDLK_KP_MINUS })) { + const i8 steps = key.has_modifier(sdl::Modifier::CTRL) ? static_cast(-100) + : key.has_modifier(sdl::Modifier::SHIFT) ? static_cast(-10) + : static_cast(-1); + change_volume(steps); return true; } } diff --git a/src/manager/music_manager.hpp b/src/manager/music_manager.hpp index 702be644..496ee7bb 100644 --- a/src/manager/music_manager.hpp +++ b/src/manager/music_manager.hpp @@ -2,6 +2,7 @@ #include "helper/optional.hpp" #include "helper/types.hpp" +#include "input/input.hpp" #include "manager/service_provider.hpp" #include @@ -46,13 +47,13 @@ struct MusicManager final { helper::optional load_effect(const std::string& name, std::filesystem::path& location); helper::optional play_effect(const std::string& name, u8 channel_num = 1, int loop = 0); - //TODO: atm the volume changers only work on the music channel, when adding more effects, this should support channels via https://wiki.libsdl.org/SDL2_mixer/Mix_Volume + //TODO(Totto): atm the volume changers only work on the music channel, when adding more effects, this should support channels via https://wiki.libsdl.org/SDL2_mixer/Mix_Volume [[nodiscard]] helper::optional get_volume() const; void set_volume(helper::optional new_volume, bool force_update = false, bool notify_listeners = true); // no nodiscard, since the return value is only a side effect, that is maybe useful helper::optional change_volume(std::int8_t steps); - bool handle_event(const SDL_Event& event); + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event); bool add_volume_listener(const std::string& name, VolumeChangeFunction change_function) { diff --git a/src/manager/sdl_key.cpp b/src/manager/sdl_key.cpp new file mode 100644 index 00000000..97498223 --- /dev/null +++ b/src/manager/sdl_key.cpp @@ -0,0 +1,466 @@ + +#include "sdl_key.hpp" +#include "helper/optional.hpp" +#include "helper/string_manipulation.hpp" +#include "helper/utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +sdl::Key::Key(SDL_KeyCode keycode, UnderlyingModifierType modifiers) + : m_keycode{ keycode }, + m_modifiers{ modifiers } { } + +sdl::Key::Key(SDL_KeyCode keycode, const std::vector& modifiers) + : Key{ keycode, sdl::Key::sdl_modifier_from_modifiers(modifiers) } { } + +sdl::Key::Key(const SDL_Keysym& keysym) : Key{ static_cast(keysym.sym), keysym.mod } { } + + +[[nodiscard]] bool sdl::Key::is_key(const sdl::Key& other) const { + return m_keycode == other.m_keycode; +} + +namespace { + constexpr SDL_Keymod to_sdl_modifier(sdl::Modifier modifier) { + switch (modifier) { + case sdl::Modifier::LSHIFT: + return KMOD_LSHIFT; + case sdl::Modifier::RSHIFT: + return KMOD_RSHIFT; + + case sdl::Modifier::LCTRL: + return KMOD_LCTRL; + case sdl::Modifier::RCTRL: + return KMOD_RCTRL; + + case sdl::Modifier::LALT: + return KMOD_LALT; + case sdl::Modifier::RALT: + return KMOD_RALT; + + case sdl::Modifier::LGUI: + return KMOD_LGUI; + case sdl::Modifier::RGUI: + return KMOD_RGUI; + + case sdl::Modifier::NUM: + return KMOD_NUM; + case sdl::Modifier::CAPS: + return KMOD_CAPS; + case sdl::Modifier::MODE: + return KMOD_MODE; + case sdl::Modifier::SCROLL: + return KMOD_SCROLL; + + case sdl::Modifier::CTRL: + return KMOD_CTRL; + case sdl::Modifier::SHIFT: + return KMOD_SHIFT; + case sdl::Modifier::ALT: + return KMOD_ALT; + case sdl::Modifier::GUI: + return KMOD_GUI; + default: + utils::unreachable(); + } + } + + [[maybe_unused]] constexpr sdl::Modifier from_sdl_modifier(SDL_Keymod modifier) { + switch (modifier) { + case KMOD_LSHIFT: + return sdl::Modifier::LSHIFT; + case KMOD_RSHIFT: + return sdl::Modifier::RSHIFT; + + case KMOD_LCTRL: + return sdl::Modifier::LCTRL; + case KMOD_RCTRL: + return sdl::Modifier::RCTRL; + + case KMOD_LALT: + return sdl::Modifier::LALT; + case KMOD_RALT: + return sdl::Modifier::RALT; + + case KMOD_LGUI: + return sdl::Modifier::LGUI; + case KMOD_RGUI: + return sdl::Modifier::RGUI; + + case KMOD_NUM: + return sdl::Modifier::NUM; + case KMOD_CAPS: + return sdl::Modifier::CAPS; + case KMOD_MODE: + return sdl::Modifier::MODE; + case KMOD_SCROLL: + return sdl::Modifier::SCROLL; + + case KMOD_CTRL: + return sdl::Modifier::CTRL; + case KMOD_SHIFT: + return sdl::Modifier::SHIFT; + case KMOD_ALT: + return sdl::Modifier::ALT; + case KMOD_GUI: + return sdl::Modifier::GUI; + default: + utils::unreachable(); + } + } + + constexpr std::tuple, std::array, std::array> + get_modifier_type_array() { + return { + { + sdl::Modifier::LSHIFT, + sdl::Modifier::RSHIFT, + sdl::Modifier::LCTRL, + sdl::Modifier::RCTRL, + sdl::Modifier::LALT, + sdl::Modifier::RALT, + sdl::Modifier::LGUI, + sdl::Modifier::RGUI, + }, + { + sdl::Modifier::NUM, + sdl::Modifier::CAPS, + sdl::Modifier::MODE, + sdl::Modifier::SCROLL, + }, + { + sdl::Modifier::CTRL, + sdl::Modifier::SHIFT, + sdl::Modifier::ALT, + sdl::Modifier::GUI, + } + }; + } + + [[nodiscard]] std::string sdl_key_name(SDL_KeyCode keycode) { + const auto* name = SDL_GetKeyName(keycode); + if (std::strlen(name) == 0) { + throw std::runtime_error(fmt::format( + "No name for the sdl key {}: {}", static_cast>(keycode), + SDL_GetError() + )); + } + return std::string{ name }; + } + + + [[maybe_unused]] sdl::ModifierType typeof_modifier(sdl::Modifier modifier) { + const auto& [normal, special, multiple] = get_modifier_type_array(); + + if (std::ranges::find(normal, modifier) != normal.cend()) { + return sdl::ModifierType::Normal; + } + + if (std::ranges::find(special, modifier) != special.cend()) { + return sdl::ModifierType::Special; + } + + if (std::ranges::find(multiple, modifier) != multiple.cend()) { + return sdl::ModifierType::Multiple; + } + + utils::unreachable(); + } + + + constexpr std::string modifier_to_string(sdl::Modifier modifier) { + switch (modifier) { + case sdl::Modifier::LSHIFT: + return "Shift-L"; + case sdl::Modifier::RSHIFT: + return "Shift-R"; + + case sdl::Modifier::LCTRL: + return "Ctrl-L"; + case sdl::Modifier::RCTRL: + return "Ctrl-R"; + + case sdl::Modifier::LALT: + return "Alt-L"; + case sdl::Modifier::RALT: + return "Alt-R"; + + case sdl::Modifier::LGUI: + return "Gui-L"; + case sdl::Modifier::RGUI: + return "Gui-R"; + + case sdl::Modifier::NUM: + return "Num"; + case sdl::Modifier::CAPS: + return "Caps"; + case sdl::Modifier::MODE: + return "Mode"; + case sdl::Modifier::SCROLL: + return "Scroll"; + + case sdl::Modifier::CTRL: + return "Ctrl"; + case sdl::Modifier::SHIFT: + return "Shift"; + case sdl::Modifier::ALT: + return "Alt"; + case sdl::Modifier::GUI: + return "Gui"; + default: + utils::unreachable(); + } + } + + + helper::optional modifier_from_string(const std::string& modifier) { + + if (modifier.empty()) { + return helper::nullopt; + } + + const auto lower_case = string::to_lower_case(modifier); + + + const std::unordered_map map{ + { "shift-l", sdl::Modifier::LSHIFT }, + { "shift-r", sdl::Modifier::RSHIFT }, + { "ctrl-l", sdl::Modifier::LCTRL }, + { "ctrl-r", sdl::Modifier::RCTRL }, + { "alt-l", sdl::Modifier::LALT }, + { "alt-r", sdl::Modifier::RALT }, + { "gui-l", sdl::Modifier::LGUI }, + { "gui-r", sdl::Modifier::RGUI }, + { "num", sdl::Modifier::NUM }, + { "caps", sdl::Modifier::CAPS }, + { "mode", sdl::Modifier::MODE }, + { "scroll", sdl::Modifier::SCROLL }, + { "ctrl", sdl::Modifier::CTRL }, + { "shift", sdl::Modifier::SHIFT }, + { "alt", sdl::Modifier::ALT }, + { "gui", sdl::Modifier::GUI }, + }; + + if (map.contains(lower_case)) { + return map.at(lower_case); + } + + return helper::nullopt; + } + +} // namespace + + +helper::expected sdl::Key::from_string(const std::string& value) { + + auto tokens = string::split_string_by_char(value, "+"); + for (auto& token : tokens) { + string::trim(token); + } + + std::vector modifiers{}; + + for (size_t i = 0; i < tokens.size(); ++i) { + const auto& token = tokens.at(i); + + if (token.empty()) { + return helper::unexpected{ "Empty token" }; + } + + if (i + 1 == tokens.size()) { + const auto keycode = sdl::Key::sdl_keycode_from_string(token); + if (not keycode.has_value()) { + const auto modifier = modifier_from_string(token); + if (modifier.has_value()) { + return helper::unexpected{ "No key but only modifiers given" }; + } + return helper::unexpected{ keycode.error() }; + } + + //search for duplicates + std::unordered_set values{}; + for (const auto& modifier : modifiers) { + if (values.contains(modifier)) { + return helper::unexpected{ + fmt::format("Duplicate modifier: '{}'", modifier_to_string(modifier)) + }; + } + values.insert(modifier); + } + + return sdl::Key{ keycode.value(), modifiers }; + } + + const auto modifier = modifier_from_string(token); + if (not modifier.has_value()) { + return helper::unexpected{ fmt::format("Not a valid modifier: '{}'", token) }; + } + + modifiers.push_back(modifier.value()); + } + + return helper::unexpected{ "empty input" }; +} + + +[[nodiscard]] bool sdl::Key::has_modifier(const Modifier& modifier) const { + const auto sdl_modifier = to_sdl_modifier(modifier); + ; + + return (m_modifiers & sdl_modifier) != 0; +} + +[[nodiscard]] bool sdl::Key::has_modifier_exact(const Modifier& modifier) const { + + sdl::Modifier has_not{}; + + switch (modifier) { + case sdl::Modifier::LSHIFT: + has_not = sdl::Modifier::RSHIFT; + break; + case sdl::Modifier::RSHIFT: + has_not = sdl::Modifier::LSHIFT; + break; + case sdl::Modifier::LCTRL: + has_not = sdl::Modifier::RCTRL; + break; + case sdl::Modifier::RCTRL: + has_not = sdl::Modifier::LCTRL; + break; + case sdl::Modifier::LALT: + has_not = sdl::Modifier::RALT; + break; + case sdl::Modifier::RALT: + has_not = sdl::Modifier::LALT; + break; + case sdl::Modifier::LGUI: + has_not = sdl::Modifier::RGUI; + break; + case sdl::Modifier::RGUI: + has_not = sdl::Modifier::LGUI; + break; + + case sdl::Modifier::NUM: + case sdl::Modifier::CAPS: + case sdl::Modifier::MODE: + case sdl::Modifier::SCROLL: + + case sdl::Modifier::CTRL: + case sdl::Modifier::SHIFT: + case sdl::Modifier::ALT: + case sdl::Modifier::GUI: { + const auto sdl_modifier = to_sdl_modifier(modifier); + return (m_modifiers & sdl_modifier) == sdl_modifier; + } + + default: + utils::unreachable(); + } + + + const auto sdl_modifier = to_sdl_modifier(modifier); + const auto sdl_modifier_not = to_sdl_modifier(has_not); + + return (m_modifiers & sdl_modifier) == sdl_modifier && (m_modifiers & sdl_modifier_not) == 0; +} + + +[[nodiscard]] bool sdl::Key::operator==(const Key& other) const { + return is_equal(other, true); +} + +[[nodiscard]] bool sdl::Key::is_equal(const Key& other, bool ignore_special_modifiers) const { + if (not is_key(other)) { + return false; + } + + + // fast path if they both are exactly the same + if (other.m_modifiers == m_modifiers) { + return true; + } + + + const auto& [_, special, multiple] = get_modifier_type_array(); + + + for (const auto& modifier : multiple) { + const auto sdl_modifier = to_sdl_modifier(modifier); + if (((other.m_modifiers & sdl_modifier) & (this->m_modifiers & sdl_modifier) //NOLINT(misc-redundant-expression) + ) + == 0) { + return false; + } + } + + if (ignore_special_modifiers) { + return true; + } + + return std::ranges::all_of(special, [this, &other](const auto& modifier) { + const auto sdl_modifier = to_sdl_modifier(modifier); + return ((other.m_modifiers & sdl_modifier) == (m_modifiers & sdl_modifier)); + }); +} + +[[nodiscard]] std::string sdl::Key::to_string() const { + std::vector parts{}; + + const auto& [normal, special, multiple] = get_modifier_type_array(); + + for (const auto modifier : special) { + if (has_modifier(modifier)) { + parts.emplace_back(modifier_to_string(modifier)); + } + } + + for (const auto modifier : multiple) { + if (has_modifier_exact(modifier)) { + parts.emplace_back(modifier_to_string(modifier)); + } + } + + for (const auto modifier : normal) { + if (has_modifier_exact(modifier)) { + parts.emplace_back(modifier_to_string(modifier)); + } + } + + + parts.emplace_back(sdl_key_name(m_keycode)); + + return fmt::format("{}", fmt::join(parts, " + ")); +} + + +[[nodiscard]] helper::expected sdl::Key::sdl_keycode_from_string(const std::string& value) { + const auto key = SDL_GetKeyFromName(value.c_str()); + if (key == SDLK_UNKNOWN) { + return helper::unexpected{ + fmt::format("No sdl key for the name '{}': {}", value, SDL_GetError()) + }; + } + + return static_cast(key); +} + +[[nodiscard]] sdl::Key::UnderlyingModifierType sdl::Key::sdl_modifier_from_modifiers( + const std::vector& modifiers +) { + UnderlyingModifierType result = KMOD_NONE; + for (const auto& modifier : modifiers) { + result |= to_sdl_modifier(modifier); + } + + return result; +} diff --git a/src/manager/sdl_key.hpp b/src/manager/sdl_key.hpp new file mode 100644 index 00000000..279850b5 --- /dev/null +++ b/src/manager/sdl_key.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include "helper/expected.hpp" + +#include "helper/types.hpp" + +#include +#include +#include +#include + +namespace sdl { + + enum class Modifier : u8 { + LSHIFT, + RSHIFT, + + LCTRL, + RCTRL, + + LALT, + RALT, + + LGUI, + RGUI, + + NUM, + CAPS, + MODE, + SCROLL, + + CTRL, + SHIFT, + ALT, + GUI, + }; + + enum class ModifierType : u8 { Normal, Multiple, Special }; + + struct Key { + // the difference between SDL_Keymod and ModifierType type is, that ModifierType is SDL_Keymod or-ed together, and supports arithmetic expressions out of the box (like & and |) + using UnderlyingModifierType = std::underlying_type_t; + + private: + SDL_KeyCode m_keycode; + UnderlyingModifierType m_modifiers; + + public: + explicit Key(SDL_KeyCode keycode, UnderlyingModifierType modifiers); + explicit Key(SDL_KeyCode keycode, const std::vector& modifiers = {}); + explicit Key(const SDL_Keysym& keysym); + + static helper::expected from_string(const std::string& value); + + [[nodiscard]] bool is_key(const Key& other) const; + + /** + * @brief Checks if the key has a modifier, this performs a logical check, e.g. LALT and ALT are treated as match + * + * @param modifier + * @return bool + */ + [[nodiscard]] bool has_modifier(const Modifier& modifier) const; + + /** + * @brief Checks if the key has a modifier, this performs a exact check, e.g. LALT and ALT are treated as NON-match + * + * @param modifier + * @return bool + */ + [[nodiscard]] bool has_modifier_exact(const Modifier& modifier) const; + + [[nodiscard]] bool operator==(const Key& other) const; + + [[nodiscard]] bool is_equal(const Key& other, bool ignore_special_modifiers = true) const; + + [[nodiscard]] std::string to_string() const; + + private: + [[nodiscard]] static helper::expected sdl_keycode_from_string(const std::string& value + ); + + [[nodiscard]] static UnderlyingModifierType sdl_modifier_from_modifiers(const std::vector& modifiers); + }; + + +} // namespace sdl + +template<> +struct fmt::formatter : formatter { + auto format(const sdl::Key& key, format_context& ctx) { + return formatter::format(key.to_string(), ctx); + } +}; + + +//TODO(Totto): add input manager and rename curretn inputmanager to game_input manager or similar + +// each devices can have multiple input devices, like keyboard, joycon etc. you can select mulltiple ones for navigation, some keys are ficed, like ctrl+c ctrl+v or arrow keys enter, esc tab,, some like wasd and similar can be chnaged by the inpout controller, support both click only and keyboard only, joyocn only, joyncon(s) + keyboard configurations + + +// each devices has a name, e.g. keabord 1, keabyord 2, SDL_NumJoysticks + +// SDL_JoystickNameForIndex +//SDL_JoystickName + +//SDL_JoystickInstanceID + +// if a type is disconnected, recognize that, and potentially pause the game !, show warning! + +//SDL_JoystickCurrentPowerLevel diff --git a/src/manager/service_provider.hpp b/src/manager/service_provider.hpp index 9d42bb79..44160039 100644 --- a/src/manager/service_provider.hpp +++ b/src/manager/service_provider.hpp @@ -1,5 +1,8 @@ #pragma once +#include "manager/event_dispatcher.hpp" + + #if defined(_HAVE_DISCORD_SDK) && !defined(_OOPETRIS_RECORDING_UTILITY) #include "discord/core.hpp" @@ -8,14 +11,19 @@ #endif +//forward declarations struct CommandLineArguments; -struct Settings; +struct SettingsManager; struct MusicManager; +struct EventDispatcher; struct Renderer; struct FontManager; -struct EventDispatcher; struct Window; +namespace input { + struct InputManager; +} + namespace scenes { struct Scene; } @@ -30,18 +38,21 @@ struct ServiceProvider { [[nodiscard]] virtual CommandLineArguments& command_line_arguments() = 0; [[nodiscard]] virtual const CommandLineArguments& command_line_arguments() const = 0; - [[nodiscard]] virtual Settings& settings() = 0; - [[nodiscard]] virtual const Settings& settings() const = 0; + [[nodiscard]] virtual SettingsManager& settings_manager() = 0; + [[nodiscard]] virtual const SettingsManager& settings_manager() const = 0; [[nodiscard]] virtual MusicManager& music_manager() = 0; [[nodiscard]] virtual const MusicManager& music_manager() const = 0; [[nodiscard]] virtual const Renderer& renderer() const = 0; - [[nodiscard]] virtual FontManager& fonts() = 0; - [[nodiscard]] virtual const FontManager& fonts() const = 0; + [[nodiscard]] virtual FontManager& font_manager() = 0; + [[nodiscard]] virtual const FontManager& font_manager() const = 0; [[nodiscard]] virtual EventDispatcher& event_dispatcher() = 0; [[nodiscard]] virtual const EventDispatcher& event_dispatcher() const = 0; [[nodiscard]] virtual const Window& window() const = 0; [[nodiscard]] virtual Window& window() = 0; + [[nodiscard]] virtual input::InputManager& input_manager() = 0; + [[nodiscard]] virtual const input::InputManager& input_manager() const = 0; + #if defined(_HAVE_DISCORD_SDK) && !defined(_OOPETRIS_RECORDING_UTILITY) [[nodiscard]] virtual helper::optional& discord_instance() = 0; diff --git a/src/manager/settings.hpp b/src/manager/settings.hpp deleted file mode 100644 index 87863cd4..00000000 --- a/src/manager/settings.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#include "helper/magic_enum_wrapper.hpp" -#include "helper/parse_json.hpp" -#include "manager/controls.hpp" -#include "platform/platform.hpp" - -#include -#include -#include -#include - -inline void to_json(nlohmann::json& j, const KeyboardControls& controls) { - j = nlohmann::json{ - { "rotate_left", magic_enum::enum_name(controls.rotate_left) }, - { "rotate_right", magic_enum::enum_name(controls.rotate_right) }, - { "move_left", magic_enum::enum_name(controls.move_left) }, - { "move_right", magic_enum::enum_name(controls.move_right) }, - { "move_down", magic_enum::enum_name(controls.move_down) }, - { "drop", magic_enum::enum_name(controls.drop) }, - { "hold", magic_enum::enum_name(controls.hold) }, - }; -} - - -inline KeyCode get_key_code_safe(const nlohmann::json& j, const std::string& name) { - - auto context = j.at(name); - - std::string input; - context.get_to(input); - - const auto& value = magic_enum::enum_cast(input); - if (not value.has_value()) { - throw nlohmann::json::type_error::create( - 302, fmt::format("Expected a valid KeyCode in key '{}', but got '{}'", name, input), &context - ); - } - return value.value(); -} - -inline void from_json(const nlohmann::json& j, KeyboardControls& controls) { - - json::check_for_no_additional_keys( - j, { "type", "rotate_left", "rotate_right", "move_left", "move_right", "move_down", "drop", "hold" } - ); - - controls.rotate_left = get_key_code_safe(j, "rotate_left"); - controls.rotate_right = get_key_code_safe(j, "rotate_right"); - controls.move_left = get_key_code_safe(j, "move_left"); - controls.move_right = get_key_code_safe(j, "move_right"); - controls.move_down = get_key_code_safe(j, "move_down"); - controls.drop = get_key_code_safe(j, "drop"); - controls.hold = get_key_code_safe(j, "hold"); - controls.validate(); -} - -using Controls = std::variant; - -inline void to_json(nlohmann::json& j, const Controls& controls) { - std::visit( - helper::overloaded{ - [&](const KeyboardControls& keyboard_controls) { - to_json(j, keyboard_controls); - j["type"] = "keyboard"; - }, - }, - controls - ); -} - -inline void from_json(const nlohmann::json& j, Controls& controls) { - const auto& type = j.at("type"); - - if (type == "keyboard") { - KeyboardControls keyboard_controls; - from_json(j, keyboard_controls); - controls = keyboard_controls; - } else { - throw std::runtime_error{ fmt::format("unsupported control type '{}'", to_string(type)) }; - } -} - -struct Settings { - Platform platform{}; - Controls controls; - float volume{ 0.2f }; - bool discord{ false }; //changing this requires a restart -}; - -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Settings, platform, controls, volume, discord) diff --git a/src/manager/settings_manager.cpp b/src/manager/settings_manager.cpp new file mode 100644 index 00000000..0ec6caee --- /dev/null +++ b/src/manager/settings_manager.cpp @@ -0,0 +1,54 @@ +#include "settings_manager.hpp" +#include "helper/graphic_utils.hpp" +#include "input/keyboard_input.hpp" +#include "input/touch_input.hpp" + + +#include + +SettingsManager::SettingsManager(ServiceProvider* service_provider) : m_service_provider{ service_provider } { + const std::filesystem::path settings_file = utils::get_root_folder() / detail::settings_filename; + + const auto result = json::try_parse_json_file(settings_file); + + if (result.has_value()) { + m_settings = result.value(); + } else { + spdlog::error("unable to load settings from \"{}\": {}", detail::settings_filename, result.error()); + spdlog::warn("applying default settings"); + + m_settings = { + detail::Settings{ {}, helper::nullopt, 1.0, false } + }; + } +} + + +[[nodiscard]] const detail::Settings& SettingsManager::settings() const { + return m_settings; +} + + +void detail::to_json(nlohmann::json& obj, const detail::Settings& settings) { + obj = nlohmann::json{ + { "controls", + nlohmann::json{ { "inputs", settings.controls }, { "selected", settings.selected } }, + { "volume", settings.volume }, + { "discord", settings.discord } } + }; +} + +void detail::from_json(const nlohmann::json& obj, detail::Settings& settings) { + + ::json::check_for_no_additional_keys(obj, { "controls", "volume", "discord" }); + + obj.at("volume").get_to(settings.volume); + obj.at("discord").get_to(settings.discord); + + const auto& controls = obj.at("controls"); + + ::json::check_for_no_additional_keys(controls, { "inputs", "selected" }); + + controls.at("inputs").get_to(settings.controls); + controls.at("selected").get_to(settings.selected); +} diff --git a/src/manager/settings_manager.hpp b/src/manager/settings_manager.hpp new file mode 100644 index 00000000..365eb794 --- /dev/null +++ b/src/manager/settings_manager.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include "helper/optional.hpp" +#include "input/joystick_input.hpp" +#include "input/keyboard_input.hpp" +#include "input/touch_input.hpp" +#include "manager/service_provider.hpp" + +#include +#include + +using Controls = std::variant; + +namespace nlohmann { + template<> + struct adl_serializer { + static Controls from_json(const json& obj) { + const auto& type = obj.at("type"); + + if (type == "keyboard") { + return Controls{ nlohmann::adl_serializer::from_json(obj) }; + } + + if (type == "joystick") { + return Controls{ nlohmann::adl_serializer::from_json(obj) }; + } + + if (type == "touch") { + return Controls{ nlohmann::adl_serializer::from_json(obj) }; + } + + throw std::runtime_error{ fmt::format("unsupported control type '{}'", to_string(type)) }; + } + + static void to_json(json& obj, Controls controls) { // NOLINT(misc-no-recursion) + std::visit( + helper::overloaded{ + [&](const input::KeyboardSettings& keyboard_settings) { // NOLINT(misc-no-recursion) + to_json(obj, keyboard_settings); + obj["type"] = "keyboard"; + }, + [&](const input::JoystickSettings& joystick_settings) { // NOLINT(misc-no-recursion) + to_json(obj, joystick_settings); + obj["type"] = "joystick"; + }, + [&](const input::TouchSettings& touch_settings) { // NOLINT(misc-no-recursion) + to_json(obj, touch_settings); + obj["type"] = "touch"; + } }, + controls + ); + } + }; +} // namespace nlohmann + + +namespace detail { + + static constexpr auto settings_filename = "settings.json"; + + + struct Settings { + std::vector controls; + helper::optional selected; + float volume{ 0.2F }; + bool discord{ false }; //changing this requires a restart + }; + + + void to_json(nlohmann::json& obj, const Settings& settings); + + void from_json(const nlohmann::json& obj, Settings& settings); + +} // namespace detail + + +struct SettingsManager { +private: + ServiceProvider* m_service_provider; + detail::Settings m_settings; + +public: + explicit SettingsManager(ServiceProvider* service_provider); + + [[nodiscard]] const detail::Settings& settings() const; +}; diff --git a/src/meson.build b/src/meson.build index b57a8dab..9152205c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,14 +14,13 @@ main_files = files( subdir('game') subdir('graphics') subdir('helper') +subdir('input') subdir('manager') -subdir('platform') subdir('recordings') subdir('scenes') subdir('thirdparty') subdir('ui') - if online_multiplayer_supported subdir('lobby') endif diff --git a/src/platform/android_input.cpp b/src/platform/android_input.cpp deleted file mode 100644 index 76229f4f..00000000 --- a/src/platform/android_input.cpp +++ /dev/null @@ -1,130 +0,0 @@ - - -#if defined(__ANDROID__) - - -#include "android_input.hpp" - -#include -#include - - -void TouchInput::handle_event(const SDL_Event& event, const Window*) { - m_event_buffer.push_back(event); -} - -void TouchInput::update(SimulationStep simulation_step_index) { - for (const auto& event : m_event_buffer) { - const auto input_event = sdl_event_to_input_event(event); - if (input_event.has_value()) { - Input::handle_event(*input_event, simulation_step_index); - } - } - m_event_buffer.clear(); - - Input::update(simulation_step_index); -} - -helper::optional TouchInput::sdl_event_to_input_event( // NOLINT(readability-function-cognitive-complexity) - const SDL_Event& event -) { - //TODO to handle those things better, holding has to be supported - - // also take into accounts fingerId, since there may be multiple fingers, each finger has it's own saved state - const SDL_FingerID finger_id = event.tfinger.fingerId; - - // this is used, to get the percentage, since it' all constexpr it's mainly for the developer to not think about percentages but about a pixel range on a certain device, but then it works as expected everywhere - constexpr auto screen_h_reference = 2160.0; - constexpr auto screen_w_reference = 1080.0; - - if (event.type == SDL_FINGERDOWN) { - if (m_finger_state.contains(finger_id) and m_finger_state.at(finger_id).has_value()) { - // there are some valid reasons, this can occur now - return helper::nullopt; - } - - const auto x = event.tfinger.x; - const auto y = event.tfinger.y; - const auto timestamp = event.tfinger.timestamp; - - m_finger_state.insert_or_assign( - finger_id, - helper::optional{ - PressedState{timestamp, x, y} - } - ); - } - - - if (event.type == SDL_FINGERUP) { - if (!m_finger_state.contains(finger_id) or !m_finger_state.at(finger_id).has_value()) { - // there are some valid reasons, this can occur now - return helper::nullopt; - } - - const auto pressed_state = m_finger_state.at(finger_id).value(); - - const auto x = event.tfinger.x; - const auto y = event.tfinger.y; - const auto timestamp = event.tfinger.timestamp; - - constexpr auto threshold_x = 150.0 / screen_w_reference; - constexpr auto threshold_y = 400.0 / screen_h_reference; - constexpr auto duration_threshold = 500.0; - constexpr auto duration_drop_threshold = 200.0; - - const auto dx = x - pressed_state.x; - const auto dy = y - pressed_state.y; - const auto duration = timestamp - pressed_state.timestamp; - - const auto dx_abs = std::fabs(dx); - const auto dy_abs = std::fabs(dy); - - m_finger_state.insert_or_assign(finger_id, helper::nullopt); - if (duration < duration_threshold) { - if (dx_abs < threshold_x and dy_abs < threshold_y) { - // tap on the right side of the screen - if (x > 0.5) { - return InputEvent::RotateRightPressed; - } - // tap on the left side of the screen - if (x <= 0.5) { - return InputEvent::RotateLeftPressed; - } - } - } - - // swipe right - if (dx > threshold_x and dy_abs < threshold_y) { - return InputEvent::MoveRightPressed; - } - // swipe left - if (dx < -threshold_x and dy_abs < threshold_y) { - return InputEvent::MoveLeftPressed; - } - // swipe down - if (dy > threshold_y and dx_abs < threshold_x) { - // swipe down to drop - if (duration < duration_drop_threshold) { - return InputEvent::DropPressed; - } - return InputEvent::MoveDownPressed; - } - - // swipe up - if (dy < -threshold_y and dx_abs < threshold_x) { - return InputEvent::HoldPressed; - } - } - - - if (event.type == SDL_FINGERMOTION) { - //TODO support hold - } - - - return helper::nullopt; -} - - -#endif diff --git a/src/platform/android_input.hpp b/src/platform/android_input.hpp deleted file mode 100644 index 71ea3439..00000000 --- a/src/platform/android_input.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#if defined(__ANDROID__) - - -#include "input.hpp" -#include "manager/event_dispatcher.hpp" - - -struct TouchInput final : public Input, public EventListener { -private: - struct PressedState { - Uint32 timestamp; - float x; - float y; - explicit PressedState(Uint32 timestamp, float x, float y) : timestamp{ timestamp }, x{ x }, y{ y } { } - }; - - std::unordered_map> m_finger_state; - std::vector m_event_buffer; - EventDispatcher* m_event_dispatcher; - -public: - explicit TouchInput(EventDispatcher* event_dispatcher) - : Input{ InputType::Touch }, - m_event_dispatcher{ event_dispatcher } { - m_event_dispatcher->register_listener(this); - } - - ~TouchInput() override { - m_event_dispatcher->unregister_listener(this); - } - - void handle_event(const SDL_Event& event, const Window* window) override; - void update(SimulationStep simulation_step_index) override; - -private: - [[nodiscard]] helper::optional sdl_event_to_input_event(const SDL_Event& event); -}; - -#endif diff --git a/src/platform/capabilities.cpp b/src/platform/capabilities.cpp deleted file mode 100644 index 9bde5b1a..00000000 --- a/src/platform/capabilities.cpp +++ /dev/null @@ -1,426 +0,0 @@ - -#include -#include - -#include "helper/utils.hpp" -#include "platform/capabilities.hpp" - -#if defined(__CONSOLE__) -#include "helper/console_helpers.hpp" -#include "platform/console_buttons.hpp" -#endif - - -namespace { - - inline std::string get_error_from_errno() { - -#if defined(_MSC_VER) - char buffer[256] = { 0 }; - const auto result = strerror_s<256>(buffer, errno); - - if (result == 0) { - return std::string{ buffer }; - - } else { - return std::string{ "Error while getting error!" }; - } - -#else - return std::string{ std::strerror(errno) }; - -#endif - } - - -} // namespace - - -[[nodiscard]] std::string utils::built_for_platform() { -#if defined(__ANDROID__) - return "Android"; -#elif defined(__SWITCH__) - return "Nintendo Switch"; -#elif defined(__3DS__) - return "Nintendo 3DS"; -#elif defined(FLATPAK_BUILD) - return "Linux (Flatpak)"; -#elif defined(__linux__) - return "Linux"; -#elif defined(_WIN32) - return "Windows"; -#elif defined(__APPLE__) - return "MacOS"; -#else -#error "Unsupported platform" -#endif -} - -// partially from: https://stackoverflow.com/questions/17347950/how-do-i-open-a-url-from-c -[[nodiscard]] bool utils::open_url(const std::string& url) { -#if defined(__ANDROID__) - const auto result = SDL_OpenURL(url.c_str()); - if (result < 0) { - spdlog::error("Error in opening url in android: {}", SDL_GetError()); - return false; - } - - return true; - -#elif defined(__CONSOLE__) - auto result = console::open_url(url); - spdlog::info("Returned string from url open was: {}", result); - return true; -#else - //TODO: this is dangerous, if we supply user input, so use SDL_OpenURL preferably - -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - const std::string shell_command = "start " + url; -#elif defined(__APPLE__) - const std::string shell_command = "open " + url; -#elif defined(__linux__) - const std::string shell_command = "xdg-open " + url; -#else -#error "Unsupported platform" -#endif - - const auto result = system(shell_command.c_str()); - if (result < 0) { - spdlog::error("Error in opening url: {}", get_error_from_errno()); - return false; - } - - - return true; - - -#endif -} - - -[[nodiscard]] bool utils::event_is_action(const SDL_Event& event, const CrossPlatformAction action) { -#if defined(__ANDROID__) - switch (action) { - case CrossPlatformAction::OK: - case CrossPlatformAction::DOWN: - case CrossPlatformAction::UP: - case CrossPlatformAction::LEFT: - case CrossPlatformAction::RIGHT: - case CrossPlatformAction::OPEN_SETTINGS: - case CrossPlatformAction::TAB: - // this can't be checked here, it has to be checked via collision on buttons etc. event_is_action(..., ...::DOWN, UP ...) can only be used inside device_supports_keys() clauses! - throw std::runtime_error("Not supported on android 'event_is_action'"); - case CrossPlatformAction::PAUSE: - return (event.type == SDL_KEYDOWN and event.key.keysym.sym == SDLK_AC_BACK); - - case CrossPlatformAction::UNPAUSE: - return event.type == SDL_FINGERDOWN; - - case CrossPlatformAction::EXIT: - case CrossPlatformAction::CLOSE: - return (event.type == SDL_KEYDOWN and event.key.keysym.sym == SDLK_AC_BACK); - - default: - utils::unreachable(); - } -#else - - const std::vector needed_events = utils::key_map.at(static_cast(action)); - for (const auto& needed_event : needed_events) { // NOLINT(readability-use-anyofallof) -#if defined(__SWITCH__) or defined(__3DS__) - //TODO: some events use the SDL_JOYAXISMOTION event, but that needs to be done in the next reiteration of these helpers! - if (event.type == SDL_JOYBUTTONDOWN and event.jbutton.button == needed_event) { - return true; - } -#else - if (event.type == SDL_KEYDOWN and event.key.keysym.sym == needed_event) { - return true; - } -#endif - } - return false; -#endif -} - - -[[nodiscard]] std::vector utils::get_bound_keys() { - std::vector bound_keys{}; - for (const auto& [_, keys] : utils::key_map) { - for (const auto key : keys) { - bound_keys.push_back(key); - } - } - return bound_keys; -} - - -[[nodiscard]] std::vector utils::get_bound_keys(const std::vector& actions) { - std::vector bound_keys{}; - for (const auto& [value, keys] : utils::key_map) { - if (std::find(actions.cbegin(), actions.cend(), static_cast(value)) - == actions.cend()) { - continue; - } - for (const auto key : keys) { - bound_keys.push_back(key); - } - } - return bound_keys; -} - -[[nodiscard]] std::string utils::action_description(CrossPlatformAction action) { -#if defined(__ANDROID__) - - switch (action) { - case CrossPlatformAction::OK: - case CrossPlatformAction::DOWN: - case CrossPlatformAction::UP: - case CrossPlatformAction::LEFT: - case CrossPlatformAction::RIGHT: - case CrossPlatformAction::OPEN_SETTINGS: - case CrossPlatformAction::TAB: - // this can't be checked here, it has to be checked via collision on buttons etc. event_is_action(..., ...::DOWN, UP ...) can only be used inside device_supports_keys() clauses! - throw std::runtime_error("Not supported on android 'action_description'"); - case CrossPlatformAction::UNPAUSE: - return "Tap anywhere"; - case CrossPlatformAction::PAUSE: - case CrossPlatformAction::EXIT: - case CrossPlatformAction::CLOSE: - return "Back"; - - default: - utils::unreachable(); - } -#elif defined(__SWITCH__) - switch (action) { - case CrossPlatformAction::OK: - return "A"; - case CrossPlatformAction::PAUSE: - case CrossPlatformAction::UNPAUSE: - return "PLUS"; - case CrossPlatformAction::CLOSE: - case CrossPlatformAction::EXIT: - return "MINUS"; - case CrossPlatformAction::DOWN: - return "Down"; - case CrossPlatformAction::UP: - return "Up"; - case CrossPlatformAction::LEFT: - return "Left"; - case CrossPlatformAction::RIGHT: - return "Right"; - case CrossPlatformAction::OPEN_SETTINGS: - return "Y"; - default: - utils::unreachable(); - } -#elif defined(__3DS__) - switch (action) { - case CrossPlatformAction::OK: - return "A"; - case CrossPlatformAction::PAUSE: - case CrossPlatformAction::UNPAUSE: - return "Y"; - case CrossPlatformAction::CLOSE: - case CrossPlatformAction::EXIT: - return "X"; - case CrossPlatformAction::DOWN: - return "Down"; - case CrossPlatformAction::UP: - return "Up"; - case CrossPlatformAction::LEFT: - return "Left"; - case CrossPlatformAction::RIGHT: - return "Right"; - case CrossPlatformAction::OPEN_SETTINGS: - return "Select"; - default: - utils::unreachable(); - } -#else - switch (action) { - case CrossPlatformAction::OK: - return "Enter"; - case CrossPlatformAction::PAUSE: - case CrossPlatformAction::UNPAUSE: - case CrossPlatformAction::CLOSE: - return "Esc"; - case CrossPlatformAction::EXIT: - return "Enter"; - case CrossPlatformAction::DOWN: - return "Down"; - case CrossPlatformAction::UP: - return "Up"; - case CrossPlatformAction::LEFT: - return "Left"; - case CrossPlatformAction::RIGHT: - return "Right"; - case CrossPlatformAction::OPEN_SETTINGS: - return "E"; - default: - utils::unreachable(); - } -#endif -} - - -//TODO: not correctly supported ButtonUp, since it only can be triggered, when a ButtonDown event is fired first and the target is not left (unhovered) - -[[nodiscard]] bool utils::event_is_click_event(const SDL_Event& event, CrossPlatformClickEvent click_type) { - -#if defined(__ANDROID__) - decltype(event.type) desired_type{}; - switch (click_type) { - case CrossPlatformClickEvent::Motion: - desired_type = SDL_FINGERMOTION; - break; - case CrossPlatformClickEvent::ButtonDown: - desired_type = SDL_FINGERDOWN; - break; - case CrossPlatformClickEvent::ButtonUp: - desired_type = SDL_FINGERUP; - break; - case CrossPlatformClickEvent::Any: - return event.type == SDL_FINGERMOTION || event.type == SDL_FINGERDOWN || event.type == SDL_FINGERUP; - default: - utils::unreachable(); - } - - return event.type == desired_type; - -#elif defined(__SWITCH__) - UNUSED(event); - UNUSED(click_type); - throw std::runtime_error("Not supported on the Nintendo switch"); -#else - decltype(event.type) desired_type{}; - switch (click_type) { - case CrossPlatformClickEvent::Motion: - desired_type = SDL_MOUSEMOTION; - break; - case CrossPlatformClickEvent::ButtonDown: - desired_type = SDL_MOUSEBUTTONDOWN; - break; - case CrossPlatformClickEvent::ButtonUp: - desired_type = SDL_MOUSEBUTTONUP; - break; - case CrossPlatformClickEvent::Any: - return event.type == SDL_MOUSEMOTION - || ((event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) - && event.button.button == SDL_BUTTON_LEFT); - default: - utils::unreachable(); - } - - - return event.type == desired_type && event.button.button == SDL_BUTTON_LEFT; -#endif -} - -[[nodiscard]] std::pair utils::get_raw_coordinates(const Window* window, const SDL_Event& event) { - - assert(utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Any) && "expected a click event"); - -#if defined(__ANDROID__) - // These are doubles, from 0-1 (or if using virtual layouts > 0) in percent, the have to be casted to absolut x coordinates! - const double x_percent = event.tfinger.x; - const double y_percent = event.tfinger.y; - const auto window_size = window->size(); - const auto x = static_cast(std::round(x_percent * window_size.x)); - const auto y = static_cast(std::round(y_percent * window_size.y)); - - -#elif defined(__SWITCH__) - UNUSED(window); - UNUSED(event); - throw std::runtime_error("Not supported on the Nintendo switch"); - int x{}; - int y{}; -#else - UNUSED(window); - - Sint32 x{}; - Sint32 y{}; - switch (event.type) { - case SDL_MOUSEMOTION: - x = event.motion.x; - y = event.motion.y; - break; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - x = event.button.x; - y = event.button.y; - break; - default: - utils::unreachable(); - } -#endif - - - return { static_cast(x), static_cast(y) }; -} - - -[[nodiscard]] bool utils::is_event_in(const Window* window, const SDL_Event& event, const shapes::URect& rect) { - - const auto& [x, y] = get_raw_coordinates(window, event); - const auto casted_rect = rect.cast(); - - const auto rect_start_x = casted_rect.top_left.x; - const auto rect_start_y = casted_rect.top_left.y; - const auto rect_end_x = casted_rect.bottom_right.x; - const auto rect_end_y = casted_rect.bottom_right.y; - - - const bool button_clicked = (x >= rect_start_x and x <= rect_end_x and y >= rect_start_y and y <= rect_end_y); - - return button_clicked; -} - - -[[nodiscard]] SDL_Event utils::offset_event(const Window* window, const SDL_Event& event, const shapes::IPoint& point) { - - - assert(utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Any) && "expected a click event"); - - - auto new_event = event; - -#if defined(__ANDROID__) - // These are doubles in percent, the have to be modified by using the windows sizes - - - const double x_percent = event.tfinger.x; - const double y_percent = event.tfinger.y; - const auto window_size = window->size(); - new_event.tfinger.x = x_percent + static_cast(point.x) / static_cast(window_size.x); - new_event.tfinger.y = y_percent + static_cast(point.y) / static_cast(window_size.y); - - -#elif defined(__SWITCH__) - UNUSED(window); - UNUSED(event); - UNUSED(point); - UNUSED(new_event); - throw std::runtime_error("Not supported on the Nintendo switch"); -#else - UNUSED(window); - - switch (event.type) { - case SDL_MOUSEMOTION: - new_event.motion.x = event.motion.x + point.x; - new_event.motion.y = event.motion.y + point.y; - break; - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - new_event.button.x = event.button.x + point.x; - new_event.button.y = event.button.y + point.y; - break; - default: - utils::unreachable(); - } -#endif - - - return new_event; -} diff --git a/src/platform/capabilities.hpp b/src/platform/capabilities.hpp deleted file mode 100644 index 31bf07a8..00000000 --- a/src/platform/capabilities.hpp +++ /dev/null @@ -1,150 +0,0 @@ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "graphics/window.hpp" -#include "helper/types.hpp" -#include "helper/utils.hpp" - -#if defined(__CONSOLE__) -#include "platform/console_buttons.hpp" -#endif - -//TODO factor this file into mulitple ones, according to logic distribution of what the functions are doing, remove and refacator the input helper functions - -namespace utils { - - enum class Orientation : u8 { - Portrait, // 9x16, e.g. smartphone - Landscape // 16x9 - }; - - struct Capabilities { - bool supports_keys; - bool supports_clicks; - Orientation orientation; - }; - - // the PAUSE and UNPAUSE might be different (e.g on android, even if androids map is stub, - // it checks in the usage of these for the CrossPlatformAction!), so don't remove the duplication here! - enum class CrossPlatformAction : u8 { OK, PAUSE, UNPAUSE, EXIT, DOWN, UP, LEFT, RIGHT, CLOSE, OPEN_SETTINGS, TAB }; - - static std::unordered_map> - key_map = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -#if defined(__ANDROID__) - { - { static_cast(CrossPlatformAction::OK), { 0 }}, - { static_cast(CrossPlatformAction::PAUSE), { 0 }}, - { static_cast(CrossPlatformAction::UNPAUSE), { 0 }}, - { static_cast(CrossPlatformAction::EXIT), { 0 }}, - { static_cast(CrossPlatformAction::DOWN), { 0 }}, - { static_cast(CrossPlatformAction::UP), { 0 }}, - { static_cast(CrossPlatformAction::LEFT), { 0 }}, - { static_cast(CrossPlatformAction::RIGHT), { 0 }}, - { static_cast(CrossPlatformAction::CLOSE), { 0 }}, - {static_cast(CrossPlatformAction::OPEN_SETTINGS), { 0 }}, - { static_cast(CrossPlatformAction::TAB), { 0 }} - }; -#elif defined(__SWITCH__) - { - { static_cast(CrossPlatformAction::OK),{ JOYCON_A } }, - { static_cast(CrossPlatformAction::PAUSE), { JOYCON_PLUS }}, - { static_cast(CrossPlatformAction::UNPAUSE), { JOYCON_PLUS }}, - { static_cast(CrossPlatformAction::EXIT), { JOYCON_MINUS }}, - { static_cast(CrossPlatformAction::DOWN), - { JOYCON_DPAD_DOWN, JOYCON_LDPAD_DOWN, JOYCON_RDPAD_DOWN } }, - { static_cast(CrossPlatformAction::UP), { JOYCON_DPAD_UP, JOYCON_LDPAD_UP, JOYCON_RDPAD_UP }}, - { static_cast(CrossPlatformAction::LEFT), - { JOYCON_DPAD_LEFT, JOYCON_LDPAD_LEFT, JOYCON_RDPAD_LEFT } }, - { static_cast(CrossPlatformAction::RIGHT), - { JOYCON_DPAD_RIGHT, JOYCON_LDPAD_RIGHT, JOYCON_RDPAD_RIGHT } }, - { static_cast(CrossPlatformAction::CLOSE), { JOYCON_MINUS }}, - {static_cast(CrossPlatformAction::OPEN_SETTINGS), { JOYCON_Y }}, - { static_cast(CrossPlatformAction::TAB), {}}, // no tab support -}; -#elif defined(__3DS__) - { - { static_cast(CrossPlatformAction::OK),{ JOYCON_A } }, - { static_cast(CrossPlatformAction::PAUSE), { JOYCON_Y }}, - { static_cast(CrossPlatformAction::UNPAUSE), { JOYCON_Y }}, - { static_cast(CrossPlatformAction::EXIT), { JOYCON_X }}, - { static_cast(CrossPlatformAction::DOWN), - { JOYCON_DPAD_DOWN, JOYCON_CSTICK_DOWN, JOYCON_CPAD_DOWN } }, - { static_cast(CrossPlatformAction::UP), { JOYCON_DPAD_UP, JOYCON_CSTICK_UP, JOYCON_CPAD_UP }}, - { static_cast(CrossPlatformAction::LEFT), - { JOYCON_DPAD_LEFT, JOYCON_CSTICK_LEFT, JOYCON_CPAD_LEFT } }, - { static_cast(CrossPlatformAction::RIGHT), - { JOYCON_DPAD_RIGHT, JOYCON_CSTICK_RIGHT, JOYCON_CPAD_RIGHT } }, - { static_cast(CrossPlatformAction::CLOSE), { JOYCON_X }}, - {static_cast(CrossPlatformAction::OPEN_SETTINGS), { JOYCON_SELECT }}, - { static_cast(CrossPlatformAction::TAB), {}}, // no tab support -}; -#else - { - { static_cast(CrossPlatformAction::OK), { SDLK_RETURN, SDLK_SPACE }}, - { static_cast(CrossPlatformAction::PAUSE), { SDLK_ESCAPE }}, - { static_cast(CrossPlatformAction::UNPAUSE), { SDLK_ESCAPE }}, - { static_cast(CrossPlatformAction::EXIT), { SDLK_RETURN }}, - { static_cast(CrossPlatformAction::DOWN), { SDLK_DOWN, SDLK_s }}, - { static_cast(CrossPlatformAction::UP), { SDLK_UP, SDLK_w }}, - { static_cast(CrossPlatformAction::LEFT), { SDLK_LEFT, SDLK_a }}, - { static_cast(CrossPlatformAction::RIGHT), { SDLK_RIGHT, SDLK_d }}, - { static_cast(CrossPlatformAction::CLOSE), { SDLK_ESCAPE }}, - {static_cast(CrossPlatformAction::OPEN_SETTINGS), { SDLK_p }}, - { static_cast(CrossPlatformAction::TAB), { SDLK_TAB }}, -}; -#endif - - //TODO: refactor - [[nodiscard]] constexpr Capabilities get_capabilities() { -#if defined(__ANDROID__) - return Capabilities{ false, true, Orientation::Portrait }; -#elif defined(__SWITCH__) or defined(__3DS__) - return Capabilities{ true, false, Orientation::Landscape }; -#else - return Capabilities{ true, true, Orientation::Landscape }; -#endif - } - - [[nodiscard]] constexpr bool device_supports_keys() { - return get_capabilities().supports_keys; - } - - [[nodiscard]] constexpr bool device_supports_clicks() { - return get_capabilities().supports_clicks; - } - - [[nodiscard]] constexpr Orientation device_orientation() { - return get_capabilities().orientation; - } - - [[nodiscard]] std::string built_for_platform(); - - [[nodiscard]] bool open_url(const std::string& url); - - [[nodiscard]] bool event_is_action(const SDL_Event& event, CrossPlatformAction action); - - [[nodiscard]] std::vector get_bound_keys(); - [[nodiscard]] std::vector get_bound_keys(const std::vector& actions); - - [[nodiscard]] std::string action_description(CrossPlatformAction action); - - enum class CrossPlatformClickEvent : u8 { Motion, ButtonDown, ButtonUp, Any }; - - [[nodiscard]] bool event_is_click_event(const SDL_Event& event, CrossPlatformClickEvent click_type); - - [[nodiscard]] std::pair get_raw_coordinates(const Window* window, const SDL_Event& event); - - [[nodiscard]] bool is_event_in(const Window* window, const SDL_Event& event, const shapes::URect& rect); - - [[nodiscard]] SDL_Event offset_event(const Window* window, const SDL_Event& event, const shapes::IPoint& point); - -} // namespace utils diff --git a/src/platform/console_input.cpp b/src/platform/console_input.cpp deleted file mode 100644 index 76947d93..00000000 --- a/src/platform/console_input.cpp +++ /dev/null @@ -1,131 +0,0 @@ - -#if defined(__CONSOLE__) -#include "console_input.hpp" - - -void JoystickInput::handle_event(const SDL_Event& event, const Window*) { - m_event_buffer.push_back(event); -} - -void JoystickInput::update(SimulationStep simulation_step_index) { - for (const auto& event : m_event_buffer) { - const auto input_event = sdl_event_to_input_event(event); - if (input_event.has_value()) { - Input::handle_event(*input_event, simulation_step_index); - } - } - m_event_buffer.clear(); - - Input::update(simulation_step_index); -} - -#if defined(__SWITCH__) - - -helper::optional JoystickInput::sdl_event_to_input_event(const SDL_Event& event) const { - if (event.type == SDL_JOYBUTTONDOWN) { - const auto button = event.jbutton.button; - if (button == JOYCON_DPAD_LEFT) { - return InputEvent::RotateLeftPressed; - } - if (button == JOYCON_DPAD_RIGHT) { - return InputEvent::RotateRightPressed; - } - if (button == JOYCON_LDPAD_DOWN or button == JOYCON_RDPAD_DOWN) { - return InputEvent::MoveDownPressed; - } - if (button == JOYCON_LDPAD_LEFT or button == JOYCON_RDPAD_LEFT) { - return InputEvent::MoveLeftPressed; - } - if (button == JOYCON_LDPAD_RIGHT or button == JOYCON_RDPAD_RIGHT) { - return InputEvent::MoveRightPressed; - } - if (button == JOYCON_X) { - return InputEvent::DropPressed; - } - if (button == JOYCON_B) { - return InputEvent::HoldPressed; - } - } else if (event.type == SDL_JOYBUTTONUP) { - const auto button = event.jbutton.button; - if (button == JOYCON_DPAD_LEFT) { - return InputEvent::RotateLeftReleased; - } - if (button == JOYCON_DPAD_RIGHT) { - return InputEvent::RotateRightReleased; - } - if (button == JOYCON_LDPAD_DOWN or button == JOYCON_RDPAD_DOWN) { - return InputEvent::MoveDownReleased; - } - if (button == JOYCON_LDPAD_LEFT or button == JOYCON_RDPAD_LEFT) { - return InputEvent::MoveLeftReleased; - } - if (button == JOYCON_LDPAD_RIGHT or button == JOYCON_RDPAD_RIGHT) { - return InputEvent::MoveRightReleased; - } - if (button == JOYCON_X) { - return InputEvent::DropReleased; - } - if (button == JOYCON_B) { - return InputEvent::HoldReleased; - } - } - return helper::nullopt; -} -#elif defined(__3DS__) - - -helper::optional JoystickInput::sdl_event_to_input_event(const SDL_Event& event) const { - if (event.type == SDL_JOYBUTTONDOWN) { - const auto button = event.jbutton.button; - if (button == JOYCON_L) { - return InputEvent::RotateLeftPressed; - } - if (button == JOYCON_R) { - return InputEvent::RotateRightPressed; - } - if (button == JOYCON_DPAD_DOWN or button == JOYCON_CSTICK_DOWN) { - return InputEvent::MoveDownPressed; - } - if (button == JOYCON_DPAD_LEFT or button == JOYCON_CSTICK_LEFT) { - return InputEvent::MoveLeftPressed; - } - if (button == JOYCON_DPAD_RIGHT or button == JOYCON_CSTICK_RIGHT) { - return InputEvent::MoveRightPressed; - } - if (button == JOYCON_A) { - return InputEvent::DropPressed; - } - if (button == JOYCON_B) { - return InputEvent::HoldPressed; - } - } else if (event.type == SDL_JOYBUTTONUP) { - const auto button = event.jbutton.button; - if (button == JOYCON_L) { - return InputEvent::RotateLeftReleased; - } - if (button == JOYCON_R) { - return InputEvent::RotateRightReleased; - } - if (button == JOYCON_DPAD_DOWN or button == JOYCON_CSTICK_DOWN) { - return InputEvent::MoveDownReleased; - } - if (button == JOYCON_DPAD_LEFT or button == JOYCON_CSTICK_LEFT) { - return InputEvent::MoveLeftReleased; - } - if (button == JOYCON_DPAD_RIGHT or button == JOYCON_CSTICK_RIGHT) { - return InputEvent::MoveRightReleased; - } - if (button == JOYCON_A) { - return InputEvent::DropReleased; - } - if (button == JOYCON_B) { - return InputEvent::HoldReleased; - } - } - return helper::nullopt; -} -#endif - - -#endif diff --git a/src/platform/console_input.hpp b/src/platform/console_input.hpp deleted file mode 100644 index 82fb2e7a..00000000 --- a/src/platform/console_input.hpp +++ /dev/null @@ -1,34 +0,0 @@ - - -#pragma once - -#if defined(__CONSOLE__) - -#include "console_buttons.hpp" -#include "input.hpp" -#include "manager/event_dispatcher.hpp" - -struct JoystickInput : public Input, public EventListener { -private: - std::vector m_event_buffer; - EventDispatcher* m_event_dispatcher; - -public: - JoystickInput(EventDispatcher* event_dispatcher) - : Input{ InputType::Controller }, - m_event_dispatcher{ event_dispatcher } { - m_event_dispatcher->register_listener(this); - } - - ~JoystickInput() override { - m_event_dispatcher->unregister_listener(this); - } - - void handle_event(const SDL_Event& event, const Window* window) override; - - void update(SimulationStep simulation_step_index) override; - -private: - [[nodiscard]] helper::optional sdl_event_to_input_event(const SDL_Event& event) const; -}; -#endif diff --git a/src/platform/input.hpp b/src/platform/input.hpp deleted file mode 100644 index bb2918db..00000000 --- a/src/platform/input.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include "helper/clock_source.hpp" -#include "helper/optional.hpp" -#include "helper/random.hpp" -#include "helper/types.hpp" -#include "manager/controls.hpp" -#include "manager/event_listener.hpp" -#include "manager/input_event.hpp" -#include "manager/settings.hpp" - -#include -#include - -struct Tetrion; - -enum class InputCommand : u8 { - MoveLeft, - MoveRight, - MoveDown, - RotateLeft, - RotateRight, - Drop, - Hold, - ReleaseMoveDown, -}; - -enum class InputType : u8 { Touch, Keyboard, Controller, Recording }; - -struct Input { -public: - using OnEventCallback = std::function; - -private: - enum class HoldableKey : u8 { - Left, - Right, - }; - - static constexpr u64 delayed_auto_shift_frames = 10; - static constexpr u64 auto_repeat_rate_frames = 2; - - std::unordered_map m_keys_hold; - InputType m_input_type; - -protected: - Tetrion* m_target_tetrion{}; - OnEventCallback m_on_event_callback{}; - - Input(InputType input_type) : m_input_type{ input_type } { } - - void handle_event(InputEvent event, SimulationStep simulation_step_index); - -public: - virtual void update(SimulationStep simulation_step_index); - virtual void late_update(SimulationStep){}; - - [[nodiscard]] InputType input_type() const { - return m_input_type; - } - - [[nodiscard]] bool supports_das() const { - return m_input_type != InputType::Touch; - } - - void set_target_tetrion(Tetrion* target_tetrion) { - m_target_tetrion = target_tetrion; - } - - void set_event_callback(OnEventCallback on_event_callback) { - m_on_event_callback = std::move(on_event_callback); - } - - Input(const Input&) = delete; - Input& operator=(const Input&) = delete; - - virtual ~Input() = default; -}; diff --git a/src/platform/keyboard_input.cpp b/src/platform/keyboard_input.cpp deleted file mode 100644 index 1500346c..00000000 --- a/src/platform/keyboard_input.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "keyboard_input.hpp" - -void KeyboardInput::handle_event(const SDL_Event& event, const Window*) { - m_event_buffer.push_back(event); -} - -void KeyboardInput::update(SimulationStep simulation_step_index) { - for (const auto& event : m_event_buffer) { - const auto input_event = sdl_event_to_input_event(event); - if (input_event.has_value()) { - Input::handle_event(*input_event, simulation_step_index); - } - } - m_event_buffer.clear(); - - Input::update(simulation_step_index); -} - -helper::optional -KeyboardInput::sdl_event_to_input_event( // NOLINT(readability-function-cognitive-complexity) - const SDL_Event& event -) const { - if (event.type == SDL_KEYDOWN and event.key.repeat == 0) { - const auto key = event.key.keysym.sym; - if (key == to_sdl_keycode(m_controls.rotate_left)) { - return InputEvent::RotateLeftPressed; - } - if (key == to_sdl_keycode(m_controls.rotate_right)) { - return InputEvent::RotateRightPressed; - } - if (key == to_sdl_keycode(m_controls.move_down)) { - return InputEvent::MoveDownPressed; - } - if (key == to_sdl_keycode(m_controls.move_left)) { - return InputEvent::MoveLeftPressed; - } - if (key == to_sdl_keycode(m_controls.move_right)) { - return InputEvent::MoveRightPressed; - } - if (key == to_sdl_keycode(m_controls.drop)) { - return InputEvent::DropPressed; - } - if (key == to_sdl_keycode(m_controls.hold)) { - return InputEvent::HoldPressed; - } - } else if (event.type == SDL_KEYUP) { - const auto key = event.key.keysym.sym; - if (key == to_sdl_keycode(m_controls.rotate_left)) { - return InputEvent::RotateLeftReleased; - } - if (key == to_sdl_keycode(m_controls.rotate_right)) { - return InputEvent::RotateRightReleased; - } - if (key == to_sdl_keycode(m_controls.move_down)) { - return InputEvent::MoveDownReleased; - } - if (key == to_sdl_keycode(m_controls.move_left)) { - return InputEvent::MoveLeftReleased; - } - if (key == to_sdl_keycode(m_controls.move_right)) { - return InputEvent::MoveRightReleased; - } - if (key == to_sdl_keycode(m_controls.drop)) { - return InputEvent::DropReleased; - } - if (key == to_sdl_keycode(m_controls.hold)) { - return InputEvent::HoldReleased; - } - } - return helper::nullopt; -} -KeyboardInput::KeyboardInput(KeyboardControls controls, EventDispatcher* event_dispatcher) - : Input{ InputType::Keyboard }, - m_controls{ controls }, - m_event_dispatcher{ event_dispatcher } { - m_event_dispatcher->register_listener(this); -} - -KeyboardInput::~KeyboardInput() { - m_event_dispatcher->unregister_listener(this); -} diff --git a/src/platform/keyboard_input.hpp b/src/platform/keyboard_input.hpp deleted file mode 100644 index 780f6303..00000000 --- a/src/platform/keyboard_input.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "input.hpp" -#include "manager/event_dispatcher.hpp" - -struct KeyboardInput : public Input, public EventListener { -private: - KeyboardControls m_controls; - std::vector m_event_buffer; - EventDispatcher* m_event_dispatcher; - -public: - KeyboardInput(KeyboardControls controls, EventDispatcher* event_dispatcher); - - ~KeyboardInput() override; - - void handle_event(const SDL_Event& event, const Window* window) override; - - void update(SimulationStep simulation_step_index) override; - -private: - [[nodiscard]] helper::optional sdl_event_to_input_event(const SDL_Event& event) const; -}; diff --git a/src/platform/meson.build b/src/platform/meson.build deleted file mode 100644 index f7d92f2f..00000000 --- a/src/platform/meson.build +++ /dev/null @@ -1,31 +0,0 @@ -graphics_src_files += files( - 'capabilities.cpp', - 'capabilities.hpp', - 'input.cpp', - 'input.hpp', - 'platform.hpp', - 'replay_input.cpp', - 'replay_input.hpp', -) - -if meson.is_cross_build() and host_machine.system() == 'android' - graphics_src_files += files( - 'android_input.cpp', - 'android_input.hpp', - ) -elif ( - meson.is_cross_build() - and (host_machine.system() == 'switch' - or host_machine.system() == '3ds') -) - graphics_src_files += files( - 'console_buttons.hpp', - 'console_input.cpp', - 'console_input.hpp', - ) -else - graphics_src_files += files( - 'keyboard_input.cpp', - 'keyboard_input.hpp', - ) -endif diff --git a/src/platform/platform.hpp b/src/platform/platform.hpp deleted file mode 100644 index ad53796a..00000000 --- a/src/platform/platform.hpp +++ /dev/null @@ -1,34 +0,0 @@ - - -#pragma once - -#include "helper/parse_json.hpp" -#include "helper/types.hpp" - -enum class Platform : u8 { PC, Android, Console }; - - -NLOHMANN_JSON_SERIALIZE_ENUM( // NOLINT(modernize-type-traits,cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) - Platform, - { - { Platform::PC, "pc"}, - {Platform::Android, "android"}, - {Platform::Console, "console"} -} -) - - -namespace utils { - - constexpr std::string get_platform() { - -#if defined(__ANDROID__) - return "android"; -#elif defined(__CONSOLE__) - return "console"; -#else - return "pc"; -#endif - }; - -} // namespace utils diff --git a/src/platform/replay_input.hpp b/src/platform/replay_input.hpp deleted file mode 100644 index 08964342..00000000 --- a/src/platform/replay_input.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "input.hpp" -#include "recordings/recording_reader.hpp" - -#include - -struct ReplayInput : public Input { -private: - std::shared_ptr m_recording_reader; - usize m_next_record_index{ 0 }; - usize m_next_snapshot_index{ 0 }; - -public: - ReplayInput(std::shared_ptr recording_reader); - - void update(SimulationStep simulation_step_index) override; - void late_update(SimulationStep simulation_step_index) override; - - [[nodiscard]] bool is_end_of_recording() const; -}; diff --git a/src/recordings/additional_information.cpp b/src/recordings/additional_information.cpp index e4c62984..0d753a05 100644 --- a/src/recordings/additional_information.cpp +++ b/src/recordings/additional_information.cpp @@ -77,9 +77,9 @@ recorder::InformationValue::to_bytes( // NOLINT(misc-no-recursion) typedef union { u32 original_value; float value; - } float_conversion; + } FloatConversion; - const float_conversion conversion_value{ .value = as() }; + const FloatConversion conversion_value{ .value = as() }; helper::writer::append_value(bytes, conversion_value.original_value); return bytes; } @@ -91,9 +91,9 @@ recorder::InformationValue::to_bytes( // NOLINT(misc-no-recursion) typedef union { u64 original_value; double value; - } double_conversion; + } DoubleConversion; - const double_conversion conversion_value{ .value = as() }; + const DoubleConversion conversion_value{ .value = as() }; helper::writer::append_value(bytes, conversion_value.original_value); return bytes; } @@ -232,11 +232,11 @@ helper::expected recorder::InformationValue::read_stri } -helper::expected recorder::InformationValue:: - read_value_from_istream( // NOLINT(readability-function-cognitive-complexity,misc-no-recursion) - std::istream& istream, - u32 recursion_depth - ) { +helper::expected +recorder::InformationValue::read_value_from_istream( // NOLINT(misc-no-recursion) + std::istream& istream, + u32 recursion_depth +) { const auto magic_byte = helper::reader::read_from_istream>(istream); if (not magic_byte.has_value()) { return helper::unexpected{ "unable to read magic byte" }; @@ -262,9 +262,9 @@ helper::expected recorder::InformationV typedef union { u32 original_value; float value; - } float_conversion; + } FloatConversion; - const float_conversion raw_float_value{ .original_value = raw_float.value() }; + const FloatConversion raw_float_value{ .original_value = raw_float.value() }; return recorder::InformationValue{ raw_float_value.value }; } @@ -278,9 +278,9 @@ helper::expected recorder::InformationV typedef union { u64 original_value; double value; - } double_conversion; + } DoubleConversion; - const double_conversion raw_double_value{ .original_value = raw_double.value() }; + const DoubleConversion raw_double_value{ .original_value = raw_double.value() }; return recorder::InformationValue{ raw_double_value.value }; } diff --git a/src/recordings/helper.hpp b/src/recordings/helper.hpp index 261ae1d2..8479b411 100644 --- a/src/recordings/helper.hpp +++ b/src/recordings/helper.hpp @@ -33,7 +33,7 @@ namespace helper { [[nodiscard]] ReadResult> read_integral_from_file(std::ifstream& file) { if (not file) { return helper::unexpected{ - {ReadErrorType::InvalidStream, "failed to read data from file (before reading)"} + { ReadErrorType::InvalidStream, "failed to read data from file (before reading)" } }; } @@ -45,7 +45,7 @@ namespace helper { if (not file) { return helper::unexpected{ - {ReadErrorType::Incomplete, "failed to read data from file (after reading)"} + { ReadErrorType::Incomplete, "failed to read data from file (after reading)" } }; } @@ -56,7 +56,7 @@ namespace helper { [[nodiscard]] ReadResult> read_array_from_file(std::ifstream& file) { if (not file) { return helper::unexpected{ - {ReadErrorType::InvalidStream, "failed to read data from file (before reading)"} + { ReadErrorType::InvalidStream, "failed to read data from file (before reading)" } }; } @@ -71,7 +71,7 @@ namespace helper { if (not file) { return helper::unexpected{ - {ReadErrorType::Incomplete, "failed to read data from file (after reading)"} + { ReadErrorType::Incomplete, "failed to read data from file (after reading)" } }; } diff --git a/src/recordings/recording_reader.cpp b/src/recordings/recording_reader.cpp index c99be804..ceac9cfa 100644 --- a/src/recordings/recording_reader.cpp +++ b/src/recordings/recording_reader.cpp @@ -101,8 +101,7 @@ recorder::RecordingReader::get_header_from_path(const std::filesystem::path& pat ); } -helper::expected -recorder::RecordingReader::from_path( // NOLINT(readability-function-cognitive-complexity) +helper::expected recorder::RecordingReader::from_path( const std::filesystem::path& path ) { @@ -117,7 +116,7 @@ recorder::RecordingReader::from_path( // NOLINT(readability-function-cognitive-c std::vector records{}; std::vector snapshots{}; - //TODO: when using larger files and recordings, we should stream the data and discard used, to far away data, to not load everything into memory at once + //TODO(Totto): when using larger files and recordings, we should stream the data and discard used, to far away data, to not load everything into memory at once while (true) { @@ -206,7 +205,7 @@ recorder::RecordingReader::from_path( // NOLINT(readability-function-cognitive-c recorder::RecordingReader::read_tetrion_header_from_file(std::ifstream& file) { if (not file) { return helper::unexpected{ - {helper::reader::ReadErrorType::InvalidStream, "failed to read data from file"} + { helper::reader::ReadErrorType::InvalidStream, "failed to read data from file" } }; } @@ -214,14 +213,14 @@ recorder::RecordingReader::read_tetrion_header_from_file(std::ifstream& file) { if (not seed.has_value()) { return helper::unexpected{ - {helper::reader::ReadErrorType::Incomplete, "field 'seed' has no value"} + { helper::reader::ReadErrorType::Incomplete, "field 'seed' has no value" } }; } const auto starting_level = helper::reader::read_integral_from_file(file); if (not starting_level.has_value()) { return helper::unexpected{ - {helper::reader::ReadErrorType::Incomplete, "field 'starting_level' has no value"} + { helper::reader::ReadErrorType::Incomplete, "field 'starting_level' has no value" } }; } @@ -233,14 +232,14 @@ recorder::RecordingReader::read_tetrion_header_from_file(std::ifstream& file) { ) { if (not file) { return helper::unexpected{ - {helper::reader::ReadErrorType::InvalidStream, "invalid input file stream while trying to read record"} + { helper::reader::ReadErrorType::InvalidStream, "invalid input file stream while trying to read record" } }; } const auto tetrion_index = helper::reader::read_integral_from_file(file); if (not tetrion_index.has_value()) { return helper::unexpected{ - {helper::reader::ReadErrorType::EndOfFile, "the field 'tetrion_index' is missing"} + { helper::reader::ReadErrorType::EndOfFile, "the field 'tetrion_index' is missing" } }; } @@ -248,27 +247,27 @@ recorder::RecordingReader::read_tetrion_header_from_file(std::ifstream& file) { helper::reader::read_integral_from_file(file); if (not simulation_step_index.has_value()) { return helper::unexpected{ - {helper::reader::ReadErrorType::Incomplete, "the field 'simulation_step_index' is missing"} + { helper::reader::ReadErrorType::Incomplete, "the field 'simulation_step_index' is missing" } }; } const auto event = helper::reader::read_integral_from_file>(file); if (not event.has_value()) { return helper::unexpected{ - {helper::reader::ReadErrorType::Incomplete, "the field 'InputEvent' is missing"} + { helper::reader::ReadErrorType::Incomplete, "the field 'InputEvent' is missing" } }; } if (not file) { return helper::unexpected{ - {helper::reader::ReadErrorType::Incomplete, "failed to read data from file"} + { helper::reader::ReadErrorType::Incomplete, "failed to read data from file" } }; } const auto maybe_event = magic_enum::enum_cast(event.value()); if (not maybe_event.has_value()) { return helper::unexpected{ - {helper::reader::ReadErrorType::Incomplete, - fmt::format("got invalid enum value for InputEvent: {}", event.value())} + { helper::reader::ReadErrorType::Incomplete, + fmt::format("got invalid enum value for InputEvent: {}", event.value()) } }; } diff --git a/src/recordings/tetrion_snapshot.cpp b/src/recordings/tetrion_snapshot.cpp index 869f7804..0dff1256 100644 --- a/src/recordings/tetrion_snapshot.cpp +++ b/src/recordings/tetrion_snapshot.cpp @@ -59,13 +59,13 @@ helper::expected TetrionSnapshot::from_istream(std MinoStack mino_stack{}; for (MinoCount i = 0; i < num_minos.value(); ++i) { - const auto x = helper::reader::read_from_istream(istream); - if (not x.has_value()) { + const auto x_coord = helper::reader::read_from_istream(istream); + if (not x_coord.has_value()) { return helper::unexpected{ "unable to read x coordinate of mino from snapshot" }; } - const auto y = helper::reader::read_from_istream(istream); - if (not y.has_value()) { + const auto y_coord = helper::reader::read_from_istream(istream); + if (not y_coord.has_value()) { return helper::unexpected{ "unable to read y coordinate of mino from snapshot" }; } @@ -81,7 +81,7 @@ helper::expected TetrionSnapshot::from_istream(std }; } - mino_stack.set(shapes::AbstractPoint(x.value(), y.value()), maybe_type.value()); + mino_stack.set(shapes::AbstractPoint(x_coord.value(), y_coord.value()), maybe_type.value()); } @@ -191,10 +191,10 @@ helper::expected TetrionSnapshot::compare_to(const TetrionSna } if (m_mino_stack != other.m_mino_stack) { - std::stringstream ss; - ss << m_mino_stack << " vs. " << other.m_mino_stack; + std::stringstream string_stream{}; + string_stream << m_mino_stack << " vs. " << other.m_mino_stack; - return helper::unexpected{ fmt::format("mino stacks do not match:\n {}", ss.str()) }; + return helper::unexpected{ fmt::format("mino stacks do not match:\n {}", string_stream.str()) }; } return true; diff --git a/src/scenes/about_page/about_page.cpp b/src/scenes/about_page/about_page.cpp index e044e06d..a8fb3ecb 100644 --- a/src/scenes/about_page/about_page.cpp +++ b/src/scenes/about_page/about_page.cpp @@ -2,9 +2,9 @@ #include "graphics/window.hpp" #include "helper/constants.hpp" #include "helper/git_helper.hpp" +#include "helper/platform.hpp" #include "helper/utils.hpp" #include "manager/resource_manager.hpp" -#include "platform/capabilities.hpp" #include "ui/components/image_view.hpp" #include "ui/components/link_label.hpp" #include "ui/layouts/tile_layout.hpp" @@ -28,30 +28,34 @@ namespace scenes { #ifdef DEBUG_BUILD m_main_grid.add( service_provider, fmt::format("Git Commit: {}", utils::git_commit()), - service_provider->fonts().get(FontId::Default), Color::white(), std::pair{ 0.3, 0.5 }, + service_provider->font_manager().get(FontId::Default), Color::white(), + std::pair{ 0.3, 0.5 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); #else m_main_grid.add( service_provider, fmt::format("Version: {}", constants::version.c_str()), - service_provider->fonts().get(FontId::Default), Color::white(), std::pair{ 0.3, 0.5 }, + service_provider->font_manager().get(FontId::Default), Color::white(), + std::pair{ 0.3, 0.5 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); #endif m_main_grid.add( service_provider, fmt::format("Build for: {}", utils::built_for_platform()), - service_provider->fonts().get(FontId::Default), Color::white(), std::pair{ 0.3, 0.5 }, + service_provider->font_manager().get(FontId::Default), Color::white(), + std::pair{ 0.3, 0.5 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); m_main_grid.add( service_provider, fmt::format("Features: {}", fmt::join(utils::supported_features(), ", ")), - service_provider->fonts().get(FontId::Default), Color::white(), std::pair{ 0.95, 0.5 }, + service_provider->font_manager().get(FontId::Default), Color::white(), + std::pair{ 0.95, 0.5 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); m_main_grid.add( - service_provider, "Authors:", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Authors:", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.2, 0.4 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Bottom } ); @@ -67,7 +71,7 @@ namespace scenes { auto* tile_layout = m_main_grid.get(tile_layout_index); tile_layout->add( - service_provider, name, link, service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, name, link, service_provider->font_manager().get(FontId::Default), Color::white(), Color{ 0xA1, 0x9F, 0x9F }, std::pair{ 0.9, 0.8 }, ui::Alignment{ ui::AlignmentHorizontal::Right, ui::AlignmentVertical::Center } ); @@ -98,12 +102,14 @@ namespace scenes { m_main_grid.render(service_provider); } - bool AboutPage::handle_event(const SDL_Event& event, const Window* window) { - if (m_main_grid.handle_event(event, window)) { + bool AboutPage::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + if (m_main_grid.handle_event(input_manager, event)) { return true; } - if (utils::event_is_action(event, utils::CrossPlatformAction::CLOSE)) { + const auto navigation_event = input_manager->get_navigation_event(event); + + if (navigation_event == input::NavigationEvent::BACK) { m_should_exit = true; return true; } diff --git a/src/scenes/about_page/about_page.hpp b/src/scenes/about_page/about_page.hpp index e6b0df70..72e2cd80 100644 --- a/src/scenes/about_page/about_page.hpp +++ b/src/scenes/about_page/about_page.hpp @@ -15,8 +15,8 @@ namespace scenes { static constexpr std::initializer_list> authors{ - {"mgerhold", "https://github.com/mgerhold", "mgerhold.jpg"}, - { "Totto16", "https://github.com/Totto16", "Totto16.png"} + { "mgerhold", "https://github.com/mgerhold", "mgerhold.jpg" }, + { "Totto16", "https://github.com/Totto16", "Totto16.png" } }; public: @@ -24,7 +24,7 @@ namespace scenes { [[nodiscard]] UpdateResult update() override; void render(const ServiceProvider& service_provider) override; - bool handle_event(const SDL_Event& event, const Window* window) override; + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; } // namespace scenes diff --git a/src/scenes/game_over/game_over.cpp b/src/scenes/game_over/game_over.cpp index 1da19b80..808aee65 100644 --- a/src/scenes/game_over/game_over.cpp +++ b/src/scenes/game_over/game_over.cpp @@ -1,9 +1,10 @@ #include "game_over.hpp" #include "graphics/renderer.hpp" #include "helper/music_utils.hpp" +#include "helper/platform.hpp" +#include "input/input.hpp" #include "manager/music_manager.hpp" #include "manager/resource_manager.hpp" -#include "platform/capabilities.hpp" #include @@ -15,11 +16,11 @@ namespace scenes { service_provider, fmt::format( "Game Over, Press {} to continue", - utils::action_description(utils::CrossPlatformAction::EXIT) + service_provider->input_manager().get_primary_input()->describe_navigation_event(input::NavigationEvent::BACK) ), - service_provider->fonts().get(FontId::Default), + service_provider->font_manager().get(FontId::Default), Color::white(), - utils::device_orientation() == utils::Orientation::Landscape + utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.7, 0.07 } : std::pair{ 0.95, 0.07 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }, @@ -37,7 +38,7 @@ namespace scenes { if (m_should_exit) { return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Switch{SceneId::MainMenu, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::Switch{ SceneId::MainMenu, ui::FullScreenLayout{ m_service_provider->window() } } }; } return UpdateResult{ SceneUpdate::StopUpdating, helper::nullopt }; @@ -48,8 +49,11 @@ namespace scenes { m_text.render(service_provider); } - bool GameOver::handle_event(const SDL_Event& event, const Window*) { - if (utils::event_is_action(event, utils::CrossPlatformAction::EXIT)) { + bool GameOver::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + + const auto navigation_event = input_manager->get_navigation_event(event); + + if (navigation_event == input::NavigationEvent::BACK) { m_should_exit = true; return true; } diff --git a/src/scenes/game_over/game_over.hpp b/src/scenes/game_over/game_over.hpp index 2a7f6552..df90b705 100644 --- a/src/scenes/game_over/game_over.hpp +++ b/src/scenes/game_over/game_over.hpp @@ -17,7 +17,7 @@ namespace scenes { void render(const ServiceProvider& service_provider) override; - bool handle_event(const SDL_Event& event, const Window* window) override; + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; } // namespace scenes diff --git a/src/scenes/main_menu/main_menu.cpp b/src/scenes/main_menu/main_menu.cpp index 68e158fd..92505a89 100644 --- a/src/scenes/main_menu/main_menu.cpp +++ b/src/scenes/main_menu/main_menu.cpp @@ -2,6 +2,7 @@ #include "graphics/window.hpp" #include "helper/constants.hpp" #include "helper/music_utils.hpp" +#include "helper/platform.hpp" #include "manager/music_manager.hpp" #include "manager/resource_manager.hpp" #include "ui/layout.hpp" @@ -16,23 +17,23 @@ namespace scenes { auto focus_helper = ui::FocusHelper{ 1 }; m_main_grid.add( - service_provider, constants::program_name, service_provider->fonts().get(FontId::Default), + service_provider, constants::program_name, service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.3, 1.0 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); - constexpr auto button_size = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_size = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.15, 0.85 } : std::pair{ 0.5, 0.85 }; constexpr auto button_alignment = ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }; - constexpr auto button_margins = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_margins = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.1, 0.1 } : std::pair{ 0.2, 0.2 }; m_main_grid.add( - service_provider, "Play", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Play", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::OpenPlaySelection; @@ -42,7 +43,7 @@ namespace scenes { ); m_main_grid.add( - service_provider, "Settings", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Settings", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::OpenSettingsMenu; @@ -52,7 +53,7 @@ namespace scenes { ); m_main_grid.add( - service_provider, "About", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "About", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::OpenAboutPage; @@ -62,7 +63,7 @@ namespace scenes { ); m_main_grid.add( - service_provider, "Achievements", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Achievements", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::OpenAchievements; @@ -73,7 +74,7 @@ namespace scenes { m_main_grid.get(4)->disable(); m_main_grid.add( - service_provider, "Exit", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Exit", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::Exit; @@ -113,28 +114,28 @@ namespace scenes { m_next_command = helper::nullopt; return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::PlaySelectMenu, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::Push{ SceneId::PlaySelectMenu, ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::OpenAboutPage: // perform a push and reset the command, so that the music keeps playing the entire time m_next_command = helper::nullopt; return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::AboutPage, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::Push{ SceneId::AboutPage, ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::OpenSettingsMenu: // perform a push and reset the command, so that the music keeps playing the entire time m_next_command = helper::nullopt; return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::SettingsMenu, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::Push{ SceneId::SettingsMenu, ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::OpenAchievements: // perform a push and reset the command, so that the music keeps playing the entire time m_next_command = helper::nullopt; return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::AchievementsPage, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::Push{ SceneId::AchievementsPage, ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::Exit: return UpdateResult{ SceneUpdate::StopUpdating, Scene::Exit{} }; @@ -149,12 +150,14 @@ namespace scenes { m_main_grid.render(service_provider); } - bool MainMenu::handle_event(const SDL_Event& event, const Window* window) { - if (m_main_grid.handle_event(event, window)) { + bool MainMenu::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + if (m_main_grid.handle_event(input_manager, event)) { return true; } - if (utils::event_is_action(event, utils::CrossPlatformAction::CLOSE)) { + const auto navigation_event = input_manager->get_navigation_event(event); + + if (navigation_event == input::NavigationEvent::BACK) { m_next_command = Command::Exit; return true; } diff --git a/src/scenes/main_menu/main_menu.hpp b/src/scenes/main_menu/main_menu.hpp index 28935559..c513ab10 100644 --- a/src/scenes/main_menu/main_menu.hpp +++ b/src/scenes/main_menu/main_menu.hpp @@ -25,7 +25,7 @@ namespace scenes { [[nodiscard]] UpdateResult update() override; void render(const ServiceProvider& service_provider) override; - bool handle_event(const SDL_Event& event, const Window* window) override; + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; } // namespace scenes diff --git a/src/scenes/meson.build b/src/scenes/meson.build index 02625d9b..ac638a46 100644 --- a/src/scenes/meson.build +++ b/src/scenes/meson.build @@ -4,7 +4,6 @@ subdir('about_page') subdir('game_over') subdir('main_menu') subdir('multiplayer_menu') -subdir('pause') subdir('play_select_menu') subdir('recording_selector') subdir('replay_game') diff --git a/src/scenes/multiplayer_menu/multiplayer_menu.cpp b/src/scenes/multiplayer_menu/multiplayer_menu.cpp index 4277e2c8..83785541 100644 --- a/src/scenes/multiplayer_menu/multiplayer_menu.cpp +++ b/src/scenes/multiplayer_menu/multiplayer_menu.cpp @@ -1,6 +1,8 @@ #include "multiplayer_menu.hpp" #include "graphics/window.hpp" #include "helper/constants.hpp" +#include "helper/platform.hpp" +#include "input/input.hpp" #include "manager/music_manager.hpp" #include "manager/resource_manager.hpp" #include "ui/layout.hpp" @@ -15,23 +17,23 @@ namespace scenes { auto focus_helper = ui::FocusHelper{ 1 }; m_main_grid.add( - service_provider, "Select MultiPlayer Mode", service_provider->fonts().get(FontId::Default), + service_provider, "Select MultiPlayer Mode", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.3, 1.0 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); - constexpr auto button_size = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_size = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.15, 0.85 } : std::pair{ 0.5, 0.85 }; constexpr auto button_alignment = ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }; - constexpr auto button_margins = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_margins = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.1, 0.1 } : std::pair{ 0.2, 0.2 }; const auto local_button_id = m_main_grid.add( - service_provider, "Local", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Local", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::LocalMultiPlayer; @@ -42,7 +44,7 @@ namespace scenes { m_main_grid.get(local_button_id)->disable(); const auto online_button_id = m_main_grid.add( - service_provider, "Online", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Online", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::OnlineMultiPlayer; @@ -57,7 +59,7 @@ namespace scenes { #endif const auto ai_button_id = m_main_grid.add( - service_provider, "vs AI", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "vs AI", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::AIMultiPlayer; @@ -68,7 +70,7 @@ namespace scenes { m_main_grid.get(ai_button_id)->disable(); m_main_grid.add( - service_provider, "Return", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Return", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::Return; @@ -85,20 +87,20 @@ namespace scenes { switch (m_next_command.value()) { case Command::LocalMultiPlayer: return UpdateResult{ - SceneUpdate::StopUpdating, Scene::Switch{SceneId::LocalMultiPlayerGame, - ui::FullScreenLayout{ m_service_provider->window() }} + SceneUpdate::StopUpdating, Scene::Switch{ SceneId::LocalMultiPlayerGame, + ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::OnlineMultiPlayer: // perform a push and reset the command, so that the music keeps playing the entire time m_next_command = helper::nullopt; return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::OnlineLobby, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::Push{ SceneId::OnlineLobby, ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::AIMultiPlayer: return UpdateResult{ - SceneUpdate::StopUpdating, Scene::Switch{SceneId::AIMultiPlayerGame, - ui::FullScreenLayout{ m_service_provider->window() }} + SceneUpdate::StopUpdating, Scene::Switch{ SceneId::AIMultiPlayerGame, + ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::Return: return UpdateResult{ SceneUpdate::StopUpdating, Scene::Pop{} }; @@ -116,12 +118,15 @@ namespace scenes { m_main_grid.render(service_provider); } - bool MultiPlayerMenu::handle_event(const SDL_Event& event, const Window* window) { - if (m_main_grid.handle_event(event, window)) { + bool + MultiPlayerMenu::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + if (m_main_grid.handle_event(input_manager, event)) { return true; } - if (utils::event_is_action(event, utils::CrossPlatformAction::CLOSE)) { + const auto navigation_event = input_manager->get_navigation_event(event); + + if (navigation_event == input::NavigationEvent::BACK) { m_next_command = Command::Return; return true; } diff --git a/src/scenes/multiplayer_menu/multiplayer_menu.hpp b/src/scenes/multiplayer_menu/multiplayer_menu.hpp index c7d5e02d..b498b418 100644 --- a/src/scenes/multiplayer_menu/multiplayer_menu.hpp +++ b/src/scenes/multiplayer_menu/multiplayer_menu.hpp @@ -19,7 +19,7 @@ namespace scenes { [[nodiscard]] UpdateResult update() override; void render(const ServiceProvider& service_provider) override; - bool handle_event(const SDL_Event& event, const Window* window) override; + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; } // namespace scenes diff --git a/src/scenes/online_lobby/online_lobby.cpp b/src/scenes/online_lobby/online_lobby.cpp index 14cd710a..538a5748 100644 --- a/src/scenes/online_lobby/online_lobby.cpp +++ b/src/scenes/online_lobby/online_lobby.cpp @@ -2,6 +2,9 @@ #include "graphics/window.hpp" #include "helper/constants.hpp" #include "helper/errors.hpp" +#include "helper/magic_enum_wrapper.hpp" +#include "helper/platform.hpp" +#include "helper/utils.hpp" #include "manager/music_manager.hpp" #include "manager/resource_manager.hpp" #include "ui/components/textinput.hpp" @@ -22,7 +25,7 @@ namespace scenes { layout } { - //TODO: after the settings have been reworked, make this url changeable! + //TODO(Totto): after the settings have been reworked, make this url changeable! auto maybe_client = lobby::Client::get_client("http://127.0.0.1:5000"); if (maybe_client.has_value()) { client = std::make_unique(std::move(maybe_client.value())); @@ -33,7 +36,7 @@ namespace scenes { auto focus_helper = ui::FocusHelper{ 1 }; m_main_layout.add( - service_provider, "Select Lobby to play in", service_provider->fonts().get(FontId::Default), + service_provider, "Select Lobby to play in", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.5, 1.0 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); @@ -49,7 +52,7 @@ namespace scenes { if (i == 2) { scroll_layout->add( ui::RelativeItemSize{ scroll_layout->layout(), 0.2 }, service_provider, - service_provider->fonts().get(FontId::Symbola), Color::white(), focus_helper.focus_id(), + service_provider->font_manager().get(FontId::Symbola), Color::white(), focus_helper.focus_id(), std::pair{ 0.9, 0.9 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }, ui::TextInputMode::Scroll @@ -57,7 +60,7 @@ namespace scenes { } else { scroll_layout->add( ui::RelativeItemSize{ scroll_layout->layout(), 0.2 }, service_provider, - fmt::format("Button Nr.: {}", i), service_provider->fonts().get(FontId::Default), + fmt::format("Button Nr.: {}", i), service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [i](const ui::TextButton&) -> bool { spdlog::info("Pressed button: {}", i); @@ -70,17 +73,17 @@ namespace scenes { } } - constexpr auto button_size = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_size = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.15, 0.85 } : std::pair{ 0.5, 0.85 }; constexpr auto button_alignment = ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }; - constexpr auto button_margins = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_margins = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.1, 0.1 } : std::pair{ 0.2, 0.2 }; m_main_layout.add( - service_provider, "Return", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Return", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::Return; @@ -97,8 +100,8 @@ namespace scenes { switch (m_next_command.value()) { case Command::Play: return UpdateResult{ - SceneUpdate::StopUpdating, Scene::Switch{SceneId::OnlineMultiplayerGame, - ui::FullScreenLayout{ m_service_provider->window() }} + SceneUpdate::StopUpdating, Scene::Switch{ SceneId::OnlineMultiplayerGame, + ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::Return: return UpdateResult{ SceneUpdate::StopUpdating, Scene::Pop{} }; @@ -115,11 +118,11 @@ namespace scenes { m_main_layout.render(service_provider); } - bool OnlineLobby::handle_event(const SDL_Event& event, const Window* window) { + bool OnlineLobby::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { // description of intentional behaviour of this scene, even if it seems off: // the return button or the scroll layout can have the focus, if the scroll_layout has the focus, it can be scrolled by the scroll wheel and you can move around the focused item of the scroll_layout with up and down, but not with TAB, with tab you can change the focus to the return button, where you can't use the scroll wheel or up / down to change the scroll items, but you still can use click events, they are not affected by focus - if (const auto event_result = m_main_layout.handle_event(event, window)) { + if (const auto event_result = m_main_layout.handle_event(input_manager, event)) { if (const auto additional = event_result.get_additional(); additional.has_value()) { const auto value = additional.value(); @@ -127,11 +130,11 @@ namespace scenes { if (value.first == ui::EventHandleType::RequestAction) { - if (auto* text_input = dynamic_cast(value.second); text_input != nullptr) { - spdlog::info("Pressed Enter on TextInput {}", text_input->get_text()); + if (auto text_input = utils::is_child_class(value.second); text_input.has_value()) { + spdlog::info("Pressed Enter on TextInput {}", text_input.value()->get_text()); - if (text_input->has_focus()) { - text_input->unfocus(); + if (text_input.value()->has_focus()) { + text_input.value()->unfocus(); } return true; } @@ -148,7 +151,9 @@ namespace scenes { return true; } - if (utils::event_is_action(event, utils::CrossPlatformAction::CLOSE)) { + const auto navigation_event = input_manager->get_navigation_event(event); + + if (navigation_event == input::NavigationEvent::BACK) { m_next_command = Command::Return; return true; } diff --git a/src/scenes/online_lobby/online_lobby.hpp b/src/scenes/online_lobby/online_lobby.hpp index 6a3b52c8..e391bc3a 100644 --- a/src/scenes/online_lobby/online_lobby.hpp +++ b/src/scenes/online_lobby/online_lobby.hpp @@ -23,7 +23,7 @@ namespace scenes { [[nodiscard]] UpdateResult update() override; void render(const ServiceProvider& service_provider) override; - bool handle_event(const SDL_Event& event, const Window* window) override; + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; } // namespace scenes diff --git a/src/scenes/pause/meson.build b/src/scenes/pause/meson.build deleted file mode 100644 index 879e64fa..00000000 --- a/src/scenes/pause/meson.build +++ /dev/null @@ -1,4 +0,0 @@ -graphics_src_files += files( - 'pause.cpp', - 'pause.hpp', -) diff --git a/src/scenes/pause/pause.cpp b/src/scenes/pause/pause.cpp deleted file mode 100644 index f98828d4..00000000 --- a/src/scenes/pause/pause.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "pause.hpp" -#include "graphics/renderer.hpp" -#include "manager/resource_manager.hpp" -#include "platform/capabilities.hpp" -#include - -namespace scenes { - - Pause::Pause(ServiceProvider* service_provider, const ui::Layout& layout) : Scene{ service_provider, layout }, m_heading { - service_provider, - fmt::format( - "Pause ({}: continue, {}: quit)", - utils::action_description(utils::CrossPlatformAction::UNPAUSE), - utils::action_description(utils::CrossPlatformAction::EXIT) - ), - service_provider->fonts().get(FontId::Default), - Color::white(),utils::device_orientation() == utils::Orientation::Landscape - ? std::pair{ 0.7, 0.07 } - : std::pair{ 0.95, 0.07 }, - ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }, - layout, - true - } - { } - - [[nodiscard]] Scene::UpdateResult scenes::Pause::update() { - if (m_should_unpause) { - return UpdateResult{ SceneUpdate::StopUpdating, Scene::Pop{} }; - } - if (m_should_exit) { - return UpdateResult{ - SceneUpdate::StopUpdating, - Scene::Switch{SceneId::MainMenu, ui::FullScreenLayout{ m_service_provider->window() }} - }; - } - return UpdateResult{ SceneUpdate::StopUpdating, helper::nullopt }; - } - - void Pause::render(const ServiceProvider& service_provider) { - service_provider.renderer().draw_rect_filled(get_layout().get_rect(), Color::black(180)); - m_heading.render(service_provider); - } - - [[nodiscard]] bool Pause::handle_event(const SDL_Event& event, const Window*) { - - if (utils::event_is_action(event, utils::CrossPlatformAction::UNPAUSE)) { - m_should_unpause = true; - return true; - } - - if (utils::event_is_action(event, utils::CrossPlatformAction::EXIT)) { - m_should_exit = true; - return true; - } - - return false; - } - -} // namespace scenes diff --git a/src/scenes/pause/pause.hpp b/src/scenes/pause/pause.hpp deleted file mode 100644 index bc830666..00000000 --- a/src/scenes/pause/pause.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "manager/event_listener.hpp" -#include "scenes/scene.hpp" -#include "ui/components/label.hpp" - -namespace scenes { - struct Pause : public scenes::Scene { - private: - ui::Label m_heading; - bool m_should_unpause{ false }; - bool m_should_exit{ false }; - - public: - explicit Pause(ServiceProvider* service_provider, const ui::Layout& layout); - - [[nodiscard]] UpdateResult update() override; - void render(const ServiceProvider& service_provider) override; - [[nodiscard]] bool handle_event(const SDL_Event& event, const Window* window) override; - }; -} // namespace scenes diff --git a/src/scenes/play_select_menu/play_select_menu.cpp b/src/scenes/play_select_menu/play_select_menu.cpp index 5d5481ae..312fafe9 100644 --- a/src/scenes/play_select_menu/play_select_menu.cpp +++ b/src/scenes/play_select_menu/play_select_menu.cpp @@ -1,6 +1,7 @@ #include "play_select_menu.hpp" #include "graphics/window.hpp" #include "helper/constants.hpp" +#include "helper/platform.hpp" #include "manager/music_manager.hpp" #include "manager/resource_manager.hpp" #include "ui/layout.hpp" @@ -15,24 +16,24 @@ namespace scenes { auto focus_helper = ui::FocusHelper{ 1 }; m_main_grid.add( - service_provider, "Select Play Mode", service_provider->fonts().get(FontId::Default), Color::white(), - std::pair{ 0.3, 1.0 }, + service_provider, "Select Play Mode", service_provider->font_manager().get(FontId::Default), + Color::white(), std::pair{ 0.3, 1.0 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); - constexpr auto button_size = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_size = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.15, 0.85 } : std::pair{ 0.5, 0.85 }; constexpr auto button_alignment = ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }; - constexpr auto button_margins = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_margins = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.1, 0.1 } : std::pair{ 0.2, 0.2 }; m_main_grid.add( - service_provider, "Single Player", service_provider->fonts().get(FontId::Default), Color::white(), - focus_helper.focus_id(), + service_provider, "Single Player", service_provider->font_manager().get(FontId::Default), + Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::SinglePlayer; return false; @@ -41,7 +42,7 @@ namespace scenes { ); m_main_grid.add( - service_provider, "Multi Player", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Multi Player", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::MultiPlayer; @@ -51,8 +52,8 @@ namespace scenes { ); m_main_grid.add( - service_provider, "Replay Recordings", service_provider->fonts().get(FontId::Default), Color::white(), - focus_helper.focus_id(), + service_provider, "Replay Recordings", service_provider->font_manager().get(FontId::Default), + Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::RecordingSelector; return false; @@ -61,7 +62,7 @@ namespace scenes { ); m_main_grid.add( - service_provider, "Return", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Return", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command::Return; @@ -77,23 +78,24 @@ namespace scenes { if (m_next_command.has_value()) { switch (m_next_command.value()) { case Command::SinglePlayer: + m_next_command = helper::nullopt; return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Switch{SceneId::SinglePlayerGame, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::Switch{ SceneId::SinglePlayerGame, ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::MultiPlayer: // perform a push and reset the command, so that the music keeps playing the entire time m_next_command = helper::nullopt; return UpdateResult{ - SceneUpdate::StopUpdating, Scene::Push{SceneId::MultiPlayerModeSelectMenu, - ui::FullScreenLayout{ m_service_provider->window() }} + SceneUpdate::StopUpdating, Scene::Push{ SceneId::MultiPlayerModeSelectMenu, + ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::RecordingSelector: // perform a push and reset the command, so that the music keeps playing the entire time m_next_command = helper::nullopt; return UpdateResult{ - SceneUpdate::StopUpdating, Scene::Push{SceneId::RecordingSelectorMenu, - ui::FullScreenLayout{ m_service_provider->window() }} + SceneUpdate::StopUpdating, Scene::Push{ SceneId::RecordingSelectorMenu, + ui::FullScreenLayout{ m_service_provider->window() } } }; case Command::Return: return UpdateResult{ SceneUpdate::StopUpdating, Scene::Pop{} }; @@ -111,12 +113,15 @@ namespace scenes { m_main_grid.render(service_provider); } - bool PlaySelectMenu::handle_event(const SDL_Event& event, const Window* window) { - if (m_main_grid.handle_event(event, window)) { + bool + PlaySelectMenu::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + if (m_main_grid.handle_event(input_manager, event)) { return true; } - if (utils::event_is_action(event, utils::CrossPlatformAction::CLOSE)) { + const auto navigation_event = input_manager->get_navigation_event(event); + + if (navigation_event == input::NavigationEvent::BACK) { m_next_command = Command::Return; return true; } diff --git a/src/scenes/play_select_menu/play_select_menu.hpp b/src/scenes/play_select_menu/play_select_menu.hpp index 1a6b0036..b362dd52 100644 --- a/src/scenes/play_select_menu/play_select_menu.hpp +++ b/src/scenes/play_select_menu/play_select_menu.hpp @@ -19,7 +19,7 @@ namespace scenes { [[nodiscard]] UpdateResult update() override; void render(const ServiceProvider& service_provider) override; - bool handle_event(const SDL_Event& event, const Window* window) override; + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; } // namespace scenes diff --git a/src/scenes/recording_selector/recording_chooser.cpp b/src/scenes/recording_selector/recording_chooser.cpp index 057aa3fe..85c193fc 100644 --- a/src/scenes/recording_selector/recording_chooser.cpp +++ b/src/scenes/recording_selector/recording_chooser.cpp @@ -27,13 +27,13 @@ custom_ui::RecordingFileChooser::RecordingFileChooser( m_main_grid.add( - service_provider, "Select Recording", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Select Recording", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [service_provider, this](const ui::TextButton&) -> bool { this->prepare_dialog(service_provider); const auto result = helper::openMultipleFilesDialog({ - {"OOPetris Recording", { constants::recording::extension }} + { "OOPetris Recording", { constants::recording::extension } } }); if (result.has_value()) { @@ -54,8 +54,8 @@ custom_ui::RecordingFileChooser::RecordingFileChooser( m_main_grid.add( - service_provider, "Select Recording Folder", service_provider->fonts().get(FontId::Default), Color::white(), - focus_helper.focus_id(), + service_provider, "Select Recording Folder", service_provider->font_manager().get(FontId::Default), + Color::white(), focus_helper.focus_id(), [this, service_provider](const ui::TextButton&) -> bool { this->prepare_dialog(service_provider); @@ -96,16 +96,18 @@ void custom_ui::RecordingFileChooser::render(const ServiceProvider& service_prov m_main_grid.render(service_provider); } -helper::BoolWrapper> -custom_ui::RecordingFileChooser::handle_event(const SDL_Event& event, const Window* window) { - //TODO: this double nested component can't correctly detect focus changes (since the checking for a focus change only occurs at one level deep) - //TODO: allow horizontal RIGHT <-> LEFT focus change on horizontal focus_layouts - if (const auto handled = m_main_grid.handle_event(event, window); handled) { +helper::BoolWrapper> custom_ui::RecordingFileChooser::handle_event( + const std::shared_ptr& input_manager, + const SDL_Event& event +) { + //TODO(Totto): this double nested component can't correctly detect focus changes (since the checking for a focus change only occurs at one level deep) + //TODO(Totto): allow horizontal RIGHT <-> LEFT focus change on horizontal focus_layouts + if (const auto handled = m_main_grid.handle_event(input_manager, event); handled) { return handled; } - if (detect_hover(event, window)) { + if (detect_hover(input_manager, event)) { return true; } @@ -118,10 +120,10 @@ custom_ui::RecordingFileChooser::handle_event(const SDL_Event& event, const Wind return currently_chosen_files; } -//TODO: solve in another way, that is better +//TODO(Totto): solve in another way, that is better void custom_ui::RecordingFileChooser::prepare_dialog(ServiceProvider* service_provider) { - //TODO: show scene on top, that hints of the dialog + //TODO(Totto): show scene on top, that hints of the dialog this->currently_chosen_files.clear(); service_provider->event_dispatcher().disable(); } @@ -130,6 +132,6 @@ void custom_ui::RecordingFileChooser::cleanup_dialog( //NOLINT(readability-conve ServiceProvider* service_provider ) { - //TODO: remove hint scene on top + //TODO(Totto): remove hint scene on top service_provider->event_dispatcher().enable(); } diff --git a/src/scenes/recording_selector/recording_chooser.hpp b/src/scenes/recording_selector/recording_chooser.hpp index cba875ac..7b824c66 100644 --- a/src/scenes/recording_selector/recording_chooser.hpp +++ b/src/scenes/recording_selector/recording_chooser.hpp @@ -27,7 +27,8 @@ namespace custom_ui { void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; [[nodiscard]] const std::vector& get_currently_chosen_files() const; diff --git a/src/scenes/recording_selector/recording_component.cpp b/src/scenes/recording_selector/recording_component.cpp index 1f8e0e31..97d50ff0 100644 --- a/src/scenes/recording_selector/recording_component.cpp +++ b/src/scenes/recording_selector/recording_component.cpp @@ -2,6 +2,7 @@ #include "recording_component.hpp" #include "helper/date.hpp" #include "helper/magic_enum_wrapper.hpp" +#include "input/input.hpp" #include "manager/font.hpp" #include "manager/resource_manager.hpp" #include "ui/widget.hpp" @@ -25,7 +26,7 @@ custom_ui::RecordingComponent::RecordingComponent( },m_metadata{std::move(metadata)}{ m_main_layout.add( - service_provider, "name: ?", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "name: ?", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.5, 0.5 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); @@ -39,19 +40,19 @@ custom_ui::RecordingComponent::RecordingComponent( information_layout->add( - service_provider, "source: ?", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "source: ?", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.9, 0.9 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); information_layout->add( - service_provider, "date: ?", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "date: ?", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.9, 0.9 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); information_layout->add( - service_provider, "playmode: ?", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "playmode: ?", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.9, 0.9 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); @@ -70,24 +71,24 @@ void custom_ui::RecordingComponent::render(const ServiceProvider& service_provid m_main_layout.render(service_provider); } -helper::BoolWrapper> -custom_ui::RecordingComponent::handle_event(const SDL_Event& event, const Window* window) { +helper::BoolWrapper> custom_ui::RecordingComponent::handle_event( + const std::shared_ptr& input_manager, + const SDL_Event& event +) { - if (utils::device_supports_keys()) { - if (has_focus() and utils::event_is_action(event, utils::CrossPlatformAction::OK)) { - return { - true, - {ui::EventHandleType::RequestAction, this} - }; - } + if (has_focus() and input_manager->get_navigation_event(event) == input::NavigationEvent::OK) { + return { + true, + { ui::EventHandleType::RequestAction, this } + }; } - if (const auto hover_result = detect_hover(event, window); hover_result) { + if (const auto hover_result = detect_hover(input_manager, event); hover_result) { if (hover_result.is(ui::ActionType::Clicked)) { return { true, - {has_focus() ? ui::EventHandleType::RequestAction : ui::EventHandleType::RequestFocus, this} + { has_focus() ? ui::EventHandleType::RequestAction : ui::EventHandleType::RequestFocus, this } }; } return true; diff --git a/src/scenes/recording_selector/recording_component.hpp b/src/scenes/recording_selector/recording_component.hpp index 289ea170..4e554b92 100644 --- a/src/scenes/recording_selector/recording_component.hpp +++ b/src/scenes/recording_selector/recording_component.hpp @@ -15,7 +15,7 @@ namespace data { - //TODO: add drop support + //TODO(Totto): add drop support enum class RecordingSource : u8 { CommandLine, Folder, Manual, Online, Drop }; struct RecordingMetadata { @@ -46,7 +46,8 @@ namespace custom_ui { void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; [[nodiscard]] data::RecordingMetadata metadata() const; diff --git a/src/scenes/recording_selector/recording_selector.cpp b/src/scenes/recording_selector/recording_selector.cpp index 2f9b6eaa..17c63795 100644 --- a/src/scenes/recording_selector/recording_selector.cpp +++ b/src/scenes/recording_selector/recording_selector.cpp @@ -1,3 +1,7 @@ +#include "helper/platform.hpp" +#include "helper/utils.hpp" + + #if defined(_HAVE_FILE_DIALOGS) #include "recording_chooser.hpp" #endif @@ -37,7 +41,7 @@ namespace scenes { auto focus_helper = ui::FocusHelper{ 1 }; m_main_layout.add( - service_provider, "Select Recording to replay", service_provider->fonts().get(FontId::Default), + service_provider, "Select Recording to replay", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.5, 1.0 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); @@ -49,17 +53,17 @@ namespace scenes { add_all_recordings(); - constexpr auto button_size = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_size = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.15, 0.85 } : std::pair{ 0.5, 0.85 }; constexpr auto button_alignment = ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }; - constexpr auto button_margins = utils::device_orientation() == utils::Orientation::Landscape + constexpr auto button_margins = utils::get_orientation() == utils::Orientation::Landscape ? std::pair{ 0.1, 0.1 } : std::pair{ 0.2, 0.2 }; m_main_layout.add( - service_provider, "Return", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Return", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command{ Return{} }; @@ -77,11 +81,10 @@ namespace scenes { helper::overloaded{ [](const Return&) { return UpdateResult{ SceneUpdate::StopUpdating, Scene::Pop{} }; }, [this](const Action& action) { - if (auto* recording_component = - dynamic_cast(action.widget); - recording_component != nullptr) { - - const auto recording_path = recording_component->metadata().path; + if (auto recording_component = + utils::is_child_class(action.widget); + recording_component.has_value()) { + const auto recording_path = recording_component.value()->metadata().path; // action is a reference to a structure inside m_next_command, so resetting it means, we need to copy everything out of it m_next_command = helper::nullopt; @@ -97,11 +100,11 @@ namespace scenes { } #if defined(_HAVE_FILE_DIALOGS) - if (auto* recording_file_chooser = - dynamic_cast(action.widget); - recording_file_chooser != nullptr) { - - for (const auto& path : recording_file_chooser->get_currently_chosen_files()) { + if (auto recording_file_chooser = + utils::is_child_class(action.widget); + recording_file_chooser.has_value()) { + for (const auto& path : + recording_file_chooser.value()->get_currently_chosen_files()) { m_chosen_paths.push_back(path); } @@ -130,10 +133,11 @@ namespace scenes { m_main_layout.render(service_provider); } - bool RecordingSelector::handle_event(const SDL_Event& event, const Window* window) { + bool + RecordingSelector::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { - if (const auto event_result = m_main_layout.handle_event(event, window); event_result) { + if (const auto event_result = m_main_layout.handle_event(input_manager, event); event_result) { if (const auto additional = event_result.get_additional(); additional.has_value() and additional.value().first == ui::EventHandleType::RequestAction) { m_next_command = Command{ Action(additional.value().second) }; @@ -142,7 +146,9 @@ namespace scenes { return true; } - if (utils::event_is_action(event, utils::CrossPlatformAction::CLOSE)) { + const auto navigation_event = input_manager->get_navigation_event(event); + + if (navigation_event == input::NavigationEvent::BACK) { m_next_command = Command{ Return{} }; return true; } diff --git a/src/scenes/recording_selector/recording_selector.hpp b/src/scenes/recording_selector/recording_selector.hpp index 2f0cb7ef..dcdd40c6 100644 --- a/src/scenes/recording_selector/recording_selector.hpp +++ b/src/scenes/recording_selector/recording_selector.hpp @@ -41,7 +41,7 @@ namespace scenes { [[nodiscard]] UpdateResult update() override; void render(const ServiceProvider& service_provider) override; - bool handle_event(const SDL_Event& event, const Window* window) override; + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; private: void add_all_recordings(); diff --git a/src/scenes/replay_game/replay_game.cpp b/src/scenes/replay_game/replay_game.cpp index 233901ea..73998625 100644 --- a/src/scenes/replay_game/replay_game.cpp +++ b/src/scenes/replay_game/replay_game.cpp @@ -22,14 +22,14 @@ namespace scenes { if (parameters.empty()) { throw std::runtime_error("An empty recording file isn't supported"); - } else if (parameters.size() == 1) { // NOLINT(readability-else-after-return) + } else if (parameters.size() == 1) { // NOLINT(readability-else-after-return,llvm-else-after-return) layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.96, 0.98 }); } else if (parameters.size() == 2) { layouts.push_back(ui::RelativeLayout{ layout, 0.02, 0.01, 0.46, 0.98 }); layouts.push_back(ui::RelativeLayout{ layout, 0.52, 0.01, 0.46, 0.98 }); } else { - //TODO: support bigger layouts than just 2 + //TODO(Totto): support bigger layouts than just 2 throw std::runtime_error("At the moment only replays from up to two players are supported"); } @@ -77,7 +77,7 @@ namespace scenes { if (all_games_finished) { return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::GameOver, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::Push{ SceneId::GameOver, ui::FullScreenLayout{ m_service_provider->window() } } }; } @@ -89,16 +89,16 @@ namespace scenes { } switch (next_scene) { - case NextScene::Pause: + /* case NextScene::Pause: return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::Pause, ui::FullScreenLayout{ m_service_provider->window() }} - }; + Scene::Push{ SceneId::Pause, ui::FullScreenLayout{ m_service_provider->window() } } + }; */ case NextScene::Settings: return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::SettingsMenu, - ui::RelativeLayout{ m_service_provider->window(), 0.15, 0.15, 0.7, 0.7 }} + Scene::Push{ SceneId::SettingsMenu, + ui::RelativeLayout{ m_service_provider->window(), 0.15, 0.15, 0.7, 0.7 } } }; default: utils::unreachable(); @@ -113,9 +113,14 @@ namespace scenes { } } - [[nodiscard]] bool ReplayGame::handle_event(const SDL_Event& event, const Window*) { + [[nodiscard]] bool + ReplayGame::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { - if (utils::event_is_action(event, utils::CrossPlatformAction::PAUSE)) { + //TODO(Totto): add gameInput to this function + //TODO(Totto): re-add pause scene + UNUSED(input_manager); + UNUSED(event); + /* if (utils::event_is_action(event, utils::CrossPlatformAction::Pause)) { for (auto& game : m_games) { if (game->is_game_finished()) { @@ -128,12 +133,13 @@ namespace scenes { return true; } - if (utils::device_supports_keys()) { - if (utils::event_is_action(event, utils::CrossPlatformAction::OPEN_SETTINGS)) { - m_next_scene = NextScene::Settings; - return true; - } - } + + if (utils::event_is_action(event, utils::CrossPlatformAction::OpenSettings)) { + m_next_scene = NextScene::Settings; + return true; + } */ + + return false; } diff --git a/src/scenes/replay_game/replay_game.hpp b/src/scenes/replay_game/replay_game.hpp index 55327d78..0c6ad5cb 100644 --- a/src/scenes/replay_game/replay_game.hpp +++ b/src/scenes/replay_game/replay_game.hpp @@ -22,7 +22,8 @@ namespace scenes { [[nodiscard]] UpdateResult update() override; void render(const ServiceProvider& service_provider) override; - [[nodiscard]] bool handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] bool + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; diff --git a/src/scenes/scene.cpp b/src/scenes/scene.cpp index 3d367443..e573efa0 100644 --- a/src/scenes/scene.cpp +++ b/src/scenes/scene.cpp @@ -3,7 +3,6 @@ #include "game_over/game_over.hpp" #include "main_menu/main_menu.hpp" #include "multiplayer_menu/multiplayer_menu.hpp" -#include "pause/pause.hpp" #include "play_select_menu/play_select_menu.hpp" #include "recording_selector/recording_selector.hpp" #include "replay_game/replay_game.hpp" @@ -25,8 +24,6 @@ namespace scenes { switch (identifier) { case SceneId::MainMenu: return std::make_unique(&service_provider, layout); - case SceneId::Pause: - return std::make_unique(&service_provider, layout); case SceneId::SinglePlayerGame: return std::make_unique(&service_provider, layout); case SceneId::GameOver: @@ -45,7 +42,7 @@ namespace scenes { case SceneId::OnlineLobby: return std::make_unique(&service_provider, layout); #endif - //TODO + //TODO(Totto): implement those /* case SceneId::LocalMultiPlayerGame: return std::make_unique(&service_provider, layout); diff --git a/src/scenes/scene.hpp b/src/scenes/scene.hpp index 7fc7a8e4..5f2b4ed4 100644 --- a/src/scenes/scene.hpp +++ b/src/scenes/scene.hpp @@ -1,9 +1,9 @@ #pragma once #include "helper/command_line_arguments.hpp" +#include "input/input.hpp" #include "manager/event_listener.hpp" #include "manager/service_provider.hpp" -#include "manager/settings.hpp" #include "scene_id.hpp" #include "ui/layout.hpp" @@ -75,7 +75,8 @@ namespace scenes { [[nodiscard]] virtual UpdateResult update() = 0; virtual void render(const ServiceProvider& service_provider) = 0; - virtual bool handle_event(const SDL_Event& event, const Window* window) = 0; + virtual bool + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) = 0; // override this, if you (the scene) could potentially be displayed in non fullscreen! virtual void on_unhover(); [[nodiscard]] const ui::Layout& get_layout() const; diff --git a/src/scenes/scene_id.hpp b/src/scenes/scene_id.hpp index f6174b34..6971e43c 100644 --- a/src/scenes/scene_id.hpp +++ b/src/scenes/scene_id.hpp @@ -12,7 +12,6 @@ enum class SceneId : u8 { MultiPlayerModeSelectMenu, OnlineLobby, OnlineMultiplayerGame, - Pause, PlaySelectMenu, SettingsMenu, SinglePlayerGame, diff --git a/src/scenes/settings_menu/color_setting_row.cpp b/src/scenes/settings_menu/color_setting_row.cpp index fdb5fbfb..c4cec4f9 100644 --- a/src/scenes/settings_menu/color_setting_row.cpp +++ b/src/scenes/settings_menu/color_setting_row.cpp @@ -1,7 +1,9 @@ #include "color_setting_row.hpp" #include "helper/errors.hpp" +#include "helper/magic_enum_wrapper.hpp" #include "helper/utils.hpp" +#include "input/input.hpp" #include "ui/components/label.hpp" #include "ui/focusable.hpp" #include "ui/hoverable.hpp" @@ -17,7 +19,7 @@ detail::ColorSettingRectangle::ColorSettingRectangle( bool is_top_level ) : Widget{ layout, ui::WidgetType::Component, is_top_level }, - Focusable{ ui::FocusHelper::FocusIDUnused() }, + Focusable{ ui::FocusHelper::focus_id_unused() }, Hoverable{ fill_rect }, m_color{ start_color }, m_fill_rect{ fill_rect } { } @@ -48,25 +50,29 @@ detail::ColorSettingRectangle::ColorSettingRectangle( void detail::ColorSettingRectangle::render(const ServiceProvider& service_provider) const { service_provider.renderer().draw_rect_filled(m_fill_rect, m_color); - //TODO: maybe use a dynamic color, to have some contrast? + //TODO(Totto): maybe use a dynamic color, to have some contrast? service_provider.renderer().draw_rect_outline(m_fill_rect, Color::white()); } -helper::BoolWrapper> -detail::ColorSettingRectangle::handle_event(const SDL_Event& event, const Window* window) { - if (utils::device_supports_keys()) { - if (has_focus() and utils::event_is_action(event, utils::CrossPlatformAction::OK)) { - return { - true, - {ui::EventHandleType::RequestAction, this} - }; - } +helper::BoolWrapper> detail::ColorSettingRectangle::handle_event( + const std::shared_ptr& input_manager, + const SDL_Event& event +) { + + const auto navigation_event = input_manager->get_navigation_event(event); + + if (has_focus() and navigation_event == input::NavigationEvent::OK) { + return { + true, + { ui::EventHandleType::RequestAction, this } + }; } - if (const auto hover_result = detect_hover(event, window); hover_result) { + + if (const auto hover_result = detect_hover(input_manager, event); hover_result) { if (hover_result.is(ui::ActionType::Clicked)) { return { true, - {ui::EventHandleType::RequestAction, this} + { ui::EventHandleType::RequestAction, this } }; } return true; @@ -102,14 +108,19 @@ void detail::ColorPickerScene::render(const ServiceProvider& service_provider) { m_color_picker.render(service_provider); } -bool detail::ColorPickerScene::handle_event(const SDL_Event& event, const Window* window) { +bool detail::ColorPickerScene::handle_event( + const std::shared_ptr& input_manager, + const SDL_Event& event +) { + + const auto navigation_event = input_manager->get_navigation_event(event); - if (utils::event_is_action(event, utils::CrossPlatformAction::CLOSE)) { + if (navigation_event == input::NavigationEvent::BACK) { m_should_exit = true; return true; } - const auto result = m_color_picker.handle_event(event, window); + const auto result = m_color_picker.handle_event(input_manager, event); if (result) { return result; } @@ -133,7 +144,7 @@ custom_ui::ColorSettingRow::ColorSettingRow( ui::Hoverable{ layout.get_rect() }, m_service_provider{ service_provider }, m_main_layout{ utils::size_t_identity<2>(), - ui::FocusHelper::FocusIDUnused(), + ui::FocusHelper::focus_id_unused(), ui::Direction::Horizontal, std::array{ 0.7 }, ui::RelativeMargin{ layout.get_rect(), ui::Direction::Vertical, 0.05 }, @@ -144,7 +155,7 @@ custom_ui::ColorSettingRow::ColorSettingRow( m_main_layout.add( - service_provider, std::move(name), service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, std::move(name), service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.5, 0.5 }, ui::Alignment{ ui::AlignmentHorizontal::Left, ui::AlignmentVertical::Center } ); @@ -160,14 +171,16 @@ void custom_ui::ColorSettingRow::render(const ServiceProvider& service_provider) m_main_layout.render(service_provider); } -helper::BoolWrapper> -custom_ui::ColorSettingRow::handle_event(const SDL_Event& event, const Window* window) { - const auto result = m_main_layout.handle_event(event, window); +helper::BoolWrapper> custom_ui::ColorSettingRow::handle_event( + const std::shared_ptr& input_manager, + const SDL_Event& event +) { + const auto result = m_main_layout.handle_event(input_manager, event); if (const auto additional = result.get_additional(); additional.has_value()) { if (additional->first == ui::EventHandleType::RequestAction) { return { result, - {ui::EventHandleType::RequestAction, this} + { ui::EventHandleType::RequestAction, this } }; } diff --git a/src/scenes/settings_menu/color_setting_row.hpp b/src/scenes/settings_menu/color_setting_row.hpp index bda2c5f0..b03d4fdf 100644 --- a/src/scenes/settings_menu/color_setting_row.hpp +++ b/src/scenes/settings_menu/color_setting_row.hpp @@ -36,7 +36,8 @@ namespace detail { void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; @@ -59,7 +60,7 @@ namespace detail { void render(const ServiceProvider& service_provider) override; - bool handle_event(const SDL_Event& event, const Window* window) override; + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; @@ -92,7 +93,8 @@ namespace custom_ui { void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; [[nodiscard]] scenes::Scene::Change get_details_scene() override; diff --git a/src/scenes/settings_menu/settings_menu.cpp b/src/scenes/settings_menu/settings_menu.cpp index 29977d61..f3f35b8b 100644 --- a/src/scenes/settings_menu/settings_menu.cpp +++ b/src/scenes/settings_menu/settings_menu.cpp @@ -1,7 +1,8 @@ #include "settings_menu.hpp" #include "color_setting_row.hpp" -#include "graphics/window.hpp" #include "helper/color_literals.hpp" +#include "helper/optional.hpp" +#include "helper/utils.hpp" #include "manager/music_manager.hpp" #include "manager/resource_manager.hpp" #include "settings_details.hpp" @@ -16,7 +17,18 @@ namespace scenes { using namespace details::settings::menu; - SettingsMenu::SettingsMenu(ServiceProvider* service_provider, const ui::Layout& layout) : Scene{service_provider, layout} + SettingsMenu::SettingsMenu(ServiceProvider* service_provider, const ui::Layout& layout) + : SettingsMenu{ service_provider, layout, helper::nullopt } { } + + SettingsMenu::SettingsMenu( + ServiceProvider* service_provider, + const ui::Layout& layout, + const std::shared_ptr& game_input + ) + : SettingsMenu{ service_provider, layout, helper::optional>{ game_input } } { + } + + SettingsMenu::SettingsMenu(ServiceProvider* service_provider, const ui::Layout& layout, const helper::optional>& game_input) : Scene{service_provider, layout} , m_main_layout{ utils::size_t_identity<3>(), 0, ui::Direction::Vertical, @@ -25,12 +37,12 @@ namespace scenes { std::pair{ 0.05, 0.03 }, layout }, - m_colors{COLOR_LITERAL("#FF33FF"), COLOR_LITERAL("hsv(281.71, 0.70085, 0.45882)"), COLOR_LITERAL("rgb(246, 255, 61)"),COLOR_LITERAL("hsv(103.12, 0.39024, 0.32157)")} + m_colors{COLOR_LITERAL("#FF33FF"), COLOR_LITERAL("hsv(281.71, 0.70085, 0.45882)"), COLOR_LITERAL("rgb(246, 255, 61)"),COLOR_LITERAL("hsv(103.12, 0.39024, 0.32157)")},m_game_input{game_input} { auto focus_helper = ui::FocusHelper{ 1 }; m_main_layout.add( - service_provider, "Settings", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Settings", service_provider->font_manager().get(FontId::Default), Color::white(), std::pair{ 0.3, 0.6 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } ); @@ -45,7 +57,8 @@ namespace scenes { scroll_layout->add( ui::RelativeItemSize{ scroll_layout->layout(), 0.2 }, service_provider, "Volume", - service_provider->fonts().get(FontId::Default), Color::white(), std::pair{ 0.1, 0.3 }, + service_provider->font_manager().get(FontId::Default), Color::white(), + std::pair{ 0.1, 0.3 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Bottom } ); @@ -58,7 +71,7 @@ namespace scenes { }, [service_provider](double amount) { const auto mapped_amount = amount <= 0.0F ? helper::nullopt : helper::optional{ amount }; - return service_provider->music_manager().set_volume(mapped_amount, false, false); + service_provider->music_manager().set_volume(mapped_amount, false, false); }, 0.05F, std::pair{ 0.6, 1.0 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center } @@ -74,7 +87,8 @@ namespace scenes { scroll_layout->add( ui::RelativeItemSize{ scroll_layout->layout(), 0.2 }, service_provider, "Colors", - service_provider->fonts().get(FontId::Default), Color::white(), std::pair{ 0.1, 0.3 }, + service_provider->font_manager().get(FontId::Default), Color::white(), + std::pair{ 0.1, 0.3 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Bottom } ); @@ -92,7 +106,7 @@ namespace scenes { } m_main_layout.add( - service_provider, "Return", service_provider->fonts().get(FontId::Default), Color::white(), + service_provider, "Return", service_provider->font_manager().get(FontId::Default), Color::white(), focus_helper.focus_id(), [this](const ui::TextButton&) -> bool { m_next_command = Command{ Return{} }; @@ -115,10 +129,11 @@ namespace scenes { return UpdateResult{ SceneUpdate::StopUpdating, Scene::Pop{} }; }, [this](const Action& action) { - if (auto* settings_details = dynamic_cast(action.widget); - settings_details != nullptr) { + if (auto settings_details = + utils::is_child_class(action.widget); + settings_details.has_value()) { - auto change_scene = settings_details->get_details_scene(); + auto change_scene = settings_details.value()->get_details_scene(); // action is a reference to a structure inside m_next_command, so resetting it means, we need to copy everything out of it m_next_command = helper::nullopt; @@ -148,8 +163,8 @@ namespace scenes { m_main_layout.render(service_provider); } - bool SettingsMenu::handle_event(const SDL_Event& event, const Window* window) { - if (const auto event_result = m_main_layout.handle_event(event, window); event_result) { + bool SettingsMenu::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + if (const auto event_result = m_main_layout.handle_event(input_manager, event); event_result) { if (const auto additional = event_result.get_additional(); additional.has_value() and additional.value().first == ui::EventHandleType::RequestAction) { m_next_command = Command{ Action{ additional.value().second } }; @@ -158,19 +173,21 @@ namespace scenes { return true; } - if (utils::event_is_action(event, utils::CrossPlatformAction::CLOSE)) { + const auto navigation_event = input_manager->get_navigation_event(event); + + if (navigation_event == input::NavigationEvent::BACK) { m_next_command = Command{ Return{} }; return true; } - if (utils::device_supports_keys()) { - if (utils::event_is_action(event, utils::CrossPlatformAction::OPEN_SETTINGS)) { - m_next_command = Command{ Return{} }; - return true; - } + if (m_game_input.has_value() + and m_game_input.value()->get_menu_event(event) == input::MenuEvent::OpenSettings) { + m_next_command = Command{ Return{} }; + return true; } + return false; } diff --git a/src/scenes/settings_menu/settings_menu.hpp b/src/scenes/settings_menu/settings_menu.hpp index b359bc2d..80b52ed4 100644 --- a/src/scenes/settings_menu/settings_menu.hpp +++ b/src/scenes/settings_menu/settings_menu.hpp @@ -36,17 +36,29 @@ namespace scenes { ui::TileLayout m_main_layout; //todo migrate to settings state std::vector m_colors; + helper::optional> m_game_input; const std::string listener_name = "settings_menu"; + explicit SettingsMenu( + ServiceProvider* service_provider, + const ui::Layout& layout, + const helper::optional>& game_input + ); + public: explicit SettingsMenu(ServiceProvider* service_provider, const ui::Layout& layout); + explicit SettingsMenu( + ServiceProvider* service_provider, + const ui::Layout& layout, + const std::shared_ptr& game_input + ); [[nodiscard]] UpdateResult update() override; void render(const ServiceProvider& service_provider) override; - bool handle_event(const SDL_Event& event, const Window* window) override; + bool handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; void on_unhover() override; }; diff --git a/src/scenes/single_player_game/meson.build b/src/scenes/single_player_game/meson.build index 1da476f9..15096996 100644 --- a/src/scenes/single_player_game/meson.build +++ b/src/scenes/single_player_game/meson.build @@ -1,4 +1,6 @@ graphics_src_files += files( + 'pause.cpp', + 'pause.hpp', 'single_player_game.cpp', 'single_player_game.hpp', ) diff --git a/src/scenes/single_player_game/pause.cpp b/src/scenes/single_player_game/pause.cpp new file mode 100644 index 00000000..c9836213 --- /dev/null +++ b/src/scenes/single_player_game/pause.cpp @@ -0,0 +1,65 @@ +#include "pause.hpp" +#include "graphics/renderer.hpp" +#include "helper/platform.hpp" +#include "input/game_input.hpp" +#include "input/input.hpp" +#include "manager/resource_manager.hpp" +#include + +namespace scenes { + + SinglePlayerPause::SinglePlayerPause(ServiceProvider* service_provider, const ui::Layout& layout, const std::shared_ptr& game_input) : Scene{ service_provider, layout }, m_heading { + service_provider, + fmt::format( + "Pause ({}: continue, {}: quit)", + game_input->describe_menu_event(input::MenuEvent::Pause), + service_provider->input_manager().get_primary_input()->describe_navigation_event(input::NavigationEvent::BACK) + ), + service_provider->font_manager().get(FontId::Default), + Color::white(),utils::get_orientation() == utils::Orientation::Landscape + ? std::pair{ 0.7, 0.07 } + : std::pair{ 0.95, 0.07 }, + ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }, + layout, + true + },m_game_input{game_input} + { } + + [[nodiscard]] Scene::UpdateResult scenes::SinglePlayerPause::update() { + if (m_should_unpause) { + return UpdateResult{ SceneUpdate::StopUpdating, Scene::Pop{} }; + } + if (m_should_exit) { + return UpdateResult{ + SceneUpdate::StopUpdating, + Scene::Switch{ SceneId::MainMenu, ui::FullScreenLayout{ m_service_provider->window() } } + }; + } + return UpdateResult{ SceneUpdate::StopUpdating, helper::nullopt }; + } + + void SinglePlayerPause::render(const ServiceProvider& service_provider) { + service_provider.renderer().draw_rect_filled(get_layout().get_rect(), Color::black(180)); + m_heading.render(service_provider); + } + + [[nodiscard]] bool + SinglePlayerPause::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + + const auto navigation_event = input_manager->get_navigation_event(event); + + + if (m_game_input->get_menu_event(event) == input::MenuEvent::Pause) { + m_should_unpause = true; + return true; + } + + if (navigation_event == input::NavigationEvent::BACK) { + m_should_exit = true; + return true; + } + + return false; + } + +} // namespace scenes diff --git a/src/scenes/single_player_game/pause.hpp b/src/scenes/single_player_game/pause.hpp new file mode 100644 index 00000000..a8394ba4 --- /dev/null +++ b/src/scenes/single_player_game/pause.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "manager/event_listener.hpp" +#include "scenes/scene.hpp" +#include "ui/components/label.hpp" + +namespace scenes { + struct SinglePlayerPause : public scenes::Scene { + private: + ui::Label m_heading; + bool m_should_unpause{ false }; + bool m_should_exit{ false }; + std::shared_ptr m_game_input; + + public: + explicit SinglePlayerPause( + ServiceProvider* service_provider, + const ui::Layout& layout, + const std::shared_ptr& game_input + ); + + [[nodiscard]] UpdateResult update() override; + void render(const ServiceProvider& service_provider) override; + [[nodiscard]] bool + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; + }; +} // namespace scenes diff --git a/src/scenes/single_player_game/single_player_game.cpp b/src/scenes/single_player_game/single_player_game.cpp index 550dcdf6..820aa45f 100644 --- a/src/scenes/single_player_game/single_player_game.cpp +++ b/src/scenes/single_player_game/single_player_game.cpp @@ -1,8 +1,15 @@ #include "single_player_game.hpp" #include "helper/date.hpp" +#include "helper/errors.hpp" #include "helper/music_utils.hpp" +#include "helper/platform.hpp" +#include "input/game_input.hpp" +#include "input/input.hpp" +#include "magic_enum.hpp" #include "manager/music_manager.hpp" #include "scenes/scene.hpp" +#include "scenes/settings_menu/settings_menu.hpp" +#include "scenes/single_player_game/pause.hpp" namespace scenes { @@ -13,14 +20,22 @@ namespace scenes { recorder::AdditionalInformation additional_information{}; additional_information.add("mode", "single_player"); - additional_information.add("platform", utils::get_platform()); + additional_information.add("platform", std::string{ magic_enum::enum_name(utils::get_platform()) }); additional_information.add("date", date.value()); - //TODO: add more information, if logged in + //TODO(Totto): add more information, if logged in - auto [input, starting_parameters] = + auto result = input::get_single_player_game_parameters(service_provider, std::move(additional_information), date); + if (not result.has_value()) { + throw helper::MajorError( + "No suitable input was configured, go into the settings to select a suitable input! " + ); + } + + auto [input, starting_parameters] = result.value(); + m_game = std::make_unique(service_provider, std::move(input), starting_parameters, layout, true); @@ -48,7 +63,7 @@ namespace scenes { if (m_game->is_game_finished()) { return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::GameOver, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::Push{ SceneId::GameOver, ui::FullScreenLayout{ m_service_provider->window() } } }; } @@ -63,13 +78,21 @@ namespace scenes { case NextScene::Pause: return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::Pause, ui::FullScreenLayout{ m_service_provider->window() }} + Scene::RawPush{ "Pause", std::make_unique( + m_service_provider, ui::FullScreenLayout{ m_service_provider->window() }, + m_game->game_input() + ) } }; case NextScene::Settings: return UpdateResult{ SceneUpdate::StopUpdating, - Scene::Push{SceneId::SettingsMenu, - ui::RelativeLayout{ m_service_provider->window(), 0.15, 0.15, 0.7, 0.7 }} + Scene::RawPush{ "SettingsMenu", std::make_unique( + m_service_provider, ui::RelativeLayout{ m_service_provider->window(), 0.15, + 0.15, 0.7, 0.7 }, + m_game->game_input() + ) + + } }; default: utils::unreachable(); @@ -82,20 +105,26 @@ namespace scenes { m_game->render(service_provider); } - [[nodiscard]] bool SinglePlayerGame::handle_event(const SDL_Event& event, const Window*) { + [[nodiscard]] bool + SinglePlayerGame::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { - if (utils::event_is_action(event, utils::CrossPlatformAction::PAUSE) and not m_game->is_game_finished()) { + const auto& game_input = m_game->game_input(); + + const auto& menu_event = game_input->get_menu_event(event); + + if ((menu_event == input::MenuEvent::Pause + or input_manager->get_navigation_event(event) == input::NavigationEvent::BACK) + and not m_game->is_game_finished()) { m_next_scene = NextScene::Pause; m_game->set_paused(true); return true; } - if (utils::device_supports_keys()) { - if (utils::event_is_action(event, utils::CrossPlatformAction::OPEN_SETTINGS)) { - m_next_scene = NextScene::Settings; - return true; - } + if (menu_event == input::MenuEvent::OpenSettings) { + m_next_scene = NextScene::Settings; + return true; } + return false; } diff --git a/src/scenes/single_player_game/single_player_game.hpp b/src/scenes/single_player_game/single_player_game.hpp index a63b978d..254205c3 100644 --- a/src/scenes/single_player_game/single_player_game.hpp +++ b/src/scenes/single_player_game/single_player_game.hpp @@ -19,7 +19,8 @@ namespace scenes { [[nodiscard]] UpdateResult update() override; void render(const ServiceProvider& service_provider) override; - [[nodiscard]] bool handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] bool + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; }; diff --git a/src/ui/components/abstract_slider.hpp b/src/ui/components/abstract_slider.hpp index 5ce553b0..ff057965 100644 --- a/src/ui/components/abstract_slider.hpp +++ b/src/ui/components/abstract_slider.hpp @@ -6,6 +6,7 @@ #include #include "graphics/rect.hpp" +#include "input/input.hpp" #include "ui/focusable.hpp" #include "ui/widget.hpp" @@ -84,26 +85,33 @@ namespace ui { } } - ~AbstractSlider() override { SDL_CaptureMouse(SDL_FALSE); } + AbstractSlider(const AbstractSlider&) = default; + AbstractSlider& operator=(const AbstractSlider&) = default; + + AbstractSlider(AbstractSlider&&) noexcept = default; + AbstractSlider& operator=(AbstractSlider&&) noexcept = default; + + Widget::EventHandleResult - handle_event(const SDL_Event& event, const Window* window) // NOLINT(readability-function-cognitive-complexity) - override { + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override { Widget::EventHandleResult handled = false; - if (utils::device_supports_keys() and has_focus()) { - if (utils::event_is_action(event, utils::CrossPlatformAction::RIGHT)) { + const auto navigation_event = input_manager->get_navigation_event(event); + + if (navigation_event.has_value() and has_focus()) { + if (navigation_event == input::NavigationEvent::RIGHT) { m_current_value = m_current_value + m_step; if (m_current_value >= m_range.second) { m_current_value = m_range.second; } handled = true; - } else if (utils::event_is_action(event, utils::CrossPlatformAction::LEFT)) { + } else if (navigation_event == input::NavigationEvent::LEFT) { m_current_value = m_current_value - m_step; if (m_current_value <= m_range.first) { m_current_value = m_range.first; @@ -113,12 +121,15 @@ namespace ui { } } - if (not handled and utils::device_supports_clicks()) { + const auto pointer_event = input_manager->get_pointer_event(event); + + + if (not handled and pointer_event.has_value()) { - const auto change_value_on_scroll = [&window, &event, this]() { + const auto change_value_on_scroll = [&pointer_event, this]() { const auto& [bar_rect, slider_rect] = this->get_rectangles(); - const auto& [x, _] = utils::get_raw_coordinates(window, event); + const auto& [x, _] = pointer_event->position(); if (x <= static_cast(bar_rect.top_left.x)) { m_current_value = m_range.first; @@ -133,41 +144,42 @@ namespace ui { }; - if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::ButtonDown)) { + if (pointer_event == input::PointerEvent::PointerDown) { - if (utils::is_event_in(window, event, m_bar_rect)) { + if (pointer_event->is_in(m_bar_rect)) { change_value_on_scroll(); m_is_dragging = true; SDL_CaptureMouse(SDL_TRUE); handled = { true, - {ui::EventHandleType::RequestFocus, this} + { ui::EventHandleType::RequestFocus, this } }; - } else if (utils::is_event_in(window, event, m_slider_rect)) { + } else if (pointer_event->is_in(m_slider_rect)) { m_is_dragging = true; SDL_CaptureMouse(SDL_TRUE); handled = { true, - {ui::EventHandleType::RequestFocus, this} + { ui::EventHandleType::RequestFocus, this } }; } - } else if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::ButtonUp)) { + } else if (pointer_event == input::PointerEvent::PointerUp) { // only handle this, if already dragging, otherwise it's a button down from previously or some other widget if (m_is_dragging) { m_is_dragging = false; SDL_CaptureMouse(SDL_FALSE); handled = true; } - } else if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Motion)) { + } else if (pointer_event == input::PointerEvent::Motion) { if (m_is_dragging) { change_value_on_scroll(); handled = true; } + //TODO(Totto): this is not working, since pointer_event.has_value() is wrong in this case } else if (event.type == SDL_MOUSEWHEEL && has_focus()) { // here we use a reverse scroll behaviour, since moving the mouse up is always considered increasing the volume, regardless of you OS setting about natural scrolling or not diff --git a/src/ui/components/button.hpp b/src/ui/components/button.hpp index 2e604d5c..8367fc43 100644 --- a/src/ui/components/button.hpp +++ b/src/ui/components/button.hpp @@ -7,7 +7,7 @@ #include "graphics/rect.hpp" #include "graphics/renderer.hpp" #include "helper/color_literals.hpp" -#include "platform/capabilities.hpp" +#include "input/input.hpp" #include "ui/focusable.hpp" #include "ui/hoverable.hpp" #include "ui/widget.hpp" @@ -19,12 +19,13 @@ namespace ui { public: using Callback = std::function; - protected: + private: Content m_content; Callback m_callback; shapes::URect m_fill_rect; bool m_enabled; + protected: explicit Button( Content&& content, u32 focus_id, @@ -47,6 +48,10 @@ namespace ui { } } + [[nodiscard]] const Callback& callback() const { + return m_callback; + }; + public: explicit Button( Content&& content, @@ -72,7 +77,7 @@ namespace ui { void render(const ServiceProvider& service_provider) const override { - //TODO: get as input a color palette and use that! + //TODO(Totto): get as input a color palette and use that! const auto color = not m_enabled ? (has_focus() ? "#A36A6A"_c : "#919191"_c) : (has_focus() ? is_hovered() ? "#FF6A00"_c : Color::red() : is_hovered() ? "#00BBFF"_c @@ -82,30 +87,33 @@ namespace ui { m_content.render(service_provider); } - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override { + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override { if (not m_enabled) { return false; } - if (utils::device_supports_keys()) { - if (has_focus() and utils::event_is_action(event, utils::CrossPlatformAction::OK)) { - spdlog::info("Button pressed"); - if (on_clicked()) { - return { - true, - {ui::EventHandleType::RequestAction, this} - }; - } - return true; + + const auto navigation_event = input_manager->get_navigation_event(event); + + if (has_focus() and navigation_event == input::NavigationEvent::OK) { + spdlog::info("Button pressed"); + if (on_clicked()) { + return { + true, + { ui::EventHandleType::RequestAction, this } + }; } + return true; } - if (const auto hover_result = detect_hover(event, window); hover_result) { + + if (const auto hover_result = detect_hover(input_manager, event); hover_result) { if (hover_result.is(ActionType::Clicked)) { if (on_clicked()) { return { true, - {ui::EventHandleType::RequestAction, this} + { ui::EventHandleType::RequestAction, this } }; } } diff --git a/src/ui/components/color_picker.cpp b/src/ui/components/color_picker.cpp index 107e1238..11cf161f 100644 --- a/src/ui/components/color_picker.cpp +++ b/src/ui/components/color_picker.cpp @@ -6,6 +6,7 @@ #include "helper/color.hpp" #include "helper/graphic_utils.hpp" #include "helper/utils.hpp" +#include "input/input.hpp" #include "manager/resource_manager.hpp" #include "ui/components/textinput.hpp" #include "ui/layout.hpp" @@ -22,7 +23,7 @@ detail::ColorSlider::ColorSlider( const ui::Layout& layout, bool is_top_level ) - : AbstractSlider{ ui::FocusHelper::FocusIDUnused(), + : AbstractSlider{ ui::FocusHelper::focus_id_unused(), std::move(range), std::move(getter), std::move(setter), @@ -81,8 +82,8 @@ detail::ColorSlider::ColorSlider( } const auto slider_rect = shapes::URect{ - shapes::UPoint{slider_start_x, layout_rect.top_left.y}, - shapes::UPoint{ slider_end_x, layout_rect.bottom_right.y} + shapes::UPoint{ slider_start_x, layout_rect.top_left.y }, + shapes::UPoint{ slider_end_x, layout_rect.bottom_right.y } }; return { layout_rect, slider_rect }; @@ -148,47 +149,44 @@ void detail::ColorCanvas::draw_pseudo_circle(const ServiceProvider& service_prov } helper::BoolWrapper> -detail::ColorCanvas::handle_event( //NOLINT(readability-function-cognitive-complexity) - const SDL_Event& event, - const Window* window -) { +detail::ColorCanvas::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { Widget::EventHandleResult handled = false; const auto fill_rect = layout().get_rect(); - if (utils::device_supports_clicks()) { - if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::ButtonDown)) { + const auto pointer_event = input_manager->get_pointer_event(event); - if (utils::is_event_in(window, event, fill_rect)) { + if (pointer_event == input::PointerEvent::PointerDown) { - m_is_dragging = true; - SDL_CaptureMouse(SDL_TRUE); - handled = { - true, - {ui::EventHandleType::RequestFocus, this} - }; - } - } else if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::ButtonUp)) { - // only handle this, if already dragging, otherwise it's a button down from previously or some other widget - if (m_is_dragging) { - m_is_dragging = false; - SDL_CaptureMouse(SDL_FALSE); - handled = true; - } - } else if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Motion)) { + if (pointer_event->is_in(fill_rect)) { - if (m_is_dragging) { - handled = true; - } + m_is_dragging = true; + SDL_CaptureMouse(SDL_TRUE); + handled = { + true, + { ui::EventHandleType::RequestFocus, this } + }; + } + } else if (pointer_event == input::PointerEvent::PointerUp) { + // only handle this, if already dragging, otherwise it's a button down from previously or some other widget + if (m_is_dragging) { + m_is_dragging = false; + SDL_CaptureMouse(SDL_FALSE); + handled = true; + } + } else if (pointer_event == input::PointerEvent::Motion) { + + if (m_is_dragging) { + handled = true; } } - if (handled) { + if (handled and pointer_event.has_value()) { const auto previous_color = m_current_color; - const auto& [x, y] = utils::get_raw_coordinates(window, event); + const auto& [x, y] = pointer_event.value().position(); if (x <= static_cast(fill_rect.top_left.x)) { m_current_color.s = 0.0; @@ -260,16 +258,18 @@ void detail::ColorCanvas::redraw_texture() { m_service_provider->renderer().set_render_target(*m_texture); - const auto w = fill_rect.width(); - const auto h = fill_rect.height(); + const auto width = fill_rect.width(); + const auto height = fill_rect.height(); const auto hue = m_current_color.h; - //TODO: try to speed this up, since it is a performance bottle neck, if hovering like a madman (xD) over the color slider - for (u32 y = 0; y < h; y++) { - const auto v = 1.0 - (static_cast(y) / static_cast(h)); - for (u32 x = 0; x < w; x++) { - const Color color = HSVColor(hue, static_cast(x) / static_cast(w), v).to_rgb_color(); + //TODO(Totto): try to speed this up, since it is a performance bottle neck, if hovering like a madman (xD) over the color slider + // maybe use shaders? + for (u32 y = 0; y < height; y++) { + const auto value = 1.0 - (static_cast(y) / static_cast(height)); + for (u32 x = 0; x < width; x++) { + const Color color = + HSVColor(hue, static_cast(x) / static_cast(width), value).to_rgb_color(); m_service_provider->renderer().draw_pixel(shapes::UPoint{ x, y }, color); } @@ -292,7 +292,7 @@ ui::ColorPicker::ColorPicker( m_mode{ ColorMode::RGB }, m_callback{ std::move(callback) } { - //TODO: add alpha slider at the side + //TODO(Totto): add alpha slider at the side constexpr double main_rect_height = 0.8; @@ -374,7 +374,7 @@ ui::ColorPicker::ColorPicker( const auto toggle_button_layout = ui::RelativeLayout{ components_fill_layout, 0.0, 0.0, toggle_button_size, 1.0 }; - const auto focus_id_unused = FocusHelper::FocusIDUnused(); + const auto focus_id_unused = FocusHelper::focus_id_unused(); const auto rgb_image_path = utils::get_assets_folder() / "icons" / "rgb_color_selector.png"; @@ -407,7 +407,7 @@ ui::ColorPicker::ColorPicker( m_color_text = std::make_unique( - service_provider, service_provider->fonts().get(FontId::Default), Color::white(), focus_id_unused, + service_provider, service_provider->font_manager().get(FontId::Default), Color::white(), focus_id_unused, std::pair{ 0.9, 0.9 }, ui::Alignment{ ui::AlignmentHorizontal::Middle, ui::AlignmentVertical::Center }, ui::TextInputMode::Scale, textinput_layout, false @@ -458,18 +458,15 @@ void ui::ColorPicker::render(const ServiceProvider& service_provider) const { } helper::BoolWrapper> -ui::ColorPicker::handle_event( //NOLINT(readability-function-cognitive-complexity) - const SDL_Event& event, - const Window* window -) { +ui::ColorPicker::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { - auto handled = m_color_slider->handle_event(event, window); + auto handled = m_color_slider->handle_event(input_manager, event); if (handled) { return handled; } - handled = m_color_canvas->handle_event(event, window); + handled = m_color_canvas->handle_event(input_manager, event); if (handled) { return handled; @@ -477,16 +474,16 @@ ui::ColorPicker::handle_event( //NOLINT(readability-function-cognitive-complexit if (m_mode == ColorMode::HSV) { - handled = m_hsv_button->handle_event(event, window); + handled = m_hsv_button->handle_event(input_manager, event); } else { - handled = m_rgb_button->handle_event(event, window); + handled = m_rgb_button->handle_event(input_manager, event); } if (handled) { return handled; } - handled = m_color_text->handle_event(event, window); + handled = m_color_text->handle_event(input_manager, event); if (handled) { if (const auto additional = handled.get_additional(); additional.has_value()) { @@ -509,7 +506,7 @@ ui::ColorPicker::handle_event( //NOLINT(readability-function-cognitive-complexit } if (not maybe_color.has_value()) { - //TODO: maybe inform the user, that the input is incorrect? + //TODO(Totto): maybe inform the user, that the input is incorrect? //m_color_text->display_error(); // reset the text diff --git a/src/ui/components/color_picker.hpp b/src/ui/components/color_picker.hpp index 9cb96e60..89b96775 100644 --- a/src/ui/components/color_picker.hpp +++ b/src/ui/components/color_picker.hpp @@ -62,7 +62,8 @@ namespace detail { void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; void on_change(ColorChangeOrigin origin, const HSVColor& color); @@ -116,7 +117,8 @@ namespace ui { void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; [[nodiscard]] Color get_color() const; diff --git a/src/ui/components/image_button.cpp b/src/ui/components/image_button.cpp index 1f402587..07e10a54 100644 --- a/src/ui/components/image_button.cpp +++ b/src/ui/components/image_button.cpp @@ -14,7 +14,7 @@ ui::ImageButton::ImageButton( bool is_top_level ) : Button{ - ImageView{service_provider, image_path, size, respect_ratio, alignment, layout, false}, + ImageView{ service_provider, image_path, size, respect_ratio, alignment, layout, false }, focus_id, std::move(callback), size, @@ -24,5 +24,5 @@ ui::ImageButton::ImageButton( } { } [[nodiscard]] bool ui::ImageButton::on_clicked() const { - return m_callback(*this); + return callback()(*this); } diff --git a/src/ui/components/image_view.cpp b/src/ui/components/image_view.cpp index 51115e74..d6f44e15 100644 --- a/src/ui/components/image_view.cpp +++ b/src/ui/components/image_view.cpp @@ -30,6 +30,6 @@ void ui::ImageView::render(const ServiceProvider& service_provider) const { } helper::BoolWrapper> -ui::ImageView::handle_event(const SDL_Event&, const Window*) { +ui::ImageView::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } diff --git a/src/ui/components/image_view.hpp b/src/ui/components/image_view.hpp index cf3b3251..04077ba4 100644 --- a/src/ui/components/image_view.hpp +++ b/src/ui/components/image_view.hpp @@ -27,6 +27,7 @@ namespace ui { void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event&, const Window*) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& even) override; }; } // namespace ui diff --git a/src/ui/components/label.cpp b/src/ui/components/label.cpp index 7d028677..622318de 100644 --- a/src/ui/components/label.cpp +++ b/src/ui/components/label.cpp @@ -27,7 +27,7 @@ void ui::Label::render(const ServiceProvider& service_provider) const { } helper::BoolWrapper> -ui::Label::handle_event(const SDL_Event&, const Window*) { +ui::Label::handle_event(const std::shared_ptr& /*input_manager*/, const SDL_Event& /*event*/) { return false; } diff --git a/src/ui/components/label.hpp b/src/ui/components/label.hpp index ae0aa374..4d0007f6 100644 --- a/src/ui/components/label.hpp +++ b/src/ui/components/label.hpp @@ -22,7 +22,8 @@ namespace ui { void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event&, const Window*) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& /*event*/) override; void set_text(const ServiceProvider& service_provider, const std::string& text); }; diff --git a/src/ui/components/link_label.cpp b/src/ui/components/link_label.cpp index 33728918..0dff0ffd 100644 --- a/src/ui/components/link_label.cpp +++ b/src/ui/components/link_label.cpp @@ -1,5 +1,6 @@ #include "link_label.hpp" +#include "helper/platform.hpp" ui::LinkLabel::LinkLabel( @@ -56,8 +57,8 @@ void ui::LinkLabel::render(const ServiceProvider& service_provider) const { } helper::BoolWrapper> -ui::LinkLabel::handle_event(const SDL_Event& event, const Window* window) { - if (const auto hover_result = detect_hover(event, window); hover_result) { +ui::LinkLabel::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + if (const auto hover_result = detect_hover(input_manager, event); hover_result) { if (hover_result.is(ActionType::Clicked)) { on_clicked(); } diff --git a/src/ui/components/link_label.hpp b/src/ui/components/link_label.hpp index 3267521c..e5c14c97 100644 --- a/src/ui/components/link_label.hpp +++ b/src/ui/components/link_label.hpp @@ -1,7 +1,6 @@ #pragma once #include "graphics/text.hpp" -#include "platform/capabilities.hpp" #include "ui/hoverable.hpp" #include "ui/widget.hpp" @@ -44,7 +43,8 @@ namespace ui { void render(const ServiceProvider& service_provider) const override; - [[nodiscard]] Widget::EventHandleResult handle_event(const SDL_Event& event, const Window* window) override; + [[nodiscard]] Widget::EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; void on_clicked(); diff --git a/src/ui/components/slider.cpp b/src/ui/components/slider.cpp index 7a42ff41..8f07cadf 100644 --- a/src/ui/components/slider.cpp +++ b/src/ui/components/slider.cpp @@ -56,8 +56,8 @@ ui::Slider::Slider( const auto slider_rect = shapes::URect{ - shapes::UPoint{slider_start_x, m_fill_rect.top_left.y}, - shapes::UPoint{ slider_end_x, m_fill_rect.bottom_right.y} + shapes::UPoint{ slider_start_x, m_fill_rect.top_left.y }, + shapes::UPoint{ slider_end_x, m_fill_rect.bottom_right.y } }; return { bar.get_rect(), slider_rect }; diff --git a/src/ui/components/text_button.cpp b/src/ui/components/text_button.cpp index 8f06914f..be5b8e90 100644 --- a/src/ui/components/text_button.cpp +++ b/src/ui/components/text_button.cpp @@ -15,12 +15,12 @@ ui::TextButton::TextButton( bool is_top_level ) : Button{ - Text{service_provider, + Text{ service_provider, text, font, text_color, { fill_rect.top_left.x + static_cast(margin.first), - fill_rect.top_left.y + static_cast(margin.second), - fill_rect.width() - 2 * static_cast(margin.first), - fill_rect.height() - 2 * static_cast(margin.second) }}, + fill_rect.top_left.y + static_cast(margin.second), + fill_rect.width() - 2 * static_cast(margin.first), + fill_rect.height() - 2 * static_cast(margin.second) } }, focus_id, std::move(callback), fill_rect, @@ -51,16 +51,16 @@ ui::TextButton::TextButton( text_color, ui::get_rectangle_aligned( layout, - { static_cast(size.first * layout.get_rect().width()), + { static_cast(size.first * layout.get_rect().width()), static_cast(size.second * layout.get_rect().height()) }, alignment ), - {static_cast(margin.first * size.first * layout.get_rect().width()), - static_cast(margin.second * size.second * layout.get_rect().height())}, + { static_cast(margin.first * size.first * layout.get_rect().width()), + static_cast(margin.second * size.second * layout.get_rect().height()) }, layout, is_top_level } { } [[nodiscard]] bool ui::TextButton::on_clicked() const { - return m_callback(*this); + return callback()(*this); } diff --git a/src/ui/components/textinput.cpp b/src/ui/components/textinput.cpp index 197e25ec..acdde28e 100644 --- a/src/ui/components/textinput.cpp +++ b/src/ui/components/textinput.cpp @@ -102,17 +102,17 @@ void ui::TextInput::render(const ServiceProvider& service_provider) const { } helper::BoolWrapper> -ui::TextInput::handle_event( // NOLINT(readability-function-cognitive-complexity) - const SDL_Event& event, - const Window* window +ui::TextInput::handle_event( //NOLINT(readability-function-cognitive-complexity) + const std::shared_ptr& input_manager, + const SDL_Event& event ) { - //TODO: if already has focus, position cursor there, where we clicked - if (const auto hover_result = detect_hover(event, window); hover_result) { + //TODO(Totto): if already has focus, position cursor there, where we clicked + if (const auto hover_result = detect_hover(input_manager, event); hover_result) { if (hover_result.is(ActionType::Clicked)) { return { true, - {EventHandleType::RequestFocus, this} + { EventHandleType::RequestFocus, this } }; } @@ -126,7 +126,7 @@ ui::TextInput::handle_event( // NOLINT(readability-function-cognitive-complexity on_unfocus(); return { true, - {EventHandleType::RequestAction, this} + { EventHandleType::RequestAction, this } }; } case SDLK_BACKSPACE: { @@ -246,7 +246,7 @@ void ui::TextInput::set_text(const std::string& text) { return m_text; } -void ui::TextInput::recalculate_textures(bool text_changed) { //NOLINT(readability-function-cognitive-complexity) +void ui::TextInput::recalculate_textures(bool text_changed) { const auto& renderer = m_service_provider->renderer(); @@ -308,17 +308,17 @@ void ui::TextInput::recalculate_textures(bool text_changed) { //NOLINT(readabili utf8::append(utf8::next(current_iterator, m_text.end()), sub_string); } - int w = 0; - int h = 0; - const int result = TTF_SizeUTF8(m_font.get(), sub_string.c_str(), &w, &h); + int width = 0; + int height = 0; + const int result = TTF_SizeUTF8(m_font.get(), sub_string.c_str(), &width, &height); if (result < 0) { throw helper::FatalError{ fmt::format("Error during SDL_TTF_SizeUTF8: {}", SDL_GetError()) }; } - const double ratio_sub_string = static_cast(h) / static_cast(fill_rect().height()); + const double ratio_sub_string = static_cast(height) / static_cast(fill_rect().height()); - cursor_offset = static_cast(static_cast(w) / ratio_sub_string); + cursor_offset = static_cast(static_cast(width) / ratio_sub_string); } m_cursor_rect = (unmoved_cursor >> fill_rect().top_left) >> shapes::UPoint{ cursor_offset, 0 }; diff --git a/src/ui/components/textinput.hpp b/src/ui/components/textinput.hpp index e76279b4..0d0924cb 100644 --- a/src/ui/components/textinput.hpp +++ b/src/ui/components/textinput.hpp @@ -59,12 +59,11 @@ namespace ui { void update() override; - //TODO: how to handle text limits (since texture for texts on the gpu can't get unlimitedly big, maybe use software texture?) + //TODO(Totto): how to handle text limits (since texture for texts on the gpu can't get unlimitedly big, maybe use software texture?) void render(const ServiceProvider& service_provider) const override; Widget::EventHandleResult - handle_event(const SDL_Event& event, const Window* window) // NOLINT(readability-function-cognitive-complexity) - override; + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; void set_text(const std::string& text); diff --git a/src/ui/focusable.hpp b/src/ui/focusable.hpp index 9e49fa48..e0c91e40 100644 --- a/src/ui/focusable.hpp +++ b/src/ui/focusable.hpp @@ -1,23 +1,24 @@ #pragma once + #include "helper/types.hpp" #include namespace ui { - //TODO: replace this by a focus helper per scene! + //TODO(Totto): replace this by a focus helper per scene! struct FocusHelper { private: u32 m_focus_id; public: - FocusHelper(u32 start_focus_id = 0) : m_focus_id{ start_focus_id } {}; + explicit FocusHelper(u32 start_focus_id = 0) : m_focus_id{ start_focus_id } {}; [[nodiscard]] u32 focus_id() { return m_focus_id++; } // return a placeholder, that signifies, this focus id is unused - [[nodiscard]] static u32 FocusIDUnused() { + [[nodiscard]] static u32 focus_id_unused() { return static_cast(-1); } }; @@ -56,7 +57,7 @@ namespace ui { } private: - virtual void on_focus(){ + virtual void on_focus() { //do nothing }; virtual void on_unfocus() { diff --git a/src/ui/hoverable.hpp b/src/ui/hoverable.hpp index 0af8af08..7d984aa0 100644 --- a/src/ui/hoverable.hpp +++ b/src/ui/hoverable.hpp @@ -3,13 +3,12 @@ #include "graphics/rect.hpp" #include "helper/bool_wrapper.hpp" #include "helper/types.hpp" -#include "platform/capabilities.hpp" - -#include +#include "helper/utils.hpp" +#include "input/input.hpp" namespace ui { - enum class ActionType : u8 { Hover, Clicked }; + enum class ActionType : u8 { Hover, Clicked, Released }; struct Hoverable { @@ -37,27 +36,34 @@ namespace ui { } - [[nodiscard]] helper::BoolWrapper detect_hover(const SDL_Event& event, const Window* window) { + [[nodiscard]] helper::BoolWrapper + detect_hover(const std::shared_ptr& input_manager, const SDL_Event& event) { - if (utils::device_supports_clicks()) { - if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Any)) { - if (utils::is_event_in(window, event, m_fill_rect)) { + if (const auto result = input_manager->get_pointer_event(event); result.has_value()) { + if (result->is_in(m_fill_rect)) { - on_hover(); + on_hover(); - if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::ButtonDown)) { + switch (result->event()) { + case input::PointerEvent::PointerDown: return { true, ActionType::Clicked }; - } + case input::PointerEvent::PointerUp: + return { true, ActionType::Released }; - return { true, ActionType::Hover }; - } + case input::PointerEvent::Motion: + return { true, ActionType::Hover }; - on_unhover(); - return false; + default: + utils::unreachable(); + } } + + on_unhover(); + return false; } + return false; } @@ -66,7 +72,7 @@ namespace ui { m_is_hovered = true; } - //TODO: this has to be used correctly, a click or focus change isn't an event, where an unhover needs to happen! + //TODO(Totto): this has to be used correctly, a click or focus change isn't an event, where an unhover needs to happen! void on_unhover() { m_is_hovered = false; } diff --git a/src/ui/layout.hpp b/src/ui/layout.hpp index 612c5ccd..e5b8e9c0 100644 --- a/src/ui/layout.hpp +++ b/src/ui/layout.hpp @@ -14,67 +14,85 @@ namespace ui { struct Layout { private: shapes::URect m_rect; - LayoutType type; + LayoutType m_type; protected: - Layout(const shapes::URect& rect, LayoutType type) : m_rect{ rect }, type{ type } { } + Layout(const shapes::URect& rect, LayoutType type) : m_rect{ rect }, m_type{ type } { } public: - Layout(const shapes::URect& rect) : m_rect{ rect }, type{ LayoutType::Raw } { } + explicit Layout(const shapes::URect& rect) : m_rect{ rect }, m_type{ LayoutType::Raw } { } [[nodiscard]] const shapes::URect& get_rect() const { return m_rect; } [[nodiscard]] bool is_full_screen() const { - return type == LayoutType::FullScreen; + return m_type == LayoutType::FullScreen; } }; struct AbsolutLayout : public Layout { - AbsolutLayout(const u32 x, const u32 y, const u32 width, const u32 height) + AbsolutLayout(const u32 start_x, const u32 start_y, const u32 width, const u32 height) : Layout{ - shapes::URect{x, y, width, height}, + shapes::URect{ start_x, start_y, width, height }, LayoutType::Absolut } { } }; struct FullScreenLayout : public Layout { - FullScreenLayout(const shapes::URect& rect) : Layout{ rect, LayoutType::FullScreen } { } - FullScreenLayout(const Window& window) : FullScreenLayout{ window.screen_rect() } { } - FullScreenLayout(const Window* window) : FullScreenLayout{ window->screen_rect() } { } + explicit FullScreenLayout(const shapes::URect& rect) : Layout{ rect, LayoutType::FullScreen } { } + explicit FullScreenLayout(const Window& window) : FullScreenLayout{ window.screen_rect() } { } + explicit FullScreenLayout(const Window* window) : FullScreenLayout{ window->screen_rect() } { } }; struct RelativeLayout : public Layout { RelativeLayout( const shapes::URect& rect, - const double x, - const double y, + const double start_x, + const double start_y, const double width, const double height ) : Layout{ shapes::URect( - static_cast(x * static_cast(rect.width())), - static_cast(y * static_cast(rect.height())), + static_cast(start_x * static_cast(rect.width())), + static_cast(start_y * static_cast(rect.height())), static_cast(width * static_cast(rect.width())), static_cast(height * static_cast(rect.height())) ) >> rect.top_left, LayoutType::Relative } { - assert(x >= 0.0 && x <= 1.0 && "x has to be in correct percentage range!"); - assert(y >= 0.0 && y <= 1.0 && "y has to be in correct percentage range!"); + assert(start_x >= 0.0 && start_x <= 1.0 && "x has to be in correct percentage range!"); + assert(start_y >= 0.0 && start_y <= 1.0 && "y has to be in correct percentage range!"); assert(width >= 0.0 && width <= 1.0 && "width has to be in correct percentage range!"); assert(height >= 0.0 && height <= 1.0 && "height has to be in correct percentage range!"); } - RelativeLayout(const Window* window, const double x, const double y, const double width, const double height) - : RelativeLayout{ window->screen_rect(), x, y, width, height } { } - RelativeLayout(const Window& window, const double x, const double y, const double width, const double height) - : RelativeLayout{ window.screen_rect(), x, y, width, height } { } - RelativeLayout(const Layout& layout, const double x, const double y, const double width, const double height) - : RelativeLayout{ layout.get_rect(), x, y, width, height } { } + RelativeLayout( + const Window* window, + const double start_x, + const double start_y, + const double width, + const double height + ) + : RelativeLayout{ window->screen_rect(), start_x, start_y, width, height } { } + RelativeLayout( + const Window& window, + const double start_x, + const double start_y, + const double width, + const double height + ) + : RelativeLayout{ window.screen_rect(), start_x, start_y, width, height } { } + RelativeLayout( + const Layout& layout, + const double start_x, + const double start_y, + const double width, + const double height + ) + : RelativeLayout{ layout.get_rect(), start_x, start_y, width, height } { } }; @@ -83,10 +101,8 @@ namespace ui { using Alignment = std::pair; - [[nodiscard]] u32 get_horizontal_alignment_offset(const Layout& layout, AlignmentHorizontal alignment, u32 width); - [[nodiscard]] u32 get_vertical_alignment_offset(const Layout& layout, AlignmentVertical alignment, u32 height); [[nodiscard]] shapes::URect @@ -95,7 +111,6 @@ namespace ui { [[nodiscard]] std::pair ratio_helper(const std::pair& size, bool respect_ratio, const shapes::UPoint& original_ratio); - enum class Direction : u8 { Horizontal, Vertical }; struct Margin { @@ -103,8 +118,7 @@ namespace ui { u32 m_margin; public: - Margin(u32 margin) : m_margin{ margin } { } - + explicit Margin(u32 margin) : m_margin{ margin } { } [[nodiscard]] u32 get_margin() const { return m_margin; @@ -112,7 +126,7 @@ namespace ui { }; struct AbsolutMargin : public Margin { - AbsolutMargin(const u32 margin) : Margin{ margin } { } + explicit AbsolutMargin(const u32 margin) : Margin{ margin } { } }; struct RelativeMargin : public Margin { diff --git a/src/ui/layouts/focus_layout.cpp b/src/ui/layouts/focus_layout.cpp index 54c47205..ecc5b214 100644 --- a/src/ui/layouts/focus_layout.cpp +++ b/src/ui/layouts/focus_layout.cpp @@ -1,8 +1,11 @@ #include "focus_layout.hpp" #include "helper/optional.hpp" +#include "input/input.hpp" #include "ui/widget.hpp" +#include + ui::FocusLayout::FocusLayout(const Layout& layout, u32 focus_id, FocusOptions options, bool is_top_level) : Widget{ layout, WidgetType::Container, is_top_level }, @@ -28,25 +31,31 @@ void ui::FocusLayout::update() { return static_cast(m_widgets.size()); } -ui::Widget::EventHandleResult ui::FocusLayout::handle_focus_change_button_events(const SDL_Event& event) { +ui::Widget::EventHandleResult ui::FocusLayout::handle_focus_change_button_events( + const std::shared_ptr& input_manager, + const SDL_Event& event +) { Widget::EventHandleResult handled = false; - if (utils::device_supports_keys()) { - if (utils::event_is_action(event, utils::CrossPlatformAction::DOWN) - or (m_options.allow_tab and utils::event_is_action(event, utils::CrossPlatformAction::TAB))) { - handled = try_set_next_focus(FocusChangeDirection::Forward); - } else if (utils::event_is_action(event, utils::CrossPlatformAction::UP)) { - handled = try_set_next_focus(FocusChangeDirection::Backward); - } + const auto navigation_action = input_manager->get_navigation_event(event); + + if (navigation_action == input::NavigationEvent::DOWN + or (m_options.allow_tab and navigation_action == input::NavigationEvent::TAB)) { + handled = try_set_next_focus(FocusChangeDirection::Forward); + } else if (navigation_action == input::NavigationEvent::UP) { + handled = try_set_next_focus(FocusChangeDirection::Backward); } + return handled; } -ui::Widget::EventHandleResult -ui::FocusLayout::handle_focus_change_events(const SDL_Event& event, const Window* window) { +ui::Widget::EventHandleResult ui::FocusLayout::handle_focus_change_events( + const std::shared_ptr& input_manager, + const SDL_Event& event +) { if (not has_focus()) { @@ -61,7 +70,7 @@ ui::FocusLayout::handle_focus_change_events(const SDL_Event& event, const Window if (widget->type() != WidgetType::Container) { - handled = handle_focus_change_button_events(event); + handled = handle_focus_change_button_events(input_manager, event); } @@ -69,13 +78,13 @@ ui::FocusLayout::handle_focus_change_events(const SDL_Event& event, const Window return handled; } - if (const auto event_result = widget->handle_event(event, window); event_result) { + if (const auto event_result = widget->handle_event(input_manager, event); event_result) { return { true, handle_event_result(event_result.get_additional(), widget.get()) }; } if (widget->type() == WidgetType::Container) { - handled = handle_focus_change_button_events(event); + handled = handle_focus_change_button_events(input_manager, event); } } @@ -84,10 +93,7 @@ ui::FocusLayout::handle_focus_change_events(const SDL_Event& event, const Window } [[nodiscard]] helper::optional -ui::FocusLayout::handle_event_result( // NOLINT(readability-function-cognitive-complexity) - const helper::optional& result, - Widget* widget -) { +ui::FocusLayout::handle_event_result(const helper::optional& result, Widget* widget) { if (not result.has_value()) { return helper::nullopt; @@ -167,16 +173,15 @@ ui::FocusLayout::handle_event_result( // NOLINT(readability-function-cognitive-c return ui::Widget::InnerState{ ui::EventHandleType::RequestAction, value.second }; } default: - std::unreachable(); + utils::unreachable(); } } [[nodiscard]] u32 ui::FocusLayout::focusable_index_by_id(const u32 id) const { - const auto find_iterator = - std::find_if(m_widgets.begin(), m_widgets.end(), [id](const std::unique_ptr& widget) { - const auto focusable = as_focusable(widget.get()); - return focusable.has_value() and focusable.value()->focus_id() == id; - }); + const auto find_iterator = std::ranges::find_if(m_widgets, [id](const std::unique_ptr& widget) { + const auto focusable = as_focusable(widget.get()); + return focusable.has_value() and focusable.value()->focus_id() == id; + }); assert(find_iterator != m_widgets.end()); const auto index = static_cast(std::distance(m_widgets.begin(), find_iterator)); return index; @@ -192,17 +197,18 @@ ui::FocusLayout::handle_event_result( // NOLINT(readability-function-cognitive-c } #ifdef DEBUG_BUILD - const auto duplicates = std::adjacent_find(result.cbegin(), result.cend()); + // this works, since result is sorted already + const auto duplicates = std::ranges::adjacent_find(result); if (duplicates != result.cend()) { throw std::runtime_error("Focusables have duplicates: " + std::to_string(*duplicates)); } #endif - std::sort(result.begin(), result.end()); + std::ranges::sort(result); return result; } [[nodiscard]] u32 ui::FocusLayout::index_of(const std::vector& ids, const u32 needle) { - return static_cast(std::distance(ids.cbegin(), std::find(ids.cbegin(), ids.cend(), needle))); + return static_cast(std::distance(ids.cbegin(), std::ranges::find(ids, needle))); } [[nodiscard]] bool ui::FocusLayout::try_set_next_focus(const FocusChangeDirection focus_direction) { diff --git a/src/ui/layouts/focus_layout.hpp b/src/ui/layouts/focus_layout.hpp index 5a4bba6d..487a4924 100644 --- a/src/ui/layouts/focus_layout.hpp +++ b/src/ui/layouts/focus_layout.hpp @@ -1,6 +1,7 @@ #pragma once +#include "helper/utils.hpp" #include "ui/focusable.hpp" #include "ui/widget.hpp" @@ -59,8 +60,8 @@ namespace ui { ) }; } - auto item = dynamic_cast(m_widgets.at(index).get()); - return item != nullptr; + auto item = utils::is_child_class(m_widgets.at(index)); + return item.has_value(); } @@ -73,12 +74,12 @@ namespace ui { ) }; } - auto item = dynamic_cast(m_widgets.at(index).get()); - if (item == nullptr) { + auto item = utils::is_child_class(m_widgets.at(index)); + if (not item.has_value()) { throw std::runtime_error("Invalid get of FocusLayout item!"); } - return item; + return item.value(); } template @@ -87,21 +88,25 @@ namespace ui { throw std::runtime_error("Invalid get of FocusLayout item: index out of bound!"); } - const auto item = dynamic_cast(m_widgets.at(index).get()); + const auto item = utils::is_child_class(m_widgets.at(index).get()); if (item == nullptr) { throw std::runtime_error("Invalid get of FocusLayout item!"); } - return item; + return item.value(); } private: - Widget::EventHandleResult handle_focus_change_button_events(const SDL_Event& event); + Widget::EventHandleResult handle_focus_change_button_events( + const std::shared_ptr& input_manager, + const SDL_Event& event + ); protected: [[nodiscard]] virtual Layout get_layout_for_index(u32 index) = 0; - Widget::EventHandleResult handle_focus_change_events(const SDL_Event& event, const Window* window); + Widget::EventHandleResult + handle_focus_change_events(const std::shared_ptr& input_manager, const SDL_Event& event); [[nodiscard]] helper::optional handle_event_result(const helper::optional& result, Widget* widget); diff --git a/src/ui/layouts/grid_layout.cpp b/src/ui/layouts/grid_layout.cpp index 0eddd1a6..f0060871 100644 --- a/src/ui/layouts/grid_layout.cpp +++ b/src/ui/layouts/grid_layout.cpp @@ -29,39 +29,35 @@ void ui::GridLayout::render(const ServiceProvider& service_provider) const { } } -ui::Widget::EventHandleResult ui::GridLayout::handle_event( - const SDL_Event& event, - const Window* window -) // NOLINT(readability-function-cognitive-complexity) -{ - Widget::EventHandleResult handled = handle_focus_change_events(event, window); +ui::Widget::EventHandleResult +ui::GridLayout::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + Widget::EventHandleResult handled = handle_focus_change_events(input_manager, event); if (handled) { return handled; } - if (utils::device_supports_clicks()) { - - if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Any)) { - - for (auto& widget : m_widgets) { - const auto layout = widget->layout(); - if (not handled and utils::is_event_in(window, event, layout.get_rect())) { - if (const auto event_result = widget->handle_event(event, window); event_result) { - handled = { true, handle_event_result(event_result.get_additional(), widget.get()) }; - continue; - } - } else { - const auto hoverable = as_hoverable(widget.get()); - if (hoverable.has_value()) { - hoverable.value()->on_unhover(); - } + + if (const auto point_event = input_manager->get_pointer_event(event); point_event.has_value()) { + + for (auto& widget : m_widgets) { + const auto layout = widget->layout(); + if (not handled and point_event->is_in(layout.get_rect())) { + if (const auto event_result = widget->handle_event(input_manager, event); event_result) { + handled = { true, handle_event_result(event_result.get_additional(), widget.get()) }; + continue; + } + } else { + const auto hoverable = as_hoverable(widget.get()); + if (hoverable.has_value()) { + hoverable.value()->on_unhover(); } } - return handled; } + return handled; } + return handled; } diff --git a/src/ui/layouts/grid_layout.hpp b/src/ui/layouts/grid_layout.hpp index e18463be..15aa41db 100644 --- a/src/ui/layouts/grid_layout.hpp +++ b/src/ui/layouts/grid_layout.hpp @@ -28,8 +28,7 @@ namespace ui { void render(const ServiceProvider& service_provider) const override; Widget::EventHandleResult - handle_event(const SDL_Event& event, const Window* window) // NOLINT(readability-function-cognitive-complexity) - override; + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; private: diff --git a/src/ui/layouts/scroll_layout.cpp b/src/ui/layouts/scroll_layout.cpp index 2a91f2c8..a769deaa 100644 --- a/src/ui/layouts/scroll_layout.cpp +++ b/src/ui/layouts/scroll_layout.cpp @@ -1,6 +1,7 @@ #include "scroll_layout.hpp" #include "helper/color_literals.hpp" +#include "input/input.hpp" ui::ItemSize::ItemSize(const u32 height, ItemSizeType type) : height{ height }, type{ type } { } @@ -106,119 +107,115 @@ void ui::ScrollLayout::render(const ServiceProvider& service_provider) const { } } -ui::Widget::EventHandleResult ui::ScrollLayout::handle_event( // NOLINT(readability-function-cognitive-complexity) - const SDL_Event& event, - const Window* window -) { +ui::Widget::EventHandleResult +ui::ScrollLayout::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { - Widget::EventHandleResult handled = handle_focus_change_events(event, window); + Widget::EventHandleResult handled = handle_focus_change_events(input_manager, event); if (handled) { auto_move_after_focus_change(); return handled; } - if (utils::device_supports_clicks()) { - const u32 total_widgets_height = m_widgets.empty() ? 0 : m_widgets.back()->layout().get_rect().bottom_right.y; + const u32 total_widgets_height = m_widgets.empty() ? 0 : m_widgets.back()->layout().get_rect().bottom_right.y; - const auto change_value_on_scroll = [&window, &event, total_widgets_height, this]() { - const auto& [_, y] = utils::get_raw_coordinates(window, event); + const auto change_value_on_scroll = [total_widgets_height, this](const input::PointerEventHelper& pointer_event) { + const auto& [_, y] = pointer_event.position(); - auto desired_scroll_height = 0; + auto desired_scroll_height = 0; - if (y <= static_cast(scrollbar_rect.top_left.y)) { - desired_scroll_height = 0; - } else if (y >= static_cast(scrollbar_rect.bottom_right.y)) { - // this is to high, but recalculate_sizes reset it to the highest possible value! - desired_scroll_height = static_cast(total_widgets_height); - } else { - - const double percentage = static_cast(y - scrollbar_rect.top_left.y) - / static_cast(scrollbar_rect.height()); + if (y <= static_cast(scrollbar_rect.top_left.y)) { + desired_scroll_height = 0; + } else if (y >= static_cast(scrollbar_rect.bottom_right.y)) { + // this is to high, but recalculate_sizes reset it to the highest possible value! + desired_scroll_height = static_cast(total_widgets_height); + } else { - // we want the final point to be in the middle, but desired_scroll_height expects the top position. - desired_scroll_height = static_cast( - static_cast(percentage * total_widgets_height) - scrollbar_rect.height() / 2 - ); - is_dragging = true; - } + const double percentage = + static_cast(y - scrollbar_rect.top_left.y) / static_cast(scrollbar_rect.height()); + // we want the final point to be in the middle, but desired_scroll_height expects the top position. + desired_scroll_height = + static_cast(static_cast(percentage * total_widgets_height) - scrollbar_rect.height() / 2); + is_dragging = true; + } - recalculate_sizes(desired_scroll_height); - }; + recalculate_sizes(desired_scroll_height); + }; - if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::ButtonDown)) { - // note: this behaviour is intentional, namely, clicking into the scroll slider doesn't move it, it just "grabs" it for dragging - if (utils::is_event_in(window, event, scrollbar_mover_rect)) { - is_dragging = true; - handled = true; - } else if (utils::is_event_in(window, event, scrollbar_rect)) { + const auto pointer_event = input_manager->get_pointer_event(event); - change_value_on_scroll(); - handled = true; - } + if (pointer_event == input::PointerEvent::PointerDown) { + // note: this behaviour is intentional, namely, clicking into the scroll slider doesn't move it, it just "grabs" it for dragging + if (pointer_event->is_in(scrollbar_mover_rect)) { + is_dragging = true; + handled = true; + } else if (pointer_event->is_in(scrollbar_rect)) { - } else if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::ButtonUp)) { - is_dragging = false; + change_value_on_scroll(pointer_event.value()); handled = true; + } - } else if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Motion)) { + } else if (pointer_event == input::PointerEvent::PointerUp) { + is_dragging = false; + handled = true; - if (is_dragging) { + } else if (pointer_event == input::PointerEvent::Motion) { - change_value_on_scroll(); - handled = true; - } + if (is_dragging) { - //TODO: support touch screen scrolling too! - } else if (event.type == SDL_MOUSEWHEEL) { + change_value_on_scroll(pointer_event.value()); + handled = true; + } - // attention the mouse direction changes (it's called natural scrolling on macos/ windows / linux) are not detected by sdl until restart, and here we use the correct scroll behaviour, as the user configured the mouse in it's OS - const bool direction_is_down = - event.wheel.direction == SDL_MOUSEWHEEL_NORMAL ? event.wheel.y < 0 : event.wheel.y > 0; + //TODO(Totto): support touch screen scrolling too, factor this out into the input manager + } else if (event.type == SDL_MOUSEWHEEL) { + // attention the mouse direction changes (it's called natural scrolling on macos/ windows / linux) are not detected by sdl until restart, and here we use the correct scroll behaviour, as the user configured the mouse in it's OS + const bool direction_is_down = + event.wheel.direction == SDL_MOUSEWHEEL_NORMAL ? event.wheel.y < 0 : event.wheel.y > 0; - auto desired_scroll_height = 0; - if (direction_is_down) { - desired_scroll_height = static_cast(m_viewport.top_left.y + m_step_size); - } else { - desired_scroll_height = static_cast(m_viewport.top_left.y - m_step_size); - } + auto desired_scroll_height = 0; - recalculate_sizes(desired_scroll_height); - handled = true; + if (direction_is_down) { + desired_scroll_height = static_cast(m_viewport.top_left.y + m_step_size); + } else { + desired_scroll_height = static_cast(m_viewport.top_left.y - m_step_size); } - if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Any)) { - - const auto offset_distance = main_rect.top_left.cast() - m_viewport.top_left.cast(); - for (auto& widget : m_widgets) { - const auto& layout_rect = widget->layout().get_rect(); - const auto& offset_rect = (layout_rect.cast()) >> offset_distance; - - if (not handled and utils::is_event_in(window, event, main_rect) - and utils::is_event_in(window, event, offset_rect.cast())) { - const auto offset_event = utils::offset_event(window, event, -offset_distance); - if (const auto event_result = widget->handle_event(offset_event, window); event_result) { - handled = { true, handle_event_result(event_result.get_additional(), widget.get()) }; - continue; - } - } else { - const auto hoverable = as_hoverable(widget.get()); - if (hoverable.has_value()) { - hoverable.value()->on_unhover(); - } + recalculate_sizes(desired_scroll_height); + handled = true; + } + + if (pointer_event.has_value()) { + + const auto offset_distance = main_rect.top_left.cast() - m_viewport.top_left.cast(); + for (auto& widget : m_widgets) { + const auto& layout_rect = widget->layout().get_rect(); + const auto& offset_rect = (layout_rect.cast()) >> offset_distance; + + if (not handled and pointer_event->is_in(main_rect) and pointer_event->is_in(offset_rect)) { + const auto offset_event = input_manager->offset_pointer_event(event, -offset_distance); + if (const auto event_result = widget->handle_event(input_manager, offset_event); event_result) { + handled = { true, handle_event_result(event_result.get_additional(), widget.get()) }; + continue; + } + } else { + const auto hoverable = as_hoverable(widget.get()); + if (hoverable.has_value()) { + hoverable.value()->on_unhover(); } } - - return handled; } + + return handled; } + return handled; } diff --git a/src/ui/layouts/scroll_layout.hpp b/src/ui/layouts/scroll_layout.hpp index 502e71fc..3f175da7 100644 --- a/src/ui/layouts/scroll_layout.hpp +++ b/src/ui/layouts/scroll_layout.hpp @@ -66,9 +66,8 @@ namespace ui { void render(const ServiceProvider& service_provider) const override; Widget::EventHandleResult - handle_event(const SDL_Event& event, const Window* window) // NOLINT(readability-function-cognitive-complexity) - override; - //TODO: with some template paramater and magic make this an option in the base class, so that only get_layout_for_new needs to be overwritten! + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; + //TODO(Totto): with some template paramater and magic make this an option in the base class, so that only get_layout_for_new needs to be overwritten! template u32 add(ItemSize size, Args... args) { diff --git a/src/ui/layouts/tile_layout.cpp b/src/ui/layouts/tile_layout.cpp index d5ce6436..dcfa4142 100644 --- a/src/ui/layouts/tile_layout.cpp +++ b/src/ui/layouts/tile_layout.cpp @@ -8,40 +8,38 @@ void ui::TileLayout::render(const ServiceProvider& service_provider) const { } } -ui::Widget::EventHandleResult ui::TileLayout::handle_event( - const SDL_Event& event, - const Window* window -) // NOLINT(readability-function-cognitive-complexity) -{ - Widget::EventHandleResult handled = handle_focus_change_events(event, window); +ui::Widget::EventHandleResult +ui::TileLayout::handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) { + Widget::EventHandleResult handled = handle_focus_change_events(input_manager, event); if (handled) { return handled; } - if (utils::device_supports_clicks()) { - - if (utils::event_is_click_event(event, utils::CrossPlatformClickEvent::Any)) { - - for (auto& widget : m_widgets) { - const auto layout = widget->layout(); - if (not handled and utils::is_event_in(window, event, layout.get_rect())) { - if (const auto event_result = widget->handle_event(event, window); event_result) { - handled = { true, handle_event_result(event_result.get_additional(), widget.get()) }; - continue; - } - } else { - const auto hoverable = ui::as_hoverable(widget.get()); - if (hoverable.has_value()) { - hoverable.value()->on_unhover(); - } + + const auto pointer_event = input_manager->get_pointer_event(event); + + if (pointer_event.has_value()) { + + for (auto& widget : m_widgets) { + const auto layout = widget->layout(); + if (not handled and pointer_event.value().is_in(layout.get_rect())) { + if (const auto event_result = widget->handle_event(input_manager, event); event_result) { + handled = { true, handle_event_result(event_result.get_additional(), widget.get()) }; + continue; + } + } else { + const auto hoverable = ui::as_hoverable(widget.get()); + if (hoverable.has_value()) { + hoverable.value()->on_unhover(); } } - - return handled; } + + return handled; } + return handled; } @@ -53,8 +51,8 @@ ui::Widget::EventHandleResult ui::TileLayout::handle_event( const auto start_point = layout().get_rect().top_left; - u32 x = start_point.x + margin.first; - u32 y = start_point.y + margin.second; + u32 x_pos = start_point.x + margin.first; + u32 y_pos = start_point.y + margin.second; u32 width = layout().get_rect().width() - (margin.first * 2); u32 height = layout().get_rect().height() - (margin.second * 2); @@ -69,7 +67,7 @@ ui::Widget::EventHandleResult ui::TileLayout::handle_event( : static_cast(width * steps.at(index)) - gap.get_margin() / 2); width = current_end - previous_start; - x += previous_start; + x_pos += previous_start; } else { const auto previous_start = index == 0 ? 0 : static_cast(height * steps.at(index - 1)) + gap.get_margin() / 2; @@ -81,13 +79,13 @@ ui::Widget::EventHandleResult ui::TileLayout::handle_event( : static_cast(height * steps.at(index)) - gap.get_margin() / 2); height = current_end - previous_start; - y += previous_start; + y_pos += previous_start; } return AbsolutLayout{ - x, - y, + x_pos, + y_pos, width, height, }; diff --git a/src/ui/layouts/tile_layout.hpp b/src/ui/layouts/tile_layout.hpp index e2e4eac0..89e103c0 100644 --- a/src/ui/layouts/tile_layout.hpp +++ b/src/ui/layouts/tile_layout.hpp @@ -52,8 +52,7 @@ namespace ui { void render(const ServiceProvider& service_provider) const override; Widget::EventHandleResult - handle_event(const SDL_Event& event, const Window* window) // NOLINT(readability-function-cognitive-complexity) - override; + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) override; private: [[nodiscard]] Layout get_layout_for_index(u32 index) override; diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp index 98d3d904..b3db6555 100644 --- a/src/ui/widget.cpp +++ b/src/ui/widget.cpp @@ -1,21 +1,12 @@ #include "widget.hpp" +#include "helper/utils.hpp" [[nodiscard]] helper::optional ui::as_focusable(ui::Widget* const widget) { - auto* const focusable = dynamic_cast(widget); - if (focusable == nullptr) { - return helper::nullopt; - } - - return focusable; + return utils::is_child_class(widget); } [[nodiscard]] helper::optional ui::as_hoverable(ui::Widget* const widget) { - auto* const hoverable = dynamic_cast(widget); - if (hoverable == nullptr) { - return helper::nullopt; - } - - return hoverable; + return utils::is_child_class(widget); } diff --git a/src/ui/widget.hpp b/src/ui/widget.hpp index 0052667d..92cd6a6a 100644 --- a/src/ui/widget.hpp +++ b/src/ui/widget.hpp @@ -55,7 +55,8 @@ namespace ui { // do nothing } virtual void render(const ServiceProvider& service_provider) const = 0; - [[nodiscard]] virtual EventHandleResult handle_event(const SDL_Event& event, const Window* window) = 0; + [[nodiscard]] virtual EventHandleResult + handle_event(const std::shared_ptr& input_manager, const SDL_Event& event) = 0; }; [[nodiscard]] helper::optional as_focusable(Widget* widget); diff --git a/tests/core/color.cpp b/tests/core/color.cpp index ada3fce9..4f274ba5 100644 --- a/tests/core/color.cpp +++ b/tests/core/color.cpp @@ -1,5 +1,7 @@ #include "helper/color.hpp" +#include "helper/magic_enum_wrapper.hpp" +#include "utils/helper.hpp" #include #include @@ -7,18 +9,18 @@ #include namespace { - using ForeachCallback = std::function; + using ForeachCallback = std::function; void foreach_loop(const ForeachCallback& callback) { - u8 r{ 0 }; - u8 g{ 0 }; - u8 b{ 0 }; + u8 red{ 0 }; + u8 green{ 0 }; + u8 blue{ 0 }; do { // NOLINT(cppcoreguidelines-avoid-do-while) do { // NOLINT(cppcoreguidelines-avoid-do-while) do { // NOLINT(cppcoreguidelines-avoid-do-while) - callback(r, g, b); - } while (r++ != 255); - } while (g++ != 255); - } while (b++ != 255); + callback(red, green, blue); + } while (red++ != 255); + } while (green++ != 255); + } while (blue++ != 255); } } // namespace @@ -38,54 +40,43 @@ void PrintTo(const HSVColor& color, std::ostream* os) { *os << color.to_string(); } +namespace color { -// make helper::expected printable -template -void PrintTo(const helper::expected& value, std::ostream* os) { - if (value.has_value()) { - *os << "Value: " << ::testing::PrintToString(value.value()); - } else { - *os << "Error: " << ::testing::PrintToString(value.error()); + void PrintTo(const SerializeMode& value, std::ostream* os) { + *os << magic_enum::enum_name(value); } -} - -MATCHER(ExpectedHasValue, "expected has value") { - return arg.has_value(); } -MATCHER(ExpectedHasError, "expected has error") { - return not arg.has_value(); -} TEST(Color, DefaultConstruction) { - const auto c1 = Color{}; - const auto c2 = Color{ 0, 0, 0, 0 }; - ASSERT_EQ(c1, c2); + const auto color1 = Color{}; + const auto color2 = Color{ 0, 0, 0, 0 }; + ASSERT_EQ(color1, color2); } TEST(Color, ConstructorProperties) { - foreach_loop([](u8 r, u8 g, u8 b) { - const auto c1 = Color{ r, g, b }; - const auto c2 = Color{ r, g, b, 0xFF }; - ASSERT_EQ(c1, c2); + foreach_loop([](u8 red, u8 green, u8 blue) { + const auto color1 = Color{ red, green, blue }; + const auto color2 = Color{ red, green, blue, 0xFF }; + ASSERT_EQ(color1, color2); }); } TEST(Color, FromStringValid) { const std::vector> valid_values{ - { "#FFAA33", { Color{ 0xFF, 0xAA, 0x33 }, color::SerializeMode::Hex, false }}, - { "#FF00FF00", { Color{ 0xFF, 0x00, 0xFF, 0x00 }, color::SerializeMode::Hex, true }}, - { "rgb(0,0,0)", { Color{ 0, 0, 0 }, color::SerializeMode::RGB, false }}, - { "rgba(0,0,0,0)", { Color{ 0, 0, 0, 0 }, color::SerializeMode::RGB, true }}, - { "hsv(0,0,0)", { HSVColor{ 0, 0, 0 }, color::SerializeMode::HSV, false }}, - { "hsva(340,0,0.5,0)", { HSVColor{ 340, 0, 0.5, 0 }, color::SerializeMode::HSV, true }}, - { "#ffaa33", { Color{ 0xff, 0xaa, 0x33 }, color::SerializeMode::Hex, false }}, - {"hsv(0, 0.00_000_000_1, 0)", { HSVColor{ 0, 0.000000001, 0 }, color::SerializeMode::HSV, false }}, - { "hsva(0, 0, 0, 0xFF)", { HSVColor{ 0, 0, 0, 0xFF }, color::SerializeMode::HSV, true }}, - { "rgba(0, 0xFF, 0, 255)", { Color{ 0, 0xFF, 0, 255 }, color::SerializeMode::RGB, true }}, - { "rgba(0, 0xFF, 0, 1_0_0)", { Color{ 0, 0xFF, 0, 100 }, color::SerializeMode::RGB, true }}, + { "#FFAA33", { Color{ 0xFF, 0xAA, 0x33 }, color::SerializeMode::Hex, false } }, + { "#FF00FF00", { Color{ 0xFF, 0x00, 0xFF, 0x00 }, color::SerializeMode::Hex, true } }, + { "rgb(0,0,0)", { Color{ 0, 0, 0 }, color::SerializeMode::RGB, false } }, + { "rgba(0,0,0,0)", { Color{ 0, 0, 0, 0 }, color::SerializeMode::RGB, true } }, + { "hsv(0,0,0)", { HSVColor{ 0, 0, 0 }, color::SerializeMode::HSV, false } }, + { "hsva(340,0,0.5,0)", { HSVColor{ 340, 0, 0.5, 0 }, color::SerializeMode::HSV, true } }, + { "#ffaa33", { Color{ 0xff, 0xaa, 0x33 }, color::SerializeMode::Hex, false } }, + { "hsv(0, 0.00_000_000_1, 0)", { HSVColor{ 0, 0.000000001, 0 }, color::SerializeMode::HSV, false } }, + { "hsva(0, 0, 0, 0xFF)", { HSVColor{ 0, 0, 0, 0xFF }, color::SerializeMode::HSV, true } }, + { "rgba(0, 0xFF, 0, 255)", { Color{ 0, 0xFF, 0, 255 }, color::SerializeMode::RGB, true } }, + { "rgba(0, 0xFF, 0, 1_0_0)", { Color{ 0, 0xFF, 0, 100 }, color::SerializeMode::RGB, true } }, }; for (const auto& [valid_string, expected_result] : valid_values) { @@ -104,64 +95,64 @@ TEST(Color, FromStringValid) { TEST(Color, FromStringInvalid) { const std::vector> invalid_strings{ - { "", "not enough data to determine the literal type"}, - { "#44", "Unrecognized HEX literal"}, - { "#Z", "Unrecognized HEX literal"}, - { "#ZZFF", "Unrecognized HEX literal"}, - { "u", "Unrecognized color literal"}, - { "#IIFFFFFF", "the input must be a valid hex character"}, - { "#FFIIFFFF", "the input must be a valid hex character"}, - { "#FFFFIIFF", "the input must be a valid hex character"}, - { "#FFFFFFII", "the input must be a valid hex character"}, - { "#FFFF4T", "the input must be a valid hex character"}, - { "#0000001", "Unrecognized HEX literal"}, - { "hsl(0,0,0)", "Unrecognized HSV literal"}, - { "rgg(0,0,0)", "Unrecognized RGB literal"}, - { "hsva(9,9,9)", "s has to be in range 0.0 - 1.0"}, - { "hsva(9,9,9,10212)", "s has to be in range 0.0 - 1.0"}, - { "hsv(-1,0,0)", "the input must be a valid decimal character"}, - { "hsv(404040,0,0)", "h has to be in range 0.0 - 360.0"}, - { "hsv(0,1.4,0)", "s has to be in range 0.0 - 1.0"}, - { "hsv(0,0,1.7)", "v has to be in range 0.0 - 1.0"}, - { "hsva(1321.4,0,0,0)", "h has to be in range 0.0 - 360.0"}, - { "hsva(0,1.4,0,0)", "s has to be in range 0.0 - 1.0"}, - { "hsva(0,0,1.7,0)", "v has to be in range 0.0 - 1.0"}, - { "hsva(0, 0, 0, 256)", "a has to be in range 0 - 255"}, - { "hsv(0,0,1.7.8)", "only one comma allowed"}, - { "hsv(0", "input ended too early"}, - { "rgba(0, 0xFFF, 0, 255)", "g has to be in range 0 - 255"}, - { "rgba(0, 0xFF, 0)", "expected ','"}, - { "rgb(0, 0xFF, 0, 255)", "expected ')'"}, - { "rgba(0, 0xFF, 0, 256)", "a has to be in range 0 - 255"}, - { "rgba(0", "input ended too early"}, - { "rgba(0, 0xFF, 0, 4_294_967_296)", "overflow detected"}, - { "rgba(0, 0xFF, 0, 4_294_967_300)", "overflow detected"}, - {"rgba(0, 0xFF, 0, 121_123_124_294_967_300)", "overflow detected"}, - { "rgb(256,0,0)", "r has to be in range 0 - 255"}, - { "rgb(0)", "expected ','"}, - { "rgb(0,256,0)", "g has to be in range 0 - 255"}, - { "rgb(0,0)", "expected ','"}, - { "rgb(0,0,256)", "b has to be in range 0 - 255"}, - { "rgb(0,0,0,", "expected ')'"}, - { "rgb(0,0,255) ", "expected end of string"}, - { "rgba(256,0,0,0)", "r has to be in range 0 - 255"}, - { "rgba(0)", "expected ','"}, - { "rgba(0,256,0,0)", "g has to be in range 0 - 255"}, - { "rgba(0,0)", "expected ','"}, - { "rgba(0,0,256,0)", "b has to be in range 0 - 255"}, - { "rgba(0,0,0)", "expected ','"}, - { "rgba(0,0,0,256)", "a has to be in range 0 - 255"}, - { "rgba(0,0,0,0,", "expected ')'"}, - { "rgba(0,0,0,255) ", "expected end of string"}, - { "hsv(0)", "expected ','"}, - { "hsv(0,0)", "expected ','"}, - { "hsv(0,0,0,", "expected ')'"}, - { "hsv(0,0,0) ", "expected end of string"}, - { "hsva(0)", "expected ','"}, - { "hsva(0,0)", "expected ','"}, - { "hsva(0,0,0)", "expected ','"}, - { "hsva(0,0,0,0,", "expected ')'"}, - { "hsva(0,0,0,255) ", "expected end of string"}, + { "", "not enough data to determine the literal type" }, + { "#44", "Unrecognized HEX literal" }, + { "#Z", "Unrecognized HEX literal" }, + { "#ZZFF", "Unrecognized HEX literal" }, + { "u", "Unrecognized color literal" }, + { "#IIFFFFFF", "the input must be a valid hex character" }, + { "#FFIIFFFF", "the input must be a valid hex character" }, + { "#FFFFIIFF", "the input must be a valid hex character" }, + { "#FFFFFFII", "the input must be a valid hex character" }, + { "#FFFF4T", "the input must be a valid hex character" }, + { "#0000001", "Unrecognized HEX literal" }, + { "hsl(0,0,0)", "Unrecognized HSV literal" }, + { "rgg(0,0,0)", "Unrecognized RGB literal" }, + { "hsva(9,9,9)", "s has to be in range 0.0 - 1.0" }, + { "hsva(9,9,9,10212)", "s has to be in range 0.0 - 1.0" }, + { "hsv(-1,0,0)", "the input must be a valid decimal character" }, + { "hsv(404040,0,0)", "h has to be in range 0.0 - 360.0" }, + { "hsv(0,1.4,0)", "s has to be in range 0.0 - 1.0" }, + { "hsv(0,0,1.7)", "v has to be in range 0.0 - 1.0" }, + { "hsva(1321.4,0,0,0)", "h has to be in range 0.0 - 360.0" }, + { "hsva(0,1.4,0,0)", "s has to be in range 0.0 - 1.0" }, + { "hsva(0,0,1.7,0)", "v has to be in range 0.0 - 1.0" }, + { "hsva(0, 0, 0, 256)", "a has to be in range 0 - 255" }, + { "hsv(0,0,1.7.8)", "only one comma allowed" }, + { "hsv(0", "input ended too early" }, + { "rgba(0, 0xFFF, 0, 255)", "g has to be in range 0 - 255" }, + { "rgba(0, 0xFF, 0)", "expected ','" }, + { "rgb(0, 0xFF, 0, 255)", "expected ')'" }, + { "rgba(0, 0xFF, 0, 256)", "a has to be in range 0 - 255" }, + { "rgba(0", "input ended too early" }, + { "rgba(0, 0xFF, 0, 4_294_967_296)", "overflow detected" }, + { "rgba(0, 0xFF, 0, 4_294_967_300)", "overflow detected" }, + { "rgba(0, 0xFF, 0, 121_123_124_294_967_300)", "overflow detected" }, + { "rgb(256,0,0)", "r has to be in range 0 - 255" }, + { "rgb(0)", "expected ','" }, + { "rgb(0,256,0)", "g has to be in range 0 - 255" }, + { "rgb(0,0)", "expected ','" }, + { "rgb(0,0,256)", "b has to be in range 0 - 255" }, + { "rgb(0,0,0,", "expected ')'" }, + { "rgb(0,0,255) ", "expected end of string" }, + { "rgba(256,0,0,0)", "r has to be in range 0 - 255" }, + { "rgba(0)", "expected ','" }, + { "rgba(0,256,0,0)", "g has to be in range 0 - 255" }, + { "rgba(0,0)", "expected ','" }, + { "rgba(0,0,256,0)", "b has to be in range 0 - 255" }, + { "rgba(0,0,0)", "expected ','" }, + { "rgba(0,0,0,256)", "a has to be in range 0 - 255" }, + { "rgba(0,0,0,0,", "expected ')'" }, + { "rgba(0,0,0,255) ", "expected end of string" }, + { "hsv(0)", "expected ','" }, + { "hsv(0,0)", "expected ','" }, + { "hsv(0,0,0,", "expected ')'" }, + { "hsv(0,0,0) ", "expected end of string" }, + { "hsva(0)", "expected ','" }, + { "hsva(0,0)", "expected ','" }, + { "hsva(0,0,0)", "expected ','" }, + { "hsva(0,0,0,0,", "expected ')'" }, + { "hsva(0,0,0,255) ", "expected end of string" }, }; for (const auto& [invalid_string, error_message] : invalid_strings) { @@ -173,37 +164,37 @@ TEST(Color, FromStringInvalid) { TEST(HSVColor, DefaultConstruction) { - const auto c1 = HSVColor{}; - const auto c2 = HSVColor{ 0, 0, 0, 0 }; - ASSERT_EQ(c1, c2); + const auto color1 = HSVColor{}; + const auto color2 = HSVColor{ 0, 0, 0, 0 }; + ASSERT_EQ(color1, color2); } TEST(HSVColor, ConstructorProperties) { const std::vector> values{ - { 0.0, 0.0, 0.0}, - {360.0, 0.0, 0.0}, - {360.0, 1.0, 0.0}, - {360.0, 1.0, 1.0}, - { 57.0, 0.6, 0.8} + { 0.0, 0.0, 0.0 }, + { 360.0, 0.0, 0.0 }, + { 360.0, 1.0, 0.0 }, + { 360.0, 1.0, 1.0 }, + { 57.0, 0.6, 0.8 } }; for (const auto& [h, s, v] : values) { - const auto c1 = HSVColor{ h, s, v }; - const auto c2 = HSVColor{ h, s, v, 0xFF }; - ASSERT_EQ(c1, c2); + const auto color1 = HSVColor{ h, s, v }; + const auto color2 = HSVColor{ h, s, v, 0xFF }; + ASSERT_EQ(color1, color2); } } TEST(HSVColor, InvalidConstructors) { const std::vector> invalid_values{ - { -1.0, 0.0, 0.0}, - {360.0, -1.0, 0.0}, - {360.0, 1.0, -1.0}, - {460.0, 1.0, 1.0}, - { 57.0, 2.6, 0.8}, - { 57.0, 1.6, 3.8} + { -1.0, 0.0, 0.0 }, + { 360.0, -1.0, 0.0 }, + { 360.0, 1.0, -1.0 }, + { 460.0, 1.0, 1.0 }, + { 57.0, 2.6, 0.8 }, + { 57.0, 1.6, 3.8 } }; for (const auto& [h, s, v] : invalid_values) { @@ -219,16 +210,16 @@ TEST(HSVColor, InvalidConstructors) { #endif -TEST(ColorConversion, HSV_to_RGB_to_HSV) { //NOLINT(readability-function-cognitive-complexity) +TEST(ColorConversion, HSVtoRGBtoHSV) { #if COLOR_TEST_MODE == 0 const std::vector colors{ - HSVColor{ 2, 0.3, 0.6}, - HSVColor{ 82, 0.3, 0.6}, - HSVColor{142, 0.3, 0.6}, - HSVColor{192, 0.3, 0.6}, - HSVColor{252, 0.3, 0.6}, - HSVColor{312, 0.3, 0.6}, + HSVColor{ 2, 0.3, 0.6 }, + HSVColor{ 82, 0.3, 0.6 }, + HSVColor{ 142, 0.3, 0.6 }, + HSVColor{ 192, 0.3, 0.6 }, + HSVColor{ 252, 0.3, 0.6 }, + HSVColor{ 312, 0.3, 0.6 }, }; for (const auto& original_color : colors) { @@ -265,16 +256,16 @@ TEST(ColorConversion, HSV_to_RGB_to_HSV) { //NOLINT(readability-function-cogniti } -TEST(ColorConversion, RGG_to_HSV_to_RGB) { //NOLINT(readability-function-cognitive-complexity) +TEST(ColorConversion, RGGtoHSVtoRGB) { #if COLOR_TEST_MODE == 0 const std::vector colors{ - Color{ 0, 0, 0}, - Color{180, 135, 223}, - Color{ 12, 34, 130}, - Color{ 79, 85, 20}, - Color{155, 174, 2}, - Color{243, 32, 34}, + Color{ 0, 0, 0 }, + Color{ 180, 135, 223 }, + Color{ 12, 34, 130 }, + Color{ 79, 85, 20 }, + Color{ 155, 174, 2 }, + Color{ 243, 32, 34 }, }; for (const auto& original_color : colors) { diff --git a/tests/graphics/meson.build b/tests/graphics/meson.build new file mode 100644 index 00000000..50a19049 --- /dev/null +++ b/tests/graphics/meson.build @@ -0,0 +1,3 @@ + + +graphics_test_src += files('sdl_key.cpp') diff --git a/tests/graphics/sdl_key.cpp b/tests/graphics/sdl_key.cpp new file mode 100644 index 00000000..5d9e2377 --- /dev/null +++ b/tests/graphics/sdl_key.cpp @@ -0,0 +1,96 @@ + +#include "manager/sdl_key.hpp" +#include "helper/expected.hpp" +#include "utils/helper.hpp" + +#include +#include +#include +#include + + +namespace sdl { + + // make keys printable + void PrintTo(const Key& key, std::ostream* os) { + *os << key.to_string(); + } + + + std::ostream& operator<<(std::ostream& os, const Key& value) { + os << value.to_string(); + return os; + } +} // namespace sdl + +TEST(SDLKey, SimpleComparision) { + const auto key1 = sdl::Key{ SDLK_k }; + + ASSERT_EQ(key1.to_string(), "K"); + ASSERT_EQ(key1.has_modifier(sdl::Modifier::ALT), false); +} + +TEST(SDLKey, FromString) { + + const std::vector, std::string>> strings{ + { sdl::Key{ SDLK_1, { sdl::Modifier::CTRL } },"Ctrl + 1" }, + { sdl::Key{ SDLK_1, { sdl::Modifier::LSHIFT } }, "Shift-L + 1" }, + { sdl::Key{ SDLK_1, { sdl::Modifier::RSHIFT } }, "Shift-R + 1" }, + { helper::unexpected{ "Not a valid modifier: 'ShiftL'" }, "ShiftL + 1" }, + { helper::unexpected{ "Duplicate modifier: 'Shift'" }, "Shift + Shift + 1" }, + { helper::unexpected{ "No key but only modifiers given" }, "Shift" }, + { helper::unexpected{ "Empty token" }, "" }, + { helper::unexpected{ "Empty token" }, " + \t " }, + { sdl::Key{ SDLK_1 }, "1" }, + { sdl::Key{ SDLK_1, { sdl::Modifier::CTRL, sdl::Modifier::ALT, sdl::Modifier::SHIFT, sdl::Modifier::GUI } }, + "Shift + Alt + Ctrl + Gui + 1" }, + { sdl::Key{ SDLK_1, { sdl::Modifier::LCTRL } }, "Ctrl-L + 1" }, + { sdl::Key{ SDLK_1, { sdl::Modifier::RCTRL } }, "Ctrl-R + 1" }, + { sdl::Key{ SDLK_1, { sdl::Modifier::LALT } }, "Alt-L + 1" }, + { sdl::Key{ SDLK_1, { sdl::Modifier::RALT } }, "Alt-R + 1" }, + { sdl::Key{ SDLK_1, { sdl::Modifier::LGUI } }, "Gui-L + 1" }, + { sdl::Key{ SDLK_1, { sdl::Modifier::RGUI } }, "Gui-R + 1" }, + }; + + for (const auto& [correct, str] : strings) { + + const auto parsed = sdl::Key::from_string(str); + + if (correct.has_value()) { + ASSERT_THAT(parsed, ExpectedHasValue()) << "Input was: " << str; + ASSERT_EQ(correct.value(), parsed.value()); + } else { + ASSERT_THAT(parsed, ExpectedHasError()) << "Input was: " << str; + ASSERT_EQ(correct.error(), parsed.error()); + } + } +} + + +TEST(SDLKey, ToString) { + + const std::vector keys{ + sdl::Key{ SDLK_1, { sdl::Modifier::CTRL } }, + sdl::Key{ SDLK_1, { sdl::Modifier::LSHIFT } }, + sdl::Key{ SDLK_1, { sdl::Modifier::RSHIFT } }, + sdl::Key{ SDLK_1 }, + sdl::Key{ SDLK_1, { sdl::Modifier::CTRL, sdl::Modifier::ALT, sdl::Modifier::SHIFT, sdl::Modifier::GUI } }, + sdl::Key{ SDLK_1, { sdl::Modifier::LCTRL } }, + sdl::Key{ SDLK_1, { sdl::Modifier::RCTRL } }, + sdl::Key{ SDLK_1, { sdl::Modifier::LALT } }, + sdl::Key{ SDLK_1, { sdl::Modifier::RALT } }, + sdl::Key{ SDLK_1, { sdl::Modifier::LGUI } }, + sdl::Key{ SDLK_1, { sdl::Modifier::RGUI } }, + }; + + for (const auto& key : keys) { + + const auto keys_string = key.to_string(); + + const auto parsed = sdl::Key::from_string(keys_string); + + + ASSERT_THAT(parsed, ExpectedHasValue()) << "Input was: " << key; + ASSERT_EQ(parsed.value(), key); + } +} diff --git a/tests/meson.build b/tests/meson.build index 91db1638..841f2fdd 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,4 +1,3 @@ - if meson.is_cross_build() error('Tests are not supported for cross builds (atm)') endif @@ -10,13 +9,13 @@ extra_src = files('entry.cpp') test_src = [] test_inc_dirs = [] core_test_src = [] +graphics_test_src = [] test_deps += dependency('gtest') test_deps += dependency('gmock') - - subdir('core') +subdir('graphics') subdir('utils') test_inc_dirs += include_directories('.') @@ -43,3 +42,24 @@ test( protocol: 'gtest', workdir: meson.project_source_root() / 'tests' / 'files', ) + +graphics_tests = executable( + 'graphics_tests', + extra_src, + test_src, + graphics_test_src, + include_directories: test_inc_dirs, + dependencies: [test_deps, liboopetris_graphics_dep], + override_options: { + 'warning_level': '3', + 'werror': true, + 'b_coverage': false, + }, +) + +test( + 'graphics_tests', + graphics_tests, + protocol: 'gtest', + workdir: meson.project_source_root() / 'tests' / 'files', +) diff --git a/tests/utils/helper.hpp b/tests/utils/helper.hpp new file mode 100644 index 00000000..15c3de0d --- /dev/null +++ b/tests/utils/helper.hpp @@ -0,0 +1,24 @@ + + +#pragma once + +#include "printer.hpp" + +#include + + +MATCHER(ExpectedHasValue, "expected has value") { + return arg.has_value(); +} + +MATCHER(ExpectedHasError, "expected has error") { + return not arg.has_value(); +} + +MATCHER(OptionalHasValue, "optional has value") { + return arg.has_value(); +} + +MATCHER(OptionalHasNoValue, "optional has no value") { + return not arg.has_value(); +} diff --git a/tests/utils/meson.build b/tests/utils/meson.build index aac4e48f..95435d7f 100644 --- a/tests/utils/meson.build +++ b/tests/utils/meson.build @@ -1,3 +1 @@ - - -test_src += files('files.cpp', 'files.hpp') +test_src += files('files.cpp', 'files.hpp', 'helper.hpp', 'printer.hpp') diff --git a/tests/utils/printer.hpp b/tests/utils/printer.hpp new file mode 100644 index 00000000..216a57f9 --- /dev/null +++ b/tests/utils/printer.hpp @@ -0,0 +1,61 @@ + + +#pragma once + +#include "helper/expected.hpp" +#include "helper/optional.hpp" + +#include + + +namespace +#ifdef _USE_TL_EXPECTED + tl +#else + std +#endif +{ + + // make helper::expected printable + template + void PrintTo(const expected& value, std::ostream* os) { + if (value.has_value()) { + *os << ": " << ::testing::PrintToString(value.value()); + } else { + *os << ": " << ::testing::PrintToString(value.error()); + } + } + + template + std::ostream& operator<<(std::ostream& os, const expected& value) { + PrintTo(value); + return os; + } + +} // namespace tl + +namespace +#ifdef __USE_TL_OPTIONAL + tl +#else + std +#endif +{ + + // make helper::optional printable + template + void PrintTo(const optional& value, std::ostream* os) { //NOLINT(cert-dcl58-cpp) + if (value.has_value()) { + *os << ": " << ::testing::PrintToString(value.value()); + } else { + *os << ""; + } + } + + template + std::ostream& operator<<(std::ostream& os, const optional& value) { //NOLINT(cert-dcl58-cpp) + PrintTo(value); + return os; + } + +} // namespace tl diff --git a/tools/dependencies/meson.build b/tools/dependencies/meson.build index 5f14c07e..fad1774a 100644 --- a/tools/dependencies/meson.build +++ b/tools/dependencies/meson.build @@ -1,5 +1,3 @@ - - only_allow_native_libs = false if meson.is_cross_build() if host_machine.system() == 'switch' or host_machine.system() == '3ds' @@ -28,13 +26,12 @@ if meson.is_cross_build() endif endif - - sdl2_dep = dependency( 'sdl2', 'SDL2', allow_fallback: false, required: only_allow_native_libs, + version: '>=2.24.0', ) if sdl2_dep.found() @@ -46,11 +43,13 @@ else 'sdl2', required: true, default_options: {'test': false}, + version: '>=2.24.0', ) sdl2main_dep = dependency( 'sdl2main', required: true, fallback: 'sdl2', + version: '>=2.24.0', ) graphic_application_deps += sdl2main_dep @@ -101,7 +100,6 @@ else endif endif - sdl2_mixer_dep = dependency( 'sdl2_mixer', 'SDL2_mixer', @@ -118,8 +116,7 @@ fmt_use_header_only = false if ( meson.is_cross_build() - and (host_machine.system() == 'switch' - or host_machine.system() == '3ds') + and (host_machine.system() == 'switch' or host_machine.system() == '3ds') ) fmt_use_header_only = true # clang with libc++ creates some really long and confusing linker errors, so just use the header only library @@ -127,7 +124,6 @@ elif cpp.get_id() == 'clang' and build_with_libcpp fmt_use_header_only = true endif - if fmt_use_header_only fmt_header_only_dep = dependency( 'fmt_header_only', @@ -142,7 +138,6 @@ endif core_lib += {'deps': [core_lib.get('deps'), fmt_dep]} - spdlog_dep = dependency( 'spdlog', required: true, @@ -211,8 +206,6 @@ else message('Compiler support std::optional, using that') endif - - magic_enum_dep = dependency( 'magic_enum', required: true, @@ -225,11 +218,9 @@ core_lib += {'deps': [core_lib.get('deps'), argparse_dep]} online_multiplayer_supported = true - if ( meson.is_cross_build() - and (host_machine.system() == 'switch' - or host_machine.system() == '3ds') + and (host_machine.system() == 'switch' or host_machine.system() == '3ds') ) online_multiplayer_supported = false @@ -262,7 +253,6 @@ utf8cpp_dep = dependency( ) core_lib += {'deps': [core_lib.get('deps'), utf8cpp_dep]} - build_installer = get_option('build_installer') is_flatpak_build = false @@ -271,8 +261,8 @@ is_flatpak_build = false if build_installer if get_option('buildtype') != 'release' error( - 'buildtype needs to be \'release\', when building the installer, but was: ' + - get_option('buildtype'), + 'buildtype needs to be \'release\', when building the installer, but was: ' + + get_option('buildtype'), ) endif @@ -288,7 +278,8 @@ if build_installer message('Adding a windows installer target: \'windows_installer\'') else error( - 'unsuported system for building the installer: ' + host_machine.system(), + 'unsuported system for building the installer: ' + + host_machine.system(), ) endif @@ -302,8 +293,6 @@ if build_installer endif - - if is_flatpak_build app_name = 'com.github.mgerhold.OOPetris' core_lib += { @@ -314,11 +303,9 @@ if is_flatpak_build } endif - have_file_dialogs = false have_discord_sdk = false - nfde_dep = dependency( 'nativefiledialog-extended', required: not meson.is_cross_build(),