diff --git a/.gitignore b/.gitignore index d47312c..52bc70c 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,6 @@ build-old .obsidian -.vscode \ No newline at end of file +.vscode + +.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 806d6a9..be1c20c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ find_package(GTest REQUIRED) qt6_standard_project_setup(REQUIRES 6.7) +#add_compile_definitions(QT_QML_DEBUG) set(CMAKE_CXX_STANDARD 20) #set(QT_VERSION_MAJOR 6) @@ -60,11 +61,9 @@ set_source_files_properties(qml/GeneralSettings.qml PROPERTIES QT_QML_SINGLETON_ add_library(firelight_lib src/app/libretro/core.cpp - src/app/libretro/coreoption.cpp src/app/libretro/game.cpp src/app/emulation_manager.cpp src/app/db/sqlite_content_database.cpp - src/app/fps_multiplier.cpp src/app/input/controller.cpp src/app/input/controller_manager.cpp src/app/input/sdl_event_loop.cpp @@ -93,17 +92,22 @@ add_library(firelight_lib src/gui/PlatformInputListModel.cpp src/gui/gamepad_profile.cpp src/gui/gamepad_mapping.cpp - src/gui/gamepad_mapping.hpp src/gui/platform_list_model.cpp - src/gui/platform_list_model.hpp + src/app/libretro/core_configuration.cpp + src/app/libretro/core_configuration.hpp + src/app/emulator_config_manager.cpp + src/app/platform_metadata.hpp ) target_include_directories(firelight_lib PRIVATE ${SDL2_INCLUDE_DIRS} include) + +if (WIN32) + target_link_libraries(firelight_lib PUBLIC mingw32) +endif () + target_link_libraries(firelight_lib PUBLIC - mingw32 SDL2::SDL2 - OpenSSL::SSL - OpenSSL::Crypto + ${OPENSSL_LIBRARIES} z Qt6::Gui Qt6::Quick @@ -127,6 +131,12 @@ qt6_add_executable(firelight include/firelight/input_mapping.hpp ) +if (APPLE) + set_target_properties(firelight PROPERTIES + MACOSX_BUNDLE ON + ) +endif () + configure_file(${CMAKE_SOURCE_DIR}/qtquickcontrols2.conf ${CMAKE_BINARY_DIR}/qtquickcontrols2.conf COPYONLY) qt6_add_qml_module(firelight @@ -157,6 +167,7 @@ install(TARGETS firelight DIRECTORIES ${SHARED_LIB_DIR} BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) @@ -196,8 +207,19 @@ add_executable(fl_test ) file(GLOB QML_TEST_FILES - qml_tests/*.qml - qml_tests/**/*.qml + qml/ColorPalette.qml + qml/Constants.qml + qml/common/DetailsButton.qml + qml/common/RightClickMenu.qml + qml/common/RightClickMenuItem.qml + qml/common/CarouselText.qml + qml/common/NavigationTabBar.qml + qml/library/GameGridItemDelegate.qml + qml/pages/LibraryPage2.qml + qml_tests/common/tst_DetailsButton.qml + qml_tests/common/tst_NavigationTabBar.qml + qml_tests/library/tst_GameGridItemDelegate.qml + qml_tests/library/tst_LibraryPage2.qml ) foreach (QML_FILE ${QML_TEST_FILES}) @@ -207,6 +229,7 @@ endforeach () qt_add_executable(fl_qml_test tests/qml_main.cpp + resources.qrc ) qt6_add_qml_module(fl_qml_test @@ -214,6 +237,11 @@ qt6_add_qml_module(fl_qml_test VERSION 1.0 QML_FILES ${QML_TEST_FILES} + # IMPORTS QMLFirelight +) + +set_target_properties(fl_qml_test PROPERTIES + QT_QML_IMPORT_PATHS ${CMAKE_BINARY_DIR}/qml ) target_link_libraries(fl_qml_test PUBLIC firelight_lib) @@ -227,7 +255,7 @@ add_test(NAME QMLTests COMMAND fl_qml_test -platform offscreen) add_dependencies(fl_test copy_resources) # target_include_directories(fl_test PRIVATE ${GTEST_INCLUDE_DIRS} include) -target_link_libraries(fl_test PUBLIC firelight_lib ${GTEST_BOTH_LIBRARIES} gmock) +target_link_libraries(fl_test PUBLIC firelight_lib ${GTEST_BOTH_LIBRARIES}) gtest_discover_tests(fl_test) #ctest_coverage_collect_gcov() #target_compile_options(firelight PRIVATE -Werror -ggdb3 -O0) @@ -255,11 +283,11 @@ install( SCRIPT ${deploy_script} ) -install( - PROGRAMS - ${SHARED_LIB_DIR}/libsqlite3-0.dll - TYPE BIN -) +#install( +# PROGRAMS +# ${SHARED_LIB_DIR}/libsqlite3-0.dll +# TYPE BIN +#) install( FILES diff --git a/CMakeLists.txt.bak b/CMakeLists.txt.bak new file mode 100644 index 0000000..d578310 --- /dev/null +++ b/CMakeLists.txt.bak @@ -0,0 +1,292 @@ +cmake_minimum_required(VERSION 3.22.1) +project(firelight + VERSION 0.4.0 + DESCRIPTION "An emulation frontend that just works" + # HOMEPAGE_URL "" + LANGUAGES CXX +) + +#include(${CMAKE_SOURCE_DIR}/cmake/rcheevos.cmake) +#include(${CMAKE_SOURCE_DIR}/cmake/clang-checks.cmake) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +#include(FetchContent) +#FetchContent_Declare( +# QtAndroidCMake +# GIT_REPOSITORY https://github.com/LaurentGomila/qt-android-cmake +# GIT_TAG master +#) +#FetchContent_MakeAvailable(QtAndroidCMake) + +#set(CMAKE_INSTALL_RPATH "") + +#set(QT_ENABLE_VERBOSE_DEPLOYMENT ON) + +find_package(PkgConfig REQUIRED) +find_package(SDL2 REQUIRED) +find_package(OpenSSL REQUIRED) +find_package(spdlog REQUIRED) +find_package(Qt6 6.7 REQUIRED COMPONENTS Quick Gui OpenGL QuickControls2 Quick3D Sql Charts QuickTest REQUIRED) +find_package(cpr REQUIRED) +find_package(GTest REQUIRED) + +message(STATUS "OPENSSL LIBRARIES: ${OPENSSL_SSL_LIBRARY}") + +qt6_standard_project_setup(REQUIRES 6.7) + +set(CMAKE_CXX_STANDARD 20) +#set(QT_VERSION_MAJOR 6) + +#include(${CMAKE_SOURCE_DIR}/libs/qwindowkit/CMakeLists.txt) +include(${CMAKE_SOURCE_DIR}/cmake/rcheevos.cmake) +#include(${CMAKE_SOURCE_DIR}/libs/QGoodWindow/QGoodWindow/QGoodWindow/QGoodWindow.cmake) + +include_directories(${CMAKE_SOURCE_DIR}/include ${OPENSSL_INCLUDE_DIR}) +add_subdirectory(src/app) + +file(GLOB QML_FILES + qml/*.qml + qml/**/*.qml +) + +foreach (QML_FILE ${QML_FILES}) + get_filename_component(QML_FILE_NAME ${QML_FILE} NAME) + set_property(SOURCE ${QML_FILE} PROPERTY QT_RESOURCE_ALIAS "${QML_FILE_NAME}") +endforeach () + +set_source_files_properties(qml/Constants.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) +set_source_files_properties(qml/ColorPalette.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) +set_source_files_properties(qml/GeneralSettings.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) + +add_library(firelight_lib + src/app/libretro/core.cpp + src/app/libretro/coreoption.cpp + src/app/libretro/game.cpp + src/app/emulation_manager.cpp + src/app/db/sqlite_content_database.cpp + src/app/fps_multiplier.cpp + src/app/input/controller.cpp + src/app/input/controller_manager.cpp + src/app/input/sdl_event_loop.cpp + src/app/manager_accessor.cpp + src/app/audio_manager.cpp + src/app/saves/save_manager.cpp + src/app/saves/savefile.cpp + src/app/emulator_renderer.cpp + src/app/input/keyboard_controller.cpp + src/app/db/sqlite_userdata_database.cpp + src/gui/controller_list_model.cpp + src/gui/library_item_model.cpp + src/gui/library_sort_filter_model.cpp + src/gui/playlist_item_model.cpp + src/gui/savefile_list_model.cpp + src/gui/mod_item_model.cpp + src/app/mods/mod_manager.cpp + src/app/achieve/ra_client.cpp + src/gui/window_resize_handler.cpp + src/gui/library_path_model.cpp + src/gui/achievement_list_model.cpp + src/gui/achievement_list_sort_filter_model.cpp + src/app/router.cpp + src/app/input/controller_profile.cpp + src/app/input/input_mapping.cpp + src/gui/PlatformInputListModel.cpp + src/gui/gamepad_profile.cpp + src/gui/gamepad_mapping.cpp + src/gui/gamepad_mapping.hpp + src/gui/platform_list_model.cpp + src/gui/platform_list_model.hpp +) + +target_include_directories(firelight_lib PRIVATE ${SDL2_INCLUDE_DIRS} include) +target_link_libraries(firelight_lib PUBLIC + SDL2::SDL2 + ${OPENSSL_LIBRARIES} + z + Qt6::Gui + Qt6::Quick + Qt6::OpenGL + Qt6::QuickControls2 + Qt6::Quick3D + Qt6::Sql + Qt6::Charts + Qt6::QuickTest + spdlog::spdlog + cpr::cpr + library + patching + rcheevos) + +# main code +qt6_add_executable(firelight + resources.qrc + src/main.cpp + include/firelight/controller_profile.hpp + include/firelight/input_mapping.hpp +) + +set_target_properties(firelight PROPERTIES + MACOSX_BUNDLE ON +) + +configure_file(${CMAKE_SOURCE_DIR}/qtquickcontrols2.conf ${CMAKE_BINARY_DIR}/qtquickcontrols2.conf COPYONLY) + +qt6_add_qml_module(firelight + URI QMLFirelight + VERSION 1.0 + QML_FILES + ${QML_FILES} +) + +#target_include_directories(firelight PRIVATE ${SDL2_INCLUDE_DIRS}) +#target_link_libraries(firelight PRIVATE firelight_lib) +target_link_libraries(firelight PUBLIC + firelight_lib) + +#if (CMAKE_BUILD_TYPE STREQUAL "Release") +# set_property(TARGET firelight PROPERTY WIN32_EXECUTABLE true) +#endif () + +find_program(MINGW_PATH gcc) +get_filename_component(MINGW_DIR ${MINGW_PATH} DIRECTORY) +set(SHARED_LIB_DIR "${MINGW_DIR}") + +#include(GNUInstallDirs) +install(TARGETS firelight + RUNTIME_DEPENDENCIES + PRE_EXCLUDE_REGEXES "api-ms-" "ext-ms-" "Qt6" + POST_EXCLUDE_REGEXES ".*system32/.*\\.dll" + DIRECTORIES ${SHARED_LIB_DIR} + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + FRAMEWORK DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +set(SOURCE_RESOURCES_DIR "${CMAKE_SOURCE_DIR}/test_resources") +set(DEST_RESOURCES_DIR "${CMAKE_BINARY_DIR}/test_resources") + +add_custom_target(copy_resources ALL + COMMAND ${CMAKE_COMMAND} -E echo "Copying test resources from ${SOURCE_RESOURCES_DIR} to ${DEST_RESOURCES_DIR}" + COMMAND ${CMAKE_COMMAND} -E make_directory ${DEST_RESOURCES_DIR} # Ensure destination directory exists + COMMAND ${CMAKE_COMMAND} -E copy_directory ${SOURCE_RESOURCES_DIR} ${DEST_RESOURCES_DIR} + COMMAND ${CMAKE_COMMAND} -E echo "Copy completed" + COMMENT "Copying test resources to build directory" +) + +file(COPY ${CMAKE_SOURCE_DIR}/_cores DESTINATION ${CMAKE_BINARY_DIR}/system) +file(COPY ${CMAKE_SOURCE_DIR}/_img DESTINATION ${CMAKE_BINARY_DIR}/system) +file(COPY ${CMAKE_SOURCE_DIR}/content.db DESTINATION ${CMAKE_BINARY_DIR}/system) + +enable_testing() +include(GoogleTest) + +add_executable(fl_test + tests/app/patching/pm_star_rod_mod_patch_test.cpp + tests/app/patching/ips_patch_test.cpp + tests/app/patching/ups_patch_test.cpp + tests/app/patching/bps_patch_test.cpp + tests/mocks/mock_library_database.hpp + tests/app/db/sqlite_userdata_database_test.cpp + tests/app/saves/save_manager_test.cpp + tests/app/library/sqlite_library_database_test.cpp + tests/app/library/playlist_test.cpp + tests/app/library/library_entry_test.cpp + tests/app/db/daos/savefile_metadata_test.cpp + tests/app/db/daos/play_session_test.cpp + tests/app/db/sqlite_content_database_test.cpp + tests/main.cpp +) + +file(GLOB QML_TEST_FILES + qml_tests/*.qml + qml_tests/**/*.qml +) + +foreach (QML_FILE ${QML_TEST_FILES}) + get_filename_component(QML_FILE_NAME ${QML_FILE} NAME) + set_property(SOURCE ${QML_FILE} PROPERTY QT_RESOURCE_ALIAS "${QML_FILE_NAME}") +endforeach () + +qt_add_executable(fl_qml_test + tests/qml_main.cpp +) + +qt6_add_qml_module(fl_qml_test + URI QMLFirelightTest + VERSION 1.0 + QML_FILES + ${QML_TEST_FILES} +) + +target_link_libraries(fl_qml_test PUBLIC firelight_lib) +add_definitions(-DQUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") + +add_test(NAME QMLTests COMMAND fl_qml_test -platform offscreen) + +#target_compile_options(fl_test PUBLIC "-fprofile-instr-generate" "-fcoverage-mapping") +#target_link_options(fl_test PUBLIC "-fprofile-instr-generate" "-fcoverage-mapping") + +add_dependencies(fl_test copy_resources) +# +target_include_directories(fl_test PRIVATE ${GTEST_INCLUDE_DIRS} include) +target_link_libraries(fl_test PUBLIC firelight_lib ${GTEST_BOTH_LIBRARIES}) +gtest_discover_tests(fl_test) +#ctest_coverage_collect_gcov() +#target_compile_options(firelight PRIVATE -Werror -ggdb3 -O0) +# +#add_test(NAME firelight_test COMMAND firelight) + +#file(COPY LICENSE.md README.txt DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +#install(FILES LICENSE.md README.txt DESTINATION ${CMAKE_INSTALL_BINDIR}) + +set(CPACK_PACKAGE_NAME "Firelight") +set(CPACK_PACKAGE_VENDOR "BiscuitCakes") +set(CPACK_PACKAGE_INSTALL_DIRECTORY, "Firelight") +#set(CPACK_RESOURCE_FILE_LICENSE, "C:/msys64/mingw64/share/cmake/Templates/ALEX.txt") +#set(CPACK_RESOURCE_FILE_README, "${CMAKE_CURRENT_BINARY_DIR}/README.md") +#set(CPACK_RESOURCE_FILE_WELCOME, "Firelight") + +include(CPack) +qt6_generate_deploy_qml_app_script( + TARGET firelight + OUTPUT_SCRIPT deploy_script +) + + +install( + SCRIPT ${deploy_script} +) + +install( + PROGRAMS + ${SHARED_LIB_DIR}/libsqlite3-0.dll + TYPE BIN +) + +install( + FILES + ${CMAKE_SOURCE_DIR}/content.db + DESTINATION ${CMAKE_INSTALL_BINDIR}/system/ +) + +install( + DIRECTORY + ${CMAKE_SOURCE_DIR}/_cores + DESTINATION ${CMAKE_INSTALL_BINDIR}/system/ +) + +install( + DIRECTORY + ${CMAKE_SOURCE_DIR}/_img + DESTINATION ${CMAKE_INSTALL_BINDIR}/system/ +) + +install( + DIRECTORY + ${CMAKE_SOURCE_DIR}/_mods + DESTINATION ${CMAKE_INSTALL_BINDIR}/system/ +) diff --git a/CMakeLists.txt.user b/CMakeLists.txt.user new file mode 100644 index 0000000..3c64d92 --- /dev/null +++ b/CMakeLists.txt.user @@ -0,0 +1,617 @@ + + + + + + EnvironmentId + {0588bae6-bd42-4a6b-843d-c36b154cbe5f} + + + ProjectExplorer.Project.ActiveTarget + 1 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + 0 + false + true + false + 2 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 8 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + MinGW + MinGW + {d83668b0-d5b2-4a27-a17f-f3c901e5f15d} + 0 + 0 + 0 + + Debug + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_SYSROOT:PATH=C:/msys64/mingw64 +-DCMAKE_C_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DCMAKE_CXX_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + 0 + C:\Users\alexs\git\firelight-emulator\firelight\build\MinGW-Debug + + + + + all + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + CMakeProjectManager.CMakeBuildConfiguration + + + Release + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=Release +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_SYSROOT:PATH=C:/msys64/mingw64 +-DCMAKE_C_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DCMAKE_CXX_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + C:\Users\alexs\git\firelight-emulator\firelight\build\MinGW-Release + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_SYSROOT:PATH=C:/msys64/mingw64 +-DCMAKE_C_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DCMAKE_CXX_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + C:\Users\alexs\git\firelight-emulator\firelight\build\MinGW-RelWithDebInfo + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Release with Debug Information + CMakeProjectManager.CMakeBuildConfiguration + + + RelWithDebInfo + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_SYSROOT:PATH=C:/msys64/mingw64 +-DCMAKE_C_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DCMAKE_CXX_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + 0 + C:\Users\alexs\git\firelight-emulator\firelight\build\MinGW-Profile + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Profile + CMakeProjectManager.CMakeBuildConfiguration + + + MinSizeRel + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=MinSizeRel +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_SYSROOT:PATH=C:/msys64/mingw64 +-DCMAKE_C_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DCMAKE_CXX_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + C:\Users\alexs\git\firelight-emulator\firelight\build\MinGW-MinSizeRel + + + + + all + + false + + true + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Minimum Size Release + CMakeProjectManager.CMakeBuildConfiguration + + 5 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + firelight + CMakeProjectManager.CMakeRunConfiguration.firelight + firelight + false + true + true + true + C:/Users/alexs/git/firelight-emulator/firelight/build/MinGW-Debug + + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + fl_qml_test + CMakeProjectManager.CMakeRunConfiguration.fl_qml_test + fl_qml_test + false + true + true + true + C:/Users/alexs/git/firelight-emulator/firelight/build/MinGW-Debug + + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + fl_test + CMakeProjectManager.CMakeRunConfiguration.fl_test + fl_test + false + true + true + true + C:/Users/alexs/git/firelight-emulator/firelight/build/MinGW-Debug + + 3 + + + + ProjectExplorer.Project.Target.1 + + Desktop + Qt 6.7.0 (mingw64) + Qt 6.7.0 (mingw64) + {5dfcfea6-e7eb-462d-9a96-e7a4b0e09d8e} + 0 + 0 + 0 + + Debug + 2 + false + + -DCMAKE_GENERATOR:STRING=Ninja +-DCMAKE_BUILD_TYPE:STRING=Debug +-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake +-DCMAKE_SYSROOT:PATH=C:/msys64/mingw64 +-DCMAKE_C_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DCMAKE_CXX_COMPILER_TARGET:STRING=x86_64-w64-mingw32 +-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable} +-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX} +-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C} +-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx} +-DCMAKE_CXX_FLAGS_INIT:STRING=%{Qt:QML_DEBUG_FLAG} + C:\Users\alexs\git\firelight-emulator\firelight + C:\Users\alexs\git\firelight-emulator\firelight\build + + + + + all + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + + clean + + false + + true + Build + CMakeProjectManager.MakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + Debug + CMakeProjectManager.CMakeBuildConfiguration + + 1 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + firelight + CMakeProjectManager.CMakeRunConfiguration.firelight + firelight + false + true + true + true + C:/Users/alexs/git/firelight-emulator/firelight/build + + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + fl_qml_test + CMakeProjectManager.CMakeRunConfiguration.fl_qml_test + fl_qml_test + false + true + true + true + C:/Users/alexs/git/firelight-emulator/firelight/build + + + true + true + 0 + true + + 2 + + false + -e cpu-cycles --call-graph "dwarf,4096" -F 250 + fl_test + CMakeProjectManager.CMakeRunConfiguration.fl_test + fl_test + false + true + true + true + C:/Users/alexs/git/firelight-emulator/firelight/build + + 3 + + + + ProjectExplorer.Project.TargetCount + 2 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/_cores/fceumm_libretro.dll b/_cores/fceumm_libretro.dll new file mode 100644 index 0000000..f51d1e1 Binary files /dev/null and b/_cores/fceumm_libretro.dll differ diff --git a/_cores/genesis_plus_gx_libretro.dll b/_cores/genesis_plus_gx_libretro.dll index f0dcbf9..a7e2859 100644 Binary files a/_cores/genesis_plus_gx_libretro.dll and b/_cores/genesis_plus_gx_libretro.dll differ diff --git a/_cores/geolith_libretro.dll b/_cores/geolith_libretro.dll new file mode 100644 index 0000000..84fd4c9 Binary files /dev/null and b/_cores/geolith_libretro.dll differ diff --git a/_img/DS.svg b/_img/DS.svg new file mode 100644 index 0000000..588bc36 --- /dev/null +++ b/_img/DS.svg @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_img/SNES-controller.png b/_img/SNES-controller.png new file mode 100644 index 0000000..65226da Binary files /dev/null and b/_img/SNES-controller.png differ diff --git a/_img/gb-dmg.png b/_img/gb-dmg.png new file mode 100644 index 0000000..40ed0e8 Binary files /dev/null and b/_img/gb-dmg.png differ diff --git a/_img/gb-light.png b/_img/gb-light.png new file mode 100644 index 0000000..871722b Binary files /dev/null and b/_img/gb-light.png differ diff --git a/_img/gb-pocket.png b/_img/gb-pocket.png new file mode 100644 index 0000000..843119b Binary files /dev/null and b/_img/gb-pocket.png differ diff --git a/_img/gbc-blue.png b/_img/gbc-blue.png new file mode 100644 index 0000000..7d4a96d Binary files /dev/null and b/_img/gbc-blue.png differ diff --git a/_img/gbc-brown.png b/_img/gbc-brown.png new file mode 100644 index 0000000..e030618 Binary files /dev/null and b/_img/gbc-brown.png differ diff --git a/_img/gbc-darkblue.png b/_img/gbc-darkblue.png new file mode 100644 index 0000000..9e9bd3e Binary files /dev/null and b/_img/gbc-darkblue.png differ diff --git a/_img/gbc-darkbrown.png b/_img/gbc-darkbrown.png new file mode 100644 index 0000000..c8b9ba5 Binary files /dev/null and b/_img/gbc-darkbrown.png differ diff --git a/_img/gbc-darkgreen.png b/_img/gbc-darkgreen.png new file mode 100644 index 0000000..613e9f2 Binary files /dev/null and b/_img/gbc-darkgreen.png differ diff --git a/_img/gbc-grayscale.png b/_img/gbc-grayscale.png new file mode 100644 index 0000000..73b99bc Binary files /dev/null and b/_img/gbc-grayscale.png differ diff --git a/_img/gbc-green.png b/_img/gbc-green.png new file mode 100644 index 0000000..3de423b Binary files /dev/null and b/_img/gbc-green.png differ diff --git a/_img/gbc-inverted.png b/_img/gbc-inverted.png new file mode 100644 index 0000000..cb5c634 Binary files /dev/null and b/_img/gbc-inverted.png differ diff --git a/_img/gbc-orange.png b/_img/gbc-orange.png new file mode 100644 index 0000000..a219f98 Binary files /dev/null and b/_img/gbc-orange.png differ diff --git a/_img/gbc-pastelmix.png b/_img/gbc-pastelmix.png new file mode 100644 index 0000000..bfd22c1 Binary files /dev/null and b/_img/gbc-pastelmix.png differ diff --git a/_img/gbc-red.png b/_img/gbc-red.png new file mode 100644 index 0000000..b3761f6 Binary files /dev/null and b/_img/gbc-red.png differ diff --git a/_img/gbc-yellow.png b/_img/gbc-yellow.png new file mode 100644 index 0000000..eaa7fe5 Binary files /dev/null and b/_img/gbc-yellow.png differ diff --git a/_img/genesis.svg b/_img/genesis.svg new file mode 100644 index 0000000..c530f65 --- /dev/null +++ b/_img/genesis.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_img/sgb-1a.png b/_img/sgb-1a.png new file mode 100644 index 0000000..eb6cd13 Binary files /dev/null and b/_img/sgb-1a.png differ diff --git a/_img/sgb-1b.png b/_img/sgb-1b.png new file mode 100644 index 0000000..9876e01 Binary files /dev/null and b/_img/sgb-1b.png differ diff --git a/_img/sgb-1c.png b/_img/sgb-1c.png new file mode 100644 index 0000000..dfbfad2 Binary files /dev/null and b/_img/sgb-1c.png differ diff --git a/_img/sgb-1d.png b/_img/sgb-1d.png new file mode 100644 index 0000000..eea6b4e Binary files /dev/null and b/_img/sgb-1d.png differ diff --git a/_img/sgb-1e.png b/_img/sgb-1e.png new file mode 100644 index 0000000..c11e346 Binary files /dev/null and b/_img/sgb-1e.png differ diff --git a/_img/sgb-1f.png b/_img/sgb-1f.png new file mode 100644 index 0000000..51d91f6 Binary files /dev/null and b/_img/sgb-1f.png differ diff --git a/_img/sgb-1g.png b/_img/sgb-1g.png new file mode 100644 index 0000000..e54083d Binary files /dev/null and b/_img/sgb-1g.png differ diff --git a/_img/sgb-1h.png b/_img/sgb-1h.png new file mode 100644 index 0000000..951b5c1 Binary files /dev/null and b/_img/sgb-1h.png differ diff --git a/_img/sgb-2a.png b/_img/sgb-2a.png new file mode 100644 index 0000000..19c1894 Binary files /dev/null and b/_img/sgb-2a.png differ diff --git a/_img/sgb-2b.png b/_img/sgb-2b.png new file mode 100644 index 0000000..5de59e8 Binary files /dev/null and b/_img/sgb-2b.png differ diff --git a/_img/sgb-2c.png b/_img/sgb-2c.png new file mode 100644 index 0000000..58e26b3 Binary files /dev/null and b/_img/sgb-2c.png differ diff --git a/_img/sgb-2d.png b/_img/sgb-2d.png new file mode 100644 index 0000000..c8afa9d Binary files /dev/null and b/_img/sgb-2d.png differ diff --git a/_img/sgb-2e.png b/_img/sgb-2e.png new file mode 100644 index 0000000..80c3c05 Binary files /dev/null and b/_img/sgb-2e.png differ diff --git a/_img/sgb-2f.png b/_img/sgb-2f.png new file mode 100644 index 0000000..778897f Binary files /dev/null and b/_img/sgb-2f.png differ diff --git a/_img/sgb-2g.png b/_img/sgb-2g.png new file mode 100644 index 0000000..7a05578 Binary files /dev/null and b/_img/sgb-2g.png differ diff --git a/_img/sgb-2h.png b/_img/sgb-2h.png new file mode 100644 index 0000000..97f9e19 Binary files /dev/null and b/_img/sgb-2h.png differ diff --git a/_img/sgb-3a.png b/_img/sgb-3a.png new file mode 100644 index 0000000..94b2194 Binary files /dev/null and b/_img/sgb-3a.png differ diff --git a/_img/sgb-3b.png b/_img/sgb-3b.png new file mode 100644 index 0000000..6634dc5 Binary files /dev/null and b/_img/sgb-3b.png differ diff --git a/_img/sgb-3c.png b/_img/sgb-3c.png new file mode 100644 index 0000000..574138f Binary files /dev/null and b/_img/sgb-3c.png differ diff --git a/_img/sgb-3d.png b/_img/sgb-3d.png new file mode 100644 index 0000000..2d75afc Binary files /dev/null and b/_img/sgb-3d.png differ diff --git a/_img/sgb-3e.png b/_img/sgb-3e.png new file mode 100644 index 0000000..f51b078 Binary files /dev/null and b/_img/sgb-3e.png differ diff --git a/_img/sgb-3f.png b/_img/sgb-3f.png new file mode 100644 index 0000000..f8835ba Binary files /dev/null and b/_img/sgb-3f.png differ diff --git a/_img/sgb-3g.png b/_img/sgb-3g.png new file mode 100644 index 0000000..e709093 Binary files /dev/null and b/_img/sgb-3g.png differ diff --git a/_img/sgb-3h.png b/_img/sgb-3h.png new file mode 100644 index 0000000..63045be Binary files /dev/null and b/_img/sgb-3h.png differ diff --git a/_img/sgb-4a.png b/_img/sgb-4a.png new file mode 100644 index 0000000..2c6150d Binary files /dev/null and b/_img/sgb-4a.png differ diff --git a/_img/sgb-4b.png b/_img/sgb-4b.png new file mode 100644 index 0000000..c7efd23 Binary files /dev/null and b/_img/sgb-4b.png differ diff --git a/_img/sgb-4c.png b/_img/sgb-4c.png new file mode 100644 index 0000000..b9ef6a1 Binary files /dev/null and b/_img/sgb-4c.png differ diff --git a/_img/sgb-4d.png b/_img/sgb-4d.png new file mode 100644 index 0000000..4c54138 Binary files /dev/null and b/_img/sgb-4d.png differ diff --git a/_img/sgb-4e.png b/_img/sgb-4e.png new file mode 100644 index 0000000..2bf678d Binary files /dev/null and b/_img/sgb-4e.png differ diff --git a/_img/sgb-4f.png b/_img/sgb-4f.png new file mode 100644 index 0000000..5551257 Binary files /dev/null and b/_img/sgb-4f.png differ diff --git a/_img/sgb-4g.png b/_img/sgb-4g.png new file mode 100644 index 0000000..57fe235 Binary files /dev/null and b/_img/sgb-4g.png differ diff --git a/_img/sgb-4h.png b/_img/sgb-4h.png new file mode 100644 index 0000000..1fa38c7 Binary files /dev/null and b/_img/sgb-4h.png differ diff --git a/_img/special1.png b/_img/special1.png new file mode 100644 index 0000000..c8e32ef Binary files /dev/null and b/_img/special1.png differ diff --git a/_img/special2.png b/_img/special2.png new file mode 100644 index 0000000..2af9d51 Binary files /dev/null and b/_img/special2.png differ diff --git a/_img/special3.png b/_img/special3.png new file mode 100644 index 0000000..f6926da Binary files /dev/null and b/_img/special3.png differ diff --git a/assets/LexendDeca[wght].ttf b/assets/LexendDeca[wght].ttf new file mode 100644 index 0000000..9ea1ad9 Binary files /dev/null and b/assets/LexendDeca[wght].ttf differ diff --git a/include/firelight/libretro/audio_data_receiver.hpp b/include/firelight/libretro/audio_data_receiver.hpp index 1d7eee1..9b6c7b5 100644 --- a/include/firelight/libretro/audio_data_receiver.hpp +++ b/include/firelight/libretro/audio_data_receiver.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include class IAudioDataReceiver { diff --git a/include/firelight/libretro/configuration_provider.hpp b/include/firelight/libretro/configuration_provider.hpp new file mode 100644 index 0000000..9288b14 --- /dev/null +++ b/include/firelight/libretro/configuration_provider.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace firelight::libretro { + class IConfigurationProvider { + protected: + ~IConfigurationProvider() = default; + + public: + class OptionValue final { + public: + std::string key; + std::string label; + }; + + class Option final { + public: + std::string key; + std::string label; + std::string description; + std::vector possibleValues; + std::string defaultValueKey; + }; + + // TODO: Option categories + + virtual void registerOption(Option option) = 0; + + virtual bool anyOptionValueHasChanged() = 0; + + virtual void setDefaultValue(std::string key, std::string value) = 0; + + virtual std::optional getOptionValue(std::string key) = 0; + + virtual void setOptionVisibility(std::string key, bool visible) = 0; + }; +} // namespace firelight::libretro diff --git a/include/firelight/libretro/retropad_provider.hpp b/include/firelight/libretro/retropad_provider.hpp index 678af02..c41f545 100644 --- a/include/firelight/libretro/retropad_provider.hpp +++ b/include/firelight/libretro/retropad_provider.hpp @@ -4,11 +4,10 @@ #include namespace firelight::libretro { + class IRetropadProvider { + public: + virtual ~IRetropadProvider() = default; -class IRetropadProvider { -public: - virtual ~IRetropadProvider() = default; - virtual std::optional getRetropadForPlayer(int t_player) = 0; -}; - + virtual std::optional getRetropadForPlayerIndex(int t_player) = 0; + }; } // namespace firelight::libretro diff --git a/include/firelight/libretro/video_data_receiver.hpp b/include/firelight/libretro/video_data_receiver.hpp index 6d3fc1c..ccf957a 100644 --- a/include/firelight/libretro/video_data_receiver.hpp +++ b/include/firelight/libretro/video_data_receiver.hpp @@ -4,21 +4,29 @@ #include typedef void (*proc_address_t)(); + typedef void (*context_reset_func)(); + typedef void (*context_destroy_func)(); namespace firelight::libretro { + class IVideoDataReceiver { + public: + virtual ~IVideoDataReceiver() = default; + + virtual void receive(const void *data, unsigned width, unsigned height, + size_t pitch) = 0; + + virtual proc_address_t getProcAddress(const char *sym) = 0; + + virtual void setResetContextFunc(context_reset_func) = 0; + + virtual void setDestroyContextFunc(context_destroy_func) = 0; + + virtual uintptr_t getCurrentFramebufferId() = 0; -class IVideoDataReceiver { -public: - virtual ~IVideoDataReceiver() = default; - virtual void receive(const void *data, unsigned width, unsigned height, - size_t pitch) = 0; - virtual proc_address_t getProcAddress(const char *sym) = 0; - virtual void setResetContextFunc(context_reset_func) = 0; - virtual void setDestroyContextFunc(context_destroy_func) = 0; - virtual uintptr_t getCurrentFramebufferId() = 0; - virtual void setSystemAVInfo(retro_system_av_info *info) = 0; -}; + virtual void setSystemAVInfo(retro_system_av_info *info) = 0; + virtual void setPixelFormat(retro_pixel_format *format) = 0; + }; } // namespace firelight::libretro diff --git a/include/firelight/userdata_database.hpp b/include/firelight/userdata_database.hpp index e54551a..d605e00 100644 --- a/include/firelight/userdata_database.hpp +++ b/include/firelight/userdata_database.hpp @@ -4,6 +4,7 @@ #include "savefile_metadata.hpp" #include +#include #include #include @@ -41,5 +42,11 @@ namespace firelight::db { virtual std::optional getLatestPlaySession(std::string contentId) = 0; + + virtual std::optional getPlatformSettingValue(int platformId, std::string key) = 0; + + virtual std::map getAllPlatformSettings(int platformId) = 0; + + virtual void setPlatformSettingValue(int platformId, std::string key, std::string value) = 0; }; } // namespace firelight::db diff --git a/qml/ActiveFocusHighlight.qml b/qml/ActiveFocusHighlight.qml new file mode 100644 index 0000000..7ea6fed --- /dev/null +++ b/qml/ActiveFocusHighlight.qml @@ -0,0 +1,10 @@ +import QtQuick + +Rectangle { + anchors.fill: parent + border.color: "lightblue" + border.width: 2 + + color: "transparent" + visible: false +} \ No newline at end of file diff --git a/qml/ColorPalette.qml b/qml/ColorPalette.qml index 81c22f6..534ff35 100644 --- a/qml/ColorPalette.qml +++ b/qml/ColorPalette.qml @@ -3,22 +3,33 @@ import QtQuick pragma Singleton Item { - readonly property color primary800: "#203d54" - readonly property color primary700: "#1a4970" - readonly property color primary600: "#2368a1" - readonly property color primary500: "#3182c7" - readonly property color primary400: "#63a1d7" - readonly property color primary300: "#a9d3f3" - readonly property color primary200: "#edf6fd" - - readonly property color neutral900: "#1a222c" - readonly property color neutral800: "#212934" - readonly property color neutral700: "#5f6b79" - readonly property color neutral600: "#8794a6" - readonly property color neutral500: "#b7c3cd" - readonly property color neutral400: "#ced5dd" - readonly property color neutral300: "#e0e6eb" - readonly property color neutral200: "#f6f7f8" + readonly property color primary800: "#142850" + readonly property color primary700: "#27496d" + readonly property color primary600: "#0c7b93" + readonly property color primary500: "#00a8cc" + readonly property color primary400: "#3bc9db" + readonly property color primary300: "#76e4ef" + readonly property color primary200: "#c4f1fa" + + readonly property color accent800: "#623d1a" + readonly property color accent700: "#7a4f23" + readonly property color accent600: "#94622d" + readonly property color accent500: "#b77c38" + readonly property color accent400: "#d89e61" + readonly property color accent300: "#ebc299" + readonly property color accent200: "#f6e1c7" + + readonly property color neutral1000: "#0e0e0e" + readonly property color neutral900: "#1e1e1e" + readonly property color neutral800: "#2a2a2a" + readonly property color neutral700: "#444444" + readonly property color neutral600: "#666666" + readonly property color neutral500: "#888888" + readonly property color neutral400: "#aaaaaa" + readonly property color neutral300: "#cccccc" + readonly property color neutral200: "#e6e6e6" + readonly property color neutral100: "#f9f9f9" + readonly property color red900: "#b71c1c" readonly property color red700: "#ca1f17" @@ -27,9 +38,9 @@ Item { readonly property color white: neutral200 - readonly property color containerVeryLowColor: "#1d1e22" - readonly property color containerLowColor: "#25282C" - readonly property color containerMidColor: "#32363a" + readonly property color containerVeryLowColor: neutral900 + readonly property color containerLowColor: neutral800 + readonly property color containerMidColor: neutral700 readonly property color dropdownBackgroundColor: containerMidColor diff --git a/qml/Constants.qml b/qml/Constants.qml index 12ba1ac..ef97148 100644 --- a/qml/Constants.qml +++ b/qml/Constants.qml @@ -49,7 +49,7 @@ Item { } readonly property string symbolFontFamily: symbols.name - readonly property string regularFontFamily: "Segoe UI" + readonly property string regularFontFamily: localFont.name readonly property string lightFontFamily: lexendLight.name readonly property string strongFontFamily: lexendBlack.name readonly property string semiboldFontFamily: semibold.name diff --git a/qml/FirelightCreatePlaylistDialog.qml b/qml/FirelightCreatePlaylistDialog.qml index 4df4fb6..6c4d52c 100644 --- a/qml/FirelightCreatePlaylistDialog.qml +++ b/qml/FirelightCreatePlaylistDialog.qml @@ -42,7 +42,6 @@ Dialog { TextInput { id: playlistNameInput anchors.fill: parent - focus: true text: "text" color: Constants.colorTestTextActive padding: 12 diff --git a/qml/FirelightRenamePlaylistDialog.qml b/qml/FirelightRenamePlaylistDialog.qml index ea82f3a..0a51879 100644 --- a/qml/FirelightRenamePlaylistDialog.qml +++ b/qml/FirelightRenamePlaylistDialog.qml @@ -44,7 +44,6 @@ Dialog { TextInput { id: playlistNameInput anchors.fill: parent - focus: true text: "text" color: Constants.colorTestTextActive padding: 12 diff --git a/qml/FirelightSplitViewHandle.qml b/qml/FirelightSplitViewHandle.qml deleted file mode 100644 index 5c218b8..0000000 --- a/qml/FirelightSplitViewHandle.qml +++ /dev/null @@ -1,25 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Item { - id: handle - implicitWidth: 9 - implicitHeight: parent.height - - Rectangle { - anchors.centerIn: parent - implicitWidth: 1 - implicitHeight: parent.height - 10 - color: handle.SplitHandle.pressed ? "#f1f1f1" - : (handle.SplitHandle.hovered ? "#727272" : "transparent") - - Behavior on color { - ColorAnimation { - duration: 150 - easing.type: Easing.InOutQuad - } - - } - } -} \ No newline at end of file diff --git a/qml/Footer.qml b/qml/Footer.qml deleted file mode 100644 index 7fd9c96..0000000 --- a/qml/Footer.qml +++ /dev/null @@ -1,16 +0,0 @@ -import QtQuick - - -Rectangle { - color: "transparent" - - Text { - text: "Firelight is made with ❤️ by BiscuitCakes" - anchors.centerIn: parent - color: Constants.colorTestTextMuted - font.pointSize: 8 - font.family: Constants.lightFontFamily - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } -} \ No newline at end of file diff --git a/qml/Header.qml b/qml/Header.qml deleted file mode 100644 index 0f11638..0000000 --- a/qml/Header.qml +++ /dev/null @@ -1,16 +0,0 @@ -import QtQuick - - -Rectangle { - color: "transparent" - - Text { - text: "😎 clock" - anchors.fill: parent - horizontalAlignment: Text.AlignRight - verticalAlignment: Text.AlignVCenter - color: Constants.colorTestText - font.pointSize: 12 - font.family: Constants.regularFontFamily - } -} \ No newline at end of file diff --git a/qml/LibraryPage.qml b/qml/LibraryPage.qml deleted file mode 100644 index dfa1503..0000000 --- a/qml/LibraryPage.qml +++ /dev/null @@ -1,486 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQml.Models - - -Flickable { - id: root - - contentHeight: theColumn.height - boundsBehavior: Flickable.StopAtBounds - - ScrollBar.vertical: ScrollBar { - } - - signal entryClicked(int entryId) - - RowLayout { - id: contentRow - anchors.fill: parent - spacing: 0 - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.horizontalStretchFactor: 1 - } - - Column { - id: theColumn - Layout.preferredWidth: Math.max(5, (Math.min(Math.floor(parent.width / libraryGrid.cellContentWidth), 8)) * libraryGrid.cellContentWidth) - - Pane { - id: header - - width: parent.width - height: 120 - background: Rectangle { - color: "transparent" - // border.color: "red" - } - - horizontalPadding: 8 - - Text { - anchors.fill: parent - text: "All Games" - color: "white" - font.pointSize: 26 - font.family: Constants.regularFontFamily - font.weight: Font.DemiBold - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - } - - Pane { - id: filters - background: Rectangle { - color: "transparent" - // border.color: "blue" - } - width: parent.width - - horizontalPadding: 8 - verticalPadding: 0 - - RowLayout { - anchors.fill: parent - - spacing: 12 - Text { - Layout.fillHeight: true - Layout.alignment: Qt.AlignBottom - text: "Showing " + library_model.count + " game" + (library_model.count === 1 ? "" : "s") - color: "#C2BBBB" - font.pointSize: 11 - font.family: Constants.regularFontFamily - font.weight: Font.Normal - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignBottom - } - - Item { - Layout.fillWidth: true - } - - // Button { - // id: melol - // Layout.preferredHeight: 32 - // horizontalPadding: 12 - // verticalPadding: 8 - // - // hoverEnabled: true - // - // Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - // background: Rectangle { - // color: melol.hovered ? "#4e535b" : "#3e434b" - // radius: 12 - // border.color: "#7d848c" - // } - // - // contentItem: Text { - // text: "Show filters" - // color: "white" - // font.pointSize: 11 - // font.family: Constants.regularFontFamily - // horizontalAlignment: Text.AlignHCenter - // verticalAlignment: Text.AlignVCenter - // } - // } - - // Item { - // Layout.preferredWidth: 8 - // } - - Text { - Layout.fillHeight: true - Layout.alignment: Qt.AlignVCenter - text: "Sort by:" - color: "white" - font.pointSize: 12 - font.family: Constants.regularFontFamily - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - - MyComboBox { - textRole: "text" - valueRole: "value" - - onActivated: library_short_model.sortType = currentValue - Component.onCompleted: currentIndex = indexOfValue(library_short_model.sortType) - - model: [ - {text: "A-Z", value: "display_name"}, - {text: "Newest first", value: "created_at"} - ] - } - } - } - - Pane { - background: Rectangle { - color: "transparent" - // border.color: "green" - } - width: parent.width - height: libraryGrid.contentHeight - - horizontalPadding: 0 - - GridView { - id: libraryGrid - width: parent.width - height: 2000 - cellWidth: cellContentWidth - cellHeight: cellContentHeight - - function itemsPerRow() { - return Math.floor(width / cellWidth); - } - - function rowWidth() { - return itemsPerRow() * cellWidth; - } - - // onWidthChanged: { - // contentRow.middleSectionWidth = libraryGrid.rowWidth() - // } - - cacheBuffer: 30 - - clip: true - - interactive: false - readonly property int cellSpacing: 12 - readonly property int cellContentWidth: 180 + cellSpacing - readonly property int cellContentHeight: 260 + cellSpacing - - populate: Transition { - } - - currentIndex: 0 - - model: library_short_model - boundsBehavior: Flickable.StopAtBounds - keyNavigationEnabled: true - - highlight: Item { - } - - delegate: Item { - id: rootItem - width: libraryGrid.cellContentWidth - height: libraryGrid.cellContentHeight - - Button { - id: libItemButton - anchors { - fill: parent - margins: libraryGrid.cellSpacing / 2 - } - - scale: pressed ? 0.97 : 1 - Behavior on scale { - NumberAnimation { - duration: 64 - easing.type: Easing.InOutQuad - } - } - - background: Rectangle { - color: libItemButton.hovered ? "#3a3e45" : "#25282C" - radius: 6 - // border.color: rootItem.GridView.isCurrentItem ? "white" : "transparent" - // border.width: 2 - // leftInset: -2 - // rightInset: -2 - // topInset: -2 - // bottomInset: -2 - } - - contentItem: ColumnLayout { - id: colLayout - spacing: 0 - anchors { - fill: parent - // margins: 2 - } - - // Image { - // asynchronous: true - // source: "file:system/_img/pmbox.jpg" - // // Layout.fillWidth: true - // // Layout.preferredHeight: 180 - // - // // implicitWidth: libItemMouse.hovered ? parent.width + 10 : parent.width - // // implicitHeight: libItemMouse.hovered ? parent.height + 10 : parent.height - // - // // scale: libItemMouse.hovered ? 1.02 : 1 - // - // // Behavior on scale { - // // NumberAnimation { - // // duration: 200 - // // easing.type: Easing.InOutQuad - // // } - // // } - // - // width: 200 - // height: 200 - // - // sourceSize.width: 180 - // sourceSize.height: 180 - // - // horizontalAlignment: Image.AlignLeft - // - // fillMode: Image.PreserveAspectCrop - // } - - Rectangle { - color: "grey" - Layout.fillWidth: true - Layout.preferredHeight: colLayout.width - topLeftRadius: 6 - topRightRadius: 6 - - Text { - text: "Box art coming soon :)" - font.pointSize: 9 - // font.weight: Font.Light - font.family: Constants.regularFontFamily - color: "#232323" - anchors.centerIn: parent - // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - - ColumnLayout { - spacing: 2 - anchors { - fill: parent - margins: 6 - } - - Text { - text: model.display_name - font.pointSize: 11 - font.weight: Font.Bold - font.family: Constants.regularFontFamily - color: "white" - Layout.fillWidth: true - elide: Text.ElideRight - maximumLineCount: 1 - // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - } - Text { - text: model.platform_name - font.pointSize: 10 - font.weight: Font.Medium - font.family: Constants.regularFontFamily - color: "#C2BBBB" - Layout.fillWidth: true - // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - } - } - - // Pane { - // Layout.fillWidth: true - // Layout.fillHeight: true - // - // // clip: true - // background: Item { - // } - // padding: 8 - // ColumnLayout { - // anchors.fill: parent - // spacing: 2 - // Text { - // text: model.display_name - // font.pointSize: 11 - // font.weight: Font.Bold - // font.family: Constants.regularFontFamily - // color: "white" - // Layout.fillWidth: true - // elide: Text.ElideRight - // maximumLineCount: 1 - // // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - // } - // Text { - // text: model.platform_name - // font.pointSize: 10 - // font.weight: Font.Medium - // font.family: Constants.regularFontFamily - // color: "#C2BBBB" - // Layout.fillWidth: true - // // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - // } - // Item { - // Layout.fillWidth: true - // Layout.fillHeight: true - // } - // } - // - // } - } - - TapHandler { - acceptedButtons: Qt.RightButton - onTapped: { - rootItem.GridView.view.currentIndex = index - libraryEntryRightClickMenu.popup() - } - } - - Keys.onReturnPressed: { - doubleClicked() - } - - onClicked: function () { - rootItem.GridView.view.currentIndex = model.index - root.StackView.view.push(gameDetailsPage, {"entryId": model.id}) - // libraryEntryRightClickMenu.popup() - } - - onDoubleClicked: function () { - entryClicked(model.id) - } - - RightClickMenu { - id: libraryEntryRightClickMenu - - RightClickMenuItem { - text: "Play " + model.display_name - onTriggered: { - entryClicked(model.id) - } - } - - // RightClickMenuItem { - // text: "Patch stuff" - // visible: model.parent_game_name !== "" - // onTriggered: function () { - // selectLibEntryDialog.open() - // } - // } - - RightClickMenuItem { - // enabled: false - text: "View details" - onTriggered: { - root.StackView.view.push(gameDetailsPage, {"entryId": model.id}) - } - } - - MenuSeparator { - contentItem: Rectangle { - implicitWidth: 188 - implicitHeight: 1 - color: "#606060" - } - } - - RightClickMenu { - id: addPlaylistRightClickMenu - enabled: ins.count > 0 - - title: "Add to folder" - - Instantiator { - id: ins - model: playlist_model - delegate: RightClickMenuItem { - text: model.display_name - onTriggered: { - playlist_model.addEntryToPlaylist(model.id, libraryEntryRightClickMenu.entryId) - library_model.updatePlaylistsForEntry(libraryEntryRightClickMenu.entryId) - // Add your action here - } - } - - onObjectAdded: function (index, object) { - addPlaylistRightClickMenu.insertItem(index, object) - } - onObjectRemoved: function (index, object) { - addPlaylistRightClickMenu.removeItem(object) - } - } - } - - MenuSeparator { - contentItem: Rectangle { - implicitWidth: 188 - implicitHeight: 1 - color: "#606060" - } - } - - RightClickMenuItem { - enabled: false - text: "Manage save data" - // onTriggered: { - // addPlaylistRightClickMenu.entryId = libraryEntryRightClickMenu.entryId - // addPlaylistRightClickMenu.popup() - // } - } - } - } - } - } - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.horizontalStretchFactor: 1 - } - } - - SelectLibraryEntryDialog { - id: selectLibEntryDialog - } - - Component { - id: gameDetailsPage - GameDetailsPage { - entryId: -1 - - onPlayPressed: function () { - entryClicked(entryId) - } - } - } - - -} \ No newline at end of file diff --git a/qml/Main.qml b/qml/Main.qml index 45d6e72..1f74668 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -2,19 +2,18 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs import QtQuick.Window +import QtQml.Models import QtQuick.Layouts 1.0 import QtQuick.Effects import Firelight 1.0 ApplicationWindow { id: window + objectName: "Application Window" width: 1280 height: 720 - minimumHeight: 720 - minimumWidth: 1280 - // flags: Qt.FramelessWindowHint visible: true @@ -26,104 +25,140 @@ ApplicationWindow { color: "#1a1b1e" } - Connections { - target: Router + function getStuff(item, depth): string { + if (!item) { + return "" + } - function onRouteChanged(route) { - let parts = route.split("/") - if (parts.length > 0) { - let id = parts[0] + let str = "" - if (id === "settings") { - let section = parts.length > 1 ? parts[1] : "library" - stackView.push(settingsPage, {section: section}) - } - } - } - } + str += " ".repeat(depth) + item.objectName + " " + item.height + "\n" - GameLaunchPopup { - id: gameLaunchPopup + // if (item.children.length === 0) { + // return + // } - Connections { - target: achievement_manager + // console.log("num children: " + item.children.length) - function onGameLoadSucceeded(imageUrl, title, numEarned, numTotal) { - gameLaunchPopup.openWith(imageUrl, title, numEarned, numTotal, achievement_manager.defaultToHardcore) + for (let i = 0; i < item.children.length; i++) { + let child = item.children[i] + if (!child) { + continue } + // str += " ".repeat(depth) + child.objectName + " " + child.id + "\n" + str += getStuff(child, depth + 1) } + + return str } - AchievementProgressIndicator { - id: achievementProgressIndicator + Window { + id: debugWindow + objectName: "Debug Window" + width: 400 + height: 400 + visible: false - Connections { - target: achievement_manager - function onAchievementProgressUpdated(imageUrl, id, name, description, current, desired) { - if (achievement_manager.progressNotificationsEnabled) { - achievementProgressIndicator.openWith(imageUrl, name, description, current, desired) + property bool locked: false + + // ListView { + // anchors.fill: parent + // model: window.contentItem.children + // delegate: ListView { + // required property var modelData + // width: parent.width + // header: Text { + // width: parent.width + // height: 20 + // // text: modelData.objectName + // text: "Item" + // } + // model: modelData.children + // delegate: Text { + // width: parent.width + // height: 20 + // + // required property var modelData + // text: modelData.height + // } + // } + // } + + ColumnLayout { + anchors.fill: parent + + Text { + Layout.fillWidth: true + text: debugWindow.locked ? "Locked (F11 to unlock)" : "Unlocked (F11 to lock)" + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + font.pointSize: 11 + color: "black" + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + contentHeight: debugText.height + contentWidth: debugText.width + Text { + id: debugText + text: "" } } } + + Component.onCompleted: { + // debugText.text = getStuff(window.activeFocusItem, 0) + } } - AchievementUnlockIndicator { - id: achievementUnlockIndicator + onActiveFocusItemChanged: function () { + if (!debugWindow.visible || debugWindow.locked) { + return + } - Connections { - target: achievement_manager + // for (let key in window.activeFocusControl) { + // console.log(key + ": " + window.activeFocusControl[key]) + // } - function onAchievementUnlocked(imageUrl, name, description) { - if (achievement_manager.unlockNotificationsEnabled) { - achievementUnlockIndicator.openWith(imageUrl, name, description) - } - } + if (window.activeFocusItem && window.activeFocusItem.parent) { + debugText.text = "Active Focus Item: " + window.activeFocusItem.objectName + "\n parent: " + + window.activeFocusItem["parent"] } + // debugText.text = getStuff(window.activeFocusItem, 0) } - Component { - id: libraryPage - LibraryPage { - property bool topLevel: true - property string topLevelName: "library" + Connections { + target: Router - onEntryClicked: function (id) { - emulator.loadGame(id) + function onRouteChanged(route) { + let parts = route.split("/") + if (parts.length > 0) { + let id = parts[0] + + if (id === "settings") { + let section = parts.length > 1 ? parts[1] : "library" + stackView.push(settingsScreen, {section: section}) + } } } } Component { - id: modsPage - DiscoverPage { - property bool topLevel: true - property string topLevelName: "mods" - } - } - + id: homeScreen - Component { - id: controllerPage - ControllersPage { - property bool topLevel: true - property string topLevelName: "controllers" + HomeScreen { + objectName: "Home Screen" + showNowPlayingButton: emulatorScreen.emulatorIsRunning } } - - // Component { - // id: controllerPage - // ControllerProfilePage { - // property bool topLevel: true - // property string topLevelName: "controllers" - // } - // } - Component { - id: settingsPage - SettingsPage { - id: me + id: settingsScreen + SettingsScreen { + objectName: "Settings Screen" property bool topLevel: true property string topLevelName: "settings" @@ -135,434 +170,66 @@ ApplicationWindow { } } - Component { - id: nowPlayingPage - NowPlayingPage { - id: me - property bool topLevel: true - property string topLevelName: "nowPlaying" + EmulatorScreen { + id: emulatorScreen + objectName: "Emulator Screen" + onGameReady: function () { + overlayFadeIn.start() + } + } - onBackToMainMenuPressed: function () { - stackView.push(mainMenu) - } + Rectangle { + objectName: "Active focus highlight" + color: "transparent" + border.color: "red" + anchors.fill: parent + parent: window.activeFocusItem + } - onResumeGamePressed: function () { - emulatorStack.pop() - } + GameLaunchPopup { + objectName: "Game Launch Popup" + id: gameLaunchPopup - onRestartGamePressed: function () { - emulator.resetGame() - emulatorStack.pop() + Connections { + target: achievement_manager + + function onGameLoadSucceeded(imageUrl, title, numEarned, numTotal) { + gameLaunchPopup.openWith(imageUrl, title, numEarned, numTotal, achievement_manager.defaultToHardcore) } + } + } - onCloseGamePressed: function () { - closeGameAnimation.start() + Component { + id: libraryPage + LibraryPage { + objectName: "Library Page" + property bool topLevel: true + property string topLevelName: "library" + + onEntryClicked: function (id) { + emulatorScreen.loadGame(id) } } } SequentialAnimation { id: closeGameAnimation + objectName: "Close Game Animation" ScriptAction { script: { - stackView.push(mainMenu) + stackView.push(homeScreen) } } ScriptAction { script: { - emulator.stopEmulation() - } - } - ScriptAction { - script: { - emulatorStack.pop() - } - } - } - - Component { - id: mainMenu - - Item { - - Keys.onEscapePressed: function (event) { - if (!event.isAutoRepeat) { - closeAppConfirmationDialog.open() - } - } - focus: true - - Pane { - id: drawer - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - width: 250 - background: Rectangle { - color: "#101114" - } - padding: 4 - KeyNavigation.right: stackview - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - Item { - Layout.fillWidth: true - Layout.preferredHeight: 10 - } - - Text { - text: "Firelight" - opacity: parent.width > 48 ? 1 : 0 - color: "#dadada" - font.pointSize: 12 - font.weight: Font.DemiBold - font.family: Constants.regularFontFamily - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - } - - Text { - text: "alpha (0.5.0a)" - opacity: parent.width > 48 ? 1 : 0 - color: "#dadada" - font.pointSize: 8 - font.weight: Font.DemiBold - font.family: Constants.regularFontFamily - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - } - - Text { - Layout.fillWidth: true - Layout.preferredHeight: 12 - } - NavMenuButton { - id: homeNavButton - KeyNavigation.down: libraryNavButton - labelText: "Dashboard" - labelIcon: "\ue871" - Layout.preferredWidth: parent.width - Layout.preferredHeight: 48 - enabled: false - - checked: stackview.topLevelName === "home" - } - NavMenuButton { - id: modNavButton - KeyNavigation.down: controllersNavButton - labelText: "Mod Shop" - labelIcon: "\uef48" - Layout.preferredWidth: parent.width - Layout.preferredHeight: 48 - enabled: false - - checked: stackview.topLevelName === "mods" - - onToggled: function () { - stackview.replace(null, modsPage) - } - } - NavMenuButton { - id: libraryNavButton - KeyNavigation.down: modNavButton - labelText: "My Library" - labelIcon: "\uf53e" - Layout.preferredWidth: parent.width - Layout.preferredHeight: 48 - - checked: stackview.topLevelName === "library" - - onToggled: function () { - stackview.replace(null, libraryPage) - } - } - NavMenuButton { - id: controllersNavButton - // KeyNavigation.down: nowPlayingNavButton - labelText: "Controllers" - labelIcon: "\uf135" - Layout.preferredWidth: parent.width - Layout.preferredHeight: 48 - - // enabled: true - - checked: stackview.topLevelName === "controllers" - - onToggled: function () { - stackview.replace(null, controllerPage) - } - } - // Rectangle { - // Layout.topMargin: 8 - // Layout.bottomMargin: 8 - // Layout.preferredWidth: parent.width - // Layout.preferredHeight: 1 - // opacity: 0.3 - // color: "#dadada" - // } - // NavMenuButton { - // id: nowPlayingNavButton - // KeyNavigation.down: settingsNavButton - // labelText: "Now Playing" - // labelIcon: "\ue037" - // Layout.preferredWidth: parent.width - // Layout.preferredHeight: 48 - // - // onToggled: function () { - // stackview.replace(nowPlayingPage) - // } - // } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - NavMenuButton { - id: nowPlayingNavButton - labelText: "Back to game" - labelIcon: "\ue037" - Layout.preferredWidth: parent.width - Layout.preferredHeight: 48 - - visible: emulator.running - - checkable: false - - onClicked: function () { - stackView.pop() - // stackview.push(nowPlayingPage) - } - } - Rectangle { - Layout.fillWidth: true - Layout.topMargin: 8 - Layout.bottomMargin: 4 - Layout.preferredHeight: 1 - color: "#404143" - } - // NavMenuButton { - // id: settingsNavButton - // labelText: "Settings" - // labelIcon: "\ue8b8" - // Layout.preferredWidth: parent.width - // Layout.preferredHeight: 48 - // - // checked: stackview.topLevelName === "settings" - // - // onToggled: function () { - // stackView.push(settingsPage) - // } - // } - - RowLayout { - Layout.preferredWidth: parent.width - Layout.preferredHeight: 48 - Layout.maximumHeight: 48 - spacing: 8 - - Button { - background: Rectangle { - color: "transparent" - radius: 4 - } - Layout.preferredHeight: 42 - Layout.fillWidth: true - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - - checkable: false - // Layout.fillHeight: true - // Layout.fillWidth: true - } - - // NavMenuButton { - // labelText: "Profile" - // labelIcon: "\ue853" - // Layout.fillWidth: true - // Layout.fillHeight: true - // - // checked: stackview.topLevelName === "profile" - // - // enabled: false - // } - - Button { - id: me - background: Rectangle { - color: enabled ? (me.hovered ? "#404143" : "transparent") : "transparent" - radius: 4 - } - Layout.preferredHeight: 42 - Layout.preferredWidth: 42 - Layout.alignment: Qt.AlignCenter - - hoverEnabled: true - - contentItem: Text { - text: "\ue8b8" - font.family: Constants.symbolFontFamily - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.pixelSize: 26 - color: "#c3c3c3" - } - - checkable: false - - onClicked: { - Router.navigateTo("settings") - } - // Layout.fillHeight: true - // Layout.fillWidth: true - } - } - - - } - } - - Pane { - id: rightSide - - anchors.top: parent.top - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.left: drawer.right - - background: Item { - } - - Pane { - width: parent.width - height: 48 - - z: 2 - - background: Item { - } - - Button { - id: melol - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - // horizontalPadding: 12 - // verticalPadding: 8 - - enabled: stackview.depth > 1 - - hoverEnabled: false - - HoverHandler { - id: myHover - cursorShape: melol.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor - } - - background: Rectangle { - color: enabled ? myHover.hovered ? "#4e535b" : "#3e434b" : "#3e434b" - radius: height / 2 - // border.color: "#7d848c" - } - - contentItem: Text { - text: "\ue5c4" - color: enabled ? "white" : "#7d848c" - font.pointSize: 11 - font.family: Constants.symbolFontFamily - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - onClicked: { - stackview.pop() - } - } - } - - StackView { - id: stackview - anchors.fill: parent - - property string topLevelName: "" - - onCurrentItemChanged: { - if (currentItem) { - let top = stackview.find(function (item, index) { - return item.topLevel === true - }) - - stackview.topLevelName = top ? top.topLevelName : "" - } - } - - initialItem: libraryPage - - pushEnter: Transition { - // PropertyAnimation { - // property: "opacity" - // from: 0 - // to: 1 - // duration: 200 - // } - } - pushExit: Transition { - // PropertyAnimation { - // property: "opacity" - // from: 1 - // to: 0 - // duration: 200 - // } - } - popEnter: Transition { - // PropertyAnimation { - // property: "opacity" - // from: 0 - // to: 1 - // duration: 200 - // } - } - popExit: Transition { - // PropertyAnimation { - // property: "opacity" - // from: 1 - // to: 0 - // duration: 200 - // } - } - replaceEnter: Transition { - // ParallelAnimation { - // PropertyAnimation { - // property: "opacity" - // from: 0 - // to: 1 - // duration: 400 - // } - // PropertyAnimation { - // property: "x" - // from: 20 - // to: 0 - // duration: 250 - // } - // } - } - replaceExit: Transition { - } - } - } - - FirelightDialog { - id: closeAppConfirmationDialog - text: "Are you sure you want to close Firelight?" - - onAccepted: { - Qt.callLater(Qt.quit) - } + emulatorScreen.stopEmulator() } } } SequentialAnimation { id: overlayFadeIn + objectName: "Start Game Animation" PropertyAction { target: overlay property: "opacity" @@ -608,299 +275,31 @@ ApplicationWindow { ScriptAction { script: { // gameLaunchPopup.open() - emulator.startEmulation() + emulatorScreen.startEmulator() // emulator.resumeGame() } } } - // GameLoader { - // id: gameLoader - // - // onGameLoaded: function (entryId, romData, saveData, corePath) { - // emulator.loadTheThing(entryId, romData, saveData, corePath) - // overlayFadeIn.start() - // } - // - // onGameLoadFailedOrphanedPatch: function (entryId) { - // patchClickedDialog.open() - // } - // } - - EmulatorPage { - id: emulator - visible: false - - onReadyToStart: function () { - overlayFadeIn.start() - } - - ChallengeIndicatorList { - id: challengeIndicators - visible: achievement_manager.challengeIndicatorsEnabled - - anchors.top: parent.top - anchors.right: parent.right - anchors.topMargin: 16 - anchors.rightMargin: 16 - height: 100 - width: 300 - } - - states: [ - State { - name: "stopped" - }, - State { - name: "suspended" - PropertyChanges { - target: emulatorDimmer - opacity: 0.4 - } - PropertyChanges { - emulator { - layer.enabled: true - blurAmount: 1 - } - } - }, - State { - name: "running" - PropertyChanges { - target: emulatorDimmer - opacity: 0 - } - PropertyChanges { - emulator { - layer.enabled: false - blurAmount: 0 - } - } - } - ] - - transitions: [ - Transition { - from: "*" - to: "suspended" - SequentialAnimation { - ScriptAction { - script: { - emulator.pauseGame() - } - } - PropertyAction { - target: emulator - property: "layer.enabled" - value: true - } - ParallelAnimation { - NumberAnimation { - properties: "blurAmount" - duration: 250 - easing.type: Easing.InOutQuad - } - NumberAnimation { - target: emulatorDimmer - properties: "opacity" - duration: 250 - easing.type: Easing.InOutQuad - } - } - } - }, - Transition { - from: "*" - to: "running" - SequentialAnimation { - ParallelAnimation { - NumberAnimation { - properties: "blurAmount" - duration: 250 - easing.type: Easing.InOutQuad - } - NumberAnimation { - target: emulatorDimmer - properties: "opacity" - duration: 250 - easing.type: Easing.InOutQuad - } - } - PropertyAction { - target: emulator - property: "layer.enabled" - value: false - } - - ScriptAction { - script: { - emulator.resumeGame() - } - - } - } - } - ] - - StackView.visible: true - - StackView.onActivating: { - state = "running" - } - - StackView.onDeactivating: { - state = "suspended" - } - - property double blurAmount: 0 - - Behavior on blurAmount { - NumberAnimation { - duration: 250 - easing.type: Easing.InOutQuad - } - } - - layer.enabled: false - layer.effect: MultiEffect { - source: emulator - anchors.fill: emulator - blurEnabled: true - blurMultiplier: 1.0 - blurMax: 64 - blur: emulator.blurAmount - } - - Rectangle { - id: emulatorDimmer - anchors.fill: parent - color: "black" - opacity: 0 - - Behavior on opacity { - NumberAnimation { - duration: 250 - easing.type: Easing.InOutQuad - } - } - } - - Connections { - target: window_resize_handler - - function onWindowResizeStarted() { - if (emulator.StackView.view.currentItem === emulator) { - emulator.pauseGame() - } - } - - function onWindowResizeFinished() { - if (emulator.StackView.view.currentItem === emulator) { - emulator.resumeGame() - } - } - } - } - - StackView { - id: emulatorStack - visible: false - - initialItem: emulator - - Keys.onEscapePressed: function (event) { - if (event.isAutoRepeat) { - return - } - - if (emulatorStack.currentItem === emulator) { - // emulatorStack.pop() - emulatorStack.push(nowPlayingPage) - } else { - emulatorStack.pop() - // emulatorStack.push(mainMenu) - } - } - - property bool suspended: false - property bool running: false - - pushEnter: Transition { - ParallelAnimation { - PropertyAnimation { - property: "opacity" - from: 0 - to: 1 - duration: 250 - easing.type: Easing.InOutQuad - } - PropertyAnimation { - property: "x" - from: -20 - to: 0 - duration: 250 - easing.type: Easing.InOutQuad - } - } - } - pushExit: Transition { - - } - popEnter: Transition { - } - popExit: Transition { - ParallelAnimation { - PropertyAnimation { - property: "opacity" - from: 1 - to: 0 - duration: 250 - easing.type: Easing.InOutQuad - } - PropertyAnimation { - property: "x" - from: 0 - to: -20 - duration: 250 - easing.type: Easing.InOutQuad - } - } - } - replaceEnter: Transition { - } - replaceExit: Transition { - } - } - - // Rectangle { - // id: bar - // color: "red" - // anchors.top: parent.top - // anchors.left: parent.left - // anchors.right: parent.right - // height: 40 - // - // DragHandler { - // grabPermissions: TapHandler.CanTakeOverFromAnything - // onActiveChanged: if (active) { - // window.startSystemMove(); - // } - // } - // - // } - StackView { id: stackView - // anchors.left: parent.left - // anchors.right: parent.right - // anchors.bottom: parent.bottom - // anchors.top: bar.bottom + objectName: "Screen Stack View" + anchors.fill: parent + initialItem: emulatorScreen focus: true - initialItem: emulatorStack + Component.onCompleted: stackView.push(homeScreen, {}, StackView.Immediate) - Component.onCompleted: stackView.push(mainMenu, {}, StackView.Immediate) + Keys.onReleased: function (event) { + if (event.key === Qt.Key_F12) { + debugWindow.visible = !debugWindow.visible + event.accepted = true + } else if (event.key === Qt.Key_F11) { + debugWindow.locked = !debugWindow.locked + event.accepted = true + } + } pushEnter: Transition { ParallelAnimation { @@ -976,8 +375,10 @@ ApplicationWindow { } } + Rectangle { id: overlay + objectName: "Screen Overlay" anchors.fill: parent color: "black" visible: false diff --git a/qml/Main2.qml b/qml/Main2.qml new file mode 100644 index 0000000..9573862 --- /dev/null +++ b/qml/Main2.qml @@ -0,0 +1,267 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Window +import QtQml.Models +import QtQuick.Layouts 1.0 +import QtQuick.Effects +import Firelight 1.0 + +ApplicationWindow { + id: window + objectName: "Application Window" + + width: 1280 + height: 720 + + // font.family: "Segoe UI" + + // flags: Qt.FramelessWindowHint + + visible: true + visibility: GeneralSettings.fullscreen ? Window.FullScreen : Window.Windowed + + title: qsTr("Firelight") + + background: Rectangle { + color: ColorPalette.neutral900 + } + + Connections { + target: Router + + function onRouteChanged(route) { + let parts = route.split("/") + if (parts.length > 0) { + let id = parts[0] + + if (id === "settings") { + let section = parts.length > 1 ? parts[1] : "library" + screenStack.push(settingsScreen, {section: section}) + } + } + } + } + + onActiveFocusItemChanged: { + if (activeFocusItem === null) { + return + } + + activeFocusItem.grabToImage(function (result) { + debugImage.width = result.width + debugImage.height = result.height + debugImage.source = result.url + }) + } + + Window { + id: debugWindow + objectName: "DebugWindow" + + width: 400 + height: 400 + + visible: false + title: "Debug" + + color: "black" + + Image { + id: debugImage + source: "" + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + } + + } + + EmulatorScreen { + id: emulatorScreen + + Component.onCompleted: { + console.log("Graphics Info:") + console.log(" API:", emulatorScreen.GraphicsInfo.api) + console.log(" Major Version:", emulatorScreen.GraphicsInfo.majorVersion) + console.log(" Minor Version:", emulatorScreen.GraphicsInfo.minorVersion) + console.log(" Profile:", emulatorScreen.GraphicsInfo.profile) + } + + onGameAboutToStop: function () { + screenStack.pushItem(homeScreen, {}, StackView.PushTransition) + } + + } + + // Rectangle { + // id: cursor + // parent: window.activeFocusItem + // anchors.fill: parent + // anchors.margins: -2 + // color: "transparent" + // border.color: "lightblue" + // border.width: 3 + // radius: 4 + // } + + StackView { + id: screenStack + anchors.fill: parent + focus: true + + Component.onCompleted: { + screenStack.pushItems([emulatorScreen, homeScreen]) + } + + Keys.onPressed: function (event) { + if (event.key === Qt.Key_F12) { + debugWindow.visible = !debugWindow.visible + } + } + + pushEnter: Transition { + ParallelAnimation { + PropertyAnimation { + property: "scale" + from: 1.05 + to: 1 + duration: 250 + easing.type: Easing.InOutQuad + } + PropertyAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + pushExit: Transition { + ParallelAnimation { + PropertyAnimation { + property: "opacity" + from: 1 + to: 0 + duration: 250 + easing.type: Easing.InOutQuad + } + PropertyAnimation { + property: "scale" + from: 1 + to: 0.95 + duration: 250 + easing.type: Easing.OutQuad + } + } + } + popEnter: Transition { + ParallelAnimation { + PropertyAnimation { + property: "scale" + from: 0.95 + to: 1 + duration: 250 + easing.type: Easing.InQuad + } + PropertyAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + popExit: Transition { + ParallelAnimation { + PropertyAnimation { + property: "opacity" + from: 1 + to: 0 + duration: 250 + easing.type: Easing.InOutQuad + } + PropertyAnimation { + property: "scale" + from: 1 + to: 1.05 + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + } + + + Rectangle { + id: overlay + objectName: "Screen Overlay" + anchors.fill: parent + color: "black" + visible: false + } + + SequentialAnimation { + id: gameStartAnimation + running: false + + property int entryId: -1 + + PropertyAction { + target: overlay + property: "opacity" + value: 0 + } + + PropertyAction { + target: overlay + property: "visible" + value: true + } + + PropertyAnimation { + target: overlay + property: "opacity" + from: 0 + to: 1 + duration: 350 + easing.type: Easing.InQuad + } + + ScriptAction { + script: { + screenStack.popCurrentItem(StackView.Immediate) + emulatorScreen.loadGame(gameStartAnimation.entryId) + } + } + + PauseAnimation { + duration: 1000 + } + + PropertyAction { + target: overlay + property: "visible" + value: false + } + } + + Component { + id: homeScreen + + HomeScreen { + onStartGame: function (entryId) { + gameStartAnimation.entryId = entryId + gameStartAnimation.running = true + } + } + } + + Component { + id: settingsScreen + + SettingsScreen { + } + } +} diff --git a/qml/NavMenuButton.qml b/qml/NavMenuButton.qml index 6ff8059..40a8947 100644 --- a/qml/NavMenuButton.qml +++ b/qml/NavMenuButton.qml @@ -37,7 +37,7 @@ Button { Layout.fillHeight: true Layout.fillWidth: true font.pointSize: 11 - font.family: Constants.regularFontFamily + // font.family: Constants.regularFontFamily color: control.enabled ? "white" : "#aaaaaa" horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter diff --git a/qml/achievements/AchievementList.qml b/qml/achievements/AchievementList.qml new file mode 100644 index 0000000..6dd63f2 --- /dev/null +++ b/qml/achievements/AchievementList.qml @@ -0,0 +1,131 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts 1.0 +import Firelight 1.0 + +FocusScope { + id: control + + required property int gameId + required property bool achievementsEnabled + required property bool loggedIn + + required property var achievementsModel + + ListView { + visible: control.loggedIn + objectName: "Achievements List View" + model: control.achievementsModel + spacing: 12 + anchors.fill: parent + focus: true + + // section.property: root.achievements.sortType === "title" ? "name" : "earned" + // section.criteria: ViewSection.FirstCharacter + // section.delegate: ListViewSectionDelegate { + // required property string section + // text: section === "t" || section === "f" ? (section === "t" ? "Earned" : "Not earned") : section + // } + + header: Pane { + width: parent.width + background: Item { + } + verticalPadding: 12 + horizontalPadding: 0 + contentItem: RowLayout { + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + Text { + Layout.fillHeight: true + verticalAlignment: Text.AlignVCenter + text: "Sort by:" + color: "white" + font.family: Constants.regularFontFamily + font.pointSize: 10 + } + + MyComboBox { + id: sortBox + Layout.fillHeight: true + Layout.fillWidth: false + textRole: "text" + valueRole: "value" + + model: [ + {text: "Default", value: "default"}, + {text: "A-Z", value: "title"}, + {text: "Earned date", value: "earned_date"}, + {text: "Points", value: "points"} + ] + + // Connections { + // target: root + // + // function onAchievementsChanged() { + // console.log("current sort type: " + root.achievements.sortType) + // sortBox.currentIndex = sortBox.indexOfValue(root.achievements.sortType) + // } + // } + + // onActivated: function () { + // root.achievements.sortType = currentValue + // } + } + } + } + + delegate: AchievementListItem { + objectName: "Achievement List Item (" + model.achievement_id + ")" + width: ListView.view.width + } + } + + ColumnLayout { + id: loginColumn + objectName: "Log in column" + visible: !control.loggedIn + anchors.fill: parent + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + Text { + text: "Log in blah blah" + color: "white" + font.family: Constants.regularFontFamily + font.pointSize: 10 + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + Button { + Layout.alignment: Qt.AlignCenter + Layout.preferredWidth: 140 + Layout.preferredHeight: 50 + focus: true + background: Rectangle { + color: parent.hovered ? "#b8b8b8" : "white" + radius: 4 + } + hoverEnabled: true + contentItem: Text { + text: qsTr("Log in") + color: Constants.colorTestBackground + font.family: Constants.regularFontFamily + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pointSize: 12 + } + onClicked: { + Router.navigateTo("settings/achievements") + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } +} \ No newline at end of file diff --git a/qml/achievements/AchievementListItem.qml b/qml/achievements/AchievementListItem.qml index 03733e6..5fa77f9 100644 --- a/qml/achievements/AchievementListItem.qml +++ b/qml/achievements/AchievementListItem.qml @@ -149,45 +149,5 @@ Pane { rightClickMenu.popup() } } - - // Row { - // Layout.fillHeight: true - // Layout.preferredWidth: 24 - // Layout.rightMargin: 12 - // spacing: 4 - // - // Text { - // id: pointsText - // anchors.verticalCenter: parent.verticalCenter - // text: row.model.points - // font.pointSize: 16 - // font.family: Constants.regularFontFamily - // font.weight: Font.DemiBold - // verticalAlignment: Text.AlignVCenter - // horizontalAlignment: Text.AlignHCenter - // color: "white" - // } - // - // Text { - // anchors.verticalCenter: parent.verticalCenter - // height: pointsText.height - // text: "pts" - // font.pointSize: 10 - // font.family: Constants.regularFontFamily - // verticalAlignment: Text.AlignBottom - // horizontalAlignment: Text.AlignHCenter - // color: "white" - // } - // } - // Text { - // id: earnedSection - // Layout.fillHeight: true - // Layout.preferredWidth: 260 - // text: row.model.earned ? "Earned on " + row.model.earned_date_hardcore : "Not earned" - // font.pointSize: 11 - // font.family: Constants.regularFontFamily - // font.weight: Font.Normal - // color: "white" - // } } } \ No newline at end of file diff --git a/qml/common/ComboBoxOption.qml b/qml/common/ComboBoxOption.qml index 5f9f47c..3b87faf 100644 --- a/qml/common/ComboBoxOption.qml +++ b/qml/common/ComboBoxOption.qml @@ -5,8 +5,35 @@ import QtQuick.Layouts Option { id: root - control: ComboBox { - model: ["Windowed", "Fullscreen"] + + required property var model + property int currentIndex: 0 + property string currentValue + property string textRole: "text" + property string valueRole: "value" + // property alias model: control.model + // property alias currentIndex: control.currentIndex + // property alias textRole: control.textRole + // property alias valueRole: control.valueRole + + control: MyComboBox { + id: control + model: root.model + visible: currentIndex !== -1 + currentIndex: root.currentIndex + onCurrentIndexChanged: function () { + root.currentIndex = control.currentIndex + root.currentValue = control.model[control.currentIndex][root.valueRole] + } + // onCurrentValueChanged: root.currentValue = control.currentValue + textRole: root.textRole + valueRole: root.valueRole + + Component.onCompleted: { + if (root.currentValue) { + control.currentIndex = find(root.currentValue) + } + } } } diff --git a/qml/common/DetailsButton.qml b/qml/common/DetailsButton.qml index 31dc49c..ca49ac9 100644 --- a/qml/common/DetailsButton.qml +++ b/qml/common/DetailsButton.qml @@ -2,16 +2,17 @@ import QtQuick import QtQuick.Controls Button { + id: root padding: 2 hoverEnabled: true background: Rectangle { - color: parent.hovered ? "#ffffff" : "transparent" + color: root.hovered ? "#ffffff" : "transparent" opacity: 0.1 radius: height / 2 } - + contentItem: Text { font.family: Constants.symbolFontFamily text: "\ue5d4" diff --git a/qml/common/MyComboBox.qml b/qml/common/MyComboBox.qml index 579426b..fdde3e8 100644 --- a/qml/common/MyComboBox.qml +++ b/qml/common/MyComboBox.qml @@ -4,38 +4,47 @@ import QtQuick.Layouts ComboBox { id: control + implicitContentWidthPolicy: ComboBox.WidestText + + indicator: Text { + x: control.mirrored ? control.padding : control.width - width - control.padding + y: control.topPadding + (control.availableHeight - height) / 2 + text: "\ue5c5" + font.family: Constants.symbolFontFamily + font.pixelSize: 32 + padding: 4 + color: "#ececec" + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } - contentItem: Pane { - background: Item { - } - padding: 0 + contentItem: TextInput { + topPadding: 8 + bottomPadding: 8 + leftPadding: 10 + rightPadding: 10 + text: control.currentText + color: "#ececec" + font.family: Constants.regularFontFamily + verticalAlignment: Text.AlignVCenter + font.pointSize: 11 + font.weight: Font.DemiBold + + readOnly: true MouseArea { anchors.fill: parent onClicked: { + control.forceActiveFocus(Qt.MouseFocusReason) control.popup.visible = !control.popup.visible } } - - Text { - topPadding: 8 - bottomPadding: 8 - leftPadding: 10 - rightPadding: 10 - anchors.fill: parent - text: control.currentText - color: "#ececec" - font.family: Constants.regularFontFamily - verticalAlignment: Text.AlignVCenter - font.pointSize: 11 - font.weight: Font.DemiBold - } } background: Rectangle { - implicitWidth: 140 + // implicitWidth: 140 implicitHeight: 40 - color: "#32363a" + color: ColorPalette.neutral700 radius: 8 } @@ -46,7 +55,7 @@ ComboBox { required property int index background: Rectangle { - color: control.highlightedIndex === index ? "#25282c" : "#1d1e22" + color: control.highlightedIndex === index ? ColorPalette.neutral800 : ColorPalette.neutral900 } width: control.width - 4 @@ -57,7 +66,7 @@ ComboBox { text: delegate.model[control.textRole] font.family: Constants.regularFontFamily font.pointSize: 11 - color: parent.highlighted ? ColorPalette.white : ColorPalette.dropdownInactiveTextColor + color: control.currentIndex === index ? ColorPalette.neutral100 : ColorPalette.neutral300 elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } @@ -128,9 +137,9 @@ ComboBox { } background: Rectangle { - color: ColorPalette.dropdownPopupBackgroundColor + color: ColorPalette.neutral900 radius: 2 - border.color: ColorPalette.dropdownPopupBorderColor + border.color: ColorPalette.neutral700 } } diff --git a/qml/common/MySlider.qml b/qml/common/MySlider.qml new file mode 100644 index 0000000..a360a63 --- /dev/null +++ b/qml/common/MySlider.qml @@ -0,0 +1,169 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Slider { + id: control + + property bool intervalMarkers: true + property int intervalMarkerWidth: 2 + property int intervalMarkerHeight: 24 + + property int numIntervals: (to - from) / stepSize + property bool valuesAsPercent: false + + property int widthDiff: width - availableWidth + property int heightDiff: height - availableHeight + + implicitHeight: 60 + + handle: Rectangle { + color: ColorPalette.neutral100 + width: 10 + height: 24 + radius: 2 + x: control.availableWidth * control.visualPosition - (width / 2) + control.widthDiff / 2 + y: 4 + z: 4 + } + + background: Item { + Item { + id: bar + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 32 + Rectangle { + height: 8 + width: control.availableWidth + x: control.widthDiff / 2 + y: parent.height / 2 - (height / 2) + color: ColorPalette.neutral500 + + // Rectangle { + // height: 24 + // color: "grey" + // width: 2 + // x: -(width / 2) + // y: -height / 2 + (parent.height / 2) + // } + + Repeater { + model: control.numIntervals + 1 + delegate: Rectangle { + required property var index + // required property var model + + height: control.intervalMarkerHeight + color: ColorPalette.neutral500 + width: control.intervalMarkerWidth + x: (index) * (control.availableWidth / control.numIntervals) - (width / 2) + y: -height / 2 + (parent.height / 2) + } + } + + + // Rectangle { + // height: 24 + // color: "grey" + // width: 2 + // x: control.availableWidth / 3 - (width / 2) + // y: -height / 2 + (parent.height / 2) + // } + + Rectangle { + color: "#d14c20" + height: parent.height + width: control.availableWidth * control.visualPosition + } + } + } + + // Rectangle { + // height: 24 + // color: "grey" + // width: 2 + // x: control.widthDiff / 2 - (width / 2) + // y: -height / 2 + (parent.height / 2) + // } + + Item { + anchors.top: bar.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + // height: parent.height / 3 + // y: parent.height * 2 / 3 + // x: control.widthDiff / 2 + + Repeater { + model: control.numIntervals + 1 + delegate: Text { + required property var index + property int val: control.from + (index * control.stepSize) + text: (val) + "%" + font.pointSize: 10 + font.weight: Font.Medium + color: ColorPalette.neutral400 + + x: (control.widthDiff / 2) + (index) * (control.availableWidth / control.numIntervals) - (width / 2) + } + + // Rectangle { + // required property var index + // // required property var model + // + // height: control.intervalMarkerHeight + // color: "grey" + // width: control.intervalMarkerWidth + // x: (index + 1) * (control.availableWidth / control.numIntervals) - (width / 2) + // y: -height / 2 + (parent.height / 2) + // } + } + } + + + // Rectangle { + // height: 24 + // color: "grey" + // width: 2 + // x: control.availableWidth / 3 - (width / 2) + // y: -height / 2 + (parent.height / 2) + // } + + // Item { + // width: parent.width + // height: parent.height / 3 + // y: parent.height * 2 / 3 + // + // Text { + // text: "None" + // font.pointSize: 8 + // color: "white" + // x: -(width / 2) + // } + // + // Text { + // text: "Light" + // font.pointSize: 8 + // color: "white" + // x: control.availableWidth / 3 - (width / 2) + // } + // + // Text { + // text: "Compatible" + // font.pointSize: 8 + // color: "white" + // x: control.availableWidth * 2 / 3 - (width / 2) + // } + // + // Text { + // text: "Max" + // font.pointSize: 8 + // color: "white" + // x: control.availableWidth - (width / 2) + // } + // } + } +} \ No newline at end of file diff --git a/qml/common/NavigationTabBar.qml b/qml/common/NavigationTabBar.qml new file mode 100644 index 0000000..85982e5 --- /dev/null +++ b/qml/common/NavigationTabBar.qml @@ -0,0 +1,59 @@ +import QtQml +import QtQuick +import QtQuick.Controls + +TabBar { + id: control + + required property int tabWidth + required property list tabs + + currentIndex: 0 + + background: Rectangle { + objectName: "background" + width: control.tabWidth + height: 2 + radius: 1 + color: "white" + x: control.tabWidth * control.currentIndex + y: control.height + + Behavior on x { + NumberAnimation { + duration: 120 + easing.type: Easing.InOutQuad + } + } + } + + Repeater { + model: control.tabs + delegate: TabButton { + required property var modelData + required property var index + + objectName: "button" + index + + width: control.tabWidth + contentItem: Text { + objectName: "text" + text: modelData + color: parent.enabled ? "#ffffff" : "#666666" + font.family: Constants.regularFontFamily + font.pointSize: 11 + font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + background: Item { + } + + HoverHandler { + objectName: "hoverHandler" + cursorShape: Qt.PointingHandCursor + } + } + } +} \ No newline at end of file diff --git a/qml/common/Option.qml b/qml/common/Option.qml index bbfa2d6..b199756 100644 --- a/qml/common/Option.qml +++ b/qml/common/Option.qml @@ -7,7 +7,10 @@ Pane { required property string label property string description - required property Component control + // required property Component control + property Component control + + property bool isSubItem: false background: Item { } @@ -18,15 +21,29 @@ Pane { RowLayout { anchors.fill: parent + Text { + Layout.fillHeight: true + visible: root.isSubItem + leftPadding: 8 + rightPadding: 8 + text: "\ue5da" + font.family: Constants.symbolFontFamily + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 24 + color: ColorPalette.neutral600 + } + ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true Layout.horizontalStretchFactor: 3 + Text { Layout.fillWidth: true text: root.label - color: "white" - font.pointSize: 12 + color: ColorPalette.neutral100 + font.pixelSize: 15 Layout.alignment: Qt.AlignLeft font.family: Constants.regularFontFamily font.weight: Font.DemiBold @@ -36,11 +53,12 @@ Pane { Layout.fillWidth: true visible: root.description !== "" text: root.description - font.pointSize: 11 + font.pixelSize: 13 Layout.alignment: Qt.AlignLeft font.family: Constants.regularFontFamily + // font.weight: Font. wrapMode: Text.WordWrap - color: "#c1c1c1" + color: ColorPalette.neutral300 } } @@ -63,7 +81,7 @@ Pane { // } Loader { - Layout.fillHeight: true + // Layout.fillHeight: true Layout.alignment: Qt.AlignRight | Qt.AlignTop sourceComponent: control diff --git a/qml/common/OptionGroup.qml b/qml/common/OptionGroup.qml new file mode 100644 index 0000000..ec17053 --- /dev/null +++ b/qml/common/OptionGroup.qml @@ -0,0 +1,67 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + required property string label + + implicitHeight: theLabel.implicitHeight + thePane.implicitHeight + implicitWidth: theLabel.implicitWidth + thePane.implicitWidth + + default property alias content: stuffCol.children + + Text { + id: theLabel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text: root.label + font.pixelSize: 15 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + Layout.bottomMargin: 4 + color: ColorPalette.neutral400 + } + + Pane { + id: thePane + anchors.top: theLabel.bottom + anchors.topMargin: 8 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + topPadding: stuffCol.spacing + bottomPadding: topPadding * 3 + + background: Rectangle { + radius: 8 + color: ColorPalette.neutral800 + } + + contentItem: ColumnLayout { + id: stuffCol + + // ToggleOption { + // Layout.fillWidth: true + // label: "Simulate LCD ghosting" + // // description: "Enables simulation of LCD ghosting effects by blending the current and previous frames." + // + // Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_mix_frames") === "accurate" + // } + // + // onCheckedChanged: { + // if (checked) { + // emulator_config_manager.setOptionValueForPlatform(1, "gambatte_mix_frames", "accurate") + // } else { + // emulator_config_manager.setOptionValueForPlatform(1, "gambatte_mix_frames", "disabled") + // } + // } + // + // } + } + } +} + diff --git a/qml/common/RadioButtonGroup.qml b/qml/common/RadioButtonGroup.qml new file mode 100644 index 0000000..ece5d26 --- /dev/null +++ b/qml/common/RadioButtonGroup.qml @@ -0,0 +1,113 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + + height: col.height + + property string label + property string description + + property string currentValue + required property var model + + ColumnLayout { + id: col + width: root.width + // anchors.fill: parent + + Text { + Layout.fillWidth: true + text: root.label + color: ColorPalette.neutral100 + font.pixelSize: 15 + Layout.alignment: Qt.AlignLeft + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + verticalAlignment: Text.AlignVCenter + } + // Text { + // Layout.fillWidth: true + // // visible: root.description !== "" + // text: root.description + // font.pixelSize: 13 + // Layout.alignment: Qt.AlignLeft + // font.family: Constants.regularFontFamily + // // font.weight: Font. + // wrapMode: Text.WordWrap + // color: ColorPalette.neutral300 + // } + + Repeater { + model: root.model + delegate: RadioButton { + id: control + + required property var model + required property var index + + Layout.fillWidth: true + Layout.preferredHeight: 60 + // checked: true + // Layout.fillWidth: true + // Layout.preferredHeight: 60 + + background: Rectangle { + radius: 8 + color: ColorPalette.neutral800 + } + + indicator: Rectangle { + implicitWidth: 26 + implicitHeight: 26 + x: control.leftPadding + y: parent.height / 2 - height / 2 + radius: 13 + border.color: control.down ? "#17a81a" : "#21be2b" + + Rectangle { + width: 14 + height: 14 + x: 6 + y: 6 + radius: 7 + color: control.down ? "#17a81a" : "#21be2b" + visible: control.checked + } + } + + contentItem: ColumnLayout { + Text { + Layout.fillWidth: true + Layout.fillHeight: true + leftPadding: control.indicator.width + control.spacing + text: model.label + color: ColorPalette.neutral100 + font.pixelSize: 15 + Layout.alignment: Qt.AlignLeft + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + verticalAlignment: Text.AlignVCenter + } + Text { + Layout.fillHeight: true + Layout.fillWidth: true + // visible: root.description !== "" + text: model.description + leftPadding: control.indicator.width + control.spacing + font.pixelSize: 13 + Layout.alignment: Qt.AlignLeft + font.family: Constants.regularFontFamily + // font.weight: Font. + wrapMode: Text.WordWrap + color: ColorPalette.neutral300 + } + } + } + } + } + + +} \ No newline at end of file diff --git a/qml/common/RightClickMenu.qml b/qml/common/RightClickMenu.qml index 15c70cd..fd7e7cf 100644 --- a/qml/common/RightClickMenu.qml +++ b/qml/common/RightClickMenu.qml @@ -1,7 +1,5 @@ import QtQuick import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Effects Menu { diff --git a/qml/common/SliderOption.qml b/qml/common/SliderOption.qml index 7678ac1..d27b1df 100644 --- a/qml/common/SliderOption.qml +++ b/qml/common/SliderOption.qml @@ -2,10 +2,13 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts - Option { id: root control: Slider { - + from: 0 + to: 50 + stepSize: 5 + value: 0 + snapMode: Slider.SnapAlways } } \ No newline at end of file diff --git a/qml/common/ToggleOption.qml b/qml/common/ToggleOption.qml index 18b7b8f..92eb15a 100644 --- a/qml/common/ToggleOption.qml +++ b/qml/common/ToggleOption.qml @@ -8,9 +8,7 @@ Option { property bool checked: false control: Switch { - id: control - Layout.fillHeight: true - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + id: theControl checked: root.checked onCheckedChanged: { root.checked = checked @@ -19,11 +17,11 @@ Option { indicator: Rectangle { implicitWidth: 50 implicitHeight: 28 - x: control.leftPadding + x: theControl.leftPadding y: parent.height / 2 - height / 2 radius: height / 2 - color: control.checked ? "#17a81a" : "#ffffff" - border.color: control.checked ? "#17a81a" : "#cccccc" + color: theControl.checked ? "#17a81a" : "#ffffff" + border.color: theControl.checked ? "#17a81a" : "#cccccc" Behavior on color { ColorAnimation { @@ -33,7 +31,7 @@ Option { } Rectangle { - x: control.checked ? parent.width - width : 0 + x: theControl.checked ? parent.width - width : 0 y: (parent.height - height) / 2 Behavior on x { @@ -46,8 +44,8 @@ Option { width: 26 height: 26 radius: height / 2 - color: control.down ? "#cccccc" : "#ffffff" - border.color: control.checked ? (control.down ? "#17a81a" : "#21be2b") : "#999999" + color: theControl.down ? "#cccccc" : "#ffffff" + border.color: theControl.checked ? (theControl.down ? "#17a81a" : "#21be2b") : "#999999" } } diff --git a/qml/controllers/ControllersPage.qml.bak b/qml/controllers/ControllersPage.qml.bak deleted file mode 100644 index c2b64a4..0000000 --- a/qml/controllers/ControllersPage.qml.bak +++ /dev/null @@ -1,386 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Pane { - id: root - - background: Rectangle { - color: "transparent" - } - - ListView { - id: playerList - width: 400 - height: 400 - anchors.centerIn: parent - spacing: 6 - - model: controller_model - delegate: RowLayout { - implicitWidth: 300 - implicitHeight: 48 - - Text { - text: "P" + (model.player_index + 1) - font.pointSize: 11 - font.family: Constants.semiboldFontFamily - color: "#b3b3b3" - Layout.fillWidth: true - Layout.fillHeight: true - Layout.horizontalStretchFactor: 1 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - - ContentPane { - Text { - text: model.model_name - anchors.fill: parent - font.pointSize: 11 - font.family: Constants.semiboldFontFamily - color: "white" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - - } - Layout.fillWidth: true - Layout.fillHeight: true - Layout.horizontalStretchFactor: 3 - } - } - - - // ContentPane { - // Text { - // text: controllerName - // anchors.fill: parent - // font.pointSize: 12 - // font.family: Constants.strongFontFamily - // color: "white" - // horizontalAlignment: Text.AlignLeft - // verticalAlignment: Text.AlignVCenter - // - // } - // width: parent.width - // height: 48 - // } - } - - // ColumnLayout { - // id: players - // anchors.centerIn: parent - // spacing: 8 - // - // RowLayout { - // Layout.preferredWidth: 300 - // Layout.preferredHeight: 48 - // - // Text { - // text: "P1" - // font.pointSize: 11 - // font.family: Constants.strongFontFamily - // color: "#b3b3b3" - // Layout.fillWidth: true - // Layout.fillHeight: true - // Layout.horizontalStretchFactor: 1 - // horizontalAlignment: Text.AlignLeft - // verticalAlignment: Text.AlignVCenter - // } - // - // ContentPane { - // Text { - // text: "Xbox One Controller for Windows" - // anchors.fill: parent - // font.pointSize: 12 - // font.family: Constants.strongFontFamily - // color: "#b3b3b3" - // horizontalAlignment: Text.AlignLeft - // verticalAlignment: Text.AlignVCenter - // - // } - // Layout.fillWidth: true - // Layout.fillHeight: true - // Layout.horizontalStretchFactor: 3 - // } - // } - - // ContentPane { - // id: playerOne - // Layout.preferredWidth: 300 - // Layout.preferredHeight: 48 - // - // Text { - // text: "Player 1" - // font.pointSize: 12 - // font.family: Constants.strongFontFamily - // color: "#b3b3b3" - // } - // } - // - // ContentPane { - // id: playerTwo - // Layout.preferredWidth: 300 - // Layout.preferredHeight: 48 - // - // Text { - // text: "Player 2" - // font.pointSize: 12 - // font.family: Constants.strongFontFamily - // color: "#b3b3b3" - // } - // } - // - // ContentPane { - // id: playerThree - // Layout.preferredWidth: 300 - // Layout.preferredHeight: 48 - // - // Text { - // text: "Player 3" - // font.pointSize: 12 - // font.family: Constants.strongFontFamily - // color: "#b3b3b3" - // } - // } - // - // ContentPane { - // id: playerFour - // Layout.preferredWidth: 300 - // Layout.preferredHeight: 48 - // - // Text { - // text: "Player 4" - // font.pointSize: 12 - // font.family: Constants.strongFontFamily - // color: "#b3b3b3" - // } - // } - - // Thing { - // Layout.preferredWidth: parent.width - // Layout.minimumHeight: 48 - // Layout.fillHeight: true - // - // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - // } - // - // Thing { - // Layout.preferredWidth: parent.width - // Layout.minimumHeight: 48 - // Layout.fillHeight: true - // - // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - // } - // - // Thing { - // Layout.preferredWidth: parent.width - // Layout.minimumHeight: 48 - // Layout.fillHeight: true - // - // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - // } - // - // Thing { - // Layout.preferredWidth: parent.width - // Layout.minimumHeight: 48 - // Layout.fillHeight: true - // - // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - // } - - // } - - - // Item { - // GridView { - // id: root - // width: parent.width - // height: parent.height - // cellWidth: parent.width - // cellHeight: parent.height / 4 - // - // displaced: Transition { - // NumberAnimation { - // properties: "x,y" - // easing.type: Easing.OutQuad - // } - // } - // - // // model: controller_model - // // delegate: Rectangle { - // // width: 300 - // // height: 72 - // // color: "black" - // // radius: 12 - // // - // // Text { - // // anchors.centerIn: parent - // // color: "white" - // // text: playerIndex + ": " + controllerName - // // } - // // - // // DragHandler { - // // id: dragHandler - // // } - // // } - // - // model: DelegateModel { - // id: visualModel - // model: controller_model - // - // component Thing: Rectangle { - // id: icon - // required property Item dragParent - // required property string controllerName - // required property int playerIndex - // - // property int visualIndex: 0 - // width: 300 - // height: 72 - // color: "black" - // - // anchors { - // horizontalCenter: parent.horizontalCenter - // verticalCenter: parent.verticalCenter - // } - // radius: 12 - // - // Text { - // anchors.centerIn: parent - // color: "white" - // text: controllerName - // } - // - // DragHandler { - // id: dragHandler - // } - // - // HoverHandler { - // cursorShape: icon.state === "Dragging" ? Qt.ClosedHandCursor : Qt.OpenHandCursor - // } - // - // Drag.active: dragHandler.active - // Drag.source: icon - // Drag.hotSpot.x: 36 - // Drag.hotSpot.y: 36 - // - // states: [ - // State { - // name: "NotDragging" - // when: !dragHandler.active - // }, - // State { - // name: "Dragging" - // when: dragHandler.active - // ParentChange { - // target: icon - // parent: icon.dragParent - // } - // - // AnchorChanges { - // target: icon - // anchors { - // horizontalCenter: undefined - // verticalCenter: undefined - // } - // } - // } - // ] - // - // transitions: [ - // Transition { - // from: "Dragging" - // to: "NotDragging" - // ScriptAction { - // script: { - // var newOrder = {}; - // for (var i = 0; i < visualModel.items.count; i++) { - // newOrder[i] = visualModel.items.get(i).model.playerIndex - // // console.log("playerIndex: " + visualModel.items.get(i).model.playerIndex) - // } - // // for (const key in visualModel.items) { - // // if (myObject.hasOwnProperty(key)) { - // // newOrder[visualModel.items[key].model.visualIndex] = visualModel.items[key].model.playerIndex; - // // } - // // } - // - // controller_model.updateControllerOrder(newOrder) - // // for (var i = 0; i < visualModel.items.count; i++) { - // // console.log("playerIndex: " + visualModel.items.get(i).model.playerIndex) - // // } - // } - // - // } - // } - // ] - // } - // - // delegate: DropArea { - // id: delegateRoot - // required property string controllerName - // required property int playerIndex - // - // width: 300 - // height: 80 - // - // onEntered: function (drag) { - // visualModel.items.move(drag.source.visualIndex, icon2.visualIndex) - // controller_model.swap(drag.source.visualIndex, icon2.visualIndex) - // } - // - // property int visualIndex: DelegateModel.itemsIndex - // - // Thing { - // id: icon2 - // dragParent: root - // visualIndex: delegateRoot.visualIndex - // controllerName: delegateRoot.controllerName - // playerIndex: delegateRoot.playerIndex - // } - // } - // } - // } - // } - - component Thing: Pane { - id: con - - background: Rectangle { - color: "white" - opacity: conHover.hovered ? 0.2 : 0.1 - Behavior on opacity { - NumberAnimation { - duration: 200 - easing.type: Easing.InOutQuad - } - } - radius: 8 - } - padding: 16 - - Rectangle { - id: conIcon - width: con.height - (padding * 2) - height: con.height - (padding * 2) - anchors.verticalCenter: parent.verticalCenter - radius: 8 - color: "white" - opacity: 0.6 - } - - Text { - anchors.left: conIcon.right - anchors.leftMargin: 16 - anchors.top: parent.top - text: "Player 1" - font.pointSize: 12 - font.family: Constants.strongFontFamily - color: "#b3b3b3" - } - - HoverHandler { - id: conHover - } - } -} \ No newline at end of file diff --git a/qml/discover/HackPage.qml b/qml/discover/HackPage.qml deleted file mode 100644 index 277f850..0000000 --- a/qml/discover/HackPage.qml +++ /dev/null @@ -1,197 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQml.Models - - -Pane { - id: root - - background: Item { - } - - ContentPane { - id: content - anchors.fill: parent - - Pane { - id: rightSection - - background: Item { - } - - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - width: parent.width * 0.3 - - ColumnLayout { - anchors.centerIn: parent - spacing: 8 - - Button { - id: addToLibraryButton - text: "Add to Library" - Layout.preferredWidth: 200 - Layout.preferredHeight: 60 - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - } - Item { - Layout.fillWidth: true - Layout.preferredHeight: 8 - } - Text { - text: "You need Pokémon Fire Red in your Library to play this game" - font.family: Constants.regularFontFamily - color: "white" - wrapMode: Text.WordWrap - font.pointSize: 10 - Layout.preferredWidth: 300 - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - horizontalAlignment: Text.AlignHCenter - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - } - } - - Pane { - id: titleBar - - background: Item { - } - - anchors.top: parent.top - anchors.right: rightSection.left - anchors.left: parent.left - - ColumnLayout { - anchors.fill: parent - spacing: 8 - - Text { - text: "Pokémon Radical Red" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - font.pointSize: 20 - font.family: Constants.semiboldFontFamily - color: "white" - } - - Text { - text: "By soupercell" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - font.pointSize: 11 - font.family: Constants.regularFontFamily - color: "white" - } - - - } - } - - Pane { - id: leftSection - - background: Item { - } - anchors.top: titleBar.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: rightSection.left - - Flickable { - anchors.fill: parent - contentWidth: leftSection.width - contentHeight: 1000 - boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.VerticalFlick - clip: true - - ColumnLayout { - id: column - anchors.fill: parent - spacing: 16 - - ListView { - id: screenshots - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.preferredHeight: 240 - Layout.fillWidth: true - clip: true - orientation: ListView.Horizontal - model: ListModel { - ListElement { - source: "file:///Users/alexs/git/firelight/build/prr1.png" - } - ListElement { - source: "file:///Users/alexs/git/firelight/build/prr2.png" - } - ListElement { - source: "file:///Users/alexs/git/firelight/build/prr3.jpg" - } - ListElement { - source: "file:///Users/alexs/git/firelight/build/prr4.jpg" - } - } - spacing: 8 - delegate: Image { - source: model.source - fillMode: Image.Stretch - width: parent.height * 1.5 - height: parent.height - } - } - - Text { - Layout.fillWidth: true - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - text: "

Pokémon Radical Red is an enhancement hack of Pokémon Fire Red.

-

This is a difficulty hack, with massive additional features added to help you navigate through this game.

-

This hack utilises the Complete Fire Red Upgrade Engine and Dynamic Pokemon Expansion built - by Skeli789, Ghoulslash, and others. It's responsible for most of the significant features - in the hack.

-

List of features (Most of them provided by CFRU and DPE):

-
    -
  • Much higher difficulty, with optional modes to add or mitigate difficulty
  • -
  • Built-in Randomizer options (Pokémon, Abilities and Learnsets)
  • -
  • Physical/Special split + Fairy Typing
  • -
  • All Pokémon up to Gen 9 obtainable (with some exceptions)
  • -
  • Most Moves up to Gen 9
  • -
  • Updated Pokémon sprites
  • -
  • Mega Evolutions & Z-Moves
  • -
  • Most Abilities up to Gen 9
  • -
  • All important battle items (with some exceptions)
  • -
  • Wish Piece Raid Battles (with Dynamax)
  • -
  • Mystery Gifts
  • -
  • Reusable TMs
  • -
  • Expanded TM list
  • -
  • Additional move tutors
  • -
  • EV Training Gear and NPCs
  • -
  • Ability popups during battle
  • -
  • Party Exp Share (can be disabled)
  • -
  • Hidden Abilities
  • -
  • Day, Dusk and Night cycle (syncs with RTC)
  • -
  • DexNav, which allows you to search for Pokémon with hidden abilities and more
  • -
  • Even faster turbo speed on bike and while surfing
  • -
  • Abilities like Magma Armor, Static, or Flash Fire have overworld effects like in recent generations
  • -
  • Destiny Knot, Everstone have updated breeding mechanics
  • -
  • Lots of Quality of Life changes
  • -
  • ... and more!
  • -
" - wrapMode: Text.WordWrap - font.pointSize: 12 - font.family: Constants.regularFontFamily - color: "white" - } - } - } - - - } - } -} \ No newline at end of file diff --git a/qml/discover/HackPage2.qml b/qml/discover/HackPage2.qml deleted file mode 100644 index f439052..0000000 --- a/qml/discover/HackPage2.qml +++ /dev/null @@ -1,336 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQml.Models - - -Item { - id: root - - ContentPane { - id: content - anchors.fill: parent - - Pane { - id: banner - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: 200 - - background: Rectangle { - color: "red" - } - - ColumnLayout { - anchors.fill: parent - spacing: 8 - - Item { - Layout.fillHeight: true - Layout.fillWidth: true - } - - Text { - text: "Pokémon Radical Red" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - font.pointSize: 20 - font.family: Constants.semiboldFontFamily - color: "white" - } - - Text { - text: "By soupercell" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - font.pointSize: 11 - font.family: Constants.regularFontFamily - color: "white" - } - - - } - } - - Pane { - id: body - anchors.top: banner.bottom - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - width: 800 - - background: Rectangle { - color: "blue" - } - - Flickable { - anchors.fill: parent - contentWidth: parent.width - contentHeight: 1000 - boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.VerticalFlick - clip: true - - ColumnLayout { - id: column - anchors.fill: parent - spacing: 16 - - ListView { - id: screenshots - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.preferredHeight: 140 - Layout.fillWidth: true - clip: true - orientation: ListView.Horizontal - model: ListModel { - ListElement { - source: "file:///Users/alexs/git/firelight/build/prr1.png" - } - ListElement { - source: "file:///Users/alexs/git/firelight/build/prr2.png" - } - ListElement { - source: "file:///Users/alexs/git/firelight/build/prr3.jpg" - } - ListElement { - source: "file:///Users/alexs/git/firelight/build/prr4.jpg" - } - } - spacing: 8 - delegate: Image { - source: model.source - fillMode: Image.Stretch - width: parent.height * 1.5 - height: parent.height - } - } - - Text { - Layout.fillWidth: true - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - text: "

Pokémon Radical Red is an enhancement hack of Pokémon Fire Red.

-

This is a difficulty hack, with massive additional features added to help you navigate through this game.

-

This hack utilises the Complete Fire Red Upgrade Engine and Dynamic Pokemon Expansion built - by Skeli789, Ghoulslash, and others. It's responsible for most of the significant features - in the hack.

-

List of features (Most of them provided by CFRU and DPE):

-
    -
  • Much higher difficulty, with optional modes to add or mitigate difficulty
  • -
  • Built-in Randomizer options (Pokémon, Abilities and Learnsets)
  • -
  • Physical/Special split + Fairy Typing
  • -
  • All Pokémon up to Gen 9 obtainable (with some exceptions)
  • -
  • Most Moves up to Gen 9
  • -
  • Updated Pokémon sprites
  • -
  • Mega Evolutions & Z-Moves
  • -
  • Most Abilities up to Gen 9
  • -
  • All important battle items (with some exceptions)
  • -
  • Wish Piece Raid Battles (with Dynamax)
  • -
  • Mystery Gifts
  • -
  • Reusable TMs
  • -
  • Expanded TM list
  • -
  • Additional move tutors
  • -
  • EV Training Gear and NPCs
  • -
  • Ability popups during battle
  • -
  • Party Exp Share (can be disabled)
  • -
  • Hidden Abilities
  • -
  • Day, Dusk and Night cycle (syncs with RTC)
  • -
  • DexNav, which allows you to search for Pokémon with hidden abilities and more
  • -
  • Even faster turbo speed on bike and while surfing
  • -
  • Abilities like Magma Armor, Static, or Flash Fire have overworld effects like in recent generations
  • -
  • Destiny Knot, Everstone have updated breeding mechanics
  • -
  • Lots of Quality of Life changes
  • -
  • ... and more!
  • -
" - wrapMode: Text.WordWrap - font.pointSize: 12 - font.family: Constants.regularFontFamily - color: "white" - } - } - } - } - - - // Pane { - // id: rightSection - // - // background: Item { - // } - // - // anchors.top: parent.top - // anchors.bottom: parent.bottom - // anchors.right: parent.right - // width: parent.width * 0.3 - // - // ColumnLayout { - // anchors.centerIn: parent - // spacing: 8 - // - // Button { - // id: addToLibraryButton - // text: "Add to Library" - // Layout.preferredWidth: 200 - // Layout.preferredHeight: 60 - // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - // } - // Item { - // Layout.fillWidth: true - // Layout.preferredHeight: 8 - // } - // Text { - // text: "You need Pokémon Fire Red in your Library to play this game" - // font.family: Constants.regularFontFamily - // color: "white" - // wrapMode: Text.WordWrap - // font.pointSize: 10 - // Layout.preferredWidth: 300 - // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - // horizontalAlignment: Text.AlignHCenter - // } - // - // Item { - // Layout.fillWidth: true - // Layout.fillHeight: true - // } - // } - // } - // - // Pane { - // id: titleBar - // - // background: Item { - // } - // - // anchors.top: parent.top - // anchors.right: rightSection.left - // anchors.left: parent.left - // - // ColumnLayout { - // anchors.fill: parent - // spacing: 8 - // - // Text { - // text: "Pokémon Radical Red" - // horizontalAlignment: Text.AlignLeft - // verticalAlignment: Text.AlignVCenter - // font.pointSize: 20 - // font.family: Constants.semiboldFontFamily - // color: "white" - // } - // - // Text { - // text: "By soupercell" - // horizontalAlignment: Text.AlignLeft - // verticalAlignment: Text.AlignVCenter - // font.pointSize: 11 - // font.family: Constants.regularFontFamily - // color: "white" - // } - // - // - // } - // } - // - // Pane { - // id: leftSection - // - // background: Item { - // } - // anchors.top: titleBar.bottom - // anchors.bottom: parent.bottom - // anchors.left: parent.left - // anchors.right: rightSection.left - // - // Flickable { - // anchors.fill: parent - // contentWidth: leftSection.width - // contentHeight: 1000 - // boundsBehavior: Flickable.StopAtBounds - // flickableDirection: Flickable.VerticalFlick - // clip: true - // - // ColumnLayout { - // id: column - // anchors.fill: parent - // spacing: 16 - // - // ListView { - // id: screenshots - // Layout.alignment: Qt.AlignLeft | Qt.AlignTop - // Layout.preferredHeight: 240 - // Layout.fillWidth: true - // clip: true - // orientation: ListView.Horizontal - // model: ListModel { - // ListElement { - // source: "file:///Users/alexs/git/firelight/build/prr1.png" - // } - // ListElement { - // source: "file:///Users/alexs/git/firelight/build/prr2.png" - // } - // ListElement { - // source: "file:///Users/alexs/git/firelight/build/prr3.jpg" - // } - // ListElement { - // source: "file:///Users/alexs/git/firelight/build/prr4.jpg" - // } - // } - // spacing: 8 - // delegate: Image { - // source: model.source - // fillMode: Image.Stretch - // width: parent.height * 1.5 - // height: parent.height - // } - // } - // - // Text { - // Layout.fillWidth: true - // Layout.alignment: Qt.AlignLeft | Qt.AlignTop - // text: "

Pokémon Radical Red is an enhancement hack of Pokémon Fire Red.

- //

This is a difficulty hack, with massive additional features added to help you navigate through this game.

- //

This hack utilises the Complete Fire Red Upgrade Engine and Dynamic Pokemon Expansion built - // by Skeli789, Ghoulslash, and others. It's responsible for most of the significant features - // in the hack.

- //

List of features (Most of them provided by CFRU and DPE):

- //
    - //
  • Much higher difficulty, with optional modes to add or mitigate difficulty
  • - //
  • Built-in Randomizer options (Pokémon, Abilities and Learnsets)
  • - //
  • Physical/Special split + Fairy Typing
  • - //
  • All Pokémon up to Gen 9 obtainable (with some exceptions)
  • - //
  • Most Moves up to Gen 9
  • - //
  • Updated Pokémon sprites
  • - //
  • Mega Evolutions & Z-Moves
  • - //
  • Most Abilities up to Gen 9
  • - //
  • All important battle items (with some exceptions)
  • - //
  • Wish Piece Raid Battles (with Dynamax)
  • - //
  • Mystery Gifts
  • - //
  • Reusable TMs
  • - //
  • Expanded TM list
  • - //
  • Additional move tutors
  • - //
  • EV Training Gear and NPCs
  • - //
  • Ability popups during battle
  • - //
  • Party Exp Share (can be disabled)
  • - //
  • Hidden Abilities
  • - //
  • Day, Dusk and Night cycle (syncs with RTC)
  • - //
  • DexNav, which allows you to search for Pokémon with hidden abilities and more
  • - //
  • Even faster turbo speed on bike and while surfing
  • - //
  • Abilities like Magma Armor, Static, or Flash Fire have overworld effects like in recent generations
  • - //
  • Destiny Knot, Everstone have updated breeding mechanics
  • - //
  • Lots of Quality of Life changes
  • - //
  • ... and more!
  • - //
" - // wrapMode: Text.WordWrap - // font.pointSize: 12 - // font.family: Constants.regularFontFamily - // color: "white" - // } - // } - // } - // - // - // } - } -} \ No newline at end of file diff --git a/qml/home/HomeContentPane.qml b/qml/home/HomeContentPane.qml new file mode 100644 index 0000000..1c19ddd --- /dev/null +++ b/qml/home/HomeContentPane.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Window +import QtQuick.Layouts 1.0 +import QtQuick.Effects +import Firelight 1.0 + + +StackView { + objectName: "Home Content Stack View" + id: stackview + anchors.fill: parent + + property alias currentPageName: stackview.topLevelName + + function goTo(page) { + stackview.replace(null, page) + } + + property string topLevelName: "" + + onCurrentItemChanged: { + if (currentItem) { + let top = stackview.find(function (item, index) { + return item.topLevel === true + }) + + stackview.topLevelName = top ? top.topLevelName : "" + } + } + + // Pane { + // width: 48 + // height: 48 + // + // z: 2 + // + // background: Item { + // } + // + // Button { + // id: melol + // anchors.left: parent.left + // anchors.verticalCenter: parent.verticalCenter + // // horizontalPadding: 12 + // // verticalPadding: 8 + // + // enabled: stackview.depth > 1 + // + // hoverEnabled: false + // + // HoverHandler { + // id: myHover + // cursorShape: melol.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor + // } + // + // background: Rectangle { + // color: enabled ? myHover.hovered ? "#4e535b" : "#3e434b" : "#3e434b" + // radius: height / 2 + // } + // + // contentItem: Text { + // text: "\ue5c4" + // color: enabled ? "white" : "#7d848c" + // font.pointSize: 11 + // font.family: Constants.symbolFontFamily + // horizontalAlignment: Text.AlignHCenter + // verticalAlignment: Text.AlignVCenter + // } + // + // onClicked: { + // stackview.pop() + // } + // } + // } + + // initialItem: libraryPage + + pushEnter: Transition { + } + pushExit: Transition { + } + popEnter: Transition { + } + popExit: Transition { + } + replaceEnter: Transition { + } + replaceExit: Transition { + } +} \ No newline at end of file diff --git a/qml/library/EntrySummaryPage.qml b/qml/library/EntrySummaryPage.qml new file mode 100644 index 0000000..6561308 --- /dev/null +++ b/qml/library/EntrySummaryPage.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ColumnLayout { + id: root + objectName: "Details Tab Content" + + required property var entryData + + Text { + Layout.fillWidth: true + text: qsTr("Content path") + color: "white" + font.pointSize: 12 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + } + Pane { + id: texxxxt + // Layout.fillWidth: true + padding: 4 + + background: Rectangle { + color: "black" + radius: 8 + } + + contentItem: TextInput { + padding: 4 + text: root.entryData.content_path + font.family: Constants.regularFontFamily + font.pointSize: 12 + color: "white" + verticalAlignment: Text.AlignVCenter + readOnly: true + } + } + Text { + text: "Active save slot: " + root.entryData.active_save_slot + color: "white" + font.family: Constants.regularFontFamily + font.pointSize: 10 + } + Text { + text: "Added to library: " + root.entryData.created_at + color: "white" + font.family: Constants.regularFontFamily + font.pointSize: 10 + } + Item { + Layout.fillHeight: true + } +} \ No newline at end of file diff --git a/qml/library/GameDetailsPage.qml b/qml/library/GameDetailsPage.qml deleted file mode 100644 index cc3f087..0000000 --- a/qml/library/GameDetailsPage.qml +++ /dev/null @@ -1,755 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Effects -import QtCharts 2.7 - -Item { - id: root - - signal playPressed() - - required property int entryId - property int tabWidth: 150 - property var achievements: null - property var achievementsSummary: { - "NumPossibleAchievements": 0, - "PossibleScore": 0, - "NumAchieved": 0, - "ScoreAchieved": 0, - "NumAchievedHardcore": 0, - "ScoreAchievedHardcore": 0 - - } - property var entryData: {} - - Component.onCompleted: { - entryData = library_database.getLibraryEntryJson(entryId) - - root.achievements = achievement_manager.getAchievementsModelForGameId(entryData.game_id) - achievement_manager.getAchievementsOverview(entryData.game_id) - } - - Connections { - target: achievement_manager - - function onAchievementSummaryAvailable(json) { - root.achievementsSummary = json - } - - // function onAchievementListAvailable(model) { - // root.achievements = model - // } - } - - // Rectangle { - // width: parent.width + 24 - // height: topRow.height + 12 - // x: -12 - // y: -12 - // - // color: "#101114" - // } - - // Image { - // id: headerBanner - // width: parent.width + 24 - // height: topRow.height - // x: -12 - // y: -12 - // - // source: "file:system/_img/smw_beachkoopa_slope.png" - // fillMode: Image.Stretch - // - // layer.enabled: true - // layer.effect: MultiEffect { - // autoPaddingEnabled: false - // source: headerBanner - // anchors.fill: headerBanner - // blurEnabled: true - // blurMultiplier: 1.0 - // blurMax: 64 - // blur: 1 - // } - // - // Rectangle { - // anchors.fill: parent - // color: "black" - // opacity: 0.3 - // } - // } - - ColumnLayout { - spacing: 0 - anchors.fill: parent - - RowLayout { - id: topRow - Layout.fillWidth: true - Layout.topMargin: 12 - Layout.minimumHeight: 84 - Layout.maximumHeight: 84 - spacing: 0 - - Text { - Layout.alignment: Qt.AlignTop - Layout.leftMargin: 48 - padding: 10 - text: root.entryData.display_name - color: "white" - font.pointSize: 22 - font.family: Constants.regularFontFamily - font.weight: Font.DemiBold - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - - Button { - Layout.alignment: Qt.AlignRight | Qt.AlignTop - Layout.preferredWidth: 140 - Layout.preferredHeight: 50 - Layout.rightMargin: 48 - background: Rectangle { - color: parent.hovered ? "#b8b8b8" : "white" - radius: 4 - } - hoverEnabled: true - contentItem: Text { - text: qsTr("Play") - color: Constants.colorTestBackground - font.family: Constants.regularFontFamily - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.pointSize: 12 - } - onClicked: { - root.playPressed() - } - } - } - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: true - - spacing: 0 - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.horizontalStretchFactor: 1 - } - - ColumnLayout { - id: theColumn - Layout.fillWidth: true - Layout.horizontalStretchFactor: 4 - Layout.minimumWidth: 700 - Layout.maximumWidth: 1200 - Layout.preferredWidth: parent.width * 3 / 4 - Layout.fillHeight: true - - TabBar { - id: bar - // Layout.topMargin: 16 - Layout.alignment: Qt.AlignTop | Qt.AlignHCenter - Layout.preferredHeight: 40 - currentIndex: -1 - - onCurrentIndexChanged: function () { - view.setCurrentIndex(currentIndex) - } - - background: Rectangle { - width: root.tabWidth - visible: bar.contentChildren[bar.currentIndex].enabled - height: 2 - radius: 1 - color: "white" - x: root.tabWidth * bar.currentIndex - y: bar.height - - Behavior on x { - NumberAnimation { - duration: 120 - easing.type: Easing.InOutQuad - } - } - } - - TabButton { - width: root.tabWidth - contentItem: Text { - text: "Details" - color: parent.enabled ? "#ffffff" : "#666666" - font.family: Constants.regularFontFamily - font.pointSize: 11 - font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - background: Item { - } - - HoverHandler { - cursorShape: Qt.PointingHandCursor - } - } - TabButton { - width: root.tabWidth - contentItem: Text { - text: "Achievements" - color: parent.enabled ? "#ffffff" : "#666666" - font.family: Constants.regularFontFamily - font.pointSize: 11 - font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - background: Item { - } - - HoverHandler { - cursorShape: Qt.PointingHandCursor - } - } - TabButton { - width: root.tabWidth - contentItem: Text { - text: "Activity" - color: parent.enabled ? "#ffffff" : "#666666" - font.family: Constants.regularFontFamily - font.pointSize: 11 - font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - background: Item { - } - - HoverHandler { - cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - } - } - TabButton { - width: root.tabWidth - contentItem: Text { - text: "Settings" - color: parent.enabled ? "#ffffff" : "#666666" - font.family: Constants.regularFontFamily - font.pointSize: 11 - font.weight: parent.enabled && parent.checked ? Font.Bold : Font.Normal - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - background: Item { - } - - HoverHandler { - cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - } - } - } - SwipeView { - id: view - Layout.fillWidth: true - Layout.fillHeight: true - - currentIndex: 0 - - clip: true - - onCurrentIndexChanged: function () { - bar.setCurrentIndex(currentIndex) - } - - // ChartView { - // title: "Line Chart" - // theme: ChartView.ChartThemeBrownSand - // antialiasing: true - // - // BarCategoryAxis { - // id: daysAxis - // categories: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] - // } - // - // ValueAxis { - // id: playtimeAxis - // min: 0 - // max: 10 - // titleText: "Playtime (hours)" - // } - // - // BarSeries { - // id: barSeries - // axisX: daysAxis - // axisY: playtimeAxis - // - // BarSet { - // label: "Playtime" - // // Sample data - // values: [3, 5, 2, 7, 6, 1, 4] - // } - // } - // - // Component.onCompleted: { - // barSeries.append("Playtime", [3, 5, 2, 7, 6, 1, 4]) - // } - // } - ColumnLayout { - Text { - Layout.fillWidth: true - text: qsTr("Content path") - color: "white" - font.pointSize: 12 - font.family: Constants.regularFontFamily - font.weight: Font.DemiBold - } - Pane { - id: texxxxt - // Layout.fillWidth: true - padding: 4 - - background: Rectangle { - color: "black" - radius: 8 - } - - contentItem: TextInput { - padding: 4 - text: root.entryData.content_path - font.family: Constants.regularFontFamily - font.pointSize: 12 - color: "white" - verticalAlignment: Text.AlignVCenter - readOnly: true - } - } - Text { - text: "Active save slot: " + root.entryData.active_save_slot - color: "white" - font.family: Constants.regularFontFamily - font.pointSize: 10 - } - Text { - text: "Added to library: " + root.entryData.created_at - color: "white" - font.family: Constants.regularFontFamily - font.pointSize: 10 - } - Item { - Layout.fillHeight: true - } - } - Flickable { - id: flickThing - contentHeight: !achievement_manager.loggedIn ? thing.height : achievementsList.height - - property real scrollMultiplier: 8.0 // Adjust this multiplier for desired scroll speed - property real maxScrollSpeed: 1000 // Maximum scroll speed - property real smoothScrollSpeed: 0.1 // Adjust this for smoothness - - // Behavior on contentY { - // NumberAnimation { - // duration: 120 - // easing.type: Easing.InOutQuad - // } - // } - - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - // preventStealing: true - onWheel: function (wheel) { - var scrollDelta; - if (wheel.pixelDelta.y !== 0) { - scrollDelta = wheel.pixelDelta.y; - } else { - scrollDelta = wheel.angleDelta.y / 8; - } - - flickThing.contentY -= scrollDelta * flickThing.scrollMultiplier - if (flickThing.contentY < 0) { - flickThing.contentY = 0 - } else if (flickThing.contentY > flickThing.contentHeight - flickThing.height) { - flickThing.contentY = flickThing.contentHeight - flickThing.height - } - - // - // // Dynamic scaling factor for fast scrolling - // var scaledDelta = scrollDelta * flickThing.scrollMultiplier; - // var absDelta = Math.abs(scrollDelta); - // - // // Increase scroll speed for larger deltas (fast scrolling) - // if (absDelta > 10) { - // scaledDelta *= (absDelta / 10); - // scaledDelta = Math.sign(scrollDelta) * Math.min(flickThing.maxScrollSpeed, Math.abs(scaledDelta)); - // } - // - // flickThing.contentY = Math.max(0, Math.min(flickThing.contentY + scaledDelta, flickThing.contentHeight - flickThing.height)); - } - } - ColumnLayout { - id: thing - visible: !achievement_manager.loggedIn - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - Text { - text: "Log in blah blah" - color: "white" - font.family: Constants.regularFontFamily - font.pointSize: 10 - Layout.alignment: Qt.AlignCenter - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - Button { - Layout.alignment: Qt.AlignCenter - Layout.preferredWidth: 140 - Layout.preferredHeight: 50 - background: Rectangle { - color: parent.hovered ? "#b8b8b8" : "white" - radius: 4 - } - hoverEnabled: true - contentItem: Text { - text: qsTr("Log in") - color: Constants.colorTestBackground - font.family: Constants.regularFontFamily - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.pointSize: 12 - } - onClicked: { - Router.navigateTo("settings/achievements") - } - } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - } - - Column { - id: achievementsList - visible: achievement_manager.loggedIn && root.achievementsSummary.NumPossibleAchievements > 0 - width: parent.width - spacing: 24 - - Item { - width: parent.width - height: 24 - } - - // Pane { - // width: 540 - // height: 106 - // padding: 8 - // - // background: Rectangle { - // color: "#292929" - // radius: 8 - // } - // - // contentItem: Item { - // Image { - // id: gameIcon - // width: parent.height - // height: parent.height - // fillMode: Image.PreserveAspectFit - // source: "https://media.retroachievements.org/Images/040155.png" - // } - // // Rectangle { - // // id: gameIcon - // // color: "red" - // // width: parent.height - // // height: parent.height - // // } - // ColumnLayout { - // anchors.leftMargin: 12 - // anchors.left: gameIcon.right - // anchors.top: parent.top - // anchors.bottom: parent.bottom - // anchors.right: parent.right - // spacing: 4 - // - // RowLayout { - // Layout.fillWidth: true - // Layout.fillHeight: true - // Layout.verticalStretchFactor: 3 - // spacing: 0 - // Text { - // Layout.fillHeight: true - // text: root.entryData.display_name - // font.family: Constants.regularFontFamily - // font.pointSize: 12 - // font.weight: Font.DemiBold - // verticalAlignment: Text.AlignVCenter - // color: "white" - // } - // Item { - // Layout.fillWidth: true - // Layout.fillHeight: true - // } - // Text { - // id: first - // text: root.achievementsSummary.NumAchievedHardcore - // font.family: Constants.regularFontFamily - // font.pointSize: 16 - // font.weight: Font.DemiBold - // color: "white" - // verticalAlignment: Text.AlignBottom - // horizontalAlignment: Text.AlignRight - // Layout.fillHeight: true - // } - // - // Text { - // id: slash - // text: " /" - // Layout.fillHeight: true - // font.family: Constants.regularFontFamily - // font.pointSize: 15 - // color: "#aaaaaa" - // verticalAlignment: Text.AlignBottom - // } - // - // Text { - // text: root.achievementsSummary.NumPossibleAchievements - // Layout.fillHeight: true - // font.family: Constants.regularFontFamily - // font.pointSize: 12 - // color: "#aaaaaa" - // verticalAlignment: Text.AlignBottom - // } - // - // Text { - // text: " earned" - // Layout.fillHeight: true - // font.family: Constants.regularFontFamily - // font.pointSize: 10 - // color: "#aaaaaa" - // verticalAlignment: Text.AlignBottom - // } - // } - // - // Item { - // Layout.fillHeight: true - // Layout.fillWidth: true - // Layout.verticalStretchFactor: 1 - // Rectangle { - // anchors.verticalCenter: parent.verticalCenter - // width: parent.width - // height: 20 - // radius: height / 2 - // color: "black" - // } - // - // Rectangle { - // anchors.verticalCenter: parent.verticalCenter - // width: parent.width / 5 - // height: 20 - // radius: height / 2 - // border.color: "black" - // color: "#bc8c0f" - // } - // - // } - // } - // } - // } - - // Rectangle { - // Rectangle { - // id: gameIcon - // width: 80 - // height: 80 - // anchors.left: parent.left - // anchors.top: parent.top - // anchors.leftMargin: 12 - // anchors.topMargin: 12 - // color: "red" - // } - // Text { - // text: root.entryData.display_name - // anchors.left: gameIcon.right - // anchors.top: parent.top - // anchors.leftMargin: 12 - // anchors.topMargin: 12 - // color: "white" - // font.family: Constants.regularFontFamily - // font.pointSize: 12 - // font.weight: Font.DemiBold - // } - // - // RowLayout { - // anchors.left: parent.left - // anchors.bottom: parent.bottom - // anchors.right: parent.right - // anchors.top: gameIcon.bottom - // anchors.topMargin: 12 - // anchors.leftMargin: 12 - // anchors.bottomMargin: 12 - // spacing: 0 - // - // Text { - // id: first - // text: "300" - // font.family: Constants.regularFontFamily - // font.pointSize: 16 - // font.weight: Font.DemiBold - // color: "white" - // verticalAlignment: Text.AlignBottom - // horizontalAlignment: Text.AlignRight - // Layout.fillHeight: true - // } - // - // Text { - // id: slash - // text: " /" - // Layout.fillHeight: true - // font.family: Constants.regularFontFamily - // font.pointSize: 15 - // color: "#aaaaaa" - // verticalAlignment: Text.AlignBottom - // } - // - // Text { - // text: "200" - // Layout.fillHeight: true - // font.family: Constants.regularFontFamily - // font.pointSize: 12 - // color: "#aaaaaa" - // verticalAlignment: Text.AlignBottom - // } - // - // Rectangle { - // Layout.fillWidth: true - // Layout.preferredHeight: 20 - // Layout.alignment: Qt.AlignCenter - // radius: height / 2 - // } - // } - // - // // Text { - // // text: root.achievementsSummary.NumAchievedHardcore - // // color: "white" - // // font.family: Constants.regularFontFamily - // // font.pointSize: 16 - // // font.weight: Font.DemiBold - // // horizontalAlignment: Text.AlignHCenter - // // verticalAlignment: Text.AlignVCenter - // // } - // } - - ListView { - model: root.achievements - spacing: 12 - width: parent.width - height: contentHeight - interactive: false - - // section.property: root.achievements.sortType === "title" ? "name" : "earned" - // section.criteria: ViewSection.FirstCharacter - // section.delegate: ListViewSectionDelegate { - // required property string section - // text: section === "t" || section === "f" ? (section === "t" ? "Earned" : "Not earned") : section - // } - - header: Pane { - width: achievementsList.width - background: Item { - } - verticalPadding: 12 - horizontalPadding: 0 - contentItem: RowLayout { - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } - Text { - Layout.fillHeight: true - verticalAlignment: Text.AlignVCenter - text: "Sort by:" - color: "white" - font.family: Constants.regularFontFamily - font.pointSize: 10 - } - - MyComboBox { - id: sortBox - Layout.fillHeight: true - Layout.fillWidth: false - textRole: "text" - valueRole: "value" - - model: [ - {text: "Default", value: "default"}, - {text: "A-Z", value: "title"}, - {text: "Earned date", value: "earned_date"}, - {text: "Points", value: "points"} - ] - - Connections { - target: root - - function onAchievementsChanged() { - console.log("current sort type: " + root.achievements.sortType) - sortBox.currentIndex = sortBox.indexOfValue(root.achievements.sortType) - } - } - - onActivated: function () { - root.achievements.sortType = currentValue - } - } - } - } - - delegate: AchievementListItem { - width: ListView.view.width - } - } - } - - - } - - Text { - text: "activity stuff" - color: "white" - font.family: Constants.regularFontFamily - font.pointSize: 10 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - Text { - text: "settings stuff" - color: "white" - font.family: Constants.regularFontFamily - font.pointSize: 10 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - } - - Item { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.horizontalStretchFactor: 1 - } - } - } -} \ No newline at end of file diff --git a/qml/library/GameGridItemDelegate.qml b/qml/library/GameGridItemDelegate.qml new file mode 100644 index 0000000..acdd3b8 --- /dev/null +++ b/qml/library/GameGridItemDelegate.qml @@ -0,0 +1,135 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: myDelegate + + signal startGame(entryId: int) + + signal openDetails(entryId: int) + + signal manageSaveData(entryId: int) + + required property var index + required property var model + + required property real cellWidth + required property real cellHeight + required property real cellSpacing + + width: cellWidth + height: cellHeight + Button { + id: button + padding: 0 + anchors { + fill: parent + margins: myDelegate.cellSpacing / 2 + } + focus: true + + Keys.onPressed: function (event) { + if (event.key === Qt.Key_Menu) { + rightClickMenu.popup(400, 400) + } + } + + TapHandler { + acceptedButtons: Qt.LeftButton | Qt.RightButton + onTapped: function (event, button) { + if (button === Qt.RightButton) { + rightClickMenu.popup() + } else if (button === Qt.LeftButton) { + // Router.navigateTo("library/" + myDelegate.model.id + "/details") + // myDelegate.openDetails(myDelegate.model.id) + myDelegate.startGame(myDelegate.model.id) + } + } + } + + RightClickMenu { + id: rightClickMenu + objectName: "rightClickMenu" + + RightClickMenuItem { + text: "Play " + myDelegate.model.display_name + onTriggered: { + myDelegate.startGame(myDelegate.model.id) + } + } + + RightClickMenuItem { + enabled: false + text: "View details" + onTriggered: { + myDelegate.openDetails(myDelegate.model.id) + } + } + + MenuSeparator { + contentItem: Rectangle { + implicitWidth: rightClickMenu.width + implicitHeight: 1 + color: "#606060" + } + } + + RightClickMenuItem { + enabled: false + text: "Manage save data" + onTriggered: { + myDelegate.manageSaveData(myDelegate.model.id) + } + // onTriggered: { + // addPlaylistRightClickMenu.entryId = libraryEntryRightClickMenu.entryId + // addPlaylistRightClickMenu.popup() + // } + } + } + + hoverEnabled: true + + background: Rectangle { + objectName: "background" + color: button.hovered ? "#3a3e45" : "#25282C" + } + + contentItem: ColumnLayout { + Rectangle { + id: image + Layout.preferredHeight: parent.width + Layout.fillWidth: true + Layout.leftMargin: -2 + Layout.rightMargin: -2 + + color: "grey" + } + Text { + text: myDelegate.model.display_name + font.pointSize: 11 + font.weight: Font.Bold + // font.family: Constants.regularFontFamily + color: "white" + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + } + + Text { + text: myDelegate.model.platform_name + font.pointSize: 10 + font.weight: Font.Medium + // font.family: Constants.regularFontFamily + color: "#C2BBBB" + Layout.fillWidth: true + // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + } + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + } + } +} \ No newline at end of file diff --git a/qml/mainmenu/MainMenuNavButton.qml b/qml/mainmenu/MainMenuNavButton.qml deleted file mode 100644 index fb3a00b..0000000 --- a/qml/mainmenu/MainMenuNavButton.qml +++ /dev/null @@ -1,109 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - - -TabButton { - id: myButton - - required property string iconCode - required property string toolTipText - - implicitHeight: width - - contentItem: Text { - // text: "Settings" - text: iconCode - color: (myHover.hovered || myButton.checked) ? "white" : "#b3b3b3" - font.pixelSize: 28 - // font.family: Constants.strongFontFamily - font.family: Constants.symbolFontFamily - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - } - scale: checked ? 1.1 : 1.0 - Behavior on scale { - NumberAnimation { - duration: 200 - easing.type: Easing.InOutQuad - } - } - - background: Rectangle { - color: "white" - opacity: myButton.checked ? 0.1 : 0.0 - radius: 4 - - anchors.centerIn: parent - - height: myButton.height * (2 / 3) - width: myButton.width * (2 / 3) - - Behavior on opacity { - NumberAnimation { - duration: 200 - easing.type: Easing.InOutQuad - } - } - } - - HoverHandler { - id: myHover - cursorShape: Qt.PointingHandCursor - } - - ToolTip.visible: myHover.hovered - ToolTip.text: toolTipText - ToolTip.delay: 400 - ToolTip.timeout: 5000 - - // ToolTip { - // id: tool - // visible: homeHover.hovered - // delay: 400 - // timeout: 5000 - // - // ParallelAnimation { - // id: fadeIn - // NumberAnimation { - // target: tool - // property: "opacity" - // to: 1 - // duration: 200 - // easing.type: Easing.InOutQuad - // } - // NumberAnimation { - // target: tool - // property: "y" - // from: 43 - // to: 48 - // duration: 200 - // easing.type: Easing.InOutQuad - // } - // } - // - // onVisibleChanged: function () { - // if (visible) { - // opacity = 0 // Start from fully transparent - // y = 43 - // fadeIn.start() - // } - // } - // - // height: 24 - // - // contentItem: Text { - // text: "Home" - // color: Constants.rightClickMenuItem_TextColor - // font.pointSize: 12 - // font.family: Constants.regularFontFamily - // horizontalAlignment: Text.AlignHCenter - // verticalAlignment: Text.AlignVCenter - // } - // - // background: Rectangle { - // color: Constants.rightClickMenu_BackgroundColor - // radius: Constants.rightClickMenu_BackgroundRadius - // } - // } -} \ No newline at end of file diff --git a/qml/controllers/ControllersPage.qml b/qml/pages/ControllersPage.qml similarity index 81% rename from qml/controllers/ControllersPage.qml rename to qml/pages/ControllersPage.qml index 0c19574..e43e809 100644 --- a/qml/controllers/ControllersPage.qml +++ b/qml/pages/ControllersPage.qml @@ -202,12 +202,12 @@ Flickable { radius: 6 } - DetailsButton { - anchors.right: parent.right - anchors.rightMargin: 8 - anchors.topMargin: 8 - anchors.top: parent.top - } + // DetailsButton { + // anchors.right: parent.right + // anchors.rightMargin: 8 + // anchors.topMargin: 8 + // anchors.top: parent.top + // } Column { id: contentColumn @@ -239,41 +239,41 @@ Flickable { // height: 6 // width: 1 // } - MyComboBox { - textRole: "text" - valueRole: "value" - width: parent.width - - // onActivated: library_short_model.sortType = currentValue - // Component.onCompleted: currentIndex = indexOfValue(library_short_model.sortType) - - model: [ - {text: "Default profile", value: "display_name"}, - {text: "Newest first", value: "created_at"} - ] - } - Button { - width: parent.width - padding: 8 - background: Rectangle { - color: "#03438c" - radius: 8 - } - contentItem: Text { - text: qsTr("Edit current profile") - color: "white" - font.pointSize: 11 - font.weight: Font.DemiBold - font.family: Constants.regularFontFamily - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - - onClicked: function () { - root.StackView.view.push(profileEditor) - // profileDialog.open() - } - } + // MyComboBox { + // textRole: "text" + // valueRole: "value" + // width: parent.width + // + // // onActivated: library_short_model.sortType = currentValue + // // Component.onCompleted: currentIndex = indexOfValue(library_short_model.sortType) + // + // model: [ + // {text: "Default profile", value: "display_name"}, + // {text: "Newest first", value: "created_at"} + // ] + // } + // Button { + // width: parent.width + // padding: 8 + // background: Rectangle { + // color: "#03438c" + // radius: 8 + // } + // contentItem: Text { + // text: qsTr("Edit current profile") + // color: "white" + // font.pointSize: 11 + // font.weight: Font.DemiBold + // font.family: Constants.regularFontFamily + // horizontalAlignment: Text.AlignHCenter + // verticalAlignment: Text.AlignVCenter + // } + // + // onClicked: function () { + // root.StackView.view.push(profileEditor) + // // profileDialog.open() + // } + // } } // Text { // text: model.model_name diff --git a/qml/DebugPage.qml b/qml/pages/DebugPage.qml similarity index 100% rename from qml/DebugPage.qml rename to qml/pages/DebugPage.qml diff --git a/qml/discover/DiscoverPage.qml b/qml/pages/DiscoverPage.qml similarity index 100% rename from qml/discover/DiscoverPage.qml rename to qml/pages/DiscoverPage.qml diff --git a/qml/EmulatorPage.qml b/qml/pages/EmulatorPage.qml similarity index 83% rename from qml/EmulatorPage.qml rename to qml/pages/EmulatorPage.qml index 4fb31e1..e133e8f 100644 --- a/qml/EmulatorPage.qml +++ b/qml/pages/EmulatorPage.qml @@ -14,11 +14,8 @@ Rectangle { color: "black" property alias currentGameName: emulatorView.currentGameName - property alias running: emulatorView.running - signal gameLoaded() - - signal readyToStart() + signal emulationStarted() function loadGame(entryId) { emulatorView.loadLibraryEntry(entryId) @@ -40,18 +37,6 @@ Rectangle { emulatorView.resumeGame() } - function isRunning() { - return emulatorView.isRunning() - } - - function startEmulation() { - emulatorView.startEmulation() - } - - function stopEmulation() { - emulatorView.stopEmulation() - } - EmulatorView { id: emulatorView @@ -60,6 +45,10 @@ Rectangle { anchors.centerIn: parent smooth: false + onEmulationStarted: { + emulatorContainer.emulationStarted() + } + // onOrphanPatchDetected: { // console.log("orphan patch detected") // everything.pop() @@ -69,11 +58,6 @@ Rectangle { // this.load(currentLibraryEntryId, romData, saveData, corePath) // } - onGameLoadSucceeded: function () { - emulatorContainer.gameLoaded() - emulatorContainer.readyToStart() - } - // onReadyToStart: function () { // emulatorContainer.readyToStart() // } @@ -127,5 +111,4 @@ Rectangle { } ] } -} -// color: "black" \ No newline at end of file +} \ No newline at end of file diff --git a/qml/pages/GameDetailsPage.qml b/qml/pages/GameDetailsPage.qml new file mode 100644 index 0000000..06635d3 --- /dev/null +++ b/qml/pages/GameDetailsPage.qml @@ -0,0 +1,162 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Effects +import QtCharts 2.7 + +FocusScope { + id: root + + signal playPressed() + + required property int entryId + property var achievements: null + property var entryData: {} + + Component.onCompleted: { + entryData = library_database.getLibraryEntryJson(entryId) + + root.achievements = achievement_manager.getAchievementsModelForGameId(entryData.game_id) + } + + ColumnLayout { + spacing: 0 + anchors.fill: parent + + RowLayout { + id: topRow + Layout.fillWidth: true + Layout.topMargin: 12 + Layout.minimumHeight: 84 + Layout.maximumHeight: 84 + spacing: 0 + + Text { + Layout.alignment: Qt.AlignTop + Layout.leftMargin: 48 + padding: 10 + text: root.entryData.display_name + color: "white" + font.pointSize: 22 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + + Button { + Layout.alignment: Qt.AlignRight | Qt.AlignTop + Layout.preferredWidth: 140 + Layout.preferredHeight: 50 + Layout.rightMargin: 48 + background: Rectangle { + color: parent.hovered ? "#b8b8b8" : "white" + radius: 4 + } + hoverEnabled: true + contentItem: Text { + text: qsTr("Play") + color: Constants.colorTestBackground + font.family: Constants.regularFontFamily + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pointSize: 12 + } + onClicked: { + root.playPressed() + } + } + } + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: true + + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.horizontalStretchFactor: 1 + } + + ColumnLayout { + id: theColumn + Layout.fillWidth: true + Layout.horizontalStretchFactor: 4 + Layout.minimumWidth: 700 + Layout.maximumWidth: 1200 + Layout.preferredWidth: parent.width * 3 / 4 + Layout.fillHeight: true + + NavigationTabBar { + id: bar + tabs: ["Details", "Achievements", "Activity", "Settings"] + tabWidth: 150 + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.preferredHeight: 40 + + onCurrentIndexChanged: function () { + view.setCurrentIndex(currentIndex) + } + } + + SwipeView { + id: view + + objectName: "Content Swipe View" + Layout.fillWidth: true + Layout.fillHeight: true + focus: true + + currentIndex: 0 + + clip: true + + onCurrentIndexChanged: function () { + bar.setCurrentIndex(currentIndex) + } + + EntrySummaryPage { + entryData: root.entryData + } + + AchievementList { + gameId: 0 + achievementsEnabled: achievement_manager.loggedIn + loggedIn: achievement_manager.loggedIn + achievementsModel: root.achievements + } + + Text { + text: "activity stuff" + color: "white" + font.family: Constants.regularFontFamily + font.pointSize: 10 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + Text { + text: "settings stuff" + color: "white" + font.family: Constants.regularFontFamily + font.pointSize: 10 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.horizontalStretchFactor: 1 + } + } + } +} \ No newline at end of file diff --git a/qml/pages/LibraryPage.qml b/qml/pages/LibraryPage.qml new file mode 100644 index 0000000..12264df --- /dev/null +++ b/qml/pages/LibraryPage.qml @@ -0,0 +1,496 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQml.Models + + +FocusScope { + id: root + objectName: "Library Focus Scope" + + signal entryClicked(int entryId) + + Flickable { + id: flick + objectName: "Library Flickable" + anchors.fill: parent + contentHeight: theColumn.height + boundsBehavior: Flickable.StopAtBounds + // focus: true + + ScrollBar.vertical: ScrollBar { + } + + RowLayout { + id: contentRow + anchors.fill: parent + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.horizontalStretchFactor: 1 + } + + Column { + id: theColumn + Layout.preferredWidth: Math.max(5, (Math.min(Math.floor(parent.width / libraryGrid.cellContentWidth), 8)) * libraryGrid.cellContentWidth) + + Pane { + id: header + + width: parent.width + height: 120 + background: Rectangle { + color: "transparent" + // border.color: "red" + } + + horizontalPadding: 8 + + Text { + anchors.fill: parent + text: "All Games" + color: "white" + font.pointSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + } + + Pane { + id: filters + background: Rectangle { + color: "transparent" + // border.color: "blue" + } + width: parent.width + + horizontalPadding: 8 + verticalPadding: 0 + + RowLayout { + anchors.fill: parent + + spacing: 12 + Text { + Layout.fillHeight: true + Layout.alignment: Qt.AlignBottom + text: "Showing " + library_model.count + " game" + (library_model.count === 1 ? "" : "s") + color: "#C2BBBB" + font.pointSize: 11 + font.family: Constants.regularFontFamily + font.weight: Font.Normal + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignBottom + } + + Item { + Layout.fillWidth: true + } + + // Button { + // id: melol + // Layout.preferredHeight: 32 + // horizontalPadding: 12 + // verticalPadding: 8 + // + // hoverEnabled: true + // + // Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom + // background: Rectangle { + // color: melol.hovered ? "#4e535b" : "#3e434b" + // radius: 12 + // border.color: "#7d848c" + // } + // + // contentItem: Text { + // text: "Show filters" + // color: "white" + // font.pointSize: 11 + // font.family: Constants.regularFontFamily + // horizontalAlignment: Text.AlignHCenter + // verticalAlignment: Text.AlignVCenter + // } + // } + + // Item { + // Layout.preferredWidth: 8 + // } + + Text { + Layout.fillHeight: true + Layout.alignment: Qt.AlignVCenter + text: "Sort by:" + color: "white" + font.pointSize: 12 + font.family: Constants.regularFontFamily + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + MyComboBox { + textRole: "text" + valueRole: "value" + + onActivated: library_short_model.sortType = currentValue + Component.onCompleted: currentIndex = indexOfValue(library_short_model.sortType) + + model: [ + {text: "A-Z", value: "display_name"}, + {text: "Newest first", value: "created_at"} + ] + } + } + } + + Pane { + background: Rectangle { + color: "transparent" + // border.color: "green" + } + width: parent.width + height: libraryGrid.contentHeight + + horizontalPadding: 0 + + GridView { + id: libraryGrid + width: parent.width + height: 2000 + cellWidth: cellContentWidth + cellHeight: cellContentHeight + focus: true + + function itemsPerRow() { + return Math.floor(width / cellWidth); + } + + function rowWidth() { + return itemsPerRow() * cellWidth; + } + + // onWidthChanged: { + // contentRow.middleSectionWidth = libraryGrid.rowWidth() + // } + + cacheBuffer: 30 + + clip: true + + interactive: false + readonly property int cellSpacing: 12 + readonly property int cellContentWidth: 180 + cellSpacing + readonly property int cellContentHeight: 260 + cellSpacing + + populate: Transition { + } + + currentIndex: 0 + + model: library_short_model + boundsBehavior: Flickable.StopAtBounds + keyNavigationEnabled: true + + highlight: Item { + } + + delegate: FocusScope { + id: rootItem + width: libraryGrid.cellContentWidth + height: libraryGrid.cellContentHeight + + Button { + id: libItemButton + anchors { + fill: parent + margins: libraryGrid.cellSpacing / 2 + } + + scale: pressed ? 0.97 : 1 + Behavior on scale { + NumberAnimation { + duration: 64 + easing.type: Easing.InOutQuad + } + } + + background: Rectangle { + color: libItemButton.hovered ? "#3a3e45" : "#25282C" + radius: 6 + // border.color: rootItem.GridView.isCurrentItem ? "white" : "transparent" + // border.width: 2 + // leftInset: -2 + // rightInset: -2 + // topInset: -2 + // bottomInset: -2 + } + + contentItem: ColumnLayout { + id: colLayout + spacing: 0 + anchors { + fill: parent + // margins: 2 + } + + // Image { + // asynchronous: true + // source: "file:system/_img/pmbox.jpg" + // // Layout.fillWidth: true + // // Layout.preferredHeight: 180 + // + // // implicitWidth: libItemMouse.hovered ? parent.width + 10 : parent.width + // // implicitHeight: libItemMouse.hovered ? parent.height + 10 : parent.height + // + // // scale: libItemMouse.hovered ? 1.02 : 1 + // + // // Behavior on scale { + // // NumberAnimation { + // // duration: 200 + // // easing.type: Easing.InOutQuad + // // } + // // } + // + // width: 200 + // height: 200 + // + // sourceSize.width: 180 + // sourceSize.height: 180 + // + // horizontalAlignment: Image.AlignLeft + // + // fillMode: Image.PreserveAspectCrop + // } + + Rectangle { + color: "grey" + Layout.fillWidth: true + Layout.preferredHeight: colLayout.width + topLeftRadius: 6 + topRightRadius: 6 + + Text { + text: "Box art coming soon :)" + font.pointSize: 9 + // font.weight: Font.Light + font.family: Constants.regularFontFamily + color: "#232323" + anchors.centerIn: parent + // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + ColumnLayout { + spacing: 2 + anchors { + fill: parent + margins: 6 + } + + Text { + text: model.display_name + font.pointSize: 11 + font.weight: Font.Bold + font.family: Constants.regularFontFamily + color: "white" + Layout.fillWidth: true + elide: Text.ElideRight + maximumLineCount: 1 + // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + } + Text { + text: model.platform_name + font.pointSize: 10 + font.weight: Font.Medium + font.family: Constants.regularFontFamily + color: "#C2BBBB" + Layout.fillWidth: true + // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + // Pane { + // Layout.fillWidth: true + // Layout.fillHeight: true + // + // // clip: true + // background: Item { + // } + // padding: 8 + // ColumnLayout { + // anchors.fill: parent + // spacing: 2 + // Text { + // text: model.display_name + // font.pointSize: 11 + // font.weight: Font.Bold + // font.family: Constants.regularFontFamily + // color: "white" + // Layout.fillWidth: true + // elide: Text.ElideRight + // maximumLineCount: 1 + // // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + // } + // Text { + // text: model.platform_name + // font.pointSize: 10 + // font.weight: Font.Medium + // font.family: Constants.regularFontFamily + // color: "#C2BBBB" + // Layout.fillWidth: true + // // Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + // } + // Item { + // Layout.fillWidth: true + // Layout.fillHeight: true + // } + // } + // + // } + } + + TapHandler { + acceptedButtons: Qt.RightButton + onTapped: { + rootItem.GridView.view.currentIndex = index + libraryEntryRightClickMenu.popup() + } + } + + Keys.onReturnPressed: { + doubleClicked() + } + + onClicked: function () { + rootItem.GridView.view.currentIndex = model.index + root.StackView.view.push(gameDetailsPage, { + "objectName": "Game Details Page", + "entryId": model.id + }) + // libraryEntryRightClickMenu.popup() + } + + onDoubleClicked: function () { + entryClicked(model.id) + } + + RightClickMenu { + id: libraryEntryRightClickMenu + + RightClickMenuItem { + text: "Play " + model.display_name + onTriggered: { + entryClicked(model.id) + } + } + + // RightClickMenuItem { + // text: "Patch stuff" + // visible: model.parent_game_name !== "" + // onTriggered: function () { + // selectLibEntryDialog.open() + // } + // } + + RightClickMenuItem { + // enabled: false + text: "View details" + onTriggered: { + root.StackView.view.push(gameDetailsPage, {"entryId": model.id}) + } + } + + MenuSeparator { + contentItem: Rectangle { + implicitWidth: 188 + implicitHeight: 1 + color: "#606060" + } + } + + RightClickMenu { + id: addPlaylistRightClickMenu + enabled: ins.count > 0 + + title: "Add to folder" + + Instantiator { + id: ins + model: playlist_model + delegate: RightClickMenuItem { + text: model.display_name + onTriggered: { + playlist_model.addEntryToPlaylist(model.id, libraryEntryRightClickMenu.entryId) + library_model.updatePlaylistsForEntry(libraryEntryRightClickMenu.entryId) + // Add your action here + } + } + + onObjectAdded: function (index, object) { + addPlaylistRightClickMenu.insertItem(index, object) + } + onObjectRemoved: function (index, object) { + addPlaylistRightClickMenu.removeItem(object) + } + } + } + + MenuSeparator { + contentItem: Rectangle { + implicitWidth: 188 + implicitHeight: 1 + color: "#606060" + } + } + + RightClickMenuItem { + enabled: false + text: "Manage save data" + // onTriggered: { + // addPlaylistRightClickMenu.entryId = libraryEntryRightClickMenu.entryId + // addPlaylistRightClickMenu.popup() + // } + } + } + } + } + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.horizontalStretchFactor: 1 + } + } + + SelectLibraryEntryDialog { + id: selectLibEntryDialog + } + + Component { + id: gameDetailsPage + GameDetailsPage { + objectName: "GameDetailsPage" + entryId: -1 + + onPlayPressed: function () { + entryClicked(entryId) + } + } + } + } +} \ No newline at end of file diff --git a/qml/pages/LibraryPage2.qml b/qml/pages/LibraryPage2.qml new file mode 100644 index 0000000..60ceb25 --- /dev/null +++ b/qml/pages/LibraryPage2.qml @@ -0,0 +1,75 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls + +FocusScope { + id: page + property alias model: listView.model + + signal openDetails(entryId: int) + signal startGame(entryId: int) + + GridView { + id: listView + objectName: "GridView" + clip: true + width: Math.min(Math.floor(parent.width / cellContentWidth), 7) * cellContentWidth + anchors.horizontalCenter: parent.horizontalCenter + height: parent.height + + preferredHighlightBegin: 0.33 * listView.height + preferredHighlightEnd: 0.66 * listView.height + highlightRangeMode: GridView.ApplyRange + + contentY: 0 + + header: Pane { + id: header + + width: parent.width + height: 120 + background: Rectangle { + color: "transparent" + // border.color: "red" + } + + horizontalPadding: 8 + + Text { + anchors.fill: parent + text: "All Games" + color: "white" + font.pointSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + } + + readonly property int cellSpacing: 12 + readonly property int cellContentWidth: 180 + cellSpacing + readonly property int cellContentHeight: 260 + cellSpacing + + cellWidth: cellContentWidth + cellHeight: cellContentHeight + focus: true + + currentIndex: 0 + boundsBehavior: Flickable.StopAtBounds + + delegate: GameGridItemDelegate { + cellSpacing: listView.cellSpacing + cellWidth: listView.cellContentWidth + cellHeight: listView.cellContentHeight + + Component.onCompleted: { + openDetails.connect(page.openDetails) + startGame.connect(page.startGame) + } + } + } +} + diff --git a/qml/NowPlayingPage.qml b/qml/pages/NowPlayingPage.qml similarity index 87% rename from qml/NowPlayingPage.qml rename to qml/pages/NowPlayingPage.qml index e8d9f59..ee2fb7a 100644 --- a/qml/NowPlayingPage.qml +++ b/qml/pages/NowPlayingPage.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts -Item { +FocusScope { id: root signal resumeGamePressed() @@ -14,9 +14,28 @@ Item { signal closeGamePressed() + Item { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 40 + } + // NavigationTabBar { + // id: navBar + // anchors.horizontalCenter: parent.horizontalCenter + // + // tabs: ["Stuff", "Controller", "Achievements", "Settings"] + // tabWidth: 150 + // height: 40 + // } + RowLayout { id: contentRow - anchors.fill: parent + // anchors.top: navBar.bottom + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom spacing: 0 Item { @@ -73,23 +92,6 @@ Item { opacity: 0.3 color: "#dadada" } - FirelightMenuItem { - labelText: "Achievements" - Layout.fillWidth: true - // Layout.preferredWidth: parent.width / 2 - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.preferredHeight: 40 - checkable: true - alignRight: true - } - Rectangle { - Layout.fillWidth: true - // Layout.preferredWidth: parent.width / 2 - Layout.preferredHeight: 1 - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - opacity: 0.3 - color: "#dadada" - } FirelightMenuItem { labelText: "Create Suspend Point" Layout.fillWidth: true @@ -125,15 +127,6 @@ Item { opacity: 0.3 color: "#dadada" } - FirelightMenuItem { - labelText: "Settings" - Layout.fillWidth: true - // Layout.preferredWidth: parent.width / 2 - Layout.alignment: Qt.AlignLeft | Qt.AlignTop - Layout.preferredHeight: 40 - checkable: true - alignRight: true - } FirelightMenuItem { labelText: "Back to Main Menu" Layout.fillWidth: true @@ -142,6 +135,7 @@ Item { Layout.preferredHeight: 40 checkable: false alignRight: true + enabled: false onClicked: function () { backToMainMenuPressed() diff --git a/qml/discover/StoreContent.qml b/qml/pages/StoreContent.qml similarity index 100% rename from qml/discover/StoreContent.qml rename to qml/pages/StoreContent.qml diff --git a/qml/platforms/GameBoyColorSettings.qml b/qml/platforms/GameBoyColorSettings.qml new file mode 100644 index 0000000..e56fb97 --- /dev/null +++ b/qml/platforms/GameBoyColorSettings.qml @@ -0,0 +1,192 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + Flickable { + anchors.fill: parent + contentHeight: column.height + ColumnLayout { + id: column + width: parent.width + + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: 40 + spacing: 8 + + Button { + id: backButton + background: Rectangle { + color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent" + radius: height / 2 + + } + + Layout.fillHeight: true + Layout.preferredWidth: parent.height + + hoverEnabled: true + + contentItem: Text { + text: "\ue5e0" + font.family: Constants.symbolFontFamily + leftPadding: 8 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 24 + color: ColorPalette.neutral400 + } + + checkable: false + + onClicked: { + root.StackView.view.pop() + } + } + + Text { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Default Game Boy Color settings") + font.pixelSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.Bold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + color: ColorPalette.neutral100 + } + Layout.bottomMargin: 8 + } + + Rectangle { + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + OptionGroup { + Layout.topMargin: 30 + Layout.fillWidth: true + label: "Display" + + content: [ + ToggleOption { + Layout.fillWidth: true + label: "Simulate LCD ghosting effects" + // description: "Enables simulation of LCD ghosting effects by blending the current and previous frames." + checked: emulator_config_manager.getOptionValueForPlatform(2, "gambatte_mix_frames") === "accurate" + + // Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_mix_frames") === "accurate" + // } + + onCheckedChanged: { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(2, "gambatte_mix_frames", "accurate") + } else { + emulator_config_manager.setOptionValueForPlatform(2, "gambatte_mix_frames", "disabled") + } + } + + }, + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + }, + ToggleOption { + id: colorCorrectionOption + Layout.fillWidth: true + label: "Adjust output colors to match original hardware" + // label: "Color correction" + // description: "Make the screen colors more accurate to the original hardware." + + checked: emulator_config_manager.getOptionValueForPlatform(2, "gambatte_gbc_color_correction") === "GBC only" + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(2, "gambatte_gbc_color_correction", "GBC only") + } else { + emulator_config_manager.setOptionValueForPlatform(2, "gambatte_gbc_color_correction", "disabled") + } + } + }, + + Option { + Layout.fillWidth: true + // Layout.leftMargin: 36 + isSubItem: true + visible: colorCorrectionOption.checked + label: "Frontlight position" + // description: "Simulates the physical response of the Game Boy Color LCD panel when illuminated from different angles." + control: MyComboBox { + id: frontlightPositionComboBox + enabled: colorCorrectionOption.checked + model: [{ + text: "Center of screen", + value: "central" + }, { + text: "Above screen", + value: "above screen" + }, { + text: "Below screen", + value: "below screen" + }] + textRole: "text" + valueRole: "value" + + Component.onCompleted: { + const val = frontlightPositionComboBox.indexOfValue(emulator_config_manager.getOptionValueForPlatform(2, "gambatte_gbc_frontlight_position")) + console.log("VALUE: " + val) + frontlightPositionComboBox.currentIndex = val + } + + onActivated: function (index) { + if (!currentValue) { + return + } + emulator_config_manager.setOptionValueForPlatform(2, "gambatte_gbc_frontlight_position", currentValue) + } + } + }, + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + }, + + Option { + Layout.fillWidth: true + label: "Darken screen to reduce harshness" + }, + + MySlider { + Layout.fillWidth: true + // Layout.preferredHeight: 48 + from: 0 + to: 50 + stepSize: 5 + snapMode: Slider.SnapOnRelease + live: false + + value: emulator_config_manager.getOptionValueForPlatform(2, "gambatte_dark_filter_level") + + onValueChanged: function () { + emulator_config_manager.setOptionValueForPlatform(2, "gambatte_dark_filter_level", value) + } + } + + + ] + } + + + } + } +} \ No newline at end of file diff --git a/qml/platforms/GameBoySettings.qml b/qml/platforms/GameBoySettings.qml new file mode 100644 index 0000000..d3cf0fa --- /dev/null +++ b/qml/platforms/GameBoySettings.qml @@ -0,0 +1,457 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + Flickable { + anchors.fill: parent + contentHeight: column.height + ColumnLayout { + id: column + width: parent.width + + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: 40 + spacing: 8 + + Button { + id: backButton + background: Rectangle { + color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent" + radius: height / 2 + + } + + Layout.fillHeight: true + Layout.preferredWidth: parent.height + + hoverEnabled: true + + contentItem: Text { + text: "\ue5e0" + font.family: Constants.symbolFontFamily + leftPadding: 8 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 24 + color: ColorPalette.neutral400 + } + + checkable: false + + onClicked: { + root.StackView.view.pop() + } + } + + Text { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Default Game Boy settings") + font.pixelSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.Bold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + color: ColorPalette.neutral100 + } + Layout.bottomMargin: 8 + } + + Rectangle { + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + OptionGroup { + Layout.fillWidth: true + label: "Colorization" + + content: [ + Option { + id: colorizePalette + Layout.fillWidth: true + // Layout.leftMargin: 36 + label: "Colorization mode" + property string currentValue + // description: "Simulates the physical response of the Game Boy Color LCD panel when illuminated from different angles." + control: MyComboBox { + model: [{ + text: "Disabled", + value: "disabled" + }, { + text: "Auto", + value: "auto" + }, { + text: "Specific palette", + value: "internal" + }] + textRole: "text" + valueRole: "value" + + Component.onCompleted: { + const value = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_gb_colorization") + + if (value) { + currentIndex = indexOfValue(value) + colorizePalette.currentValue = value + } else { + currentIndex = 0 + } + } + + onActivated: function (index) { + emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gb_colorization", currentValue) + colorizePalette.currentValue = currentValue + } + } + }, + Option { + id: colorizationOption + Layout.fillWidth: true + // Layout.leftMargin: 36 + isSubItem: true + visible: colorizePalette.currentValue === "internal" + label: "Color palette" + property string currentValue + control: MyComboBox { + enabled: colorizePalette.currentValue === "internal" + model: [{ + text: "GB - DMG", + value: "GB - DMG" + }, { + text: "GB - Pocket", + value: "GB - Pocket" + }, { + text: "GB - Light", + value: "GB - Light" + }, { + text: "GBC - Blue", + value: "GBC - Blue" + }, { + text: "GBC - Brown", + value: "GBC - Brown" + }, { + text: "GBC - Dark Blue", + value: "GBC - Dark Blue" + }, { + text: "GBC - Dark Brown", + value: "GBC - Dark Brown" + }, { + text: "GBC - Dark Green", + value: "GBC - Dark Green" + }, { + text: "GBC - Grayscale", + value: "GBC - Grayscale" + }, { + text: "GBC - Green", + value: "GBC - Green" + }, { + text: "GBC - Inverted", + value: "GBC - Inverted" + }, { + text: "GBC - Orange", + value: "GBC - Orange" + }, { + text: "GBC - Pastel Mix", + value: "GBC - Pastel Mix" + }, { + text: "GBC - Red", + value: "GBC - Red" + }, { + text: "GBC - Yellow", + value: "GBC - Yellow" + }, { + text: "SGB - 1A", + value: "SGB - 1A" + }, { + text: "SGB - 1B", + value: "SGB - 1B" + }, { + text: "SGB - 1C", + value: "SGB - 1C" + }, { + text: "SGB - 1D", + value: "SGB - 1D" + }, { + text: "SGB - 1E", + value: "SGB - 1E" + }, { + text: "SGB - 1F", + value: "SGB - 1F" + }, { + text: "SGB - 1G", + value: "SGB - 1G" + }, { + text: "SGB - 1H", + value: "SGB - 1H" + }, { + text: "SGB - 2A", + value: "SGB - 2A" + }, { + text: "SGB - 2B", + value: "SGB - 2B" + }, { + text: "SGB - 2C", + value: "SGB - 2C" + }, { + text: "SGB - 2D", + value: "SGB - 2D" + }, { + text: "SGB - 2E", + value: "SGB - 2E" + }, { + text: "SGB - 2F", + value: "SGB - 2F" + }, { + text: "SGB - 2G", + value: "SGB - 2G" + }, { + text: "SGB - 2H", + value: "SGB - 2H" + }, { + text: "SGB - 3A", + value: "SGB - 3A" + }, { + text: "SGB - 3B", + value: "SGB - 3B" + }, { + text: "SGB - 3C", + value: "SGB - 3C" + }, { + text: "SGB - 3D", + value: "SGB - 3D" + }, { + text: "SGB - 3E", + value: "SGB - 3E" + }, { + text: "SGB - 3F", + value: "SGB - 3F" + }, { + text: "SGB - 3G", + value: "SGB - 3G" + }, { + text: "SGB - 3H", + value: "SGB - 3H" + }, { + text: "SGB - 4A", + value: "SGB - 4A" + }, { + text: "SGB - 4B", + value: "SGB - 4B" + }, { + text: "SGB - 4C", + value: "SGB - 4C" + }, { + text: "SGB - 4D", + value: "SGB - 4D" + }, { + text: "SGB - 4E", + value: "SGB - 4E" + }, { + text: "SGB - 4F", + value: "SGB - 4F" + }, { + text: "SGB - 4G", + value: "SGB - 4G" + }, { + text: "SGB - 4H", + value: "SGB - 4H" + }, { + text: "Special 1", + value: "Special 1" + }, { + text: "Special 2", + value: "Special 2" + }, { + text: "Special 3", + value: "Special 3" + }] + textRole: "text" + valueRole: "value" + + Component.onCompleted: { + const value = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_gb_internal_palette") + + if (value) { + currentIndex = indexOfValue(value) + colorizationOption.currentValue = value + } else { + currentIndex = 0 + } + } + + onActivated: function (index) { + emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gb_internal_palette", currentValue) + colorizationOption.currentValue = currentValue + } + } + }, + Text { + Layout.topMargin: 8 + Layout.fillWidth: true + visible: colorizePalette.currentValue === "internal" + text: "Example" + color: ColorPalette.neutral300 + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 15 + Layout.alignment: Qt.AlignLeft + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + }, + Item { + Layout.topMargin: 8 + Layout.bottomMargin: 16 + Layout.fillWidth: true + Layout.preferredHeight: 400 + visible: colorizePalette.currentValue === "internal" + Image { + id: colorPreviewImg + anchors.fill: parent + source: "file:system/_img/gb-dmg.png" + fillMode: Image.PreserveAspectFit + + Connections { + target: colorizationOption + + function onCurrentValueChanged() { + let filename = colorizationOption.currentValue ? colorizationOption.currentValue.toLowerCase() : "" + let removedSpacesText = filename.replace( + / /g, + "" + ); + colorPreviewImg.source = "file:system/_img/" + removedSpacesText + ".png" + } + } + } + } + ] + } + + OptionGroup { + Layout.topMargin: 30 + Layout.fillWidth: true + label: "Display" + + content: [ + ToggleOption { + Layout.fillWidth: true + label: "Simulate LCD ghosting effects" + // description: "Enables simulation of LCD ghosting effects by blending the current and previous frames." + checked: emulator_config_manager.getOptionValueForPlatform(1, "gambatte_mix_frames") === "accurate" + + // Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "gambatte_mix_frames") === "accurate" + // } + + onCheckedChanged: { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "gambatte_mix_frames", "accurate") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "gambatte_mix_frames", "disabled") + } + } + + }, + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + }, + ToggleOption { + id: colorCorrectionOption + Layout.fillWidth: true + label: "Adjust output colors to match original hardware" + // label: "Color correction" + // description: "Make the screen colors more accurate to the original hardware." + + checked: emulator_config_manager.getOptionValueForPlatform(1, "gambatte_gbc_color_correction") === "GBC only" + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gbc_color_correction", "GBC only") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gbc_color_correction", "disabled") + } + } + }, + + Option { + Layout.fillWidth: true + // Layout.leftMargin: 36 + isSubItem: true + visible: colorCorrectionOption.checked + label: "Frontlight position" + // description: "Simulates the physical response of the Game Boy Color LCD panel when illuminated from different angles." + control: MyComboBox { + id: frontlightPositionComboBox + enabled: colorCorrectionOption.checked + model: [{ + text: "Center of screen", + value: "central" + }, { + text: "Above screen", + value: "above screen" + }, { + text: "Below screen", + value: "below screen" + }] + textRole: "text" + valueRole: "value" + + Component.onCompleted: { + const val = frontlightPositionComboBox.indexOfValue(emulator_config_manager.getOptionValueForPlatform(1, "gambatte_gbc_frontlight_position")) + console.log("VALUE: " + val) + frontlightPositionComboBox.currentIndex = val + } + + onActivated: function (index) { + if (!currentValue) { + return + } + emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gbc_frontlight_position", currentValue) + } + } + }, + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + }, + + Option { + Layout.fillWidth: true + label: "Darken screen to reduce harshness" + }, + + MySlider { + Layout.fillWidth: true + // Layout.preferredHeight: 48 + from: 0 + to: 50 + stepSize: 5 + snapMode: Slider.SnapOnRelease + live: false + + value: emulator_config_manager.getOptionValueForPlatform(1, "gambatte_dark_filter_level") + + onValueChanged: function () { + emulator_config_manager.setOptionValueForPlatform(1, "gambatte_dark_filter_level", value) + } + } + + + ] + } + + + } + } +} \ No newline at end of file diff --git a/qml/platforms/GbaSettings.qml b/qml/platforms/GbaSettings.qml new file mode 100644 index 0000000..58d952e --- /dev/null +++ b/qml/platforms/GbaSettings.qml @@ -0,0 +1,215 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + Flickable { + anchors.fill: parent + contentHeight: column.height + ColumnLayout { + id: column + width: parent.width + + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: 40 + spacing: 8 + + Button { + id: backButton + background: Rectangle { + color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent" + radius: height / 2 + + } + + Layout.fillHeight: true + Layout.preferredWidth: parent.height + + hoverEnabled: true + + contentItem: Text { + text: "\ue5e0" + font.family: Constants.symbolFontFamily + leftPadding: 8 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 24 + color: ColorPalette.neutral400 + } + + checkable: false + + onClicked: { + root.StackView.view.pop() + } + } + + Text { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Default Game Boy Advance settings") + font.pixelSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.Bold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + color: ColorPalette.neutral100 + } + Layout.bottomMargin: 8 + } + + Rectangle { + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + OptionGroup { + Layout.fillWidth: true + label: "General" + + content: [ + // ToggleOption { + // Layout.fillWidth: true + // label: "Allow opposing D-Pad directions" + // description: "Enabling this will allow pressing / quickly alternating / holding both left and right (or up and down) directions at the same time. This may cause movement-based glitches." + // }, + // + // Rectangle { + // Layout.fillWidth: true + // Layout.preferredHeight: 1 + // color: "#333333" + // }, + + ToggleOption { + Layout.fillWidth: true + label: "Filter out overly harsh audio" + // description: "Enables a low pass audio filter to reduce the 'harshness' of generated audio." + + checked: emulator_config_manager.getOptionValueForPlatform(3, "mgba_audio_low_pass_filter") === "enabled" + + onCheckedChanged: { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(3, "mgba_audio_low_pass_filter", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(3, "mgba_audio_low_pass_filter", "disabled") + } + } + }, + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + }, + + ToggleOption { + Layout.fillWidth: true + label: "Adjust output colors to match original hardware" + // description: "Adjusts output colors to match the display of real GBA hardware." + + checked: emulator_config_manager.getOptionValueForPlatform(3, "mgba_color_correction") === "Auto" + + onCheckedChanged: { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(3, "mgba_color_correction", "Auto") + } else { + emulator_config_manager.setOptionValueForPlatform(3, "mgba_color_correction", "OFF") + } + } + }, + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + }, + + // ToggleOption { + // Layout.fillWidth: true + // label: "Game Boy Player Rumble (Restart)" + // description: "Enabling this will allow compatible games with the Game Boy Player boot logo to make the controller rumble. Due to how Nintendo decided this feature should work, it may cause glitches such as flickering or lag in some of these games." + // }, + // + // Rectangle { + // Layout.fillWidth: true + // Layout.preferredHeight: 1 + // color: "#333333" + // }, + + // ToggleOption { + // Layout.fillWidth: true + // label: "Idle Loop Optimization" + // description: "Reduce system load by optimizing so-called 'idle-loops' - sections in the code where nothing happens, but the CPU runs at full speed (like a car revving in neutral). Improves performance, and should be enabled on low-end hardware." + // }, + // + // Rectangle { + // Layout.fillWidth: true + // Layout.preferredHeight: 1 + // color: "#333333" + // }, + + ToggleOption { + Layout.fillWidth: true + label: "Simulate LCD ghosting effects" + // description: "Simulates LCD ghosting effects. 'Simple' performs a 50:50 mix of the current and previous frames. 'Smart' attempts to detect screen flickering, and only performs a 50:50 mix on affected pixels. 'LCD Ghosting' mimics natural LCD response times by combining multiple buffered frames. 'Simple' or 'Smart' blending is required when playing games that aggressively exploit LCD ghosting for transparency effects (Wave Race, Chikyuu Kaihou Gun ZAS, F-Zero, the Boktai series...)." + + checked: emulator_config_manager.getOptionValueForPlatform(3, "mgba_interframe_blending") === "mix_smart" + + onCheckedChanged: { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(3, "mgba_interframe_blending", "mix_smart") + } else { + emulator_config_manager.setOptionValueForPlatform(3, "mgba_interframe_blending", "OFF") + } + } + }, + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + }, + + Option { + Layout.fillWidth: true + label: "Solar sensor level" + // description: "Sets ambient sunlight intensity. Can be used by games that included a solar sensor in their cartridges, e.g: the Boktai series." + }, + + MySlider { + Layout.fillWidth: true + // Layout.preferredHeight: 48 + from: 0 + to: 100 + stepSize: 10 + snapMode: Slider.SnapOnRelease + live: false + + value: emulator_config_manager.getOptionValueForPlatform(3, "mgba_solar_sensor_level") * 10 + + onValueChanged: function () { + emulator_config_manager.setOptionValueForPlatform(3, "mgba_solar_sensor_level", value / 10) + } + } + + // Rectangle { + // Layout.fillWidth: true + // Layout.preferredHeight: 1 + // color: "#333333" + // }, + // + // ToggleOption { + // Layout.fillWidth: true + // label: "Use BIOS" + // description: "Use official BIOS/bootloader for emulated hardware, if present in RetroArch's system directory." + // } + ] + } + } + } +} \ No newline at end of file diff --git a/qml/platforms/NesSettings.qml b/qml/platforms/NesSettings.qml new file mode 100644 index 0000000..d22ae8e --- /dev/null +++ b/qml/platforms/NesSettings.qml @@ -0,0 +1,82 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + Flickable { + anchors.fill: parent + contentHeight: column.height + ColumnLayout { + id: column + width: parent.width + + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: 40 + spacing: 8 + + Button { + id: backButton + background: Rectangle { + color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent" + radius: height / 2 + + } + + Layout.fillHeight: true + Layout.preferredWidth: parent.height + + hoverEnabled: true + + contentItem: Text { + text: "\ue5e0" + font.family: Constants.symbolFontFamily + leftPadding: 8 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 24 + color: ColorPalette.neutral400 + } + + checkable: false + + onClicked: { + root.StackView.view.pop() + } + } + + Text { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Default NES settings") + font.pixelSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.Bold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + color: ColorPalette.neutral100 + } + Layout.bottomMargin: 8 + } + + Rectangle { + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + Text { + Layout.fillWidth: true + text: "There's nothing here yet." + color: ColorPalette.neutral100 + font.pixelSize: 15 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignHCenter + } + } + } +} \ No newline at end of file diff --git a/qml/platforms/Nintendo64Settings.qml b/qml/platforms/Nintendo64Settings.qml new file mode 100644 index 0000000..da09263 --- /dev/null +++ b/qml/platforms/Nintendo64Settings.qml @@ -0,0 +1,82 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + Flickable { + anchors.fill: parent + contentHeight: column.height + ColumnLayout { + id: column + width: parent.width + + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: 40 + spacing: 8 + + Button { + id: backButton + background: Rectangle { + color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent" + radius: height / 2 + + } + + Layout.fillHeight: true + Layout.preferredWidth: parent.height + + hoverEnabled: true + + contentItem: Text { + text: "\ue5e0" + font.family: Constants.symbolFontFamily + leftPadding: 8 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 24 + color: ColorPalette.neutral400 + } + + checkable: false + + onClicked: { + root.StackView.view.pop() + } + } + + Text { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Default Nintendo 64 settings") + font.pixelSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.Bold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + color: ColorPalette.neutral100 + } + Layout.bottomMargin: 8 + } + + Rectangle { + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + Text { + Layout.fillWidth: true + text: "There's nothing here yet." + color: ColorPalette.neutral100 + font.pixelSize: 15 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignHCenter + } + } + } +} \ No newline at end of file diff --git a/qml/platforms/PlatformSettingsPage.qml b/qml/platforms/PlatformSettingsPage.qml new file mode 100644 index 0000000..1c301ad --- /dev/null +++ b/qml/platforms/PlatformSettingsPage.qml @@ -0,0 +1,303 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + + Component { + id: n64Settings + Nintendo64Settings { + } + } + + Component { + id: snesSettings + SnesSettings { + } + } + + Component { + id: nesSettings + NesSettings { + } + } + + Component { + id: gbaSettings + GbaSettings { + } + } + + Component { + id: gbcSettings + GameBoyColorSettings { + } + } + + Component { + id: gbSettings + GameBoySettings { + } + } + + + Flickable { + anchors.fill: parent + contentHeight: col.height + ColumnLayout { + spacing: 0 + id: col + width: parent.width + + Text { + Layout.fillWidth: true + text: qsTr("Platform settings") + font.pixelSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.Bold + Layout.bottomMargin: 8 + color: "white" + } + + Text { + Layout.fillWidth: true + // text: qsTr("Here's where you can change settings for each platform. You can change settings on a per-game basis by selecting the game in your library and going to 'Settings'.") + text: qsTr("Here's where you can change the default settings for each platform. Later you'll be able to change them on a per-game basis.") + font.pixelSize: 15 + font.family: Constants.regularFontFamily + font.weight: Font.Normal + wrapMode: Text.WordWrap + color: ColorPalette.neutral300 + Layout.bottomMargin: 8 + } + + Rectangle { + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + Button { + Layout.fillWidth: true + Layout.preferredHeight: 80 + + onClicked: function () { + root.StackView.view.push(gbSettings) + } + hoverEnabled: true + background: Rectangle { + color: parent.hovered ? "#333333" : "transparent" + radius: 8 + } + contentItem: Row { + spacing: 8 + Image { + source: "file:system/_img/gb.svg" + width: parent.height + height: parent.height + fillMode: Image.PreserveAspectFit + sourceSize.height: parent.height + } + Text { + text: qsTr("Game Boy") + font.pointSize: 14 + height: parent.height + font.family: Constants.regularFontFamily + font.weight: Font.Normal + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + color: "white" + } + + } + + } + Button { + Layout.fillWidth: true + Layout.preferredHeight: 80 + + onClicked: function () { + root.StackView.view.push(gbcSettings) + } + hoverEnabled: true + background: Rectangle { + color: parent.hovered ? "#333333" : "transparent" + radius: 8 + } + contentItem: Row { + spacing: 8 + Image { + source: "file:system/_img/gbc.svg" + width: parent.height + height: parent.height + fillMode: Image.PreserveAspectFit + sourceSize.height: parent.height + } + Text { + text: qsTr("Game Boy Color") + font.pointSize: 14 + height: parent.height + font.family: Constants.regularFontFamily + font.weight: Font.Normal + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + color: "white" + } + + } + + } + Button { + Layout.fillWidth: true + Layout.preferredHeight: 80 + + onClicked: function () { + root.StackView.view.push(gbaSettings) + } + hoverEnabled: true + background: Rectangle { + color: parent.hovered ? "#333333" : "transparent" + radius: 8 + } + contentItem: Row { + spacing: 8 + Image { + source: "file:system/_img/gba.svg" + width: parent.height + height: parent.height + fillMode: Image.PreserveAspectFit + sourceSize.height: parent.height + } + Text { + text: qsTr("Game Boy Advance") + font.pointSize: 14 + height: parent.height + font.family: Constants.regularFontFamily + font.weight: Font.Normal + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + color: "white" + } + + } + + } + Button { + Layout.fillWidth: true + Layout.preferredHeight: 80 + + onClicked: function () { + root.StackView.view.push(nesSettings) + } + hoverEnabled: true + background: Rectangle { + color: parent.hovered ? "#333333" : "transparent" + radius: 8 + } + contentItem: Row { + spacing: 8 + Image { + source: "file:system/_img/nes.svg" + width: parent.height + height: parent.height + fillMode: Image.PreserveAspectFit + sourceSize.height: parent.height + } + Text { + text: qsTr("NES") + font.pointSize: 14 + height: parent.height + font.family: Constants.regularFontFamily + font.weight: Font.Normal + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + color: "white" + } + + } + + } + + Button { + Layout.fillWidth: true + Layout.preferredHeight: 80 + + onClicked: function () { + root.StackView.view.push(snesSettings) + } + + hoverEnabled: true + background: Rectangle { + color: parent.hovered ? "#333333" : "transparent" + radius: 8 + } + contentItem: Row { + spacing: 8 + Image { + source: "file:system/_img/SNES.svg" + width: parent.height + height: parent.height + fillMode: Image.PreserveAspectFit + sourceSize.height: parent.height + } + Text { + text: qsTr("SNES") + height: parent.height + font.pointSize: 14 + font.family: Constants.regularFontFamily + font.weight: Font.Normal + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + color: "white" + } + + } + + } + + Button { + Layout.fillWidth: true + Layout.preferredHeight: 80 + + onClicked: function () { + root.StackView.view.push(n64Settings) + } + hoverEnabled: true + background: Rectangle { + color: parent.hovered ? "#333333" : "transparent" + radius: 8 + } + contentItem: Row { + spacing: 8 + Image { + source: "file:system/_img/N64.svg" + width: parent.height + height: parent.height + fillMode: Image.PreserveAspectFit + sourceSize.height: parent.height + } + Text { + text: qsTr("Nintendo 64") + height: parent.height + font.pointSize: 14 + font.family: Constants.regularFontFamily + font.weight: Font.Normal + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + color: "white" + } + + } + + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } +} \ No newline at end of file diff --git a/qml/platforms/SnesSettings.qml b/qml/platforms/SnesSettings.qml new file mode 100644 index 0000000..84a1e67 --- /dev/null +++ b/qml/platforms/SnesSettings.qml @@ -0,0 +1,82 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + Flickable { + anchors.fill: parent + contentHeight: column.height + ColumnLayout { + id: column + width: parent.width + + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: 40 + spacing: 8 + + Button { + id: backButton + background: Rectangle { + color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent" + radius: height / 2 + + } + + Layout.fillHeight: true + Layout.preferredWidth: parent.height + + hoverEnabled: true + + contentItem: Text { + text: "\ue5e0" + font.family: Constants.symbolFontFamily + leftPadding: 8 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 24 + color: ColorPalette.neutral400 + } + + checkable: false + + onClicked: { + root.StackView.view.pop() + } + } + + Text { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Default SNES settings") + font.pixelSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.Bold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + color: ColorPalette.neutral100 + } + Layout.bottomMargin: 8 + } + + Rectangle { + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + Text { + Layout.fillWidth: true + text: "There's nothing here yet." + color: ColorPalette.neutral100 + font.pixelSize: 15 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + horizontalAlignment: Text.AlignHCenter + } + } + } +} \ No newline at end of file diff --git a/qml/platforms/SnesSettings.qml.bak b/qml/platforms/SnesSettings.qml.bak new file mode 100644 index 0000000..01a9596 --- /dev/null +++ b/qml/platforms/SnesSettings.qml.bak @@ -0,0 +1,1114 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +FocusScope { + id: root + Flickable { + anchors.fill: parent + contentHeight: column.height + ColumnLayout { + id: column + width: parent.width + + // Image { + // Layout.fillWidth: true + // Layout.preferredHeight: parent.width + // source: "file:system/_img/snes.svg" + // sourceSize.height: height + // fillMode: Image.PreserveAspectFit + // } + + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: 40 + spacing: 8 + + Button { + id: backButton + background: Rectangle { + color: enabled ? (backButton.hovered ? "#404143" : "transparent") : "transparent" + radius: height / 2 + + } + + Layout.fillHeight: true + Layout.preferredWidth: parent.height + + hoverEnabled: true + + contentItem: Text { + text: "\ue5e0" + font.family: Constants.symbolFontFamily + leftPadding: 8 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 24 + color: ColorPalette.neutral400 + } + + checkable: false + + onClicked: { + console.log(":)") + root.StackView.view.pop() + } + } + + Text { + Layout.fillWidth: true + Layout.fillHeight: true + text: qsTr("Default SNES Settings") + font.pixelSize: 26 + font.family: Constants.regularFontFamily + font.weight: Font.Bold + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + color: ColorPalette.neutral100 + } + Layout.bottomMargin: 8 + } + + Rectangle { + Layout.topMargin: 8 + Layout.bottomMargin: 8 + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + Text { + Layout.fillWidth: true + text: qsTr("Advanced settings") + font.pixelSize: 14 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + Layout.bottomMargin: 8 + color: ColorPalette.neutral400 + } + + // Option { + // Layout.fillWidth: true + // Layout.minimumHeight: 42 + // label: "Console region" + // description: "Specify which region the system is from. 'PAL' is 50hz, 'NTSC' is 60hz. Games will run faster or slower than normal if the incorrect region is selected." + // control: MyComboBox { + // model: [{ + // text: "Auto", + // value: "auto" + // }, { + // text: "NTSC", + // value: "ntsc" + // }, { + // text: "PAL", + // value: "pal" + // }] + // textRole: "text" + // valueRole: "value" + // + // Component.onCompleted: { + // // currentIndex = find(emulator_config_manager.getOptionValueForPlatform(1, "snes9x_region")) + // emulator_config_manager.getOptionValueForPlatform(1, "snes9x_region") + // } + // + // onCurrentValueChanged: { + // emulator_config_manager.setOptionValueForPlatform(1, "snes9x_region", currentValue) + // } + // } + // } + + // Text { + // Layout.fillWidth: true + // text: "Console region" + // color: ColorPalette.neutral100 + // font.pixelSize: 15 + // Layout.alignment: Qt.AlignLeft + // font.family: Constants.regularFontFamily + // font.weight: Font.DemiBold + // verticalAlignment: Text.AlignVCenter + // } + + RadioButtonGroup { + Layout.fillWidth: true + + label: "Console region" + // description: "Specify which region the system is from. 'PAL' is 50hz, 'NTSC' is 60hz. Games will run faster or slower than normal if the incorrect region is selected." + + model: [{ + label: "Auto", + description: "Chooses NTSC or PAL based on the game when it is loaded." + }, { + label: "NTSC", + description: "Runs games at 60hz." + }, { + label: "PAL", + description: "Run games at 50hz" + }] + } + + Text { + Layout.topMargin: 30 + Layout.fillWidth: true + text: qsTr("Stuff") + font.pixelSize: 14 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + Layout.bottomMargin: 4 + color: "#a6a6a6" + } + + Pane { + Layout.fillWidth: true + verticalPadding: overclockCol.stuffCol + background: Rectangle { + radius: 8 + color: ColorPalette.neutral800 + } + + contentItem: ColumnLayout { + id: stuffCol + + ToggleOption { + Layout.fillWidth: true + label: "Allow opposing D-Pad directions" + description: "Enabling this will allow pressing / quickly alternating / holding both left and right (or up and down) directions at the same time. This may cause movement-based glitches." + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_up_down_allowed", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_up_down_allowed", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + label: "Crop overscan" + description: "Remove the borders at the top and bottom of the screen, typically unused by games and hidden by the bezel of a standard-definition television. 'Auto' will attempt to detect and crop the ~8 pixel overscan based on the current content." + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_overscan") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_overscan", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_overscan", "disabled") + } + } + + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + label: "Enable graphic clip windows" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_gfx_clip") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_gfx_clip", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_gfx_clip", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + label: "Enable transparency effects" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_gfx_transp") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_gfx_transp", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_gfx_transp", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + label: "Hi-Res blending" + description: "Blend adjacent pixels when game switches to hi-res mode (512x448). Required for certain games that use hi-res mode to produce transparency effects (Kirby's Dream Land, Jurassic Park...)." + + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_hires_blend") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_hires_blend", "blur") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_hires_blend", "disabled") + } + } + } + } + } + + Text { + Layout.topMargin: 30 + Layout.fillWidth: true + text: qsTr("Overclocking") + font.pixelSize: 14 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + Layout.bottomMargin: 4 + color: "#a6a6a6" + } + + Pane { + Layout.fillWidth: true + verticalPadding: overclockCol.spacing + background: Rectangle { + radius: 8 + color: ColorPalette.neutral800 + } + + contentItem: ColumnLayout { + id: overclockCol + Option { + Layout.fillWidth: true + label: "SuperFX overclock" + description: "SuperFX coprocessor frequency multiplier. Can improve frame rate or cause timing errors. Values under 100% can improve game performance on slow devices." + + control: MyComboBox { + model: [{ + text: "50%", + value: "50%" + }, { + text: "60%", + value: "60%" + }, { + text: "70%", + value: "70%" + }, { + text: "80%", + value: "80%" + }, { + text: "90%", + value: "90%" + }, { + text: "100%", + value: "100%" + }, { + text: "150%", + value: "150%" + }, { + text: "200%", + value: "200%" + }, { + text: "250%", + value: "250%" + }, { + text: "300%", + value: "300%" + }, { + text: "350%", + value: "350%" + }, { + text: "400%", + value: "400%" + }, { + text: "450%", + value: "450%" + }, { + text: "500%", + value: "500%" + }] + + currentIndex: 5 + textRole: "text" + valueRole: "value" + + Component.onCompleted: { + // currentIndex = find(emulator_config_manager.getOptionValueForPlatform(1, "snes9x_region")) + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_overclock_superfx") + } + + onCurrentValueChanged: { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_overclock_superfx", currentValue) + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + Option { + Layout.fillWidth: true + Layout.minimumHeight: 42 + label: "CPU overclock" + description: "Overclock SNES CPU. May cause games to crash! Use 'Light' for shorter loading times, 'Compatible' for most games exhibiting slowdown and 'Max' only if absolutely required (Gradius 3, Super R-type...)." + // control: MyComboBox { + // model: [{ + // text: "None", + // value: "disabled" + // }, { + // text: "Light", + // value: "light" + // }, { + // text: "Compatible", + // value: "compatible" + // }, { + // text: "Max", + // value: "max" + // }] + // textRole: "text" + // valueRole: "value" + // + // Component.onCompleted: { + // // currentIndex = find(emulator_config_manager.getOptionValueForPlatform(1, "snes9x_region")) + // emulator_config_manager.getOptionValueForPlatform(1, "snes9x_overclock_cycles") + // } + // + // onCurrentValueChanged: { + // emulator_config_manager.setOptionValueForPlatform(1, "snes9x_overclock_cycles", currentValue) + // } + // } + } + + Slider { + id: slider + Layout.fillWidth: true + Layout.preferredHeight: 48 + from: 0 + to: 3 + stepSize: 1 + snapMode: Slider.SnapAlways + + handle: Rectangle { + color: "white" + width: 10 + height: 24 + radius: 2 + x: slider.width * slider.visualPosition - (width / 2) + y: ((slider.height * 2 / 3) / 2) - (height / 2) + z: 4 + } + + background: Item { + Item { + height: parent.height * 2 / 3 + width: parent.width + Rectangle { + height: 8 + width: parent.width + y: parent.height / 2 - (height / 2) + color: "grey" + Rectangle { + height: 24 + color: "grey" + width: 2 + x: -(width / 2) + y: -height / 2 + (parent.height / 2) + } + + Rectangle { + height: 24 + color: "grey" + width: 2 + x: slider.width / 3 - (width / 2) + y: -height / 2 + (parent.height / 2) + } + + Rectangle { + height: 24 + width: 2 + color: "grey" + x: slider.width * 2 / 3 - (width / 2) + y: -height / 2 + (parent.height / 2) + } + + Rectangle { + height: 24 + width: 2 + color: "grey" + x: slider.width - (width / 2) + y: -height / 2 + (parent.height / 2) + } + + Rectangle { + color: "lightblue" + height: parent.height + width: slider.width * slider.visualPosition + } + } + } + Item { + width: parent.width + height: parent.height / 3 + y: parent.height * 2 / 3 + + Text { + text: "None" + font.pointSize: 8 + color: "white" + x: -(width / 2) + } + + Text { + text: "Light" + font.pointSize: 8 + color: "white" + x: slider.width / 3 - (width / 2) + } + + Text { + text: "Compatible" + font.pointSize: 8 + color: "white" + x: slider.width * 2 / 3 - (width / 2) + } + + Text { + text: "Max" + font.pointSize: 8 + color: "white" + x: slider.width - (width / 2) + } + } + } + } + } + } + + Text { + Layout.topMargin: 30 + Layout.fillWidth: true + text: qsTr("Emulation hacks") + font.pixelSize: 14 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + Layout.bottomMargin: 4 + color: ColorPalette.neutral400 + } + + Pane { + Layout.fillWidth: true + verticalPadding: hackCol.spacing + background: Rectangle { + radius: 8 + color: ColorPalette.neutral800 + } + + contentItem: ColumnLayout { + id: hackCol + ToggleOption { + Layout.fillWidth: true + label: "Increase sprite limit to reduce flickering" + + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_reduce_sprite_flicker") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_reduce_sprite_flicker", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_reduce_sprite_flicker", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + label: "Randomize system RAM at startup" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_randomize_memory") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_randomize_memory", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_randomize_memory", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 42 + label: "Block invalid VRAM access" + description: "Some homebrew/ROM hacks require this option to be disabled for correct operation." + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_block_invalid_vram_access") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_block_invalid_vram_access", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_block_invalid_vram_access", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 42 + label: "Echo buffer hack" + description: "Some homebrew/ROM hacks require this option to be enabled for correct operation." + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_echo_buffer_hack") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_echo_buffer_hack", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_echo_buffer_hack", "disabled") + } + } + } + } + } + + Text { + Layout.topMargin: 30 + Layout.fillWidth: true + text: qsTr("Graphical layers") + font.pixelSize: 14 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + Layout.bottomMargin: 4 + color: ColorPalette.neutral400 + } + + Pane { + Layout.fillWidth: true + verticalPadding: layerCol.spacing + background: Rectangle { + radius: 8 + color: ColorPalette.neutral800 + } + + contentItem: ColumnLayout { + id: layerCol + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 20 + label: "Show layer 1" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_1") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_1", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_1", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 20 + label: "Show layer 2" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_2") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_2", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_2", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 20 + label: "Show layer 3" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_3") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_3", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_3", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 20 + label: "Show layer 4" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_4") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_4", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_4", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 20 + label: "Show layer 5 (sprite layer)" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_layer_5") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_5", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_layer_5", "disabled") + } + } + } + } + } + + Text { + Layout.topMargin: 30 + Layout.fillWidth: true + text: qsTr("Sound channels") + font.pixelSize: 14 + font.family: Constants.regularFontFamily + font.weight: Font.DemiBold + Layout.bottomMargin: 4 + color: ColorPalette.neutral400 + } + + Pane { + Layout.fillWidth: true + verticalPadding: soundCol.spacing + background: Rectangle { + radius: 8 + color: ColorPalette.neutral800 + } + + contentItem: ColumnLayout { + id: soundCol + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 24 + label: "Enable channel 1" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_1") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_1", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_1", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 24 + label: "Enable channel 2" + + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_2") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_2", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_2", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 24 + label: "Enable channel 3" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_3") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_3", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_3", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 24 + label: "Enable channel 4" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_4") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_4", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_4", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 24 + label: "Enable channel 5" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_5") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_5", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_5", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 24 + label: "Enable channel 6" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_6") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_6", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_6", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 24 + label: "Enable channel 7" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_7") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_7", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_7", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 24 + label: "Enable channel 8" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_8") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_8", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_8", "disabled") + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "#333333" + } + + ToggleOption { + Layout.fillWidth: true + Layout.minimumHeight: 24 + label: "Enable channel 9" + + Component.onCompleted: { + // checked = emulator_config_manager.getOptionValueForPlatform(1, "snes9x_up_down_allowed") === "enabled" + emulator_config_manager.getOptionValueForPlatform(1, "snes9x_sndchan_9") + } + + onCheckedChanged: function () { + if (checked) { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_9", "enabled") + } else { + emulator_config_manager.setOptionValueForPlatform(1, "snes9x_sndchan_9", "disabled") + } + } + } + } + } + + // TODO: Advanced option + // ToggleOption { + // Layout.fillWidth: true + // label: "Allow Opposing Directions" + // description: "Allow pressing (D-Pad Up + D-Pad Down) or (D-Pad Left + D-Pad Right) simultaneously. This isn't allowed on original hardware, so it may cause issues in some games." + // + // checked: achievement_manager.unlockNotificationsEnabled + // + // onCheckedChanged: { + // achievement_manager.unlockNotificationsEnabled = checked + // } + // } + + // ToggleOption { + // Layout.fillWidth: true + // label: "Play sound" + // Layout.leftMargin: 24 + // } + + // Rectangle { + // Layout.fillWidth: true + // Layout.preferredHeight: 1 + // color: "#333333" + // } + + // ToggleOption { + // id: colorCorrectionOption + // Layout.fillWidth: true + // Layout.minimumHeight: 42 + // label: "Color correction" + // description: "Make the screen colors more accurate to the original hardware." + // } + // + // ComboBoxOption { + // Layout.fillWidth: true + // Layout.minimumHeight: 42 + // Layout.leftMargin: 24 + // visible: colorCorrectionOption.checked + // label: "Frontlight position" + // description: "Simulates the physical response of the Game Boy Color LCD panel when illuminated from different angles." + // } + // + // Rectangle { + // Layout.fillWidth: true + // Layout.preferredHeight: 1 + // color: "#333333" + // } + // + // SliderOption { + // Layout.fillWidth: true + // Layout.minimumHeight: 42 + // label: "Dark filter level" + // description: "Darken the screen to reduce glare and/or eye strain. This is useful for games with white backgrounds, as they appear harsher than intended on modern displays." + // } + // + // Rectangle { + // Layout.fillWidth: true + // Layout.preferredHeight: 1 + // color: "#333333" + // } + // + // ToggleOption { + // Layout.fillWidth: true + // Layout.minimumHeight: 42 + // label: "Simulate LCD ghosting" + // description: "Enables simulation of LCD ghosting effects by blending the current and previous frames." + // + // onCheckedChanged: { + // if (checked) { + // emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gb_colorization", "auto") + // } else { + // emulator_config_manager.setOptionValueForPlatform(1, "gambatte_gb_colorization", "disabled") + // } + // } + // + // } + // + // Rectangle { + // Layout.fillWidth: true + // Layout.preferredHeight: 1 + // color: "#333333" + // } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + +} \ No newline at end of file diff --git a/qml/screens/EmulatorScreen.qml b/qml/screens/EmulatorScreen.qml new file mode 100644 index 0000000..c5744b0 --- /dev/null +++ b/qml/screens/EmulatorScreen.qml @@ -0,0 +1,393 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Window +import QtQuick.Layouts 1.0 +import QtQuick.Effects +import Firelight 1.0 + +FocusScope { + id: root + + property bool emulatorIsRunning: emulatorStack.depth > 0 + + signal gameReady() + + signal gameAboutToStop() + + signal gameStopped() + + function loadGame(id) { + emulatorStack.pushItem(emulatorComponent, {entryId: id}, StackView.Immediate) + // emulator.loadGame(id) + } + + Rectangle { + id: background + color: "black" + anchors.fill: parent + visible: root.emulatorIsRunning + } + + StackView { + id: emulatorStack + anchors.fill: parent + focus: true + + property bool suspended: false + property bool running: false + + // Keys.onEscapePressed: function (event) { + // if (event.isAutoRepeat) { + // return + // } + // + // if (emulatorStack.depth === 1) { + // emulatorStack.pushItem(nowPlayingPage, {}, StackView.PushTransition) + // } else if (emulatorStack.depth === 2) { + // emulatorStack.pop() + // } + // } + + pushEnter: Transition { + ParallelAnimation { + PropertyAnimation { + property: "opacity" + from: 0 + to: 1 + duration: 250 + easing.type: Easing.InOutQuad + } + PropertyAnimation { + property: "x" + from: -20 + to: 0 + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + pushExit: Transition { + + } + popEnter: Transition { + } + popExit: Transition { + ParallelAnimation { + PropertyAnimation { + property: "opacity" + from: 1 + to: 0 + duration: 250 + easing.type: Easing.InOutQuad + } + PropertyAnimation { + property: "x" + from: 0 + to: -20 + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + replaceEnter: Transition { + } + replaceExit: Transition { + } + } + + Component { + id: emulatorComponent + + EmulatorPage { + id: emulator + + property bool loaded: false + + required property int entryId + + Component.onCompleted: { + emulator.loadGame(entryId) + } + + onEmulationStarted: function () { + emulator.loaded = true + } + + ChallengeIndicatorList { + id: challengeIndicators + visible: achievement_manager.challengeIndicatorsEnabled + + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: 16 + anchors.rightMargin: 16 + height: 100 + width: 300 + } + + Keys.onEscapePressed: function (event) { + if (event.isAutoRepeat || !emulator.loaded) { + return + } + + if (emulator.StackView.status === StackView.Active) { + emulatorStack.pushItem(nowPlayingPage, {}, StackView.PushTransition) + } + } + + states: [ + State { + name: "stopped" + }, + State { + name: "suspended" + PropertyChanges { + target: emulatorDimmer + opacity: 0.4 + } + PropertyChanges { + emulator { + layer.enabled: true + blurAmount: 1 + } + } + }, + State { + name: "running" + PropertyChanges { + target: emulatorDimmer + opacity: 0 + } + PropertyChanges { + emulator { + layer.enabled: false + blurAmount: 0 + } + } + } + ] + + transitions: [ + Transition { + from: "*" + to: "suspended" + SequentialAnimation { + ScriptAction { + script: { + emulator.pauseGame() + } + } + PropertyAction { + target: emulator + property: "layer.enabled" + value: true + } + ParallelAnimation { + NumberAnimation { + properties: "blurAmount" + duration: 250 + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: emulatorDimmer + properties: "opacity" + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + }, + Transition { + from: "*" + to: "running" + SequentialAnimation { + ParallelAnimation { + NumberAnimation { + properties: "blurAmount" + duration: 250 + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: emulatorDimmer + properties: "opacity" + duration: 250 + easing.type: Easing.InOutQuad + } + } + PropertyAction { + target: emulator + property: "layer.enabled" + value: false + } + + ScriptAction { + script: { + emulator.resumeGame() + } + + } + } + } + ] + + StackView.visible: true + + StackView.onActivating: { + state = "running" + } + + StackView.onDeactivating: { + state = "suspended" + } + + property double blurAmount: 0 + + Behavior on blurAmount { + NumberAnimation { + duration: 250 + easing.type: Easing.InOutQuad + } + } + + layer.enabled: false + layer.effect: MultiEffect { + source: emulator + anchors.fill: emulator + blurEnabled: true + blurMultiplier: 1.0 + blurMax: 64 + blur: emulator.blurAmount + } + + Rectangle { + id: emulatorDimmer + anchors.fill: parent + color: "black" + opacity: 0 + + Behavior on opacity { + NumberAnimation { + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + + Connections { + target: window_resize_handler + + function onWindowResizeStarted() { + if (emulator.StackView.status === StackView.Active) { + emulator.pauseGame() + } + } + + function onWindowResizeFinished() { + if (emulator.StackView.status === StackView.Active) { + emulator.resumeGame() + } + } + } + } + } + + SequentialAnimation { + id: closeGameAnimation + + ScriptAction { + script: { + root.gameAboutToStop() + } + } + + PauseAnimation { + duration: 500 + } + + ScriptAction { + script: { + emulatorStack.clear() + } + } + + ScriptAction { + script: { + root.gameStopped() + } + } + + + } + + + Component { + id: nowPlayingPage + NowPlayingPage { + id: me + property bool topLevel: true + property string topLevelName: "nowPlaying" + + Keys.onEscapePressed: function (event) { + if (event.isAutoRepeat) { + return + } + + if (me.StackView.status === StackView.Active) { + // emulatorStack.pop() + me.StackView.view.popCurrentItem() + } + } + + onBackToMainMenuPressed: function () { + // root.StackView.view.popCurrentItem() + // stackView.push(homeScreen) + } + + onResumeGamePressed: function () { + emulatorStack.pop() + } + + onRestartGamePressed: function () { + const emu = emulatorStack.get(0) + emu.resetGame() + // emulator.resetGame() + emulatorStack.pop() + } + + onCloseGamePressed: function () { + closeGameAnimation.running = true + // emulatorStack.popCurrentItem() + // closeGameAnimation.start() + } + } + } + + AchievementProgressIndicator { + id: achievementProgressIndicator + + Connections { + target: achievement_manager + + function onAchievementProgressUpdated(imageUrl, id, name, description, current, desired) { + if (achievement_manager.progressNotificationsEnabled) { + achievementProgressIndicator.openWith(imageUrl, name, description, current, desired) + } + } + } + } + + AchievementUnlockIndicator { + id: achievementUnlockIndicator + + Connections { + target: achievement_manager + + function onAchievementUnlocked(imageUrl, name, description) { + if (achievement_manager.unlockNotificationsEnabled) { + achievementUnlockIndicator.openWith(imageUrl, name, description) + } + } + } + } +} \ No newline at end of file diff --git a/qml/screens/HomeScreen.qml b/qml/screens/HomeScreen.qml new file mode 100644 index 0000000..2b8b76c --- /dev/null +++ b/qml/screens/HomeScreen.qml @@ -0,0 +1,368 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs +import QtQuick.Window +import QtQuick.Layouts 1.0 +import Firelight 1.0 + +FocusScope { + id: root + + signal startGame(entryId: int) + + property bool showNowPlayingButton: false + + Keys.onEscapePressed: function (event) { + if (root.StackView.status !== StackView.Active && !event.isAutoRepeat) { + closeAppConfirmationDialog.open() + } + } + + Component { + id: modsPage + DiscoverPage { + property bool topLevel: true + property string topLevelName: "mods" + } + } + + Component { + id: libraryPage2 + LibraryPage2 { + objectName: "Library Page 2" + property bool topLevel: true + property string topLevelName: "library" + model: library_short_model + + onOpenDetails: function (id) { + contentStack.push(gameDetailsPage) + } + + Component.onCompleted: { + startGame.connect(root.startGame) + } + + Component { + id: gameDetailsPage + GameDetailsPage { + entryId: 115 + } + } + + // onEntryClicked: function (id) { + // emulatorScreen.loadGame(id) + // } + } + } + + + Component { + id: controllerPage + ControllersPage { + property bool topLevel: true + property string topLevelName: "controllers" + } + } + + Pane { + id: drawer + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 200 + background: Rectangle { + color: "#101114" + } + padding: 4 + focus: true + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 10 + } + + Text { + text: "Firelight" + opacity: parent.width > 48 ? 1 : 0 + color: "#dadada" + font.pointSize: 12 + font.weight: Font.DemiBold + font.family: Constants.regularFontFamily + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Text { + text: "alpha (0.5.0a)" + opacity: parent.width > 48 ? 1 : 0 + color: "#dadada" + font.pointSize: 8 + font.weight: Font.DemiBold + font.family: Constants.regularFontFamily + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + } + + Text { + Layout.fillWidth: true + Layout.preferredHeight: 12 + } + NavMenuButton { + id: homeNavButton + KeyNavigation.down: libraryNavButton + labelText: "Dashboard" + labelIcon: "\ue871" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 48 + enabled: false + + + checked: contentStack.currentPageName === "home" + // checked: stackview.topLevelName === "home" + } + NavMenuButton { + id: modNavButton + KeyNavigation.down: controllersNavButton + labelText: "Mod Shop" + labelIcon: "\uef48" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 48 + enabled: false + checked: contentStack.currentPageName === "mods" + + // checked: stackview.topLevelName === "mods" + + onToggled: function () { + // stackview.replace(null, modsPage) + contentStack.goTo(modsPage) + } + } + NavMenuButton { + id: libraryNavButton + KeyNavigation.down: modNavButton + labelText: "My Library" + labelIcon: "\uf53e" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 48 + focus: true + + // checked: stackview.topLevelName === "library" + checked: contentStack.currentPageName === "library" + + onToggled: function () { + // stackview.replace(null, libraryPage) + contentStack.goTo(libraryPage2) + } + } + NavMenuButton { + id: controllersNavButton + // KeyNavigation.down: nowPlayingNavButton + labelText: "Controllers" + labelIcon: "\uf135" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 48 + + // enabled: true + + // checked: stackview.topLevelName === "controllers" + checked: contentStack.currentPageName === "controllers" + + onToggled: function () { + contentStack.goTo(controllerPage) + // stackview.replace(null, controllerPage) + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + NavMenuButton { + id: nowPlayingNavButton + labelText: "Back to game" + labelIcon: "\ue037" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 48 + + visible: root.showNowPlayingButton + // visible: emulator.running + + checkable: false + + onClicked: function () { + root.StackView.view.pop() + } + } + Rectangle { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.bottomMargin: 4 + Layout.preferredHeight: 1 + color: "#404143" + } + + RowLayout { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 48 + Layout.maximumHeight: 48 + spacing: 8 + + Button { + background: Rectangle { + color: "transparent" + radius: 4 + } + Layout.preferredHeight: 42 + Layout.fillWidth: true + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + + checkable: false + } + + Button { + id: me + background: Rectangle { + color: enabled ? (me.hovered ? "#404143" : "transparent") : "transparent" + radius: 4 + } + Layout.preferredHeight: 42 + Layout.preferredWidth: 42 + Layout.alignment: Qt.AlignCenter + + hoverEnabled: true + + contentItem: Text { + text: "\ue8b8" + font.family: Constants.symbolFontFamily + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pixelSize: 26 + color: "#c3c3c3" + } + + checkable: false + + onClicked: { + Router.navigateTo("settings") + } + } + } + + + } + } + + StackView { + id: contentStack + anchors.top: parent.top + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.left: drawer.right + KeyNavigation.left: drawer + + // + objectName: "Home Content Stack View" + // anchors.fill: parent + + property alias currentPageName: contentStack.topLevelName + + function goTo(page) { + contentStack.replace(null, page) + } + + property string topLevelName: "" + + onCurrentItemChanged: { + if (currentItem) { + let top = contentStack.find(function (item, index) { + return item.topLevel === true + }) + + contentStack.topLevelName = top ? top.topLevelName : "" + } + } + + // Pane { + // width: 48 + // height: 48 + // + // z: 2 + // + // background: Item { + // } + // + // Button { + // id: melol + // anchors.left: parent.left + // anchors.verticalCenter: parent.verticalCenter + // // horizontalPadding: 12 + // // verticalPadding: 8 + // + // enabled: stackview.depth > 1 + // + // hoverEnabled: false + // + // HoverHandler { + // id: myHover + // cursorShape: melol.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor + // } + // + // background: Rectangle { + // color: enabled ? myHover.hovered ? "#4e535b" : "#3e434b" : "#3e434b" + // radius: height / 2 + // } + // + // contentItem: Text { + // text: "\ue5c4" + // color: enabled ? "white" : "#7d848c" + // font.pointSize: 11 + // font.family: Constants.symbolFontFamily + // horizontalAlignment: Text.AlignHCenter + // verticalAlignment: Text.AlignVCenter + // } + // + // onClicked: { + // stackview.pop() + // } + // } + // } + + initialItem: libraryPage2 + + pushEnter: Transition { + } + pushExit: Transition { + } + popEnter: Transition { + } + popExit: Transition { + } + replaceEnter: Transition { + } + replaceExit: Transition { + } + } + + // HomeContentPane { + // id: homeContentPane + // anchors.top: parent.top + // anchors.right: parent.right + // anchors.bottom: parent.bottom + // anchors.left: drawer.right + // + // KeyNavigation.left: drawer + // } + + + FirelightDialog { + id: closeAppConfirmationDialog + text: "Are you sure you want to close Firelight?" + + onAccepted: { + Qt.callLater(Qt.quit) + } + } +} \ No newline at end of file diff --git a/qml/SettingsPage.qml b/qml/screens/SettingsScreen.qml similarity index 87% rename from qml/SettingsPage.qml rename to qml/screens/SettingsScreen.qml index 3ae0efc..76f1700 100644 --- a/qml/SettingsPage.qml +++ b/qml/screens/SettingsScreen.qml @@ -15,6 +15,8 @@ Item { color: "#101114" height: parent.height width: leftSpacer.width + menu.width + contentPane.horizontalPadding + 6 + + } RowLayout { @@ -32,7 +34,7 @@ Item { Pane { id: contentPane Layout.fillWidth: true - Layout.horizontalStretchFactor: 6 + Layout.horizontalStretchFactor: 8 Layout.fillHeight: true verticalPadding: 80 @@ -40,6 +42,7 @@ Item { background: Item { } + ColumnLayout { id: menu spacing: 4 @@ -96,6 +99,19 @@ Item { } } } + FirelightMenuItem { + labelText: "Platforms" + // labelIcon: "\ue88e" + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.preferredHeight: 40 + checked: root.section === "platforms" + onToggled: { + if (checked) { + root.section = "platforms" + } + } + } FirelightMenuItem { labelText: "System" // labelIcon: "\uf522" @@ -165,6 +181,14 @@ Item { rightHalf.replace(retroAchievementSettings) } else if (root.section === "audiovideo") { rightHalf.replace(videoSettings) + } else if (root.section === "platforms/gbc") { + rightHalf.replace(gbcSettings) + } else if (root.section === "platforms/snes") { + rightHalf.replace(snesSettings) + } else if (root.section === "platforms") { + rightHalf.replace(platformSettings) + } else if (root.section === "platforms/gba") { + rightHalf.replace(gbaSettings) } } } @@ -258,6 +282,12 @@ Item { } } + Component { + id: platformSettings + PlatformSettingsPage { + } + } + Component { id: librarySettings LibrarySettings { @@ -276,6 +306,24 @@ Item { } } + Component { + id: gbcSettings + GameBoyColorSettings { + } + } + + Component { + id: gbaSettings + GbaSettings { + } + } + + Component { + id: snesSettings + SnesSettings { + } + } + Component { id: debugSettings DebugPage { diff --git a/qml/settings/RetroAchievementSettings.qml b/qml/settings/RetroAchievementSettings.qml index 2735f88..09577ed 100644 --- a/qml/settings/RetroAchievementSettings.qml +++ b/qml/settings/RetroAchievementSettings.qml @@ -215,130 +215,131 @@ Flickable { Layout.fillHeight: true Layout.fillWidth: true Layout.bottomMargin: 12 - text: "You can change this setting for individual games." - font.pointSize: 10 + text: "Only Hardcore mode is available at the moment." + font.pixelSize: 14 Layout.alignment: Qt.AlignLeft font.family: Constants.regularFontFamily + // font.weight: Font. wrapMode: Text.WordWrap - color: "#c1c1c1" + color: ColorPalette.neutral300 } - RadioButton { - Layout.fillWidth: true - Layout.preferredHeight: 140 - checked: achievement_manager.defaultToHardcore - horizontalPadding: 12 - verticalPadding: 6 - - background: Rectangle { - radius: 8 - color: "#333333" - border.color: parent.checked ? "#f16205" : "transparent" - border.width: 3 - } - - indicator: Item { - } - - onCheckedChanged: { - if (checked) { - achievement_manager.defaultToHardcore = true - } - } - - HoverHandler { - id: hardcoreHover - cursorShape: Qt.PointingHandCursor - } - - contentItem: ColumnLayout { - Text { - Layout.fillWidth: true - text: "Hardcore mode" - color: "white" - font.pointSize: 11 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - font.family: Constants.regularFontFamily - font.weight: Font.DemiBold - } - - Text { - text: "Challenge yourself and earn Mastery points by disabling save states, cheats, and rewinding." - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pointSize: 11 - font.family: Constants.regularFontFamily - font.weight: Font.Normal - color: "#c1c1c1" - } - Item { - Layout.fillHeight: true - } - } - } - - RadioButton { - Layout.fillWidth: true - Layout.preferredHeight: 60 - horizontalPadding: 12 - checked: !achievement_manager.defaultToHardcore - verticalPadding: 6 - - background: Rectangle { - radius: 8 - color: "#333333" - border.color: parent.checked ? "#f16205" : "transparent" - border.width: 3 - } - - indicator: Item { - } - - onCheckedChanged: { - if (checked) { - achievement_manager.defaultToHardcore = false - } - } - - Connections { - target: achievement_manager - - function onDefaultToHardcoreChanged() { - console.log(":)") - } - } - - HoverHandler { - id: casualHover - cursorShape: Qt.PointingHandCursor - } - - contentItem: ColumnLayout { - Text { - Layout.fillWidth: true - text: "Casual mode" - color: "white" - font.pointSize: 11 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - font.family: Constants.regularFontFamily - font.weight: Font.DemiBold - } - - Text { - text: "Play with save states, cheats, and rewinding enabled." - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pointSize: 11 - font.family: Constants.regularFontFamily - font.weight: Font.Normal - color: "#c1c1c1" - } - - Item { - Layout.fillHeight: true - } - } - } + // RadioButton { + // Layout.fillWidth: true + // Layout.preferredHeight: 140 + // checked: achievement_manager.defaultToHardcore + // horizontalPadding: 12 + // verticalPadding: 6 + // + // background: Rectangle { + // radius: 8 + // color: "#333333" + // border.color: parent.checked ? "#f16205" : "transparent" + // border.width: 3 + // } + // + // indicator: Item { + // } + // + // onCheckedChanged: { + // if (checked) { + // achievement_manager.defaultToHardcore = true + // } + // } + // + // HoverHandler { + // id: hardcoreHover + // cursorShape: Qt.PointingHandCursor + // } + // + // contentItem: ColumnLayout { + // Text { + // Layout.fillWidth: true + // text: "Hardcore mode" + // color: "white" + // font.pointSize: 11 + // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + // font.family: Constants.regularFontFamily + // font.weight: Font.DemiBold + // } + // + // Text { + // text: "Challenge yourself and earn Mastery points by disabling save states, cheats, and rewinding." + // Layout.fillWidth: true + // wrapMode: Text.WordWrap + // font.pointSize: 11 + // font.family: Constants.regularFontFamily + // font.weight: Font.Normal + // color: "#c1c1c1" + // } + // Item { + // Layout.fillHeight: true + // } + // } + // } + // + // RadioButton { + // Layout.fillWidth: true + // Layout.preferredHeight: 60 + // horizontalPadding: 12 + // checked: !achievement_manager.defaultToHardcore + // verticalPadding: 6 + // + // background: Rectangle { + // radius: 8 + // color: "#333333" + // border.color: parent.checked ? "#f16205" : "transparent" + // border.width: 3 + // } + // + // indicator: Item { + // } + // + // onCheckedChanged: { + // if (checked) { + // achievement_manager.defaultToHardcore = false + // } + // } + // + // Connections { + // target: achievement_manager + // + // function onDefaultToHardcoreChanged() { + // console.log(":)") + // } + // } + // + // HoverHandler { + // id: casualHover + // cursorShape: Qt.PointingHandCursor + // } + // + // contentItem: ColumnLayout { + // Text { + // Layout.fillWidth: true + // text: "Casual mode" + // color: "white" + // font.pointSize: 11 + // Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + // font.family: Constants.regularFontFamily + // font.weight: Font.DemiBold + // } + // + // Text { + // text: "Play with save states, cheats, and rewinding enabled." + // Layout.fillWidth: true + // wrapMode: Text.WordWrap + // font.pointSize: 11 + // font.family: Constants.regularFontFamily + // font.weight: Font.Normal + // color: "#c1c1c1" + // } + // + // Item { + // Layout.fillHeight: true + // } + // } + // } // ToggleOption { // Layout.fillWidth: true diff --git a/qml/SettingsItem.qml b/qml/settings/SettingsItem.qml similarity index 100% rename from qml/SettingsItem.qml rename to qml/settings/SettingsItem.qml diff --git a/qml_tests/common/tst_DetailsButton.qml b/qml_tests/common/tst_DetailsButton.qml new file mode 100644 index 0000000..a57fbef --- /dev/null +++ b/qml_tests/common/tst_DetailsButton.qml @@ -0,0 +1,37 @@ +import QtQuick 2.3 +import QtTest 1.0 +import QMLFirelightTest + +Item { + width: 400 + height: 400 + + DetailsButton { + id: button + width: 48 + height: 48 + } + + TestCase { + name: "DetailsButton" + when: windowShown + + function test_background_color() { + compare(button.background.color, "#00000000", "Background is transparent") + mouseMove(button) + verify(button.hovered, "Button is hovered") + compare(button.background.color, "#ffffff", "Background color is correct 2") + verify(button.background.opacity === 0.1, "Background opacity is correct") + } + + function test_radius() { + compare(button.background.radius, 24, "Radius is correct") + } + + function test_symbol() { + compare(button.contentItem.text, "\ue5d4", "Symbol is correct") + compare(button.contentItem.color, "#ffffff", "Symbol color is correct") + compare(button.contentItem.font.pointSize, 16, "Symbol size is correct") + } + } +} diff --git a/qml_tests/common/tst_NavigationTabBar.qml b/qml_tests/common/tst_NavigationTabBar.qml new file mode 100644 index 0000000..4478318 --- /dev/null +++ b/qml_tests/common/tst_NavigationTabBar.qml @@ -0,0 +1,112 @@ +import QtQuick 2.3 +import QtTest 1.0 +import QMLFirelightTest + +Item { + width: 400 + height: 400 + + NavigationTabBar { + id: control + width: parent.width + height: 100 + + tabs: ["Hi", "There"] + tabWidth: 100 + } + + TestCase { + name: "NavigationTabBar" + when: windowShown + + function init() { + control.currentIndex = 0 + } + + function test_clicking() { + verify(control.contentChildren.length === 2) + + const buttonOne = control.contentChildren[0] + const buttonTwo = control.contentChildren[1] + + compare(control.currentIndex, 0, "currentIndex is correct") + mouseClick(buttonTwo) + compare(control.currentIndex, 1, "currentIndex is correct") + mouseClick(buttonOne) + compare(control.currentIndex, 0, "currentIndex is correct") + + // verify(control.contentChildren[0].text === "Hi") + } + + function test_highlight() { + verify(control.contentChildren.length === 2) + const highlight = findChild(control, "background") + + const buttonOne = control.contentChildren[0] + const buttonTwo = control.contentChildren[1] + + compare(control.currentIndex, 0, "currentIndex is correct") + compare(highlight.width, 100, "highlight width is correct") + compare(highlight.height, 2, "highlight height is correct") + compare(highlight.radius, 1, "highlight radius is correct") + compare(highlight.color, "#ffffff", "highlight color is correct") + compare(highlight.y, 100, "highlight y is correct") + compare(highlight.x, 0, "highlight x is correct") + + mouseClick(buttonTwo) + wait(200) + compare(control.currentIndex, 1, "currentIndex is correct") + compare(highlight.x, 100, "highlight x is correct") + } + + function test_tabWidth() { + verify(control.contentChildren.length === 2) + + const buttonOne = control.contentChildren[0] + const buttonTwo = control.contentChildren[1] + + compare(buttonOne.width, 100, "buttonOne width is correct") + compare(buttonTwo.width, 100, "buttonTwo width is correct") + } + + function test_tab_labels() { + verify(control.contentChildren.length === 2) + + const buttonOne = control.contentChildren[0] + const buttonTwo = control.contentChildren[1] + + compare(control.currentIndex, 0, "currentIndex is correct") + const textOne = findChild(buttonOne, "text") + const textTwo = findChild(buttonTwo, "text") + + compare(textOne.text, "Hi", "buttonOne text is correct") + compare(textOne.color, "#ffffff", "text color is correct") + compare(textOne.font.pointSize, 11, "text pointSize is correct") + compare(textOne.font.weight, Font.Bold, "text weight is correct") + verify(buttonOne.checked, "buttonOne is checked") + + compare(textTwo.text, "There", "buttonTwo text is correct") + compare(textTwo.color, "#ffffff", "text color is correct") + compare(textTwo.font.pointSize, 11, "text pointSize is correct") + compare(textTwo.font.weight, Font.Normal, "text weight is correct") + verify(!buttonTwo.checked, "buttonTwo is not checked") + + mouseClick(buttonTwo) + + compare(textTwo.text, "There", "buttonTwo text is correct") + compare(textTwo.color, "#ffffff", "text color is correct") + compare(textTwo.font.pointSize, 11, "text pointSize is correct") + compare(textTwo.font.weight, Font.Bold, "text weight is correct") + verify(buttonTwo.checked, "buttonTwo is checked") + + compare(textOne.text, "Hi", "buttonOne text is correct") + compare(textOne.color, "#ffffff", "text color is correct") + compare(textOne.font.pointSize, 11, "text pointSize is correct") + compare(textOne.font.weight, Font.Normal, "text weight is correct") + verify(!buttonOne.checked, "buttonOne is not checked") + + + } + + } +} \ No newline at end of file diff --git a/qml_tests/common/tst_RightClickMenu.qml b/qml_tests/common/tst_RightClickMenu.qml index 625667e..9ac1710 100644 --- a/qml_tests/common/tst_RightClickMenu.qml +++ b/qml_tests/common/tst_RightClickMenu.qml @@ -1,10 +1,29 @@ import QtQuick 2.3 -import QtTest 1.0 -TestCase { - name: "MathTests" +Item { + width: 400 + height: 400 - function test_math() { - compare(2 + 2, 4, "2 + 2 = 4") - } -} \ No newline at end of file + // DetailsButton { + // id: button + // width: 48 + // height: 48 + // } + // + // TestCase { + // name: "DetailsButton" + // when: windowShown + // + // function test_background_color() { + // compare(button.background.color, "#00000000", "Background is transparent") + // mouseMove(button) + // verify(button.hovered, "Button is hovered") + // compare(button.background.color, "#ffffff", "Background color is correct 2") + // verify(button.background.opacity === 0.1, "Background opacity is correct") + // } + // + // function test_radius() { + // compare(button.background.radius, 24, "Radius is correct") + // } + // } +} diff --git a/qml_tests/library/tst_GameGridItemDelegate.qml b/qml_tests/library/tst_GameGridItemDelegate.qml new file mode 100644 index 0000000..5066e95 --- /dev/null +++ b/qml_tests/library/tst_GameGridItemDelegate.qml @@ -0,0 +1,151 @@ +import QtQuick 2.3 +import QtTest 1.0 +import QMLFirelightTest + +Item { + id: root + width: 400 + height: 400 + + SignalSpy { + id: spy + target: button + signalName: "openDetails" + } + + SignalSpy { + id: startGameSpy + target: button + signalName: "startGame" + } + + SignalSpy { + id: manageSaveDataSpy + target: button + signalName: "manageSaveData" + } + + GameGridItemDelegate { + id: button + cellWidth: 100 + cellHeight: 100 + cellSpacing: 8 + anchors.centerIn: parent + + index: 0 + model: { + return { + "id": 5, + "display_name": "Test", + "platform_name": "Test description" + } + } + } + + TestCase { + name: "GameGridItemDelegate" + when: windowShown + + function init() { + spy.clear() + startGameSpy.clear() + } + + function test_size_is_correct() { + compare(button.width, 100, "Width is correct") + compare(button.height, 100, "Height is correct") + } + + function test_background_color_changes_on_hover() { + const background = findChild(button, "background") + + compare(background.color, "#25282c", "Background is correct when not hovering") + mouseMove(button) + compare(background.color, "#3a3e45", "Background is correct when hovering") + } + + function test_left_click_emits_detailsOpened() { + mouseClick(button) + compare(spy.count, 1, "openDetails signal was emitted") + compare(spy.signalArguments[0][0], 5, "openDetails signal was emitted with correct id") + } + + function test_right_click_opens_menu() { + const rightClick = findChild(button, "rightClickMenu") + + compare(rightClick.visible, false, "Right click is not visible") + + mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton) + compare(rightClick.visible, true, "Right click is visible") + + mouseClick(button, 10, 10, Qt.RightButton) + compare(rightClick.visible, true, "Right click is still visible") + } + + function test_right_click_menu_play_game() { + const rightClick = findChild(button, "rightClickMenu") + mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton) + + const play = rightClick.itemAt(0) + compare(play.enabled, true, "Play button is enabled") + mouseClick(play) + + compare(startGameSpy.count, 1, "startGame signal was emitted") + compare(startGameSpy.signalArguments[0][0], 5, "startGame signal was emitted with correct id") + } + + function test_right_click_menu_view_details() { + const rightClick = findChild(button, "rightClickMenu") + mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton) + + const viewDetails = rightClick.itemAt(1) + compare(viewDetails.enabled, true, "View details is enabled") + mouseClick(viewDetails) + + compare(spy.count, 1, "openDetails signal was emitted") + compare(spy.signalArguments[0][0], 5, "openDetails signal was emitted with correct id") + } + + function test_right_click_menu_manage_save_data() { + const rightClick = findChild(button, "rightClickMenu") + mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton) + + const manageSaveDataSpy = rightClick.itemAt(3) + compare(manageSaveDataSpy.enabled, true, "View details is enabled") + mouseClick(manageSaveDataSpy) + + compare(manageSaveDataSpy.count, 1, "manageSaveDataSpy signal was emitted") + compare(manageSaveDataSpy.signalArguments[0][0], 5, "manageSaveDataSpy signal was emitted with correct id") + } + + function test_esc_closes_right_click_menu() { + const rightClick = findChild(button, "rightClickMenu") + + mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton) + compare(rightClick.visible, true, "Right click is visible") + + keyClick(Qt.Key_Escape) + compare(rightClick.visible, false, "Right click is not visible") + } + + function test_click_outside_closes_right_click_menu() { + const rightClick = findChild(button, "rightClickMenu") + + mouseClick(button, button.width / 2, button.height / 2, Qt.RightButton) + compare(rightClick.visible, true, "Right click is visible") + + mouseClick(button, button.width, button.height, Qt.LeftButton) + compare(rightClick.visible, false, "Right click is not visible") + } + + function test_right_click_menu_has_correct_items() { + const rightClick = findChild(button, "rightClickMenu") + + compare(rightClick.count, 4, "Right click menu has 3 items") + compare(rightClick.itemAt(0).text, "Play Test", "First item is 'Play'") + compare(rightClick.itemAt(1).text, "View details", "Second item is 'View details'") + compare(rightClick.itemAt(2).height - (rightClick.itemAt(2).verticalPadding * 2), 1, "Third item is menu separator") // It's stupid but it works. + compare(rightClick.itemAt(3).text, "Manage save data", "Second item is 'Manage save data'") + } + } +} diff --git a/qml_tests/library/tst_LibraryPage2.qml b/qml_tests/library/tst_LibraryPage2.qml new file mode 100644 index 0000000..fd66be9 --- /dev/null +++ b/qml_tests/library/tst_LibraryPage2.qml @@ -0,0 +1,61 @@ +import QtQuick 2.3 +import QtTest 1.0 +import QMLFirelightTest + +Item { + id: root + width: 800 + height: 800 + + LibraryPage2 { + id: page + anchors.fill: parent + } + + TestCase { + name: "LibraryPage" + when: windowShown + + function test_width_data() { + return [ + {rootWidth: 200, expectedNumCells: 1}, + {rootWidth: 300, expectedNumCells: 1}, + {rootWidth: 400, expectedNumCells: 2}, + {rootWidth: 500, expectedNumCells: 2}, + {rootWidth: 600, expectedNumCells: 3}, + {rootWidth: 700, expectedNumCells: 3}, + {rootWidth: 800, expectedNumCells: 4}, + {rootWidth: 900, expectedNumCells: 4}, + {rootWidth: 1000, expectedNumCells: 5}, + {rootWidth: 1100, expectedNumCells: 5}, + {rootWidth: 1200, expectedNumCells: 6}, + {rootWidth: 1300, expectedNumCells: 6}, + {rootWidth: 1400, expectedNumCells: 7}, + {rootWidth: 1500, expectedNumCells: 7}, + {rootWidth: 1600, expectedNumCells: 7}, // Make sure it caps at 7 + {rootWidth: 1700, expectedNumCells: 7}, + {rootWidth: 1800, expectedNumCells: 7}, + {rootWidth: 1900, expectedNumCells: 7} + ] + } + + function test_width(data) { + root.width = data.rootWidth + const grid = findChild(page, "GridView") + compare(grid.width, grid.cellWidth * data.expectedNumCells, "Width is correct") + } + + function test_default_values() { + const grid = findChild(page, "GridView") + compare(grid.preferredHighlightBegin, 0.33 * grid.height, "Preferred highlight begin is correct") + compare(grid.preferredHighlightEnd, 0.66 * grid.height, "Preferred highlight end is correct") + compare(grid.highlightRangeMode, GridView.ApplyRange, "Highlight range mode is correct") + verify(grid.clip, "Clip is enabled") + verify(grid.focus, "Focus is enabled") + compare(grid.boundsBehavior, GridView.StopAtBounds, "Bounds behavior is correct") + compare(grid.cellSpacing, 12, "Cell spacing is correct") + compare(grid.cellWidth, 192, "Cell width is correct") + compare(grid.cellHeight, 272, "Cell height is correct") + } + } +} diff --git a/resources.qrc b/resources.qrc index 3e4bd1f..9c22451 100644 --- a/resources.qrc +++ b/resources.qrc @@ -5,10 +5,22 @@ - assets/Lexend-VariableFont_wght.ttf + assets/LexendDeca[wght].ttf assets/NunitoSans_10pt-Regular.ttf assets/Lexend-Medium.ttf assets/NunitoSans_10pt_SemiCondensed-Black.ttf assets/MaterialSymbols.ttf + + _img/nes.svg + _img/SNES.svg + _img/gb.svg + _img/gbc.svg + _img/gba.svg + _img/N64.svg + _img/SwitchPro.svg + + + _img/SwitchPro.svg + \ No newline at end of file diff --git a/src/app/audio_manager.cpp b/src/app/audio_manager.cpp index 6e24e09..ab42e20 100644 --- a/src/app/audio_manager.cpp +++ b/src/app/audio_manager.cpp @@ -10,10 +10,10 @@ void AudioManager::initialize(const double new_freq) { SDL_AudioSpec want, have; SDL_memset(&want, 0, sizeof(want)); - want.freq = new_freq; // Sample rate (e.g., 44.1 kHz) + want.freq = new_freq; // Sample rate (e.g., 44.1 kHz) want.format = AUDIO_S16; // Audio format (16-bit signed) - want.channels = 2; // Number of audio channels (stereo) - want.samples = 4096; // Audio buffer size (samples) + want.channels = 2; // Number of audio channels (stereo) + want.samples = 128; // Audio buffer size (samples) want.callback = nullptr; this->audioDevice = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0); @@ -30,3 +30,8 @@ void AudioManager::toggle_mute() { muted = !muted; SDL_PauseAudioDevice(audioDevice, muted); } + +AudioManager::~AudioManager() { + printf("Destroying AudioManager\n"); + SDL_CloseAudioDevice(this->audioDevice); +} diff --git a/src/app/audio_manager.hpp b/src/app/audio_manager.hpp index 68d4d62..20dac7f 100644 --- a/src/app/audio_manager.hpp +++ b/src/app/audio_manager.hpp @@ -6,10 +6,15 @@ class AudioManager : public IAudioDataReceiver { public: size_t receive(const int16_t *data, size_t numFrames) override; + void initialize(double new_freq) override; + bool is_muted() override; + void toggle_mute() override; + ~AudioManager() override; + private: SDL_AudioDeviceID audioDevice = 0; bool muted = false; diff --git a/src/app/db/sqlite_content_database.cpp b/src/app/db/sqlite_content_database.cpp index e8f3ee5..593919e 100644 --- a/src/app/db/sqlite_content_database.cpp +++ b/src/app/db/sqlite_content_database.cpp @@ -320,6 +320,10 @@ namespace firelight::db { return {Platform{.id = 13}}; } + if (ext == ".nes") { + return {Platform{.id = 5}}; + } + return {}; } diff --git a/src/app/db/sqlite_userdata_database.cpp b/src/app/db/sqlite_userdata_database.cpp index 532f2ee..381724e 100644 --- a/src/app/db/sqlite_userdata_database.cpp +++ b/src/app/db/sqlite_userdata_database.cpp @@ -5,6 +5,8 @@ #include namespace firelight::db { + constexpr auto DATABASE_PREFIX = "userdata_"; + SqliteUserdataDatabase::SqliteUserdataDatabase( const std::filesystem::path &dbFile) : m_database_path(dbFile) { @@ -64,6 +66,18 @@ namespace firelight::db { spdlog::error("Table creation failed: {}", createInputMappings.lastError().text().toStdString()); } + + QSqlQuery createPlatformSettings(m_database); + createPlatformSettings.prepare("CREATE TABLE IF NOT EXISTS platform_settings(" + "platform_id INTEGER NOT NULL, " + "key TEXT NOT NULL," + "value TEXT NOT NULL," + "UNIQUE(platform_id, key));"); + + if (!createPlatformSettings.exec()) { + spdlog::error("Table creation failed: {}", + createPlatformSettings.lastError().text().toStdString()); + } } SqliteUserdataDatabase::~SqliteUserdataDatabase() { @@ -277,4 +291,67 @@ namespace firelight::db { // // return std::nullopt; } + + std::optional SqliteUserdataDatabase::getPlatformSettingValue( + const int platformId, const std::string key) { + const QString queryString = "SELECT value FROM platform_settings " + "WHERE platform_id = :platformId AND key = :key LIMIT 1;"; + auto query = QSqlQuery(m_database); + query.prepare(queryString); + + query.bindValue(":platformId", platformId); + query.bindValue(":key", QString::fromStdString(key)); + + if (!query.exec()) { + spdlog::warn("Get from platform_settings failed: {}", + query.lastError().text().toStdString()); + return std::nullopt; + } + + if (query.next()) { + return query.value("value").toString().toStdString(); + } + + return std::nullopt; + } + + std::map SqliteUserdataDatabase::getAllPlatformSettings(int platformId) { + const QString queryString = "SELECT key, value FROM platform_settings " + "WHERE platform_id = :platformId;"; + auto query = QSqlQuery(m_database); + query.prepare(queryString); + + query.bindValue(":platformId", platformId); + + if (!query.exec()) { + spdlog::warn("Get all from platform_settings failed: {}", + query.lastError().text().toStdString()); + return {}; + } + + std::map settings; + while (query.next()) { + settings[query.value("key").toString().toStdString()] = query.value("value").toString().toStdString(); + } + + return settings; + } + + void SqliteUserdataDatabase::setPlatformSettingValue(const int platformId, const std::string key, + const std::string value) { + const QString queryString = "INSERT OR REPLACE INTO platform_settings " + "(platform_id, key, value) " + "VALUES (:platformId, :key, :value);"; + auto query = QSqlQuery(m_database); + query.prepare(queryString); + + query.bindValue(":platformId", platformId); + query.bindValue(":key", QString::fromStdString(key)); + query.bindValue(":value", QString::fromStdString(value)); + + if (!query.exec()) { + spdlog::warn("Insert into platform_settings failed: {}", + query.lastError().text().toStdString()); + } + } } // namespace firelight::db diff --git a/src/app/db/sqlite_userdata_database.hpp b/src/app/db/sqlite_userdata_database.hpp index d4b6c93..642d465 100644 --- a/src/app/db/sqlite_userdata_database.hpp +++ b/src/app/db/sqlite_userdata_database.hpp @@ -3,7 +3,6 @@ #include "firelight/userdata_database.hpp" #include #include -#include namespace firelight::db { class SqliteUserdataDatabase final : public IUserdataDatabase { @@ -35,6 +34,13 @@ namespace firelight::db { std::optional getLatestPlaySession(std::string contentId) override; + std::optional + getPlatformSettingValue(int platformId, std::string key) override; + + std::map getAllPlatformSettings(int platformId) override; + + void setPlatformSettingValue(int platformId, std::string key, std::string value) override; + private: std::filesystem::path m_database_path; QSqlDatabase m_database; diff --git a/src/app/emulation_manager.cpp b/src/app/emulation_manager.cpp index 769d4dc..510d3c5 100644 --- a/src/app/emulation_manager.cpp +++ b/src/app/emulation_manager.cpp @@ -19,124 +19,74 @@ #include #include #include -#include + +#include "platform_metadata.hpp" constexpr int SAVE_FREQUENCY_MILLIS = 10000; EmulationManager::EmulationManager(QQuickItem *parent) - : QQuickFramebufferObject(parent) { + : QQuickFramebufferObject(parent) { + printf("Creating EmulationManager\n"); setTextureFollowsItemSize(false); setMirrorVertically(true); setFlag(ItemHasContents); - connect(this, &EmulationManager::emulationStarted, this, - &QQuickFramebufferObject::update, Qt::QueuedConnection); - connect(this, &EmulationManager::gamePaused, this, - &QQuickFramebufferObject::update, Qt::QueuedConnection); - connect(this, &EmulationManager::gameResumed, this, - &QQuickFramebufferObject::update, Qt::QueuedConnection); - - connect( - this, &EmulationManager::gameLoadSucceeded, this, - [this] { - m_gameLoadedSignalReady = true; - if (m_achievementsLoadedSignalReady) { - emit readyToStart(); - m_gameLoadedSignalReady = false; - m_achievementsLoadedSignalReady = false; - } - }, - Qt::QueuedConnection); - - connect( - getAchievementManager(), - &firelight::achievements::RAClient::gameLoadSucceeded, this, - [this] { - m_achievementsLoadedSignalReady = true; - if (m_gameLoadedSignalReady) { - emit readyToStart(); - m_gameLoadedSignalReady = false; - m_achievementsLoadedSignalReady = false; - } - }, - Qt::QueuedConnection); - - m_autosaveTimer.setInterval(SAVE_FREQUENCY_MILLIS); - m_autosaveTimer.setSingleShot(false); - m_autosaveTimer.callOnTimeout([this] { - spdlog::info("Autosaving SRAM data (interval {}ms)", SAVE_FREQUENCY_MILLIS); - save(); - }); + // connect(this, &EmulationManager::emulationStarted, this, + // &QQuickFramebufferObject::update, Qt::QueuedConnection); + // connect(this, &EmulationManager::gamePaused, this, + // &QQuickFramebufferObject::update, Qt::QueuedConnection); + // connect(this, &EmulationManager::gameResumed, this, + // &QQuickFramebufferObject::update, Qt::QueuedConnection); + + // connect( + // this, &EmulationManager::gameLoadSucceeded, this, + // [this] { + // m_currentPlaySession = std::make_unique(); + // m_currentPlaySession->contentId = m_currentEntry.contentId; + // m_currentPlaySession->startTime = QDateTime::currentMSecsSinceEpoch(); + // m_currentPlaySession->slotNumber = m_currentEntry.activeSaveSlot; + // + // m_playtimeTimer.start(); + // QMetaObject::invokeMethod(&m_autosaveTimer, "start", Qt::QueuedConnection); + // + // emit emulationStarted(); + // }, + // Qt::QueuedConnection); + + // m_autosaveTimer.setInterval(SAVE_FREQUENCY_MILLIS); + // m_autosaveTimer.setSingleShot(false); + // m_autosaveTimer.callOnTimeout([this] { + // spdlog::info("Autosaving SRAM data (interval {}ms)", SAVE_FREQUENCY_MILLIS); + // save(); + // }); } EmulationManager::~EmulationManager() { - if (!m_isRunning) { - return; - } - - QMetaObject::invokeMethod(&m_autosaveTimer, "stop", Qt::QueuedConnection); - - m_isRunning = false; - - if (m_currentPlaySession) { - m_currentPlaySession->endTime = QDateTime::currentMSecsSinceEpoch(); + printf("Destroying EmulationManager\n"); - const auto timerValue = m_playtimeTimer.restart(); - if (!m_paused) { - m_currentPlaySession->unpausedDurationMillis += timerValue; - } - - const auto session = m_currentPlaySession.release(); - getUserdataManager()->createPlaySession(*session); - } - - save(true); + // QMetaObject::invokeMethod(&m_autosaveTimer, "stop", Qt::QueuedConnection); + // + // if (m_currentPlaySession) { + // m_currentPlaySession->endTime = QDateTime::currentMSecsSinceEpoch(); + // + // const auto timerValue = m_playtimeTimer.restart(); + // if (!m_paused) { + // m_currentPlaySession->unpausedDurationMillis += timerValue; + // } + // + // const auto session = m_currentPlaySession.release(); + // getUserdataManager()->createPlaySession(*session); + // } + // + // save(true); - if (m_core) { - // m_core->unloadGame(); - // m_core->deinit(); - // m_core.reset(); - } + // getAchievementManager()->unloadGame(); } QQuickFramebufferObject::Renderer *EmulationManager::createRenderer() const { - return new EmulatorRenderer(this); -} - -void EmulationManager::setGetProcAddressFunction( - const std::function &getProcAddressFunction) { - m_getProcAddressFunction = getProcAddressFunction; -} - -std::function EmulationManager::consumeContextResetFunction() { - if (m_resetContextFunction) { - auto func = m_resetContextFunction; - m_resetContextFunction = nullptr; - return func; - } - - return nullptr; -} - -std::function EmulationManager::consumeContextDestroyFunction() { - if (m_destroyContextFunction) { - auto func = m_destroyContextFunction; - m_destroyContextFunction = nullptr; - return func; - } - - return nullptr; -} - -void EmulationManager::setReceiveVideoDataFunction( - const std::function &receiveVideoDataFunction) { - m_receiveVideoDataFunction = receiveVideoDataFunction; + return new EmulatorRenderer(); } -void EmulationManager::setCurrentFboId(const int fboId) { - m_currentFboId = fboId; -} QString EmulationManager::currentGameName() const { return QString::fromStdString(m_currentEntry.displayName); @@ -149,283 +99,50 @@ float EmulationManager::nativeAspectRatio() const { return m_nativeAspectRatio; } -void EmulationManager::receive(const void *data, unsigned width, - unsigned height, size_t pitch) { - if (!m_usingHwRendering && m_receiveVideoDataFunction && data != nullptr) { - m_receiveVideoDataFunction(data, width, height, pitch); - } -} - -proc_address_t EmulationManager::getProcAddress(const char *sym) { - if (!m_getProcAddressFunction) { - return nullptr; - } - - return m_getProcAddressFunction(sym); -} - -void EmulationManager::setResetContextFunc(context_reset_func resetFunction) { - printf("Setting reset context function\n"); - m_usingHwRendering = true; - m_resetContextFunction = resetFunction; -} - -void EmulationManager::setDestroyContextFunc( - context_destroy_func destroyFunction) { - printf("Setting destroy context function\n"); - m_usingHwRendering = true; - m_destroyContextFunction = destroyFunction; -} - void EmulationManager::pauseGame() { - if (!m_isRunning) { - return; - } - - if (!m_paused) { - m_currentPlaySession->unpausedDurationMillis += m_playtimeTimer.restart(); - emit gamePaused(); - } - m_paused = true; + update(); } void EmulationManager::resumeGame() { - if (!m_isRunning) { - return; - } - - if (m_paused) { - m_playtimeTimer.restart(); - emit gameResumed(); - } - m_paused = false; + update(); } -void EmulationManager::startEmulation() { - if (m_isRunning) { - return; - } - - QThreadPool::globalInstance()->start([this] { - m_currentPlaySession = std::make_unique(); - m_currentPlaySession->contentId = m_currentEntry.contentId; - m_currentPlaySession->startTime = QDateTime::currentMSecsSinceEpoch(); - m_currentPlaySession->slotNumber = m_currentEntry.activeSaveSlot; - - m_isRunning = true; - m_paused = false; - m_playtimeTimer.start(); - QMetaObject::invokeMethod(&m_autosaveTimer, "start", Qt::QueuedConnection); - - emit emulationStarted(); - }); +void EmulationManager::resetEmulation() { + m_shouldReset = true; + update(); } -void EmulationManager::stopEmulation() { - if (!m_isRunning) { - return; +void EmulationManager::setGeometry(int nativeWidth, int nativeHeight, float nativeAspectRatio) { + if (m_nativeWidth != nativeWidth) { + m_nativeWidth = nativeWidth; + emit nativeWidthChanged(); } - QMetaObject::invokeMethod(&m_autosaveTimer, "stop", Qt::QueuedConnection); - - QThreadPool::globalInstance()->start([this] { - m_currentPlaySession->endTime = QDateTime::currentMSecsSinceEpoch(); - - const auto timerValue = m_playtimeTimer.restart(); - if (!m_paused) { - m_currentPlaySession->unpausedDurationMillis += timerValue; - } - - const auto session = m_currentPlaySession.get(); - getUserdataManager()->createPlaySession(*session); - m_currentPlaySession.reset(); - - getAchievementManager()->unloadGame(); - m_achievementsLoadedSignalReady = false; - m_gameLoadedSignalReady = false; - - m_isRunning = false; - save(true); - m_nativeWidth = 0; - m_nativeHeight = 0; - m_nativeAspectRatio = 0; - - emit nativeWidthChanged(); + if (m_nativeHeight != nativeHeight) { + m_nativeHeight = nativeHeight; emit nativeHeightChanged(); - - // if (m_destroyContextFunction) { - // m_destroyContextFunction(); - // } - shouldUnload = true; - - m_usingHwRendering = false; - // m_core->unloadGame(); - // m_core->deinit(); - // m_core.reset(); - - emit emulationStopped(); - }); - - update(); -} - -void EmulationManager::resetEmulation() { - if (m_core) { - m_core->reset(); - update(); } -} - -bool EmulationManager::isRunning() const { return m_isRunning; } - -void EmulationManager::save(const bool waitForFinish) { - firelight::saves::Savefile saveData( - m_core->getMemoryData(libretro::SAVE_RAM)); - // saveData.setImage(m_fbo->toImage()); - - QFuture result = - getSaveManager()->writeSaveDataForEntry(m_currentEntry, saveData); - if (waitForFinish) { - result.waitForFinished(); + if (m_nativeAspectRatio != nativeAspectRatio) { + m_nativeAspectRatio = nativeAspectRatio; + emit nativeAspectRatioChanged(); } } -uintptr_t EmulationManager::getCurrentFramebufferId() { return m_currentFboId; } - -void EmulationManager::setSystemAVInfo(retro_system_av_info *info) { - const auto width = info->geometry.base_width; - const auto height = info->geometry.base_height; - - if (width > 0 && height > 0) { - if (width != m_nativeWidth) { - m_nativeWidth = width; - emit nativeWidthChanged(); - } - - if (height != m_nativeHeight) { - m_nativeHeight = height; - emit nativeHeightChanged(); - } - - const auto aspectRatio = - static_cast(width) / static_cast(height); - if (aspectRatio != m_nativeAspectRatio) { - m_nativeAspectRatio = aspectRatio; - emit nativeAspectRatioChanged(); +void EmulationManager::setIsRunning(bool running) { + if (m_running != running) { + m_running = running; + if (m_running) { + emit emulationStarted(); + } else { + emit emulationStopped(); } } } -bool EmulationManager::runFrame() { - if (shouldUnload) { - m_core->unloadGame(); - m_core->deinit(); - m_core.reset(); - - shouldUnload = false; - } - - if (m_isRunning && !m_paused) { - m_core->run(0); - getAchievementManager()->doFrame(m_core.get(), m_currentEntry); - return true; - } - - return false; - - // - // if (m_running) { - // if (!m_paused) { - // const auto frameBegin = SDL_GetPerformanceCounter(); - // lastTick = thisTick; - // thisTick = SDL_GetPerformanceCounter(); - // - // auto deltaTime = - // (thisTick - lastTick) * 1000 / - // (double)SDL_GetPerformanceFrequency(); - // - // m_millisSinceLastSave += static_cast(deltaTime); - // if (m_millisSinceLastSave < 0) { - // m_millisSinceLastSave = 0; - // } - // - // if (m_millisSinceLastSave >= SAVE_FREQUENCY_MILLIS) { - // m_millisSinceLastSave = 0; - // // gameImage = gameFbo->toImage(); - // - // const auto state = core->serializeState(); - // const SuspendPoint suspendPoint{ - // .state = state, .timestamp = - // QDateTime::currentMSecsSinceEpoch()}; - // - // m_suspendPoints.push_back(suspendPoint); - // } - // - // frameCount++; - // if (frameSkipRatio == 0 || (frameCount % frameSkipRatio == 0)) { - // core->run(deltaTime); - // - // auto frameEnd = SDL_GetPerformanceCounter(); - // auto frameDiff = ((frameEnd - frameBegin) * 1000 / - // static_cast(SDL_GetPerformanceFrequency())); - // totalFrameWorkDurationMillis += frameDiff; - // numFrames++; - // - // if (numFrames == 300) { - // printf("Average frame work duration: %fms\n", - // totalFrameWorkDurationMillis / numFrames); - // totalFrameWorkDurationMillis = 0; - // numFrames = 0; - // } - // } - // update(); - // } - // m_ranLastFrame = true; - // - // // printf("Serialize size: %lu\n", core->getSerializeSize()); - // - // if (m_fbo != nullptr) { - // const auto image = m_fbo->toImage(); - // // printf("image size bytes: %lld\n", image.sizeInBytes()); - // - // // Get a pointer to the raw data - // // auto future = QtConcurrent::run([image] { - // // const uchar *data = image.constBits(); - // // // Compress the image data using zlib - // // uLongf compressedDataSize = compressBound(image.sizeInBytes()); - // // auto *compressedData = new uchar[compressedDataSize]; - // // if (compress2(compressedData, &compressedDataSize, data, - // // image.sizeInBytes(), Z_BEST_COMPRESSION) != Z_OK) { - // // // printf("Failed to compress image data\n"); - // // } else { - // // printf("Compressed image size bytes: %lu\n", - // compressedDataSize); - // // // Now you can use 'compressedData' to transmit the compressed - // // // image - // // // // over a network connection Be sure to also transmit the - // // size - // // // of the - // // // compressed data, which is compressedDataSize - // // } - // // - // // delete[] compressedData; - // // }); - // - // // Now you can use 'data' to transmit the image over a network - // // connection - // // Be sure to also transmit the size of the data, which is - // // image.sizeInBytes() - // } - // } -} - void EmulationManager::loadLibraryEntry(int entryId) { - m_gameLoadedSignalReady = false; - m_achievementsLoadedSignalReady = false; - QThreadPool::globalInstance()->start([this, entryId] { spdlog::info("Loading entry with id {}", entryId); auto entry = getLibraryDatabase()->getLibraryEntry(entryId); @@ -504,44 +221,12 @@ void EmulationManager::loadLibraryEntry(int entryId) { saveData->getSaveRamData().size()); } - std::string corePath; - if (parent->platformId == 7) { - corePath = "./system/_cores/mupen64plus_next_libretro.dll"; - } else if (parent->platformId == 6) { - corePath = "./system/_cores/snes9x_libretro.dll"; - } else if (parent->platformId == 2) { - corePath = "./system/_cores/gambatte_libretro.dll"; - } else if (parent->platformId == 1) { - corePath = "./system/_cores/gambatte_libretro.dll"; - } else if (parent->platformId == 3) { - corePath = "./system/_cores/mgba_libretro.dll"; - } else if (parent->platformId == 10) { - corePath = "./system/_cores/melondsds_libretro.dll"; - } else if (parent->platformId == 13) { - corePath = "./system/_cores/genesis_plus_gx_libretro.dll"; - } - + std::string corePath = firelight::PlatformMetadata::getCoreDllPath(parent->platformId); m_gameData = QByteArray(gameData.data(), gameData.size()); m_saveData = saveDataBytes; m_corePath = QString::fromStdString(corePath); } else { - std::string corePath; - if (entry->platformId == 7) { - corePath = "./system/_cores/mupen64plus_next_libretro.dll"; - } else if (entry->platformId == 6) { - corePath = "./system/_cores/snes9x_libretro.dll"; - } else if (entry->platformId == 2) { - corePath = "./system/_cores/gambatte_libretro.dll"; - } else if (entry->platformId == 1) { - corePath = "./system/_cores/gambatte_libretro.dll"; - } else if (entry->platformId == 3) { - corePath = "./system/_cores/mgba_libretro.dll"; - } else if (entry->platformId == 10) { - corePath = "./system/_cores/melondsds_libretro.dll"; - } else if (entry->platformId == 13) { - corePath = "./system/_cores/genesis_plus_gx_libretro.dll"; - } - + std::string corePath = firelight::PlatformMetadata::getCoreDllPath(entry->platformId); auto size = std::filesystem::file_size(entry->contentPath); QByteArray saveDataBytes; @@ -560,34 +245,31 @@ void EmulationManager::loadLibraryEntry(int entryId) { m_gameData = QByteArray(gameDataVec.data(), gameDataVec.size()); m_saveData = saveDataBytes; m_corePath = QString::fromStdString(corePath); - } - - m_core = std::make_unique(m_corePath.toStdString()); - - m_core->setVideoReceiver(this); - m_core->setAudioReceiver(new AudioManager()); - m_core->setRetropadProvider(getControllerManager()); - m_core->setSystemDirectory("./system"); - // m_core->setSaveDirectory("."); - m_core->init(); - - libretro::Game game( - entry->contentPath, - vector(m_gameData.begin(), m_gameData.end())); - m_core->loadGame(&game); - - if (m_saveData.size() > 0) { - m_core->writeMemoryData(libretro::SAVE_RAM, - vector(m_saveData.begin(), m_saveData.end())); + m_gameReady = true; } - auto md5 = calculateMD5(m_gameData.data(), m_gameData.size()); - QMetaObject::invokeMethod( - getAchievementManager(), "loadGame", Qt::QueuedConnection, - Q_ARG(int, m_currentEntry.platformId), - Q_ARG(QString, QString::fromStdString(entry->contentId))); + // m_core = std::make_unique(m_corePath.toStdString()); + // + // m_core->setAudioReceiver(std::make_shared()); + // m_core->setRetropadProvider(getControllerManager()); + // + // m_core->setSystemDirectory("./system"); + // // m_core->setSaveDirectory("."); + // m_core->init(); + // + // libretro::Game game( + // entry->contentPath, + // vector(m_gameData.begin(), m_gameData.end())); + // m_core->loadGame(&game); + // + // if (m_saveData.size() > 0) { + // m_core->writeMemoryData(libretro::SAVE_RAM, + // vector(m_saveData.begin(), m_saveData.end())); + // } + + // auto md5 = calculateMD5(m_gameData.data(), m_gameData.size()); - emit gameLoadSucceeded(); + // emit gameLoadSucceeded(); }); } diff --git a/src/app/emulation_manager.hpp b/src/app/emulation_manager.hpp index ecf6114..8eeb1e5 100644 --- a/src/app/emulation_manager.hpp +++ b/src/app/emulation_manager.hpp @@ -9,143 +9,88 @@ #include #include +#include "libretro/core_configuration.hpp" + class EmulationManager : public QQuickFramebufferObject, - public firelight::ManagerAccessor, - public firelight::libretro::IVideoDataReceiver { - Q_OBJECT - Q_PROPERTY(QString currentGameName READ currentGameName NOTIFY - currentGameNameChanged) - Q_PROPERTY(int nativeWidth READ nativeWidth NOTIFY nativeWidthChanged) - Q_PROPERTY(int nativeHeight READ nativeHeight NOTIFY nativeHeightChanged) - Q_PROPERTY(float nativeAspectRatio READ nativeAspectRatio NOTIFY - nativeAspectRatioChanged) - Q_PROPERTY(bool running READ isRunning NOTIFY emulationStarted NOTIFY - emulationStopped) + public firelight::ManagerAccessor { + Q_OBJECT + Q_PROPERTY(QString currentGameName READ currentGameName NOTIFY + currentGameNameChanged) + Q_PROPERTY(int nativeWidth READ nativeWidth NOTIFY nativeWidthChanged) + Q_PROPERTY(int nativeHeight READ nativeHeight NOTIFY nativeHeightChanged) + Q_PROPERTY(float nativeAspectRatio READ nativeAspectRatio NOTIFY + nativeAspectRatioChanged) public: - [[nodiscard]] Renderer *createRenderer() const override; - - explicit EmulationManager(QQuickItem *parent = nullptr); - - ~EmulationManager() override; - - void setCurrentFboId(int fboId); - - void - setGetProcAddressFunction(const std::function - &getProcAddressFunction); - - [[nodiscard]] std::function consumeContextResetFunction(); - - [[nodiscard]] std::function consumeContextDestroyFunction(); + [[nodiscard]] Renderer *createRenderer() const override; - void setReceiveVideoDataFunction( - const std::function - &receiveVideoDataFunction); + explicit EmulationManager(QQuickItem *parent = nullptr); - [[nodiscard]] QString currentGameName() const; + ~EmulationManager() override; - [[nodiscard]] int nativeWidth() const; + [[nodiscard]] QString currentGameName() const; - [[nodiscard]] int nativeHeight() const; + [[nodiscard]] int nativeWidth() const; - [[nodiscard]] float nativeAspectRatio() const; + [[nodiscard]] int nativeHeight() const; - void receive(const void *data, unsigned width, unsigned height, - size_t pitch) override; + [[nodiscard]] float nativeAspectRatio() const; - proc_address_t getProcAddress(const char *sym) override; + void setGeometry(int nativeWidth, int nativeHeight, float nativeAspectRatio); - void setResetContextFunc(context_reset_func) override; + void setIsRunning(bool running); - void setDestroyContextFunc(context_destroy_func) override; + QByteArray m_gameData; + QByteArray m_saveData; + QString m_corePath; + firelight::db::LibraryEntry m_currentEntry; + std::shared_ptr m_coreConfiguration = nullptr; + bool m_paused = false; + bool m_shouldReset = false; - uintptr_t getCurrentFramebufferId() override; + bool m_running = false; - void setSystemAVInfo(retro_system_av_info *info) override; - - [[nodiscard]] bool runFrame(); + bool m_gameReady = false; public slots: - void loadLibraryEntry(int entryId); - - void startEmulation(); - - void pauseGame(); + void loadLibraryEntry(int entryId); - void resumeGame(); + void pauseGame(); - void stopEmulation(); + void resumeGame(); - void resetEmulation(); - - bool isRunning() const; - - void save(bool waitForFinish = false); + void resetEmulation(); signals: - void gameLoadSucceeded(); - - void gameLoadFailed(); + void gameLoadSucceeded(); - void readyToStart(); + void gameLoadFailed(); - void gamePaused(); + void readyToStart(); - void gameResumed(); + void gamePaused(); - void emulationStarted(); + void gameResumed(); - void emulationStopped(); + void emulationStarted(); - void currentGameNameChanged(); + void emulationStopped(); - void nativeWidthChanged(); + void currentGameNameChanged(); - void nativeHeightChanged(); + void nativeWidthChanged(); - void nativeAspectRatioChanged(); + void nativeHeightChanged(); - void loadAchievements(QString contentId); + void nativeAspectRatioChanged(); private: - bool shouldUnload = false; - - std::unique_ptr m_core; - firelight::db::LibraryEntry m_currentEntry; - - bool m_gameLoadedSignalReady = false; - bool m_achievementsLoadedSignalReady = false; - - bool m_isRunning = false; - bool m_paused = false; - - std::unique_ptr m_currentPlaySession; - std::vector m_suspendPoints; - QElapsedTimer m_playtimeTimer; - QTimer m_autosaveTimer; - - QByteArray m_gameData; - QByteArray m_saveData; - QString m_corePath; - - int m_nativeWidth = 0; - int m_nativeHeight = 0; - float m_nativeAspectRatio = 0; - - int numFramesRun = 0; - - /**************************************************************************** - * Ugly rendering stuff - ***************************************************************************/ - std::function m_getProcAddressFunction = - nullptr; - std::function m_resetContextFunction = nullptr; - std::function m_destroyContextFunction = nullptr; - std::function - m_receiveVideoDataFunction = nullptr; - bool m_usingHwRendering = false; - int m_currentFboId = -1; + std::unique_ptr m_currentPlaySession; + std::vector m_suspendPoints; + QElapsedTimer m_playtimeTimer; + QTimer m_autosaveTimer; + + int m_nativeWidth = 0; + int m_nativeHeight = 0; + float m_nativeAspectRatio = 0; }; diff --git a/src/app/emulator_config_manager.cpp b/src/app/emulator_config_manager.cpp new file mode 100644 index 0000000..a7d2fd8 --- /dev/null +++ b/src/app/emulator_config_manager.cpp @@ -0,0 +1,61 @@ +#include "emulator_config_manager.hpp" +#include "platform_metadata.hpp" + +#include + +static QString thing = "disabled"; + +EmulatorConfigManager::EmulatorConfigManager(firelight::db::IUserdataDatabase &userdataDatabase) : m_userdataDatabase( + userdataDatabase) { +} + +EmulatorConfigManager::~EmulatorConfigManager() = default; + +std::shared_ptr EmulatorConfigManager::getCoreConfigFor(const int platformId, const int entryId) { + const auto key = std::to_string(platformId) + "_" + std::to_string(entryId); + + if (!m_coreConfigs.contains(key)) { + m_coreConfigs[key] = std::make_shared(); + } + + // m_coreConfigs[key] = std::make_shared(); + + const auto allSettings = m_userdataDatabase.getAllPlatformSettings(platformId); + for (const auto &setting: allSettings) { + m_coreConfigs[key]->setPlatformValue(setting.first, setting.second); + } + + return m_coreConfigs.at(key); +} + +void EmulatorConfigManager::setOptionValueForPlatform(const int platformId, const QString &key, const QString &value) { + printf("Setting PLATFORM option %s to %s\n", key.toStdString().c_str(), value.toStdString().c_str()); + m_userdataDatabase.setPlatformSettingValue(platformId, key.toStdString(), value.toStdString()); +} + +void EmulatorConfigManager::setOptionValueForEntry(int entryId, const QString &key, const QString &value) { + printf("Changing ENTRY option %s to %s\n", key.toStdString().c_str(), value.toStdString().c_str()); +} + +QString EmulatorConfigManager::getOptionValueForPlatform(const int platformId, const QString &key) { + printf("Getting PLATFORM option %s\n", key.toStdString().c_str()); + + auto val = m_userdataDatabase.getPlatformSettingValue(platformId, key.toStdString()); + if (!val.has_value()) { + val = firelight::PlatformMetadata::getDefaultConfigValue(platformId, key.toStdString()); + } + + if (val.has_value()) { + printf("Found value (%s)!\n", val.value().c_str()); + return QString::fromStdString(val.value()); + } + + printf("No value\n"); + return ""; +} + +QString EmulatorConfigManager::getOptionValueForEntry(int entryId, const QString &key) { + printf("Getting ENTRY option %s\n", key.toStdString().c_str()); + return ""; +} + diff --git a/src/app/emulator_config_manager.hpp b/src/app/emulator_config_manager.hpp new file mode 100644 index 0000000..55fa8ff --- /dev/null +++ b/src/app/emulator_config_manager.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "libretro/core_configuration.hpp" + +class EmulatorConfigManager : public QObject { + Q_OBJECT + +public: + explicit EmulatorConfigManager(firelight::db::IUserdataDatabase &userdataDatabase); + + ~EmulatorConfigManager() override; + + [[nodiscard]] std::shared_ptr getCoreConfigFor(int platformId, int entryId); + + Q_INVOKABLE void setOptionValueForPlatform(int platformId, const QString &key, const QString &value); + + Q_INVOKABLE void setOptionValueForEntry(int entryId, const QString &key, const QString &value); + + Q_INVOKABLE QString getOptionValueForPlatform(int platformId, const QString &key); + + Q_INVOKABLE QString getOptionValueForEntry(int entryId, const QString &key); + +private: + firelight::db::IUserdataDatabase &m_userdataDatabase; + std::map > m_coreConfigs; +}; diff --git a/src/app/emulator_renderer.cpp b/src/app/emulator_renderer.cpp index 3f1a5ce..e2a121d 100644 --- a/src/app/emulator_renderer.cpp +++ b/src/app/emulator_renderer.cpp @@ -8,41 +8,130 @@ #include #include -EmulatorRenderer::EmulatorRenderer(const EmulationManager *manager) { +#include "audio_manager.hpp" + +static constexpr int AUTOSAVE_INTERVAL_MILLIS = 10000; + +EmulatorRenderer::EmulatorRenderer() { initializeOpenGLFunctions(); - m_manager = const_cast(manager); - m_manager->setReceiveVideoDataFunction( - [this](const void *data, unsigned width, unsigned height, size_t pitch) { - receiveVideoData(data, width, height, pitch); - }); - - m_manager->setGetProcAddressFunction([this](const char *sym) { - return QOpenGLContext::currentContext()->getProcAddress(sym); - }); + autosaveTimer.setSingleShot(false); + autosaveTimer.setInterval(AUTOSAVE_INTERVAL_MILLIS); + QObject::connect(&autosaveTimer, &QTimer::timeout, + [this] { + m_shouldSave = true; + } + ); +} + +EmulatorRenderer::~EmulatorRenderer() { + autosaveTimer.stop(); + getAchievementManager()->unloadGame(); + + save(true); + // TODO: SAVE + + if (m_destroyContextFunction) { + m_destroyContextFunction(); + } +} + +proc_address_t EmulatorRenderer::getProcAddress(const char *sym) { + return QOpenGLContext::currentContext()->getProcAddress(sym); +} + +void EmulatorRenderer::setResetContextFunc(context_reset_func func) { + m_usingHwRendering = true; + m_resetContextFunction = func; +} + +void EmulatorRenderer::setDestroyContextFunc(context_destroy_func func) { + m_usingHwRendering = true; + m_destroyContextFunction = func; +} + +uintptr_t EmulatorRenderer::getCurrentFramebufferId() { + if (!m_fbo) { + return -1; + } + + return m_fbo->handle(); +} + +void EmulatorRenderer::setSystemAVInfo(retro_system_av_info *info) { + invalidateFramebufferObject(); + + const auto width = info->geometry.base_width; + const auto height = info->geometry.base_height; + + if (width > 0 && height > 0) { + m_nativeWidth = width; + m_nativeHeight = height; + + const auto aspectRatio = + static_cast(width) / static_cast(height); + m_nativeAspectRatio = aspectRatio; + } + + update(); +} + +void EmulatorRenderer::setPixelFormat(retro_pixel_format *format) { + switch (*format) { + case RETRO_PIXEL_FORMAT_0RGB1555: + printf("Pixel format: 0RGB1555\n"); + break; + case RETRO_PIXEL_FORMAT_XRGB8888: + m_pixelFormat = QImage::Format_RGB32; + break; + case RETRO_PIXEL_FORMAT_RGB565: + m_pixelFormat = QImage::Format_RGB16; + break; + case RETRO_PIXEL_FORMAT_UNKNOWN: + printf("Pixel format: UNKNOWN\n"); + break; + } +} + +void EmulatorRenderer::save(const bool waitForFinish) { + spdlog::debug("Saving game data\n"); + firelight::saves::Savefile saveData( + m_core->getMemoryData(libretro::SAVE_RAM)); + // saveData.setImage(m_fbo->toImage()); + + QFuture result = + getSaveManager()->writeSaveDataForEntry(m_currentEntry, saveData); + + if (waitForFinish) { + result.waitForFinished(); + } } void EmulatorRenderer::synchronize(QQuickFramebufferObject *fbo) { const auto manager = reinterpret_cast(fbo); - if (m_fbo) { - manager->setCurrentFboId(m_fbo->handle()); - } + m_paused = manager->m_paused; + m_gameReady = manager->m_gameReady; - if (manager->nativeWidth() != m_nativeWidth) { - invalidateFramebufferObject(); - m_runAFrame = false; + if (manager->m_shouldReset) { + if (m_core) { + m_core->reset(); + } + manager->m_shouldReset = false; } - if (!m_resetContextFunction) { - m_resetContextFunction = manager->consumeContextResetFunction(); + if (!m_core && m_gameReady) { + m_gameData = manager->m_gameData; + m_saveData = manager->m_saveData; + m_corePath = manager->m_corePath; + m_currentEntry = manager->m_currentEntry; } - // if (!m_destroyContextFunction) { - // m_destroyContextFunction = manager->consumeContextDestroyFunction(); - // } + if (m_coreConfiguration) { + manager->m_coreConfiguration = m_coreConfiguration; + } - m_nativeWidth = manager->nativeWidth(); - m_nativeHeight = manager->nativeHeight(); + manager->setGeometry(m_nativeWidth, m_nativeHeight, m_nativeAspectRatio); + manager->setIsRunning(m_running); Renderer::synchronize(fbo); } @@ -50,43 +139,86 @@ void EmulatorRenderer::synchronize(QQuickFramebufferObject *fbo) { QOpenGLFramebufferObject * EmulatorRenderer::createFramebufferObject(const QSize &size) { if (m_nativeWidth != 0 && m_nativeHeight != 0) { + m_fboIsNew = true; m_fbo = Renderer::createFramebufferObject(QSize(m_nativeWidth, m_nativeHeight)); + + if (m_resetContextFunction) { + m_resetContextFunction(); + } return m_fbo; } m_fbo = Renderer::createFramebufferObject(size); - return m_fbo; } void EmulatorRenderer::render() { - if (m_resetContextFunction) { - m_resetContextFunction(); - m_resetContextFunction = nullptr; - } + if (!m_core && m_gameReady) { + auto configProvider = getEmulatorConfigManager()->getCoreConfigFor(m_currentEntry.platformId, m_currentEntry.id); + m_core = std::make_unique(m_corePath.toStdString(), configProvider); + + m_core->setVideoReceiver(this); + m_core->setAudioReceiver(std::make_shared()); + m_core->setRetropadProvider(getControllerManager()); + + m_core->setSystemDirectory("./system"); + // m_core->setSaveDirectory("."); + m_core->init(); + + libretro::Game game( + m_currentEntry.contentPath, + vector(m_gameData.begin(), m_gameData.end())); + m_core->loadGame(&game); + + if (m_saveData.size() > 0) { + m_core->writeMemoryData(libretro::SAVE_RAM, + vector(m_saveData.begin(), m_saveData.end())); + } + + autosaveTimer.start(); + + QMetaObject::invokeMethod( + getAchievementManager(), "loadGame", Qt::QueuedConnection, + Q_ARG(int, m_currentEntry.platformId), + Q_ARG(QString, QString::fromStdString(m_currentEntry.contentId))); - if (m_manager->runFrame()) { update(); - m_runAFrame = true; - } else if (!m_runAFrame) { - m_fbo->bind(); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - m_fbo->release(); + return; } + + if (m_fbo && m_core && !m_paused) { + m_running = true; + m_core->run(0); + getAchievementManager()->doFrame(m_core.get(), m_currentEntry); + + if (m_shouldSave) { + save(false); + m_shouldSave = false; + } + } else if (m_fboIsNew) { + // m_fbo->bind(); + // glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + // glClear(GL_COLOR_BUFFER_BIT); + // m_fbo->release(); + } + + update(); } -void EmulatorRenderer::receiveVideoData(const void *data, unsigned width, - unsigned height, size_t pitch) const { - QOpenGLPaintDevice paint_device; - paint_device.setSize(m_fbo->size()); - QPainter painter(&paint_device); +void EmulatorRenderer::receive(const void *data, unsigned width, + unsigned height, size_t pitch) { + if (!m_usingHwRendering && data != nullptr) { + QOpenGLPaintDevice paint_device; + paint_device.setSize(m_fbo->size()); + QPainter painter(&paint_device); - m_fbo->bind(); - const QImage image((uchar *)data, width, height, pitch, QImage::Format_RGB16); + m_fbo->bind(); + const QImage image((uchar *) data, width, height, pitch, m_pixelFormat); - painter.drawImage(QRect(0, 0, m_fbo->width(), m_fbo->height()), image, - image.rect()); - m_fbo->release(); + // TODO: Check native size, etc. make sure we use max size and base size correctly + painter.drawImage(QRect(0, 0, m_fbo->width(), m_fbo->height()), image, + image.rect()); + m_fbo->release(); + } } diff --git a/src/app/emulator_renderer.hpp b/src/app/emulator_renderer.hpp index 7c235f0..f04207e 100644 --- a/src/app/emulator_renderer.hpp +++ b/src/app/emulator_renderer.hpp @@ -7,31 +7,70 @@ #include #include +#include "libretro/core_configuration.hpp" + class EmulatorRenderer final : public QQuickFramebufferObject::Renderer, public QOpenGLFunctions, - public firelight::ManagerAccessor { + public firelight::ManagerAccessor, + public firelight::libretro::IVideoDataReceiver { public: - explicit EmulatorRenderer(const EmulationManager *manager); + explicit EmulatorRenderer(); + + void receive(const void *data, unsigned width, unsigned height, size_t pitch) override; + + proc_address_t getProcAddress(const char *sym) override; + + void setResetContextFunc(context_reset_func) override; + + void setDestroyContextFunc(context_destroy_func) override; + + uintptr_t getCurrentFramebufferId() override; + + void setSystemAVInfo(retro_system_av_info *info) override; + + void setPixelFormat(retro_pixel_format *format) override; protected: - void synchronize(QQuickFramebufferObject *fbo) override; + ~EmulatorRenderer() override; - QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override; + void synchronize(QQuickFramebufferObject *fbo) override; - void render() override; + QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override; + + void render() override; private: - EmulationManager *m_manager = nullptr; - QOpenGLFramebufferObject *m_fbo = nullptr; + void save(bool waitForFinish = false); + + std::unique_ptr m_core = nullptr; + std::shared_ptr m_coreConfiguration = nullptr; + + QOpenGLFramebufferObject *m_fbo = nullptr; + bool m_fboIsNew = true; + + QByteArray m_gameData; + QByteArray m_saveData; + QString m_corePath; + firelight::db::LibraryEntry m_currentEntry; + + // Default according to libretro docs + QImage::Format m_pixelFormat = QImage::Format_RGB16; + + bool m_paused = false; + bool m_gameReady = false; + + bool m_running = false; - bool m_runAFrame = false; + bool m_usingHwRendering = false; - int m_nativeWidth = 0; - int m_nativeHeight = 0; + QTimer autosaveTimer; + bool m_shouldSave = false; + bool m_runAFrame = false; - void receiveVideoData(const void *data, unsigned width, unsigned height, - size_t pitch) const; + int m_nativeWidth = 0; + int m_nativeHeight = 0; + float m_nativeAspectRatio = 0.0f; - std::function m_resetContextFunction = nullptr; - std::function m_destroyContextFunction = nullptr; + std::function m_resetContextFunction = nullptr; + std::function m_destroyContextFunction = nullptr; }; diff --git a/src/app/fps_multiplier.cpp b/src/app/fps_multiplier.cpp deleted file mode 100644 index e188618..0000000 --- a/src/app/fps_multiplier.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// -// Created by nicho on 12/27/2023. -// - -#include "fps_multiplier.hpp" - -void FpsMultiplier::stop() { std::printf("Stop!"); } - -void FpsMultiplier::start() { std::printf("Start!"); } - -void FpsMultiplier::setSliderValue(double value) { - if (value == m_sliderValue) { - return; - } - std::printf("Changed value from: %f to %f \r\n", m_sliderValue, value); - m_sliderValue = value; - emit sliderValueChanged(m_sliderValue); -} \ No newline at end of file diff --git a/src/app/fps_multiplier.hpp b/src/app/fps_multiplier.hpp deleted file mode 100644 index 3f4cdfd..0000000 --- a/src/app/fps_multiplier.hpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// Created by nicho on 12/27/2023. -// - -#ifndef FPS_MULTIPLIER_H -#define FPS_MULTIPLIER_H -#include -#include -#include -#include -#include - -class FpsMultiplier : public QObject { - Q_OBJECT - Q_PROPERTY(double sliderValue READ sliderValue WRITE setSliderValue NOTIFY - sliderValueChanged) - -public: - inline double sliderValue() const { return m_sliderValue; } - -signals: - void sliderValueChanged(double newValue); - -public slots: - void stop(); - void start(); - void setSliderValue(double); - -private: - double m_sliderValue = 1; -}; - -#endif // FPS_MULTIPLIER_H diff --git a/src/app/input/controller_manager.cpp b/src/app/input/controller_manager.cpp index e615faf..1fa4ec0 100644 --- a/src/app/input/controller_manager.cpp +++ b/src/app/input/controller_manager.cpp @@ -45,7 +45,7 @@ namespace firelight::Input { } std::optional - ControllerManager::getRetropadForPlayer(const int t_player) { + ControllerManager::getRetropadForPlayerIndex(const int t_player) { return getControllerForPlayer(t_player); } diff --git a/src/app/input/controller_manager.hpp b/src/app/input/controller_manager.hpp index 78ceb89..087a47e 100644 --- a/src/app/input/controller_manager.hpp +++ b/src/app/input/controller_manager.hpp @@ -20,7 +20,7 @@ namespace firelight::Input { getControllerForPlayer(int t_player) const; std::optional - getRetropadForPlayer(int t_player) override; + getRetropadForPlayerIndex(int t_player) override; Q_INVOKABLE void updateControllerOrder(const QVector &order); diff --git a/src/app/input/sdl_event_loop.cpp b/src/app/input/sdl_event_loop.cpp index bdf2f07..77f903e 100644 --- a/src/app/input/sdl_event_loop.cpp +++ b/src/app/input/sdl_event_loop.cpp @@ -5,13 +5,15 @@ #include "sdl_event_loop.hpp" #define SDL_MAIN_HANDLED +#include +#include #include #include #include namespace firelight { - SdlEventLoop::SdlEventLoop(Input::ControllerManager *manager) - : m_controllerManager(manager) { + SdlEventLoop::SdlEventLoop(QObject *window, Input::ControllerManager *manager) + : m_window(window), m_controllerManager(manager) { SDL_SetHint(SDL_HINT_APP_NAME, "Firelight"); SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); @@ -54,14 +56,53 @@ namespace firelight { m_controllerManager->handleSDLControllerEvent(ev); break; case SDL_CONTROLLERAXISMOTION: - if (ev.caxis.value > 15000 || ev.caxis.value < -15000) { - printf("axis: %d, value: %d\n", ev.caxis.axis, ev.caxis.value); + break; + case SDL_CONTROLLERBUTTONUP: { + Qt::Key key; + + auto button = ev.cbutton.button; + if (button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) { + key = Qt::Key_Right; + } else if (button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) { + key = Qt::Key_Left; + } else if (button == SDL_CONTROLLER_BUTTON_DPAD_UP) { + key = Qt::Key_Up; + } else if (button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) { + key = Qt::Key_Down; + } else if (button == SDL_CONTROLLER_BUTTON_X) { + key = Qt::Key_Menu; + } else { + break; } + + QApplication::postEvent( + m_window, new QKeyEvent(QEvent::KeyRelease, key, Qt::KeyboardModifier::NoModifier)); break; - case SDL_CONTROLLERBUTTONUP: - case SDL_CONTROLLERBUTTONDOWN: - printf("button: %d, state: %d\n", ev.cbutton.button, ev.cbutton.state); + } + case SDL_CONTROLLERBUTTONDOWN: { + Qt::Key key; + + auto button = ev.cbutton.button; + if (button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) { + key = Qt::Key_Right; + } else if (button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) { + key = Qt::Key_Left; + } else if (button == SDL_CONTROLLER_BUTTON_DPAD_UP) { + key = Qt::Key_Up; + } else if (button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) { + key = Qt::Key_Down; + } else if (button == SDL_CONTROLLER_BUTTON_X) { + key = Qt::Key_Menu; + } else { + break; + } + + QApplication::postEvent( + m_window, new QKeyEvent(QEvent::KeyPress, key, Qt::KeyboardModifier::NoModifier)); break; + } + // printf("button: %d, state: %d\n", ev.cbutton.button, ev.cbutton.state); + // break; case SDL_JOYAXISMOTION: case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN: diff --git a/src/app/input/sdl_event_loop.hpp b/src/app/input/sdl_event_loop.hpp index 73a4eb1..7d2c076 100644 --- a/src/app/input/sdl_event_loop.hpp +++ b/src/app/input/sdl_event_loop.hpp @@ -5,20 +5,22 @@ #include namespace firelight { + class SdlEventLoop final : public QThread { + public: + explicit SdlEventLoop(QObject *window, Input::ControllerManager *manager); -class SdlEventLoop final : public QThread { -public: - explicit SdlEventLoop(Input::ControllerManager *manager); - ~SdlEventLoop() override; - void stopProcessing(); + ~SdlEventLoop() override; -protected: - void run() override; + void stopProcessing(); -private: - void processEvents() const; - bool m_running = true; - Input::ControllerManager *m_controllerManager; -}; + protected: + void run() override; + private: + void processEvents() const; + + QObject *m_window; + bool m_running = true; + Input::ControllerManager *m_controllerManager; + }; } // namespace firelight diff --git a/src/app/library/CMakeLists.txt b/src/app/library/CMakeLists.txt index 43ccfe1..3c92e4c 100644 --- a/src/app/library/CMakeLists.txt +++ b/src/app/library/CMakeLists.txt @@ -7,4 +7,4 @@ set(LIBRARY_SOURCES add_library(library ${LIBRARY_SOURCES}) -target_link_libraries(library PUBLIC Qt6::Concurrent Qt6::Sql spdlog::spdlog fmt::fmt ssl crypto) \ No newline at end of file +target_link_libraries(library PUBLIC Qt6::Concurrent Qt6::Sql spdlog::spdlog fmt::fmt ${OPENSSL_LIBRARIES}) diff --git a/src/app/library/library_scanner.cpp b/src/app/library/library_scanner.cpp index efb5555..4965135 100644 --- a/src/app/library/library_scanner.cpp +++ b/src/app/library/library_scanner.cpp @@ -13,9 +13,9 @@ constexpr int MAX_FILESIZE_BYTES = 750000000; LibraryScanner::LibraryScanner( - firelight::db::ILibraryDatabase *lib_database, - firelight::db::IContentDatabase *content_database) - : library_database_(lib_database), content_database_(content_database) { + firelight::db::ILibraryDatabase *lib_database, + firelight::db::IContentDatabase *content_database) + : library_database_(lib_database), content_database_(content_database) { scanner_thread_pool_ = std::make_unique(); scanner_thread_pool_->setMaxThreadCount(thread_pool_size_); // directory_watcher_.addPath( @@ -54,8 +54,8 @@ void LibraryScanner::startScan() { ScanResults scan_results; auto paths = library_database_->getAllLibraryContentDirectories(); - for (const auto &path : paths) { - for (const auto &entry : + for (const auto &path: paths) { + for (const auto &entry: std::filesystem::recursive_directory_iterator(path.path)) { if (entry.is_directory()) { continue; @@ -73,7 +73,7 @@ void LibraryScanner::startScan() { scan_results.all_filenames.emplace_back(filename); auto existing = library_database_->getMatchingLibraryEntries( - firelight::db::LibraryEntry{.contentPath = filename}); + firelight::db::LibraryEntry{.contentPath = filename}); if (!existing.empty()) { spdlog::debug("Found library entry with filename {}; skipping", filename); @@ -87,9 +87,9 @@ void LibraryScanner::startScan() { // Check against content database if (auto ext = entry.path().extension(); - ext.string() == ".mod" || ext.string() == ".ips" || - ext.string() == ".ups" || ext.string() == ".bps" || - ext.string() == ".ups") { + ext.string() == ".mod" || ext.string() == ".ips" || + ext.string() == ".ups" || ext.string() == ".bps" || + ext.string() == ".ups") { // std::vector contents(filesize); // std::ifstream file(entry.path(), std::ios::binary); // @@ -153,9 +153,10 @@ void LibraryScanner::startScan() { } else if (ext.string() == ".smc" || ext.string() == ".n64" || ext.string() == ".v64" || ext.string() == ".z64" || ext.string() == ".gb" || ext.string() == ".gbc" || - ext.string() == ".gba" || ext.string() == ".sfc") { + ext.string() == ".gba" || ext.string() == ".sfc" || + ext.string() == ".nes") { auto platforms = content_database_->getMatchingPlatforms( - {.supportedExtensions = {ext.string()}}); + {.supportedExtensions = {ext.string()}}); if (platforms.empty()) { printf("File extension not recognized: %s\n", @@ -188,20 +189,31 @@ void LibraryScanner::startScan() { // std::copy(thing.begin() + 512, thing.end(), // new_thing.begin()); } + } else if (ext == ".nes") { + auto firstFourAsString = std::string(thing.begin(), thing.begin() + 4); + if (firstFourAsString == "NES\x1A") { + printf("FOUND HEADER!!! %s\n", + entry.path().filename().string().c_str()); + thing.erase(thing.begin(), thing.begin() + 16); + filesize -= 16; + + contentId = calculateMD5(thing.data(), filesize); + } } auto display_name = entry.path().filename().string(); firelight::db::LibraryEntry e = { - .displayName = display_name, - .contentId = contentId, - .platformId = platforms.at(0).id, - .type = firelight::db::LibraryEntry::EntryType::ROM, - .fileMd5 = md5, - .fileCrc32 = md5, // TODO: Calculate CRC32 - .sourceDirectory = - canonical(entry.path().parent_path()).string(), - .contentPath = canonical(entry.path()).string()}; + .displayName = display_name, + .contentId = contentId, + .platformId = platforms.at(0).id, + .type = firelight::db::LibraryEntry::EntryType::ROM, + .fileMd5 = md5, + .fileCrc32 = md5, // TODO: Calculate CRC32 + .sourceDirectory = + canonical(entry.path().parent_path()).string(), + .contentPath = canonical(entry.path()).string() + }; auto roms = content_database_->getMatchingRoms({.md5 = contentId}); @@ -222,17 +234,17 @@ void LibraryScanner::startScan() { auto allPaths = library_database_->getAllContentPaths(); - for (const auto &path : allPaths) { + for (const auto &path: allPaths) { if (std::ranges::find(scan_results.all_filenames, path) == scan_results.all_filenames.end()) { auto matchingEntry = library_database_->getMatchingLibraryEntries( - {.contentPath = path}); + {.contentPath = path}); library_database_->deleteLibraryEntry(matchingEntry.at(0).id); } } - for (auto &new_entry : scan_results.new_entries) { + for (auto &new_entry: scan_results.new_entries) { library_database_->createLibraryEntry(new_entry); } @@ -254,11 +266,12 @@ bool LibraryScanner::scanning() const { return scanning_; } void LibraryScanner::refreshDirectories() { auto dirs = library_database_->getAllLibraryContentDirectories(); - for (const auto &dir : dirs) { + for (const auto &dir: dirs) { directory_watcher_.addPath(QString::fromStdString(dir.path)); } } void LibraryScanner::handleScannedPatchFile( - const std::filesystem::directory_entry &entry, - ScanResults &scan_results) const {} + const std::filesystem::directory_entry &entry, + ScanResults &scan_results) const { +} diff --git a/src/app/libretro/core.cpp b/src/app/libretro/core.cpp index d8110bc..0a40fc3 100644 --- a/src/app/libretro/core.cpp +++ b/src/app/libretro/core.cpp @@ -3,6 +3,7 @@ #include "SDL2/SDL.h" #include "virtual_filesystem.hpp" #include +#include #include @@ -31,7 +32,7 @@ namespace libretro { } const auto controllerOpt = - currentCore->getRetropadProvider()->getRetropadForPlayer(port); + currentCore->getRetropadProvider()->getRetropadForPlayerIndex(port); if (!controllerOpt.has_value()) { return 0; } @@ -127,10 +128,9 @@ namespace libretro { } case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_PIXEL_FORMAT"); - auto ptr = (retro_pixel_format *) data; - // TODO: Implement - // video->setPixelFormat((retro_pixel_format *)data); - return true; + auto ptr = static_cast(data); + videoReceiver->setPixelFormat(ptr); + break; } case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS"); @@ -174,10 +174,12 @@ namespace libretro { } case RETRO_ENVIRONMENT_SET_HW_RENDER: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_HW_RENDER"); - // TODO I think this is actually mostly stuff informing the frontend - printf("Setting hw render\n"); auto *renderCallback = static_cast(data); + renderCallback->context_type = RETRO_HW_CONTEXT_OPENGL_CORE; + renderCallback->version_major = 4; + renderCallback->version_minor = 1; + renderCallback->get_proc_address = [](const char *sym) -> retro_proc_address_t { return currentCore->videoReceiver->getProcAddress(sym); @@ -187,75 +189,74 @@ namespace libretro { return currentCore->videoReceiver->getCurrentFramebufferId(); }; - // printf("huh\n"); currentCore->videoReceiver->setResetContextFunc( renderCallback->context_reset); if (renderCallback->context_destroy) { printf("context destroy is not null!\n"); - // currentCore->videoReceiver->setDestroyContextFunc( - // renderCallback->context_destroy); + currentCore->videoReceiver->setDestroyContextFunc( + renderCallback->context_destroy); currentCore->destroyContextFunction = renderCallback->context_destroy; } - // return false; - return true; + break; } case RETRO_ENVIRONMENT_GET_VARIABLE: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_VARIABLE"); auto ptr = static_cast(data); - for (const auto &opt: options) { - if (strcmp(opt.key, ptr->key) == 0) { - // auto strr = "mupen64plus-pak1"; - // auto val = "rumble"; - // if (strcmp(opt.key, strr) == 0) { - // ptr->value = val; - // } else { - // ptr->value = opt.currentValue; - // } - // auto strr = "mupen64plus-rsp-plugin"; - // auto val = "parallel"; - // if (strcmp(opt.key, strr) == 0) { - // printf("Setting rsp plugin to %s\n", val); - // ptr->value = val; - // } else { - // ptr->value = opt.currentValue; - // } - - ptr->value = opt.currentValue; - - // auto strr2 = "mupen64plus-rdp-plugin"; - // auto val2 = "angrylion"; - // if (strcmp(opt.key, strr2) == 0) { - // printf("Setting rdp plugin to %s\n", val2); - // ptr->value = val2; - // } - return true; - } + + auto configProvider = currentCore->m_configurationProvider; + if (!configProvider) { + return false; } - return true; + + auto val = configProvider->getOptionValue(ptr->key); + if (!val.has_value()) { + return false; + } + + ptr->value = val->key.c_str(); + break; } + case RETRO_ENVIRONMENT_SET_VARIABLES: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_VARIABLES"); auto ptr = static_cast(data); - // TODO sane default - for (int i = 0; i < 100; ++i) { + + auto configProvider = currentCore->m_configurationProvider; + if (!configProvider) { + return false; + } + + for (int i = 0; i < 200; ++i) { auto opt = ptr[i]; - printf("Variable KEY: %s VALUE: %s\n", opt.key, opt.value); if (opt.key == nullptr) { break; } + + firelight::libretro::IConfigurationProvider::Option option; + option.key = opt.key; + option.label = opt.value; + + configProvider->registerOption(option); } - return true; + break; } case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE"); - *static_cast(data) = false; // TODO: actually implement - return true; + + const auto configProvider = currentCore->m_configurationProvider; + if (!configProvider) { + *static_cast(data) = configProvider->anyOptionValueHasChanged(); + } else { + *static_cast(data) = false; + } + break; } case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME"); canRunWithNoGame = *static_cast(data); + break; } case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_LIBRETRO_PATH"); @@ -284,7 +285,7 @@ namespace libretro { ptr->set_rumble_state = [](unsigned port, enum retro_rumble_effect effect, uint16_t strength) { const auto con = - currentCore->getRetropadProvider()->getRetropadForPlayer(port); + currentCore->getRetropadProvider()->getRetropadForPlayerIndex(port); if (!con.has_value()) { return true; } @@ -298,7 +299,7 @@ namespace libretro { return true; }; - return true; + break; } case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES: { environmentCalls.emplace_back( @@ -312,7 +313,7 @@ namespace libretro { // RETRO_DEVICE_ANALOG). *ptr = (1 << RETRO_DEVICE_JOYPAD) | (1 << RETRO_DEVICE_ANALOG); - return false; + return true; } case RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE"); @@ -432,13 +433,12 @@ namespace libretro { case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO"); videoReceiver->setSystemAVInfo(static_cast(data)); - // video->setGameGeometry(&retroSystemAVInfo->geometry); return true; } - case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK: - environmentCalls.emplace_back( - "RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK"); - break; + // case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK: + // environmentCalls.emplace_back( + // "RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK"); + // break; case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO"); auto ptr = static_cast(data); @@ -458,17 +458,31 @@ namespace libretro { case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CONTROLLER_INFO"); auto ptr = static_cast(data); - for (unsigned i = 0; i < ptr->num_types; ++i) { - auto info = ptr->types[i]; - if (info.desc == nullptr) { + + + for (unsigned i = 0; i < 100; ++i) { + auto info = ptr[i]; + + if (!info.types) { break; } - controllerInfo.emplace_back(info); - if (i == 100) { - recordPotentialAPIViolation("Over 100 controller infos"); + for (unsigned j = 0; j < info.num_types; ++j) { + auto type = info.types[j]; + printf("Type: %d, Value: %s\n", type.id, type.desc); } } + // for (unsigned i = 0; i < ptr->num_types; ++i) { + // auto info = ptr->types[i]; + // if (info.desc == nullptr) { + // break; + // } + // + // controllerInfo.emplace_back(info); + // if (i == 100) { + // recordPotentialAPIViolation("Over 100 controller infos"); + // } + // } return true; } case RETRO_ENVIRONMENT_SET_MEMORY_MAPS: { @@ -519,16 +533,22 @@ namespace libretro { auto ptr = static_cast(data); ptr->interface_type = RETRO_HW_RENDER_INTERFACE_DUMMY; ptr->interface_version = 0; + return true; } case RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SUPPORT_ACHIEVEMENTS"); supportsAchievements = *static_cast(data); return true; } - case RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE: + case RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE: { environmentCalls.emplace_back( "RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE"); + auto ptr = static_cast(data); + auto type = ptr->interface_type; + auto version = ptr->interface_version; + break; + } case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS: environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS"); break; @@ -583,6 +603,7 @@ namespace libretro { break; case RETRO_ENVIRONMENT_GET_FASTFORWARDING: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_FASTFORWARDING"); + // TODO: Get from video provider? auto ptr = static_cast(data); *ptr = fastforwarding; return true; @@ -592,138 +613,319 @@ namespace libretro { break; case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS: environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_INPUT_BITMASKS"); - break; + // TODO: Implement + return false; case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION"); - // TODO: Set this behind some user-settable flag? auto ptr = static_cast(data); *ptr = 2; - return true; + break; } case RETRO_ENVIRONMENT_SET_CORE_OPTIONS: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS"); auto ptr = static_cast(data); - // TODO sane default + + auto configProvider = currentCore->m_configurationProvider; + if (!configProvider) { + return false; + } + for (int i = 0; i < 200; ++i) { - auto opt = *ptr[i]; - printf("OPTION KEY: %s\n", opt.key); - if (opt.key == nullptr) { + auto opt = ptr[i]; + if (opt->key == nullptr) { break; } - // TODO: pointer? - CoreOption coreOption(opt); - options.emplace_back(coreOption); + firelight::libretro::IConfigurationProvider::Option option; + option.key = opt->key; + option.label = opt->desc; + option.description = opt->info; + + if (opt->default_value != nullptr) { + option.defaultValueKey = opt->default_value; + } else { + option.defaultValueKey = opt->values[0].value; + } + + for (int j = 0; j < 100; ++j) { + auto val = opt->values[j]; + if (val.value == nullptr) { + break; + } + + firelight::libretro::IConfigurationProvider::OptionValue optionValue; + optionValue.key = val.value; + if (val.label != nullptr) { + optionValue.label = val.label; + } else { + optionValue.label = val.value; + } + + option.possibleValues.emplace_back(optionValue); + } + + configProvider->registerOption(option); } - return true; + break; } case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL"); auto ptr = static_cast(data); - // TODO sane default + + auto configProvider = currentCore->m_configurationProvider; + if (!configProvider) { + return false; + } + for (int i = 0; i < 200; ++i) { auto opt = ptr->us[i]; if (opt.key == nullptr) { break; } - CoreOption coreOption(opt); - options.emplace_back(coreOption); + firelight::libretro::IConfigurationProvider::Option option; + option.key = opt.key; + option.label = opt.desc; + option.description = opt.info; + + if (opt.default_value != nullptr) { + option.defaultValueKey = opt.default_value; + } else { + option.defaultValueKey = opt.values[0].value; + } + + for (int j = 0; j < 100; ++j) { + auto val = opt.values[j]; + if (val.value == nullptr) { + break; + } + + firelight::libretro::IConfigurationProvider::OptionValue optionValue; + optionValue.key = val.value; + if (val.label != nullptr) { + optionValue.label = val.label; + } else { + optionValue.label = val.value; + } + + option.possibleValues.emplace_back(optionValue); + } + + configProvider->registerOption(option); } - return true; + + + break; } case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY"); auto ptr = static_cast(data); - for (auto opt: options) { - if (strcmp(ptr->key, opt.key) == 0) { - opt.displayToUser = ptr->visible; - return true; - } + + auto configProvider = currentCore->m_configurationProvider; + if (!configProvider) { + return false; } - return false; + + configProvider->setOptionVisibility(ptr->key, ptr->visible); + break; } case RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER: environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER"); *static_cast(data) = RETRO_HW_CONTEXT_OPENGL; return true; - case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION: - environmentCalls.emplace_back( - "RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION"); - break; - case RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE: - environmentCalls.emplace_back( - "RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE"); - break; - case RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION: + // case RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION: + // environmentCalls.emplace_back( + // "RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION"); + // return false; + // case RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE: + // environmentCalls.emplace_back( + // "RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE"); + // return false; + case RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION: { environmentCalls.emplace_back( "RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION"); + auto ptr = static_cast(data); + *ptr = 1; break; - case RETRO_ENVIRONMENT_SET_MESSAGE_EXT: + } + case RETRO_ENVIRONMENT_SET_MESSAGE_EXT: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_MESSAGE_EXT"); + auto ptr = static_cast(data); + + // TODO + printf("Msg: %s\n", ptr->msg); break; - case RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS: - environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS"); - break; - case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK: + } + // case RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS: + // environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS"); + // return false; + case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK: { environmentCalls.emplace_back( "RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK"); + if (!data) { + break; + } + + auto ptr = static_cast(data); + + ptr->callback = [](bool active, unsigned occupancy, bool underrun_likely) { + printf("Active: %d, Occupancy: %d, Underrun Likely: %d\n", active, occupancy, underrun_likely); + }; + break; - case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY: + } + case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY: { environmentCalls.emplace_back( "RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY"); + // TODO break; - case RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE: - environmentCalls.emplace_back( - "RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE"); - break; - case RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE: + } + // case RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE: + // environmentCalls.emplace_back( + // "RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE"); + // break; + case RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE: { environmentCalls.emplace_back( "RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE"); - break; - case RETRO_ENVIRONMENT_GET_GAME_INFO_EXT: + auto ptr = static_cast(data); + for (int i = 0; i < 100; ++i) { + auto info = ptr[i]; + if (info.extensions == nullptr) { + break; + } + } + return false; + // break; + } + case RETRO_ENVIRONMENT_GET_GAME_INFO_EXT: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_GAME_INFO_EXT"); - break; + auto ptr = static_cast(data); + return false; + // break; + } case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2"); auto ptr = static_cast(data); + + auto configProvider = currentCore->m_configurationProvider; + if (!configProvider) { + return false; + } + for (int i = 0; i < 100; ++i) { auto opt = ptr->categories[i]; if (opt.key == nullptr) { break; } } + for (int i = 0; i < 200; ++i) { auto opt = ptr->definitions[i]; if (opt.key == nullptr) { break; } - CoreOption coreOption(opt); - options.emplace_back(coreOption); + firelight::libretro::IConfigurationProvider::Option option; + option.key = opt.key; + + if (opt.default_value != nullptr) { + option.defaultValueKey = opt.default_value; + } else { + option.defaultValueKey = opt.values[0].value; + } + + if (opt.desc_categorized != nullptr) { + option.label = opt.desc_categorized; + } else { + option.label = opt.desc; + } + + if (opt.info_categorized != nullptr) { + option.description = opt.info_categorized; + } else { + option.description = opt.info; + } + + for (int j = 0; j < 100; ++j) { + auto val = opt.values[j]; + if (val.value == nullptr) { + break; + } + + firelight::libretro::IConfigurationProvider::OptionValue optionValue; + optionValue.key = val.value; + if (val.label != nullptr) { + optionValue.label = val.label; + } else { + optionValue.label = val.value; + } + + option.possibleValues.emplace_back(optionValue); + } + + configProvider->registerOption(option); } - return true; + break; } case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2_INTL"); // TODO auto ptr = static_cast(data); - for (int i = 0; i < 100; ++i) { - auto opt = ptr->us->categories[i]; - if (opt.key == nullptr) { - break; - } + + auto configProvider = currentCore->m_configurationProvider; + if (!configProvider) { + return false; } + for (int i = 0; i < 200; ++i) { auto opt = ptr->us->definitions[i]; if (opt.key == nullptr) { break; } - CoreOption coreOption(opt); - options.emplace_back(coreOption); + firelight::libretro::IConfigurationProvider::Option option; + option.key = opt.key; + + if (opt.default_value != nullptr) { + option.defaultValueKey = opt.default_value; + } else { + option.defaultValueKey = opt.values[0].value; + } + + if (opt.desc_categorized != nullptr) { + option.label = opt.desc_categorized; + } else if (opt.desc != nullptr) { + option.label = opt.desc; + } else { + option.label = opt.key; + } + + if (opt.info_categorized != nullptr) { + option.description = opt.info_categorized; + } else if (opt.info != nullptr) { + option.description = opt.info; + } + + for (int j = 0; j < 100; ++j) { + auto val = opt.values[j]; + if (val.value == nullptr) { + break; + } + + firelight::libretro::IConfigurationProvider::OptionValue optionValue; + optionValue.key = val.value; + if (val.label != nullptr) { + optionValue.label = val.label; + } else { + optionValue.label = val.value; + } + + option.possibleValues.emplace_back(optionValue); + } + + configProvider->registerOption(option); } - return true; + break; } case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK: { environmentCalls.emplace_back( @@ -737,26 +939,8 @@ namespace libretro { } case RETRO_ENVIRONMENT_SET_VARIABLE: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_SET_VARIABLE"); - auto ptr = static_cast(data); - if (ptr == nullptr) { - return true; - } - - for (auto opt: options) { - if (strcmp(opt.key, ptr->key) == 0) { - for (auto v: opt.values) { - if (strcmp(ptr->value, v.value) == 0) { - opt.currentValue = ptr->value; - return true; - } - recordPotentialAPIViolation( - "SET_VARIABLE with unknown value for key TODO"); - } - // TODO: Make sure value is one of the allowed strings - } - } - - return true; + // TODO: Implement + break; } case RETRO_ENVIRONMENT_GET_THROTTLE_STATE: { environmentCalls.emplace_back("RETRO_ENVIRONMENT_GET_THROTTLE_STATE"); @@ -828,8 +1012,10 @@ namespace libretro { default: printf("Unimplemented env command: %d\n", cmd); environmentCalls.emplace_back("UNIMPLEMENTED"); + return false; } - return false; + + return true; } template @@ -936,7 +1122,9 @@ namespace libretro { // return j.dump(); // } - Core::Core(const std::string &libPath) { + Core::Core(const std::string &libPath, + std::shared_ptr configProvider) : m_configurationProvider( + std::move(configProvider)) { coreLib = std::make_unique(QString::fromStdString(libPath)); // dll = SDL_LoadObject(libPath.c_str()); @@ -956,7 +1144,7 @@ namespace libretro { reinterpret_cast( coreLib->resolve("retro_set_controller_port_device")); symRetroReset = coreLib->resolve("retro_reset"); - symRetroRun = reinterpret_cast(coreLib->resolve("retro_run")); + symRetroRun = coreLib->resolve("retro_run"); symRetroSerializeSize = reinterpret_cast(coreLib->resolve("retro_serialize_size")); symRetroSerialize = reinterpret_cast( @@ -964,7 +1152,7 @@ namespace libretro { symRetroUnserialize = reinterpret_cast( coreLib->resolve("retro_unserialize")); symRetroCheatReset = - reinterpret_cast(coreLib->resolve("retro_cheat_reset")); + coreLib->resolve("retro_cheat_reset"); symRetroCheatSet = reinterpret_cast( coreLib->resolve("retro_cheat_set")); @@ -974,7 +1162,7 @@ namespace libretro { reinterpret_cast( coreLib->resolve("retro_load_game_special")); symRetroUnloadGame = - reinterpret_cast(coreLib->resolve("retro_unload_game")); + coreLib->resolve("retro_unload_game"); symRetroGetRegion = reinterpret_cast( coreLib->resolve("retro_get_region")); @@ -1069,18 +1257,17 @@ namespace libretro { // loadRetroFunc(dll, "retro_set_input_poll")([]() {}); // loadRetroFunc(dll, // "retro_set_input_state")(inputStateCallback); - - symRetroSetControllerPortDevice(0, RETRO_DEVICE_ANALOG); } Core::~Core() { + printf("Destroying core\n"); // if (destroyContextFunction) { // printf("Destroying context\n"); // destroyContextFunction(); // } - // unloadGame(); - // deinit(); + unloadGame(); + deinit(); // SDL_UnloadObject(dll); // @@ -1113,12 +1300,6 @@ namespace libretro { } void Core::unloadGame() { - if (destroyContextFunction) { - printf("Destroying context\n"); - destroyContextFunction(); - destroyContextFunction = nullptr; - } - symRetroUnloadGame(); } @@ -1216,7 +1397,7 @@ namespace libretro { return m_retropadProvider; } - void Core::setAudioReceiver(IAudioDataReceiver *receiver) { - audioReceiver = receiver; + void Core::setAudioReceiver(std::shared_ptr receiver) { + audioReceiver = std::move(receiver); } } // namespace libretro diff --git a/src/app/libretro/core.hpp b/src/app/libretro/core.hpp index 3ce7c6b..1bf5f84 100644 --- a/src/app/libretro/core.hpp +++ b/src/app/libretro/core.hpp @@ -1,14 +1,13 @@ #pragma once -#include "coreoption.hpp" #include "firelight/libretro/audio_data_receiver.hpp" #include "firelight/libretro/retropad_provider.hpp" #include "firelight/libretro/video_data_receiver.hpp" +#include "firelight/libretro/configuration_provider.hpp" #include "game.hpp" #include "libretro/libretro.h" #include -#include #include #include @@ -17,188 +16,194 @@ using std::string; using std::vector; namespace libretro { + enum MemoryType { + SAVE_RAM = RETRO_MEMORY_SAVE_RAM, + RTC = RETRO_MEMORY_RTC, + SYSTEM_RAM = RETRO_MEMORY_SYSTEM_RAM, + VIDEO_RAM = RETRO_MEMORY_VIDEO_RAM + }; -enum MemoryType { - SAVE_RAM = RETRO_MEMORY_SAVE_RAM, - RTC = RETRO_MEMORY_RTC, - SYSTEM_RAM = RETRO_MEMORY_SYSTEM_RAM, - VIDEO_RAM = RETRO_MEMORY_VIDEO_RAM -}; + typedef void (*RetroSetEnvironment)(bool (*)(unsigned cmd, void *data)); -typedef void (*RetroSetEnvironment)(bool (*)(unsigned cmd, void *data)); -typedef void (*RetroSetVideoRefresh)(retro_video_refresh_t); -typedef void (*RetroSetAudioSample)(retro_audio_sample_t); -typedef void (*RetroSetAudioSampleBatch)(retro_audio_sample_batch_t); -typedef void (*RetroInputState)(retro_input_state_t); -typedef void (*RetroInputPoll)(retro_input_poll_t); -typedef void (*RetroRunFunc)(); + typedef void (*RetroSetVideoRefresh)(retro_video_refresh_t); -class Core { + typedef void (*RetroSetAudioSample)(retro_audio_sample_t); -public: - std::basic_string dumpJson(); + typedef void (*RetroSetAudioSampleBatch)(retro_audio_sample_batch_t); - Core(const std::string &libPath); + typedef void (*RetroInputState)(retro_input_state_t); - virtual ~Core(); + typedef void (*RetroInputPoll)(retro_input_poll_t); - void setVideoReceiver(firelight::libretro::IVideoDataReceiver *receiver); - void setRetropadProvider(firelight::libretro::IRetropadProvider *provider); - firelight::libretro::IRetropadProvider *getRetropadProvider() const; + typedef void (*RetroRunFunc)(); - void setAudioReceiver(IAudioDataReceiver *receiver); + class Core { + public: + std::basic_string dumpJson(); - bool handleEnvironmentCall(unsigned cmd, void *data); + Core(const std::string &libPath, std::shared_ptr configProvider); - void init(); + virtual ~Core(); - void deinit(); + void setVideoReceiver(firelight::libretro::IVideoDataReceiver *receiver); - void reset(); + void setRetropadProvider(firelight::libretro::IRetropadProvider *provider); - void run(double deltaTime); + firelight::libretro::IRetropadProvider *getRetropadProvider() const; - bool loadGame(Game *game); + void setAudioReceiver(std::shared_ptr receiver); - void unloadGame(); + bool handleEnvironmentCall(unsigned cmd, void *data); - std::vector serializeState() const; - void deserializeState(const std::vector &data) const; + void init(); - size_t getSerializeSize() const; + void deinit(); - void setSystemDirectory(const string &); + void reset(); - void setSaveDirectory(const string &); + void run(double deltaTime); - [[nodiscard]] std::vector getMemoryData(MemoryType memType) const; + bool loadGame(Game *game); - void writeMemoryData(MemoryType memType, const std::vector &data); - firelight::libretro::IVideoDataReceiver *videoReceiver; + void unloadGame(); - void *getMemoryData(unsigned id) const; - size_t getMemorySize(unsigned id) const; + std::vector serializeState() const; - retro_memory_map *getMemoryMap(); + void deserializeState(const std::vector &data) const; - std::function destroyContextFunction = nullptr; + size_t getSerializeSize() const; -private: - std::unique_ptr coreLib; + void setSystemDirectory(const string &); - firelight::libretro::IRetropadProvider *m_retropadProvider; - IAudioDataReceiver *audioReceiver; + void setSaveDirectory(const string &); - retro_vfs_interface m_vfsInterface; + [[nodiscard]] std::vector getMemoryData(MemoryType memType) const; - vector environmentCalls; + void writeMemoryData(MemoryType memType, const std::vector &data); - retro_system_info *retroSystemInfo; - retro_system_av_info *retroSystemAVInfo; + firelight::libretro::IVideoDataReceiver *videoReceiver; - // Informational to frontend. - bool canRunWithNoGame = false; - unsigned performanceLevel = 0; - bool supportsAchievements = false; - bool shutdown = false; - vector inputDescriptors; + void *getMemoryData(unsigned id) const; - // Informational to core. - string systemDirectory; - string coreAssetsDirectory; - string saveDirectory; - string libretroPath; - string username; - unsigned frontendLanguage; - bool isJITCapable; + size_t getMemorySize(unsigned id) const; - retro_disk_control_callback *diskControlCallback; - unsigned diskControlInterfaceVersion; - retro_disk_control_ext_callback *diskControlExtCallback; - retro_rumble_interface *rumbleInterface; - uint64_t serializationQuirksBitmap; - retro_vfs_interface_info *virtualFileSystemInterfaceInfo; - retro_led_interface *ledInterface; - unsigned messageInterfaceVersion; - retro_message_ext *messageExt; // todo - retro_fastforwarding_override *fastforwardingOverride; - retro_system_content_info_override *contentInfoOverride; - retro_game_info_ext *gameInfoExt; - retro_throttle_state *throttleState; - int saveStateContext; - retro_microphone_interface *microphoneInterface; - retro_netpacket_callback *netpacketCallback; - retro_device_power *devicePower; - bool fastforwarding; + retro_memory_map *getMemoryMap(); - unsigned coreOptionsVersion; - std::vector options; + std::function destroyContextFunction = nullptr; - retro_sensor_interface *sensorInterface; - retro_camera_callback *cameraCallback; - retro_log_callback *logCallback; - retro_perf_callback *performanceCallback; - retro_location_callback *locationCallback; - retro_get_proc_address_interface *procAddressCallback; - vector subsystemInfo; - vector memoryDescriptors; + private: + std::unique_ptr coreLib; - retro_memory_map memoryMap{}; + firelight::libretro::IRetropadProvider *m_retropadProvider; + std::shared_ptr audioReceiver; + std::shared_ptr m_configurationProvider; - int audioVideoEnableBitmap; + retro_vfs_interface m_vfsInterface; - retro_audio_callback *audioCallback; - unsigned minimumAudioLatency; - retro_midi_interface *midiInterface; - retro_audio_buffer_status_callback *audioBufferStatusCallback; + vector environmentCalls; - unsigned numActiveInputDevices; - bool supportsInputBitmasks; - vector controllerInfo; - uint64_t inputDeviceCapabilitiesBitmask; - retro_keyboard_callback *keyboardCallback; + retro_system_info *retroSystemInfo; + retro_system_av_info *retroSystemAVInfo; - void recordPotentialAPIViolation(const string &msg); + // Informational to frontend. + bool canRunWithNoGame = false; + unsigned performanceLevel = 0; + bool supportsAchievements = false; + bool shutdown = false; + vector inputDescriptors; - void *dll; + // Informational to core. + string systemDirectory; + string coreAssetsDirectory; + string saveDirectory; + string libretroPath; + string username; + unsigned frontendLanguage; + bool isJITCapable; - void (*symRetroInit)(); + retro_disk_control_callback *diskControlCallback; + unsigned diskControlInterfaceVersion; + retro_disk_control_ext_callback *diskControlExtCallback; + retro_rumble_interface *rumbleInterface; + uint64_t serializationQuirksBitmap; + retro_vfs_interface_info *virtualFileSystemInterfaceInfo; + retro_led_interface *ledInterface; + unsigned messageInterfaceVersion; + retro_message_ext *messageExt; // todo + retro_fastforwarding_override *fastforwardingOverride; + retro_system_content_info_override *contentInfoOverride; + retro_game_info_ext *gameInfoExt; + retro_throttle_state *throttleState; + int saveStateContext; + retro_microphone_interface *microphoneInterface; + retro_netpacket_callback *netpacketCallback; + retro_device_power *devicePower; + bool fastforwarding; - void (*symRetroDeinit)(); + retro_sensor_interface *sensorInterface; + retro_camera_callback *cameraCallback; + retro_log_callback *logCallback; + retro_perf_callback *performanceCallback; + retro_location_callback *locationCallback; + retro_get_proc_address_interface *procAddressCallback; + vector subsystemInfo; + vector memoryDescriptors; - unsigned (*symRetroApiVersion)(); + retro_memory_map memoryMap{}; - void (*symRetroGetSystemInfo)(retro_system_info *); + int audioVideoEnableBitmap; - void (*symRetroGetSystemAVInfo)(retro_system_av_info *); + retro_audio_callback *audioCallback; + unsigned minimumAudioLatency; + retro_midi_interface *midiInterface; + retro_audio_buffer_status_callback *audioBufferStatusCallback; - void (*symRetroSetControllerPortDevice)(unsigned, unsigned); + unsigned numActiveInputDevices; + bool supportsInputBitmasks; + vector controllerInfo; + uint64_t inputDeviceCapabilitiesBitmask; + retro_keyboard_callback *keyboardCallback; - void (*symRetroReset)(); + void recordPotentialAPIViolation(const string &msg); - RetroRunFunc symRetroRun; + void *dll; - size_t (*symRetroSerializeSize)(); + void (*symRetroInit)(); - bool (*symRetroSerialize)(void *, size_t); + void (*symRetroDeinit)(); - bool (*symRetroUnserialize)(const void *, size_t); + unsigned (*symRetroApiVersion)(); - void (*symRetroCheatReset)(); + void (*symRetroGetSystemInfo)(retro_system_info *); - void (*symRetroCheatSet)(unsigned, bool, const char *); + void (*symRetroGetSystemAVInfo)(retro_system_av_info *); - bool (*symRetroLoadGame)(const retro_game_info *); + void (*symRetroSetControllerPortDevice)(unsigned, unsigned); - bool (*symRetroLoadGameSpecial)(unsigned, const retro_game_info *, size_t); + void (*symRetroReset)(); - void (*symRetroUnloadGame)(); + RetroRunFunc symRetroRun; - unsigned int (*symRetroGetRegion)(); + size_t (*symRetroSerializeSize)(); - void *(*symRetroGetMemoryData)(unsigned); + bool (*symRetroSerialize)(void *, size_t); - size_t (*symRetroGetMemoryDataSize)(unsigned); -}; + bool (*symRetroUnserialize)(const void *, size_t); + void (*symRetroCheatReset)(); + + void (*symRetroCheatSet)(unsigned, bool, const char *); + + bool (*symRetroLoadGame)(const retro_game_info *); + + bool (*symRetroLoadGameSpecial)(unsigned, const retro_game_info *, size_t); + + void (*symRetroUnloadGame)(); + + unsigned int (*symRetroGetRegion)(); + + void *(*symRetroGetMemoryData)(unsigned); + + size_t (*symRetroGetMemoryDataSize)(unsigned); + }; } // namespace libretro diff --git a/src/app/libretro/core_configuration.cpp b/src/app/libretro/core_configuration.cpp new file mode 100644 index 0000000..5531e5a --- /dev/null +++ b/src/app/libretro/core_configuration.cpp @@ -0,0 +1,94 @@ +#include "core_configuration.hpp" + +#include + +static bool firstAccess = true; + +void CoreConfiguration::registerOption(Option option) { + m_options.emplace(option.key, option); + + if (!m_defaultValues.contains(option.key)) { + setDefaultValue(option.key, option.defaultValueKey); + } +} + +bool CoreConfiguration::anyOptionValueHasChanged() { + const auto val = m_changedSinceLastChecked; + m_changedSinceLastChecked = false; + + return val; +} + +void CoreConfiguration::setDefaultValue(const std::string key, const std::string value) { + const auto opt = m_options.find(key); + if (opt == m_options.end()) { + return; + } + + for (auto &possibleValue: opt->second.possibleValues) { + if (possibleValue.key == value) { + m_defaultValues[key] = value; + m_changedSinceLastChecked = true; + break; + } + } +} + +std::optional +CoreConfiguration::getOptionValue(const std::string key) { + // if (firstAccess) { + // // print all keys and possible values + // for (const auto &option: m_options) { + // printf("Option %s (default: %s)\n", option.first.c_str(), option.second.defaultValueKey.c_str()); + // //for (const auto &possibleValue: option.second.possibleValues) { + // // printf(" %s\n", possibleValue.key.c_str()); + // //} + // } + // + // + // firstAccess = false; + // } + + printf("Getting value for key %s...", key.c_str()); + + if (!m_options.contains(key)) { + return std::nullopt; + } + + // First, check for values set by core + // Second, check for values set specifically for game + // Third, check for values set specifically for platformconst + // + auto value = m_gameValues.find(key); + if (value != m_gameValues.end()) { + printf("found game value %s\n", value->second.c_str()); + return {{value->second, value->second}}; + } + + value = m_platformValues.find(key); + if (value != m_platformValues.end()) { + printf("found platform value %s\n", value->second.c_str()); + return {{value->second, value->second}}; + } + + value = m_defaultValues.find(key); + if (value != m_defaultValues.end()) { + printf("found default value %s\n", value->second.c_str()); + return {{value->second, value->second}}; + } + + return std::nullopt; +} + +void CoreConfiguration::setOptionVisibility(std::string key, bool visible) { +} + +void CoreConfiguration::setPlatformValue(const std::string &key, const std::string &value) { + m_platformValues[key] = value; + m_changedSinceLastChecked = true; +} + +void CoreConfiguration::setGameValue(const std::string &key, const std::string &value) { + m_gameValues[key] = value; + m_changedSinceLastChecked = true; +} diff --git a/src/app/libretro/core_configuration.hpp b/src/app/libretro/core_configuration.hpp new file mode 100644 index 0000000..6ce3db0 --- /dev/null +++ b/src/app/libretro/core_configuration.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include + +class CoreConfiguration final : public firelight::libretro::IConfigurationProvider { +public: + CoreConfiguration() = default; + + virtual ~CoreConfiguration() = default; + + void registerOption(Option option) override; + + bool anyOptionValueHasChanged() override; + + void setDefaultValue(std::string key, std::string value) override; + + std::optional getOptionValue(std::string key) override; + + void setOptionVisibility(std::string key, bool visible) override; + + void setPlatformValue(const std::string &key, const std::string &value); + + void setGameValue(const std::string &key, const std::string &value); + +private: + std::map m_options; + bool m_changedSinceLastChecked = false; + + std::map m_defaultValues; + std::map m_platformValues; + std::map m_gameValues; +}; diff --git a/src/app/libretro/coreoption.cpp b/src/app/libretro/coreoption.cpp deleted file mode 100644 index 588a87f..0000000 --- a/src/app/libretro/coreoption.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "coreoption.hpp" - -namespace libretro { -CoreOption::CoreOption(retro_core_option_definition definition) { - this->key = definition.key; - this->defaultValue = definition.default_value; - this->description = definition.desc; - this->info = definition.info; - this->defaultValueIndex = 0; - this->currentValue = this->defaultValue; - - for (auto value : definition.values) { - if (definition.key == nullptr) { - break; - } - - this->values.push_back(value); - } -} - -CoreOption::CoreOption(retro_core_option_v2_definition definition) { - this->key = definition.key; - this->defaultValue = definition.default_value; - this->descCategorized = definition.desc_categorized; - this->description = definition.desc; - this->info = definition.info; - this->infoCategorized = definition.info_categorized; - this->categoryKey = definition.category_key; - this->defaultValueIndex = 0; - this->currentValue = this->defaultValue; - - for (auto value : definition.values) { - if (definition.key == nullptr) { - break; - } - - this->values.push_back(value); - } -} - -// json CoreOption::dumpJson() { -// json opt; -// -// if (this->key != nullptr) { -// opt["key"] = this->key; -// } -// -// if (this->currentValue != nullptr) { -// opt["currentValue"] = this->currentValue; -// } -// -// if (this->description != nullptr) { -// opt["description"] = this->description; -// } -// if (this->info != nullptr) { -// opt["info"] = this->info; -// } -// if (this->defaultValue != nullptr) { -// opt["defaultValue"] = this->defaultValue; -// } -// if (this->infoCategorized != nullptr) { -// opt["infoCategorized"] = this->infoCategorized; -// } -// if (this->categoryKey != nullptr) { -// opt["categoryKey"] = this->categoryKey; -// } -// -// opt["visible"] = this->displayToUser; -// -// return opt; -// } -} // namespace libretro \ No newline at end of file diff --git a/src/app/libretro/coreoption.hpp b/src/app/libretro/coreoption.hpp deleted file mode 100644 index e85a036..0000000 --- a/src/app/libretro/coreoption.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "libretro/libretro.h" -#include - -namespace libretro { - -class CoreOption { -public: - explicit CoreOption(retro_core_option_definition definition); - explicit CoreOption(retro_core_option_v2_definition definition); - - const char *currentValue; - bool displayToUser = true; - const char *key; - const char *description; - const char *descCategorized = nullptr; - const char *info; - const char *infoCategorized = nullptr; - const char *categoryKey = nullptr; - std::vector values; - const char *defaultValue; - int defaultValueIndex; -}; - -} // namespace libretro diff --git a/src/app/libretro/game.cpp b/src/app/libretro/game.cpp index 9d372d6..7a2d991 100644 --- a/src/app/libretro/game.cpp +++ b/src/app/libretro/game.cpp @@ -12,38 +12,38 @@ using std::vector; using std::string; namespace libretro { -Game::Game(const string &filePath, const std::vector &data) { - // this->path = filePath; - // this->path = - // R"(/c/Users/alexs/git/firelight/build/roms/6149 - Pokemon - Black Version 2 - // (U) (frieNDS).nds)"; - this->data = data; // todo -} + Game::Game(const string &filePath, const std::vector &data) { + this->path = filePath; + // this->path = + // R"(/c/Users/alexs/git/firelight/build/roms/6149 - Pokemon - Black Version 2 + // (U) (frieNDS).nds)"; + this->data = data; // todo + } -Game::Game(const string &filePath) { - this->path = filePath; + Game::Game(const string &filePath) { + this->path = filePath; - const auto path = std::filesystem::path(filePath); - const auto size = file_size(path); - std::ifstream file(path, std::ios::binary); + const auto path = std::filesystem::path(filePath); + const auto size = file_size(path); + std::ifstream file(path, std::ios::binary); - this->data.clear(); - this->data.resize(size); + this->data.clear(); + this->data.resize(size); - file.read(reinterpret_cast(this->data.data()), size); - file.close(); + file.read(reinterpret_cast(this->data.data()), size); + file.close(); - if (this->data.empty()) { - // TODO + if (this->data.empty()) { + // TODO + } } -} -string Game::getPath() { return this->path; } + string Game::getPath() { return this->path; } -void *Game::getData() { return &this->data[0]; } + void *Game::getData() { return &this->data[0]; } -size_t Game::getSize() { - printf("size: %llu\n", this->data.size()); - return this->data.size(); -} + size_t Game::getSize() { + printf("size: %llu\n", this->data.size()); + return this->data.size(); + } } // namespace libretro diff --git a/src/app/libretro/virtual_filesystem.hpp b/src/app/libretro/virtual_filesystem.hpp index ab62c56..55c3ef8 100644 --- a/src/app/libretro/virtual_filesystem.hpp +++ b/src/app/libretro/virtual_filesystem.hpp @@ -193,89 +193,105 @@ // namespace libretro::vfs { -static const char *get_path(retro_vfs_file_handle *stream) { - throw std::runtime_error("Not implemented"); - printf("Called get_path\n"); -} -static retro_vfs_file_handle *open(const char *path, unsigned mode, - unsigned hints) { - throw std::runtime_error("Not implemented"); - printf("Called open\n"); -} -static int close(retro_vfs_file_handle *stream) { - throw std::runtime_error("Not implemented"); - printf("Called close\n"); -} -static int64_t size(retro_vfs_file_handle *stream) { - throw std::runtime_error("Not implemented"); - printf("Called size\n"); -} -static int64_t tell(retro_vfs_file_handle *stream) { - throw std::runtime_error("Not implemented"); - printf("Called tell\n"); -} -static int64_t seek(retro_vfs_file_handle *stream, int64_t offset, - int seek_position) { - throw std::runtime_error("Not implemented"); - printf("Called seek\n"); -} -static int64_t read(retro_vfs_file_handle *stream, void *s, uint64_t len) { - throw std::runtime_error("Not implemented"); - printf("Called read\n"); -} -static int64_t write(retro_vfs_file_handle *stream, const void *s, - uint64_t len) { - throw std::runtime_error("Not implemented"); - printf("Called write\n"); -} -static int flush(retro_vfs_file_handle *stream) { - throw std::runtime_error("Not implemented"); - printf("Called flush\n"); -} -static int remove(const char *path) { - throw std::runtime_error("Not implemented"); - printf("Called remove\n"); -} -static int rename(const char *old_path, const char *new_path) { - throw std::runtime_error("Not implemented"); - printf("Called rename\n"); -} + static const char *get_path(retro_vfs_file_handle *stream) { + printf("Called get_path\n"); + throw std::runtime_error("Not implemented"); + } -/** V2 */ + static retro_vfs_file_handle *open(const char *path, unsigned mode, + unsigned hints) { + printf("Called open\n"); + throw std::runtime_error("Not implemented"); + } -static int64_t truncate(retro_vfs_file_handle *stream, int64_t length) { - throw std::runtime_error("Not implemented"); - printf("Called truncate\n"); -} + static int close(retro_vfs_file_handle *stream) { + printf("Called close\n"); + throw std::runtime_error("Not implemented"); + } -/** Below here is V3, not needed by any cores yet */ + static int64_t size(retro_vfs_file_handle *stream) { + printf("Called size\n"); + throw std::runtime_error("Not implemented"); + } -static int stat(const char *path, int32_t *size) { - throw std::runtime_error("Not implemented"); - printf("Called stat\n"); -} -static int mkdir(const char *dir) { - throw std::runtime_error("Not implemented"); - printf("Called mkdir\n"); -} -static retro_vfs_dir_handle *opendir(const char *dir, bool include_hidden) { - throw std::runtime_error("Not implemented"); - printf("Called opendir\n"); -} -static bool readdir(retro_vfs_dir_handle *dirstream) { - throw std::runtime_error("Not implemented"); - printf("Called readdir\n"); -} -static const char *dirent_get_name(retro_vfs_dir_handle *dirstream) { - throw std::runtime_error("Not implemented"); - printf("Called dirent_get_name\n"); -} -static bool dirent_is_dir(retro_vfs_dir_handle *dirstream) { - throw std::runtime_error("Not implemented"); - printf("Called dirent_is_dir\n"); -} -static int closedir(retro_vfs_dir_handle *dirstream) { - throw std::runtime_error("Not implemented"); - printf("Called closedir\n"); -} -} // namespace libretro::vfs \ No newline at end of file + static int64_t tell(retro_vfs_file_handle *stream) { + printf("Called tell\n"); + throw std::runtime_error("Not implemented"); + } + + static int64_t seek(retro_vfs_file_handle *stream, int64_t offset, + int seek_position) { + printf("Called seek\n"); + throw std::runtime_error("Not implemented"); + } + + static int64_t read(retro_vfs_file_handle *stream, void *s, uint64_t len) { + printf("Called read\n"); + throw std::runtime_error("Not implemented"); + } + + static int64_t write(retro_vfs_file_handle *stream, const void *s, + uint64_t len) { + printf("Called write\n"); + throw std::runtime_error("Not implemented"); + } + + static int flush(retro_vfs_file_handle *stream) { + printf("Called flush\n"); + throw std::runtime_error("Not implemented"); + } + + static int remove(const char *path) { + printf("Called remove\n"); + throw std::runtime_error("Not implemented"); + } + + static int rename(const char *old_path, const char *new_path) { + printf("Called rename\n"); + throw std::runtime_error("Not implemented"); + } + + /** V2 */ + + static int64_t truncate(retro_vfs_file_handle *stream, int64_t length) { + printf("Called truncate\n"); + throw std::runtime_error("Not implemented"); + } + + /** Below here is V3, not needed by any cores yet */ + + static int stat(const char *path, int32_t *size) { + printf("Called stat\n"); + throw std::runtime_error("Not implemented"); + } + + static int mkdir(const char *dir) { + printf("Called mkdir\n"); + throw std::runtime_error("Not implemented"); + } + + static retro_vfs_dir_handle *opendir(const char *dir, bool include_hidden) { + printf("Called opendir\n"); + throw std::runtime_error("Not implemented"); + } + + static bool readdir(retro_vfs_dir_handle *dirstream) { + printf("Called readdir\n"); + throw std::runtime_error("Not implemented"); + } + + static const char *dirent_get_name(retro_vfs_dir_handle *dirstream) { + printf("Called dirent_get_name\n"); + throw std::runtime_error("Not implemented"); + } + + static bool dirent_is_dir(retro_vfs_dir_handle *dirstream) { + printf("Called dirent_is_dir\n"); + throw std::runtime_error("Not implemented"); + } + + static int closedir(retro_vfs_dir_handle *dirstream) { + printf("Called closedir\n"); + throw std::runtime_error("Not implemented"); + } +} // namespace libretro::vfs diff --git a/src/app/manager_accessor.cpp b/src/app/manager_accessor.cpp index 0e9de93..95a1f88 100644 --- a/src/app/manager_accessor.cpp +++ b/src/app/manager_accessor.cpp @@ -1,52 +1,72 @@ #include "manager_accessor.hpp" -namespace firelight { +#include -Input::ControllerManager *ManagerAccessor::m_controllerManager; -saves::SaveManager *ManagerAccessor::m_saveManager; -LibraryScanner *ManagerAccessor::m_libraryManager; -db::IUserdataDatabase *ManagerAccessor::m_userdataDatabase; -db::ILibraryDatabase *ManagerAccessor::m_libraryDatabase; -achievements::RAClient *ManagerAccessor::m_achievementManager; +namespace firelight { + Input::ControllerManager *ManagerAccessor::m_controllerManager; + saves::SaveManager *ManagerAccessor::m_saveManager; + LibraryScanner *ManagerAccessor::m_libraryManager; + db::IUserdataDatabase *ManagerAccessor::m_userdataDatabase; + db::ILibraryDatabase *ManagerAccessor::m_libraryDatabase; + achievements::RAClient *ManagerAccessor::m_achievementManager; + std::shared_ptr ManagerAccessor::m_emulatorConfigManager; -void ManagerAccessor::setControllerManager( + void ManagerAccessor::setControllerManager( Input::ControllerManager *t_manager) { - m_controllerManager = t_manager; -} -void ManagerAccessor::setSaveManager(saves::SaveManager *t_manager) { - m_saveManager = t_manager; -} -void ManagerAccessor::setLibraryManager(LibraryScanner *t_libraryManager) { - m_libraryManager = t_libraryManager; -} -void ManagerAccessor::setUserdataManager( + m_controllerManager = t_manager; + } + + void ManagerAccessor::setSaveManager(saves::SaveManager *t_manager) { + m_saveManager = t_manager; + } + + void ManagerAccessor::setLibraryManager(LibraryScanner *t_libraryManager) { + m_libraryManager = t_libraryManager; + } + + void ManagerAccessor::setUserdataManager( db::IUserdataDatabase *t_userdataManager) { - m_userdataDatabase = t_userdataManager; -} -void ManagerAccessor::setLibraryDatabase( + m_userdataDatabase = t_userdataManager; + } + + void ManagerAccessor::setLibraryDatabase( db::ILibraryDatabase *t_libraryDatabase) { - m_libraryDatabase = t_libraryDatabase; -} -void ManagerAccessor::setAchievementManager( + m_libraryDatabase = t_libraryDatabase; + } + + void ManagerAccessor::setAchievementManager( achievements::RAClient *t_achievementManager) { - m_achievementManager = t_achievementManager; -} - -Input::ControllerManager *ManagerAccessor::getControllerManager() { - // TODO: Check for nullptr and throw exception or something - return m_controllerManager; -} -saves::SaveManager *ManagerAccessor::getSaveManager() { return m_saveManager; } -LibraryScanner *ManagerAccessor::getLibraryManager() { - return m_libraryManager; -} -db::IUserdataDatabase *ManagerAccessor::getUserdataManager() { - return m_userdataDatabase; -} -db::ILibraryDatabase *ManagerAccessor::getLibraryDatabase() { - return m_libraryDatabase; -} -achievements::RAClient *ManagerAccessor::getAchievementManager() { - return m_achievementManager; -} + m_achievementManager = t_achievementManager; + } + + void ManagerAccessor::setEmulatorConfigManager(std::shared_ptr t_emulatorConfigManager) { + m_emulatorConfigManager = std::move(t_emulatorConfigManager); + } + + Input::ControllerManager *ManagerAccessor::getControllerManager() { + // TODO: Check for nullptr and throw exception or something + return m_controllerManager; + } + + saves::SaveManager *ManagerAccessor::getSaveManager() { return m_saveManager; } + + LibraryScanner *ManagerAccessor::getLibraryManager() { + return m_libraryManager; + } + + db::IUserdataDatabase *ManagerAccessor::getUserdataManager() { + return m_userdataDatabase; + } + + db::ILibraryDatabase *ManagerAccessor::getLibraryDatabase() { + return m_libraryDatabase; + } + + achievements::RAClient *ManagerAccessor::getAchievementManager() { + return m_achievementManager; + } + + std::shared_ptr ManagerAccessor::getEmulatorConfigManager() { + return m_emulatorConfigManager; + } } // namespace firelight diff --git a/src/app/manager_accessor.hpp b/src/app/manager_accessor.hpp index 27a73d9..21e87ce 100644 --- a/src/app/manager_accessor.hpp +++ b/src/app/manager_accessor.hpp @@ -1,5 +1,6 @@ #pragma once +#include "emulator_config_manager.hpp" #include "achieve/ra_client.hpp" #include "firelight/userdata_database.hpp" #include "input/controller_manager.hpp" @@ -7,31 +8,45 @@ #include "saves/save_manager.hpp" namespace firelight { + class ManagerAccessor { + public: + static void setControllerManager(Input::ControllerManager *t_manager); -class ManagerAccessor { -public: - static void setControllerManager(Input::ControllerManager *t_manager); - static void setSaveManager(saves::SaveManager *t_manager); - static void setLibraryManager(LibraryScanner *t_libraryManager); - static void setUserdataManager(db::IUserdataDatabase *t_userdataManager); - static void setLibraryDatabase(db::ILibraryDatabase *t_libraryDatabase); - static void - setAchievementManager(achievements::RAClient *t_achievementManager); - - static Input::ControllerManager *getControllerManager(); - static saves::SaveManager *getSaveManager(); - static LibraryScanner *getLibraryManager(); - static db::IUserdataDatabase *getUserdataManager(); - static db::ILibraryDatabase *getLibraryDatabase(); - static achievements::RAClient *getAchievementManager(); - -private: - static Input::ControllerManager *m_controllerManager; - static saves::SaveManager *m_saveManager; - static LibraryScanner *m_libraryManager; - static db::IUserdataDatabase *m_userdataDatabase; - static db::ILibraryDatabase *m_libraryDatabase; - static achievements::RAClient *m_achievementManager; -}; + static void setSaveManager(saves::SaveManager *t_manager); + static void setLibraryManager(LibraryScanner *t_libraryManager); + + static void setUserdataManager(db::IUserdataDatabase *t_userdataManager); + + static void setLibraryDatabase(db::ILibraryDatabase *t_libraryDatabase); + + static void + setAchievementManager(achievements::RAClient *t_achievementManager); + + static void setEmulatorConfigManager( + std::shared_ptr t_emulatorConfigManager); + + static Input::ControllerManager *getControllerManager(); + + static saves::SaveManager *getSaveManager(); + + static LibraryScanner *getLibraryManager(); + + static db::IUserdataDatabase *getUserdataManager(); + + static db::ILibraryDatabase *getLibraryDatabase(); + + static achievements::RAClient *getAchievementManager(); + + static std::shared_ptr getEmulatorConfigManager(); + + private: + static Input::ControllerManager *m_controllerManager; + static saves::SaveManager *m_saveManager; + static LibraryScanner *m_libraryManager; + static db::IUserdataDatabase *m_userdataDatabase; + static db::ILibraryDatabase *m_libraryDatabase; + static achievements::RAClient *m_achievementManager; + static std::shared_ptr m_emulatorConfigManager; + }; } // namespace firelight diff --git a/src/app/platform_metadata.hpp b/src/app/platform_metadata.hpp new file mode 100644 index 0000000..2e8c881 --- /dev/null +++ b/src/app/platform_metadata.hpp @@ -0,0 +1,263 @@ +#pragma once + +namespace firelight { + const static std::map > defaultPlatformValues = { + { + // GB + 1, { + {"gambatte_gbc_color_correction", "GBC only"}, + {"gambatte_gbc_color_correction_mode", "accurate"}, + {"gambatte_gbc_frontlight_position", "central"}, + {"gambatte_dark_filter_level", "0"}, + {"gambatte_up_down_allowed", "disabled"}, + {"gambatte_mix_frames", "disabled"}, + {"gambatte_gb_colorization", "auto"}, + {"gambatte_gb_internal_palette", "GB - DMG"}, + {"gambatte_gb_bootloader", "disabled"}, + {"gambatte_gb_hwmode", "auto"} + } + }, + // GBC + { + 2, { + {"gambatte_gbc_color_correction", "GBC only"}, + {"gambatte_gbc_color_correction_mode", "accurate"}, + {"gambatte_gbc_frontlight_position", "central"}, + {"gambatte_dark_filter_level", "0"}, + {"gambatte_up_down_allowed", "disabled"}, + {"gambatte_mix_frames", "disabled"}, + {"gambatte_gb_colorization", "auto"}, + {"gambatte_gb_internal_palette", "GB - DMG"}, + {"gambatte_gb_bootloader", "disabled"}, + {"gambatte_gb_hwmode", "auto"} + } + }, + // GBA + { + 3, { + {"mgba_solar_sensor_level", "0"}, + {"mgba_gb_model", "Autodetect"}, + {"mgba_sgb_borders", "ON"}, + {"mgba_gb_colors_preset", "0"}, + {"mgba_gb_colors", "Grayscale"}, + {"mgba_use_bios", "OFF"}, + {"mgba_skip_bios", "ON"}, + {"mgba_sgb_borders", "ON"}, + {"mgba_frameskip", "disabled"}, + {"mgba_frameskip_threshold", "33"}, + {"mgba_frameskip_interval", "0"}, + {"mgba_audio_low_pass_filter", "disabled"}, + {"mgba_audio_low_pass_range", "60"}, + {"mgba_idle_optimization", "Remove Known"}, + {"mgba_force_gbp", "OFF"}, + {"mgba_color_correction", "OFF"}, + {"mgba_interframe_blending", "mix_smart"} + } + }, + // NES + { + 5, { + {"fceumm_apu_1", "enabled"}, + {"fceumm_apu_2", "enabled"}, + {"fceumm_apu_3", "enabled"}, + {"fceumm_apu_4", "enabled"}, + {"fceumm_apu_5", "enabled"}, + {"fceumm_arkanoid_mode", "mouse"}, + {"fceumm_aspect", "8:7 PAR"}, // TODO: Pixel perfect? + {"fceumm_game_genie", "disabled"}, + {"fceumm_mouse_sensitivity", "100"}, + {"fceumm_nospritelimit", "disabled"}, + {"fceumm_ntsc_filter", "disabled"}, + {"fceumm_overclocking", "disabled"}, + {"fceumm_overscan_h_left", "0"}, + {"fceumm_overscan_h_right", "0"}, + {"fceumm_overscan_v_bottom", "8"}, + {"fceumm_overscan_v_top", "8"}, + {"fceumm_palette", "default"}, + {"fceumm_ramstate", "$ff"}, + {"fceumm_show_adv_sound_options", "disabled"}, + {"fceumm_show_adv_system_options", "disabled"}, + {"fceumm_show_crosshair", "enabled"}, + {"fceumm_sndlowpass", "disabled"}, + {"fceumm_sndquality", "Very High"}, + {"fceumm_sndstereodelay", "15_ms_delay"}, + {"fceumm_sndvolume", "7"}, + {"fceumm_swapduty", "disabled"}, + {"fceumm_turbo_delay", "3"}, + {"fceumm_turbo_enable", "None"}, + {"fceumm_up_down_allowed", "disabled"}, + {"fceumm_zapper_mode", "clightgun"}, + {"fceumm_zapper_sensor", "enabled"}, + {"fceumm_zapper_tolerance", "6"}, + {"fceumm_zapper_trigger", "enabled"}, + {"fceumm_up_down_allowed", "disabled"} + } + }, + // SNES + { + 6, { + {"snes9x_hires_blend", "disabled"}, + {"snes9x_overclock_superfx", "100%"}, + {"snes9x_up_down_allowed", "disabled"}, + {"snes9x_sndchan_1", "enabled"}, + {"snes9x_sndchan_2", "enabled"}, + {"snes9x_sndchan_3", "enabled"}, + {"snes9x_sndchan_4", "enabled"}, + {"snes9x_sndchan_5", "enabled"}, + {"snes9x_sndchan_6", "enabled"}, + {"snes9x_sndchan_7", "enabled"}, + {"snes9x_sndchan_8", "enabled"}, + {"snes9x_layer_1", "enabled"}, + {"snes9x_layer_2", "enabled"}, + {"snes9x_layer_3", "enabled"}, + {"snes9x_layer_4", "enabled"}, + {"snes9x_layer_5", "enabled"}, + {"snes9x_gfx_clip", "enabled"}, + {"snes9x_gfx_transp", "enabled"}, + {"snes9x_audio_interpolation", "gaussian"}, + {"snes9x_overclock_cycles", "disabled"}, + {"snes9x_reduce_sprite_flicker", "disabled"}, + {"snes9x_randomize_memory", "disabled"}, + {"snes9x_overscan", "enabled"}, + {"snes9x_aspect", "4:3"}, + {"snes9x_region", "auto"}, + {"snes9x_lightgun_mode", "Lightgun"}, + {"snes9x_superscope_reverse_buttons", "disabled"}, + {"snes9x_superscope_crosshair", "2"}, + {"snes9x_superscope_color", "White"}, + {"snes9x_justifier1_crosshair", "4"}, + {"snes9x_justifier1_color", "Blue"}, + {"snes9x_justifier2_crosshair", "4"}, + {"snes9x_justifier2_color", "Pink"}, + {"snes9x_rifle_crosshair", "2"}, + {"snes9x_rifle_color", "White"}, + {"snes9x_block_invalid_vram_access", "enabled"}, + {"snes9x_echo_buffer_hack", "disabled"}, + {"snes9x_blargg", "disabled"} + } + }, + // N64 + { + 7, { + {"mupen64plus-169screensize", "960x540"}, + {"mupen64plus-43screensize", "640x480"}, + {"mupen64plus-BackgroundMode", "OnePiece"}, + {"mupen64plus-BilinearMode", "standard"}, + {"mupen64plus-CorrectTexrectCoords", "Off"}, + {"mupen64plus-CountPerOp", "0"}, + {"mupen64plus-DitheringPattern", "False"}, + {"mupen64plus-DitheringQuantization", "False"}, + {"mupen64plus-EnableCopyAuxToRDRAM", "False"}, + {"mupen64plus-EnableCopyColorToRDRAM", "Async"}, + {"mupen64plus-EnableCopyDepthToRDRAM", "Software"}, + {"mupen64plus-EnableEnhancedHighResStorage", "False"}, + {"mupen64plus-EnableEnhancedTextureStorage", "False"}, + {"mupen64plus-EnableFBEmulation", "True"}, + {"mupen64plus-EnableFragmentDepthWrite", "True"}, + {"mupen64plus-EnableHWLighting", "False"}, + {"mupen64plus-EnableHiResAltCRC", "False"}, + {"mupen64plus-EnableLODEmulation", "True"}, + {"mupen64plus-EnableLegacyBlending", "False"}, + {"mupen64plus-EnableN64DepthCompare", "False"}, + {"mupen64plus-EnableNativeResFactor", "0"}, + {"mupen64plus-EnableNativeResTexrects", "Disabled"}, + {"mupen64plus-EnableOverscan", "Enabled"}, + {"mupen64plus-EnableShadersStorage", "True"}, + {"mupen64plus-EnableTexCoordBounds", "False"}, + {"mupen64plus-EnableTextureCache", "True"}, + {"mupen64plus-FXAA", "0"}, + {"mupen64plus-ForceDisableExtraMem", "False"}, + {"mupen64plus-FrameDuping", "False"}, + {"mupen64plus-Framerate", "Original"}, + {"mupen64plus-GLideN64IniBehaviour", "late"}, + {"mupen64plus-HybridFilter", "True"}, + {"mupen64plus-IgnoreTLBExceptions", "False"}, + {"mupen64plus-MaxTxCacheSize", "8000"}, + {"mupen64plus-MultiSampling", "0"}, + {"mupen64plus-OverscanBottom", "0"}, + {"mupen64plus-OverscanLeft", "0"}, + {"mupen64plus-OverscanRight", "0"}, + {"mupen64plus-OverscanTop", "0"}, + {"mupen64plus-RDRAMImageDitheringMode", "False"}, + {"mupen64plus-ThreadedRenderer", "False"}, + {"mupen64plus-alt-map", "False"}, + {"mupen64plus-angrylion-multithread", "all threads"}, + {"mupen64plus-angrylion-overscan", "disabled"}, + {"mupen64plus-angrylion-sync", "Low"}, + {"mupen64plus-angrylion-vioverlay", "Filtered"}, + {"mupen64plus-aspect", "4:3"}, + {"mupen64plus-astick-deadzone", "15"}, + {"mupen64plus-astick-sensitivity", "100"}, + {"mupen64plus-cpucore", "dynamic_recompiler"}, + {"mupen64plus-d-cbutton", "C3"}, + {"mupen64plus-l-cbutton", "C2"}, + {"mupen64plus-pak1", "memory"}, + {"mupen64plus-pak2", "none"}, + {"mupen64plus-pak3", "none"}, + {"mupen64plus-pak4", "none"}, + {"mupen64plus-parallel-rdp-deinterlace-method", "Bob"}, + {"mupen64plus-parallel-rdp-dither-filter", "True"}, + {"mupen64plus-parallel-rdp-divot-filter", "True"}, + {"mupen64plus-parallel-rdp-downscaling", "disable"}, + {"mupen64plus-parallel-rdp-gamma-dither", "True"}, + {"mupen64plus-parallel-rdp-native-tex-rect", "True"}, + {"mupen64plus-parallel-rdp-native-texture-lod", "False"}, + {"mupen64plus-parallel-rdp-overscan", "0"}, + {"mupen64plus-parallel-rdp-super-sampled-read-back", "False"}, + {"mupen64plus-parallel-rdp-super-sampled-read-back-dither", "True"}, + {"mupen64plus-parallel-rdp-synchronous", "True"}, + {"mupen64plus-parallel-rdp-upscaling", "1x"}, + {"mupen64plus-parallel-rdp-vi-aa", "True"}, + {"mupen64plus-parallel-rdp-vi-bilinear", "True"}, + {"mupen64plus-r-cbutton", "C1"}, + {"mupen64plus-rdp-plugin", "gliden64"}, + {"mupen64plus-rsp-plugin", "hle"}, + {"mupen64plus-txCacheCompression", "True"}, + {"mupen64plus-txEnhancementMode", "None"}, + {"mupen64plus-txFilterIgnoreBG", "True"}, + {"mupen64plus-txFilterMode", "None"}, + {"mupen64plus-txHiresEnable", "False"}, + {"mupen64plus-txHiresFullAlphaChannel", "False"}, + {"mupen64plus-u-cbutton", "C4"}, + {"mupen64plus-virefresh", "Auto"} + } + } + }; + + class PlatformMetadata { + public: + static std::optional getDefaultConfigValue(int platformId, std::string key) { + if (defaultPlatformValues.find(platformId) == defaultPlatformValues.end()) { + return std::nullopt; + } + + if (defaultPlatformValues.at(platformId).find(key) == defaultPlatformValues.at(platformId).end()) { + return std::nullopt; + } + + return defaultPlatformValues.at(platformId).at(key); + } + + static std::string getCoreDllPath(const int platformId) { + switch (platformId) { + case 1: + case 2: + return "./system/_cores/gambatte_libretro.dll"; + case 3: + return "./system/_cores/mgba_libretro.dll"; + case 5: + return "./system/_cores/fceumm_libretro.dll"; + case 6: + return "./system/_cores/snes9x_libretro.dll"; + case 7: + return "./system/_cores/mupen64plus_next_libretro.dll"; + case 10: + return "./system/_cores/melondsds_libretro.dll"; + case 13: + return "./system/_cores/genesis_plus_gx_libretro.dll"; + default: + return ""; + } + } + }; +} // firelight diff --git a/src/app/saves/save_manager.cpp b/src/app/saves/save_manager.cpp index 4f05742..2544213 100644 --- a/src/app/saves/save_manager.cpp +++ b/src/app/saves/save_manager.cpp @@ -9,109 +9,113 @@ #include namespace firelight::saves { -SaveManager::SaveManager(std::filesystem::path saveDir, - db::IUserdataDatabase &userdataDatabase) + SaveManager::SaveManager(std::filesystem::path saveDir, + db::IUserdataDatabase &userdataDatabase) : m_userdataDatabase(userdataDatabase), m_saveDir(std::move(saveDir)) { - m_ioThreadPool = std::make_unique(); - m_ioThreadPool->setMaxThreadCount(1); -} - -QFuture SaveManager::writeSaveDataForEntry(db::LibraryEntry &entry, - Savefile &saveData) const { - return QtConcurrent::run([this, entry, saveData] { - // TODO: Add some verification that the metadata is correct - // TODO: Save file could have been deleted, etc - auto slot = entry.activeSaveSlot; + m_ioThreadPool = std::make_unique(); + m_ioThreadPool->setMaxThreadCount(1); + } - auto exists = false; - db::SavefileMetadata metadata; + QFuture SaveManager::writeSaveDataForEntry(db::LibraryEntry &entry, + Savefile &saveData) const { + return QtConcurrent::run([this, entry, saveData] { + // TODO: Add some verification that the metadata is correct + // TODO: Save file could have been deleted, etc + auto slot = entry.activeSaveSlot; - auto metadataOpt = - m_userdataDatabase.getSavefileMetadata(entry.contentId, slot); + auto exists = false; + db::SavefileMetadata metadata; - if (metadataOpt.has_value()) { - metadata = metadataOpt.value(); - exists = true; - } else { - metadata.contentId = entry.contentId; - metadata.slotNumber = slot; - } + auto metadataOpt = + m_userdataDatabase.getSavefileMetadata(entry.contentId, slot); - const auto bytes = saveData.getSaveRamData(); + if (metadataOpt.has_value()) { + // printf("Metadata exists\n"); + metadata = metadataOpt.value(); + exists = true; + } else { + // printf("Metadata DOES NOT exist\n"); + metadata.contentId = entry.contentId; + metadata.slotNumber = slot; + } - auto savefileMd5 = calculateMD5(bytes); + const auto bytes = saveData.getSaveRamData(); - if (metadata.savefileMd5 == savefileMd5) { - return true; - } + auto savefileMd5 = calculateMD5(bytes); - spdlog::info("Writing updated savefile for {} slot {}", entry.contentId, - slot); - metadata.savefileMd5 = savefileMd5; + if (metadata.savefileMd5 == savefileMd5) { + return true; + } - const auto directory = - m_saveDir / metadata.contentId / ("slot" + std::to_string(slot)); - create_directories(directory); + spdlog::info("Writing updated savefile for {} slot {}", entry.contentId, + slot); + metadata.savefileMd5 = savefileMd5; - const auto tempSaveFile = directory / "savefile.srm.tmp"; - const auto saveFile = directory / "savefile.srm"; + const auto directory = + m_saveDir / metadata.contentId / ("slot" + std::to_string(slot)); + create_directories(directory); - std::ofstream saveFileStream(tempSaveFile, std::ios::binary); - saveFileStream.write(bytes.data(), bytes.size()); - saveFileStream.close(); + const auto tempSaveFile = directory / "savefile.srm.tmp"; + const auto saveFile = directory / "savefile.srm"; - std::filesystem::rename(tempSaveFile, saveFile); + std::ofstream saveFileStream(tempSaveFile, std::ios::binary); + saveFileStream.write(bytes.data(), bytes.size()); + saveFileStream.close(); - auto timestamp = 0; - metadata.lastModifiedAt = timestamp; + std::filesystem::rename(tempSaveFile, saveFile); - if (exists) { - m_userdataDatabase.updateSavefileMetadata(metadata); - } else { - m_userdataDatabase.createSavefileMetadata(metadata); - } + auto timestamp = 0; + metadata.lastModifiedAt = timestamp; - // if (image != nullptr) { - // } - // const auto screenshotFile = directory / "screenshot.png"; - // if (!image.save(QString::fromStdString(screenshotFile.string()))) { - // spdlog::warn("Unable to save screenshot for entry {}", md5); - // } - - // spdlog::info("writing save data for entry {}", md5); - return true; - }); -} - -std::optional -SaveManager::readSaveDataForEntry(db::LibraryEntry &entry) const { - auto slot = entry.activeSaveSlot; - - const auto directory = - m_saveDir / entry.contentId / ("slot" + std::to_string(slot)); - if (!exists(directory)) { - return std::nullopt; - } + if (exists) { + m_userdataDatabase.updateSavefileMetadata(metadata); + } else { + m_userdataDatabase.createSavefileMetadata(metadata); + } - const auto saveFile = directory / "savefile.srm"; + // if (image != nullptr) { + // } + // const auto screenshotFile = directory / "screenshot.png"; + // if (!image.save(QString::fromStdString(screenshotFile.string()))) { + // spdlog::warn("Unable to save screenshot for entry {}", md5); + // } - if (!exists(saveFile)) { - return std::nullopt; + // spdlog::info("writing save data for entry {}", md5); + return true; + }); } - std::ifstream saveFileStream(saveFile, std::ios::binary); + std::optional + SaveManager::readSaveDataForEntry(db::LibraryEntry &entry) const { + auto slot = entry.activeSaveSlot; - auto size = file_size(saveFile); + const auto directory = + m_saveDir / entry.contentId / ("slot" + std::to_string(slot)); + if (!exists(directory)) { + return std::nullopt; + } + + const auto saveFile = directory / "savefile.srm"; + + printf("Reading from save file: %s\n", saveFile.string().c_str()); - std::vector data(size); + if (!exists(saveFile)) { + return std::nullopt; + } - saveFileStream.read(data.data(), size); - saveFileStream.close(); + std::ifstream saveFileStream(saveFile, std::ios::binary); - auto fileContents = std::vector(data.data(), data.data() + size); + auto size = file_size(saveFile); - Savefile saveData(fileContents); + std::vector data(size); - return {saveData}; -} + saveFileStream.read(data.data(), size); + saveFileStream.close(); + + auto fileContents = std::vector(data.data(), data.data() + size); + + Savefile saveData(fileContents); + + return {saveData}; + } } // namespace firelight::saves diff --git a/src/gui/controller_list_model.cpp b/src/gui/controller_list_model.cpp index 0204d5d..ed7064f 100644 --- a/src/gui/controller_list_model.cpp +++ b/src/gui/controller_list_model.cpp @@ -65,7 +65,7 @@ namespace firelight::gui { imageUrl = "file:system/_img/Xbox.svg"; break; case NINTENDO_SWITCH_PRO: - imageUrl = "file:system/_img/SwitchPro.svg"; + imageUrl = "qrc:/images/platform-icons/switch-pro.svg"; break; case NINTENDO_NSO_N64: imageUrl = "file:system/_img/N64.svg"; diff --git a/src/gui/platform_list_model.cpp b/src/gui/platform_list_model.cpp index 9b6e165..b2b7b56 100644 --- a/src/gui/platform_list_model.cpp +++ b/src/gui/platform_list_model.cpp @@ -10,7 +10,7 @@ namespace firelight::gui { m_items.push_back({ 0, "NES", - "file:system/_img/nes.svg", + "qrc:images/platform-icons/nes.svg", { { {"display_name", "A"}, @@ -58,7 +58,7 @@ namespace firelight::gui { m_items.push_back({ 1, "SNES", - "file:system/_img/SNES.svg", + "qrc:images/platform-icons/snes.svg", { { {"display_name", "A"}, @@ -126,7 +126,7 @@ namespace firelight::gui { m_items.push_back({ 2, "Game Boy", - "file:system/_img/gb.svg", + "qrc:images/platform-icons/gb.svg", { { {"display_name", "A"}, @@ -174,7 +174,7 @@ namespace firelight::gui { m_items.push_back({ 3, "Game Boy Color", - "file:system/_img/gbc.svg", + "qrc:images/platform-icons/gbc.svg", { { {"display_name", "A"}, @@ -222,7 +222,7 @@ namespace firelight::gui { m_items.push_back({ 4, "Game Boy Advance", - "file:system/_img/gba.svg", + "qrc:images/platform-icons/gba.svg", { { {"display_name", "A"}, @@ -280,7 +280,7 @@ namespace firelight::gui { m_items.push_back({ 5, "Nintendo 64", - "file:system/_img/N64.svg", + "qrc:images/platform-icons/n64.svg", { { {"display_name", "A"}, diff --git a/src/main.cpp b/src/main.cpp index f1cfb08..3707ba6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,12 +11,12 @@ #include #include +#include #include "app/achieve/ra_client.hpp" #include "app/db/sqlite_content_database.hpp" #include "app/db/sqlite_userdata_database.hpp" #include "app/emulation_manager.hpp" -#include "app/fps_multiplier.hpp" #include "app/router.hpp" #include "app/input/controller_manager.hpp" #include "app/input/sdl_event_loop.hpp" @@ -48,6 +48,8 @@ bool create_dirs(const std::initializer_list list) { } int main(int argc, char *argv[]) { + SDL_setenv("QT_QUICK_FLICKABLE_WHEEL_DECELERATION", "5000", true); + if (auto debug = std::getenv("FL_DEBUG"); debug != nullptr) { spdlog::set_level(spdlog::level::debug); } else { @@ -58,6 +60,11 @@ int main(int argc, char *argv[]) { QApplication::setApplicationName("Firelight"); QApplication::setAttribute(Qt::AA_UseDesktopOpenGL); + QSurfaceFormat format; + format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); + format.setVersion(4, 1); + QSurfaceFormat::setDefaultFormat(format); + QApplication app(argc, argv); // TODO: @@ -75,6 +82,11 @@ int main(int argc, char *argv[]) { // userdata db // controller profiles // library db + + auto docPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + + printf("Documents Path: %s\n", docPath.toStdString().c_str()); + auto appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); @@ -100,13 +112,10 @@ int main(int argc, char *argv[]) { } firelight::Input::ControllerManager controllerManager; - firelight::SdlEventLoop sdlEventLoop(&controllerManager); firelight::ManagerAccessor::setControllerManager(&controllerManager); controllerManager.refreshControllerList(); - - sdlEventLoop.start(); QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); firelight::db::SqliteUserdataDatabase userdata_database(appDataDir / @@ -151,6 +160,9 @@ int main(int argc, char *argv[]) { LibraryScanner libraryManager(&libraryDatabase, &contentDatabase); firelight::ManagerAccessor::setLibraryManager(&libraryManager); + auto emulatorConfigManager = std::make_shared(userdata_database); + firelight::ManagerAccessor::setEmulatorConfigManager(emulatorConfigManager); + QObject::connect( &libraryDatabase, &firelight::db::SqliteLibraryDatabase::contentDirectoriesUpdated, @@ -164,7 +176,6 @@ int main(int argc, char *argv[]) { // qRegisterMetaType("GamepadMapping"); qmlRegisterType("Firelight", 1, 0, "EmulatorView"); - qmlRegisterType("Firelight", 1, 0, "FpsMultiplier"); qmlRegisterType("Firelight", 1, 0, "GamepadMapping"); qmlRegisterType("Firelight", 1, 0, "GamepadProfile"); @@ -172,6 +183,7 @@ int main(int argc, char *argv[]) { QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("Router", &router); + engine.rootContext()->setContextProperty("emulator_config_manager", emulatorConfigManager.get()); engine.rootContext()->setContextProperty("achievement_manager", &raClient); engine.rootContext()->setContextProperty("playlist_model", &playlistModel); engine.rootContext()->setContextProperty("library_model", &libModel); @@ -196,12 +208,16 @@ int main(int argc, char *argv[]) { QObject::connect( &engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); - engine.loadFromModule("QMLFirelight", "Main"); + engine.loadFromModule("QMLFirelight", "Main2"); QObject *rootObject = engine.rootObjects().value(0); auto window = qobject_cast(rootObject); window->installEventFilter(resizeHandler); + + firelight::SdlEventLoop sdlEventLoop(window, &controllerManager); + sdlEventLoop.start(); + int exitCode = QApplication::exec(); sdlEventLoop.stopProcessing(); diff --git a/tests/qml_main.cpp b/tests/qml_main.cpp index fe3a69a..1aef2bb 100644 --- a/tests/qml_main.cpp +++ b/tests/qml_main.cpp @@ -1,2 +1,33 @@ +// src_qmltest_qquicktest.cpp #include -QUICK_TEST_MAIN(example) \ No newline at end of file +#include +#include +#include +#include + +class Setup : public QObject { + Q_OBJECT + +public: + Setup() { + } + +public slots: + void applicationAvailable() { + // Initialization that only requires the QGuiApplication object to be available + QQuickStyle::setStyle("Basic"); + } + + void qmlEngineAvailable(QQmlEngine *engine) { + // Initialization requiring the QQmlEngine to be constructed + // engine->rootContext()->setContextProperty("myContextProperty", QVariant(true)); + } + + void cleanupTestCase() { + // Implement custom resource cleanup + } +}; + +QUICK_TEST_MAIN_WITH_SETUP(Firelight, Setup) + +#include "qml_main.moc"