diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index c7cbdb053..1e6c9fe05 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -20,13 +20,13 @@ jobs: - name: macOS os: macos-14 - deps_cmdline: brew install libvpx + deps_cmdline: brew install libvpx molten-vk vulkan-volk build_type: Release - name: macOS os: macos-14 extra_options: -G Xcode -DDYN_OPENAL=OFF - deps_cmdline: brew install libvpx + deps_cmdline: brew install libvpx molten-vk vulkan-volk build_type: Debug - name: Linux GCC 9 # oldest version available @@ -46,6 +46,7 @@ jobs: extra_options: -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ deps_cmdline: sudo apt-get update && sudo apt-get install libsdl2-dev libvpx-dev libwebp-dev build_type: Release + export_appimage: true - name: Linux Clang 11 # oldest version available os: ubuntu-22.04 @@ -58,12 +59,14 @@ jobs: extra_options: -DCMAKE_C_COMPILER=clang-15 -DCMAKE_CXX_COMPILER=clang++-15 deps_cmdline: sudo apt-get update && sudo apt-get install clang-15 libsdl2-dev libvpx-dev libwebp-dev build_type: Release + export_appimage: true - name: Linux Clang Latest # rolling default, not actually latest os: ubuntu-latest extra_options: -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ deps_cmdline: sudo apt-get update && sudo apt-get install libsdl2-dev libvpx-dev libwebp-dev build_type: Release + export_appimage: true steps: - uses: actions/checkout@v4 @@ -113,7 +116,6 @@ jobs: fi - name: Upload Package - if: runner.os == 'Windows' || matrix.config.name == 'Linux GCC 12' uses: actions/upload-artifact@v4 with: path: build/package diff --git a/.gitignore b/.gitignore index d8d361a3c..8fea570f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # build artifacts /build* /wadsrc/*.pk3 -/appimage-build .flatpak-builder/ *.AppImage *.flatpak @@ -21,6 +20,7 @@ fmodapi*linux/ .vscode .cache *.kate-swp +*.geany # private dev files *.user @@ -40,5 +40,4 @@ fmodapi*linux/ /llvm /src/gl/unused /mapfiles_release/*.map -/AppDir .cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 84b2706d1..55bcfa81a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,7 +316,9 @@ if (HAVE_VULKAN) add_subdirectory( libraries/ZVulkan ) endif() +set(ZWIDGET_BUILD_EXAMPLE OFF CACHE INTERNAL "" FORCE) add_subdirectory( libraries/ZWidget ) + add_subdirectory( libraries/webp ) add_subdirectory( libraries/discordrpc EXCLUDE_FROM_ALL ) diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake index 02c6bba06..b2399ee28 100644 --- a/cmake/FindSDL2.cmake +++ b/cmake/FindSDL2.cmake @@ -20,7 +20,6 @@ # this module will try to find on your behalf.) Also for OS X, this # module will automatically add the -framework Cocoa on your behalf. # -# # Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration # and no SDL2_LIBRARY, it means CMake did not find your SDL2 library # (SDL2.dll, libsdl2.so, SDL2.framework, etc). @@ -29,7 +28,6 @@ # as appropriate. These values are used to generate the final SDL2_LIBRARY # variable, but when these values are unset, SDL2_LIBRARY does not get created. # -# # $SDL2DIR is an environment variable that would # correspond to the ./configure --prefix=$SDL2DIR # used in building SDL2. @@ -50,30 +48,26 @@ # SDL2_LIBRARY to override this selection or set the CMake environment # CMAKE_INCLUDE_PATH to modify the search paths. # -# Note that the header path has changed from SDL2/SDL.h to just SDL.h -# This needed to change because "proper" SDL2 convention -# is #include "SDL.h", not . This is done for portability -# reasons because not all systems place things in SDL2/ (see FreeBSD). -# # Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake # module with the minor edit of changing "SDL" to "SDL2" where necessary. This # was not created for redistribution, and exists temporarily pending official # SDL2 CMake modules. -FIND_PATH(SDL2_INCLUDE_DIR SDL.h +FIND_PATH(SDL2_INCLUDE_DIR SDL2/SDL.h HINTS $ENV{SDL2DIR} - PATH_SUFFIXES include/SDL2 include + PATH_SUFFIXES include PATHS ~/Library/Frameworks /Library/Frameworks - /usr/local/include/SDL2 - /usr/include/SDL2 + /usr/local + /usr /sw # Fink /opt/local # DarwinPorts /opt/csw # Blastwave + /opt/homebrew /opt - /boot/system/develop/headers/SDL2 #Hiaku OS + /boot/system/develop/headers #Haiku OS ) #MESSAGE("SDL2_INCLUDE_DIR is ${SDL2_INCLUDE_DIR}") @@ -86,10 +80,10 @@ FIND_LIBRARY(SDL2_LIBRARY_TEMP /sw /opt/local /opt/csw + /opt/homebrew /opt - /system/lib #Hiaku OS + /system/lib #Haiku OS ) - #MESSAGE("SDL2_LIBRARY_TEMP is ${SDL2_LIBRARY_TEMP}") IF(NOT SDL2_BUILDING_LIBRARY) @@ -107,6 +101,7 @@ IF(NOT SDL2_BUILDING_LIBRARY) /sw /opt/local /opt/csw + /opt/homebrew /opt ) ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") diff --git a/libraries/ZVulkan/CMakeLists.txt b/libraries/ZVulkan/CMakeLists.txt index 79e719dcb..a95f80075 100644 --- a/libraries/ZVulkan/CMakeLists.txt +++ b/libraries/ZVulkan/CMakeLists.txt @@ -1,4 +1,5 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.24) + project(zvulkan) option( VULKAN_USE_XLIB "Use Vulkan xlib (X11) WSI integration" ON ) @@ -198,15 +199,19 @@ source_group("include\\volk" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/inc include_directories(include include/zvulkan src) if(WIN32) - set(ZVULKAN_SOURCES ${ZVULKAN_SOURCES} ${ZVULKAN_WIN32_SOURCES}) + list(APPEND ZVULKAN_SOURCES ${ZVULKAN_WIN32_SOURCES}) add_definitions(-DUNICODE -D_UNICODE) else() - set(ZVULKAN_SOURCES ${ZVULKAN_SOURCES} ${ZVULKAN_UNIX_SOURCES}) - if(NOT HAIKU) - set(ZVULKAN_LIBS ${CMAKE_DL_LIBS} -ldl) + if(APPLE) + find_package(Vulkan REQUIRED COMPONENTS MoltenVK) + list(APPEND ZVULKAN_LIBS Vulkan::Vulkan) + elseif(NOT HAIKU AND NOT CMAKE_SYSTEM_NAME MATCHES "FreeBSD|NetBSD|OpenBSD|DragonFly") + list(APPEND ZVULKAN_LIBS ${CMAKE_DL_LIBS} -ldl) else() - set(ZVULKAN_LIBS ${CMAKE_DL_LIBS}) + list(APPEND ZVULKAN_LIBS ${CMAKE_DL_LIBS}) endif() + + list(APPEND ZVULKAN_SOURCES ${ZVULKAN_UNIX_SOURCES}) add_definitions(-DUNIX -D_UNIX) add_link_options(-pthread) endif() @@ -220,7 +225,7 @@ if(MSVC) endif() add_library(zvulkan STATIC ${ZVULKAN_SOURCES} ${ZVULKAN_INCLUDES} ${VULKAN_INCLUDES}) -target_link_libraries(zvulkan ${ZVULKAN_LIBS}) +target_link_libraries(zvulkan PUBLIC ${ZVULKAN_LIBS}) set_target_properties(zvulkan PROPERTIES CXX_STANDARD 17) if(MSVC) diff --git a/libraries/ZVulkan/include/zvulkan/vulkanobjects.h b/libraries/ZVulkan/include/zvulkan/vulkanobjects.h index 0097d1abd..e4655e6bd 100644 --- a/libraries/ZVulkan/include/zvulkan/vulkanobjects.h +++ b/libraries/ZVulkan/include/zvulkan/vulkanobjects.h @@ -569,7 +569,7 @@ inline RenderPassBegin& RenderPassBegin::Framebuffer(VulkanFramebuffer* framebuf inline RenderPassBegin& RenderPassBegin::AddClearColor(float r, float g, float b, float a) { VkClearValue clearValue = { }; - clearValue.color = { r, g, b, a }; + clearValue.color = {{ r, g, b, a }}; clearValues.push_back(clearValue); renderPassInfo.clearValueCount = (uint32_t)clearValues.size(); diff --git a/libraries/ZWidget/.github/workflows/build.yaml b/libraries/ZWidget/.github/workflows/build.yaml index 396970061..6e8c51f6e 100644 --- a/libraries/ZWidget/.github/workflows/build.yaml +++ b/libraries/ZWidget/.github/workflows/build.yaml @@ -64,30 +64,30 @@ jobs: shell: bash run: cmake --build . --config $BUILD_TYPE -# macos-build: -# runs-on: macos-10.15 -# name: 🍎 macOS 10.15 -# steps: + macos-build: + runs-on: macos-latest + name: 🍎 macOS arm64 + steps: -# - name: 🧰 Checkout -# uses: actions/checkout@v2 -# with: -# fetch-depth: 0 -# submodules: true + - name: 🧰 Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: true -# - name: ⬇️ Install dependencies -# run: brew install sdl2 sdl2_mixer + - name: ⬇️ Install dependencies + run: brew install sdl2 sdl2_mixer -# - name: Create Build Environment -# run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build -# - name: Configure CMake -# shell: bash -# working-directory: ${{runner.workspace}}/build -# run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -# - name: Build -# working-directory: ${{runner.workspace}}/build -# shell: bash -# run: | -# cmake --build . --config $BUILD_TYPE + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: | + cmake --build . --config $BUILD_TYPE diff --git a/libraries/ZWidget/CMakeLists.txt b/libraries/ZWidget/CMakeLists.txt index f67f52f4c..bc5f43a51 100644 --- a/libraries/ZWidget/CMakeLists.txt +++ b/libraries/ZWidget/CMakeLists.txt @@ -17,6 +17,14 @@ if (UNIX AND NOT APPLE) ) endif() +# SDL2 finding stuff +# Optional on all platforms +find_package(SDL2 QUIET) +if(NOT ${SDL2_FOUND}) + include(FindPkgConfig) + pkg_search_module(SDL2 sdl2) +endif() + set(ZWIDGET_SOURCES src/core/canvas.cpp src/core/font.cpp @@ -138,6 +146,8 @@ set(ZWIDGET_X11_SOURCES src/window/x11/x11_display_backend.h src/window/x11/x11_display_window.cpp src/window/x11/x11_display_window.h + src/window/x11/x11_connection.cpp + src/window/x11/x11_connection.h ) set(ZWIDGET_WAYLAND_SOURCES @@ -147,6 +157,10 @@ set(ZWIDGET_WAYLAND_SOURCES src/window/wayland/wayland_display_window.h src/window/wayland/wl_fractional_scaling_protocol.cpp src/window/wayland/wl_fractional_scaling_protocol.hpp + src/window/wayland/wl_cursor_shape.cpp + src/window/wayland/wl_cursor_shape.hpp + src/window/wayland/wl_xdg_toplevel_icon.cpp + src/window/wayland/wl_xdg_toplevel_icon.hpp ) source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+") @@ -226,11 +240,12 @@ elseif(APPLE) set(ZWIDGET_DEFINES -DUNIX -D_UNIX) set(ZWIDGET_LINK_OPTIONS -pthread) else() - set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES} ${ZWIDGET_X11_SOURCES}) - set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl -lX11) - set(ZWIDGET_DEFINES -DUNIX -D_UNIX -DUSE_SDL2 -DUSE_X11) + set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_X11_SOURCES}) + set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -lX11 -lXi) + set(ZWIDGET_DEFINES -DUNIX -D_UNIX -DUSE_X11) set(ZWIDGET_LINK_OPTIONS -pthread) if (DBUS_FOUND) + include_directories("/usr/lib64/dbus-1.0/include") # Bazzite (and probably Fedora 42) keeps the platform-specific dbus headers in this folder set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_DBUS_SOURCES}) set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS}) set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${DBUS_LDFLAGS}) @@ -244,14 +259,77 @@ else() endif() endif() +if(SDL2_FOUND AND NOT WIN32) + set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES}) + set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_SDL2) +endif() + +if(MSVC) + set(CXX_WARNING_FLAGS /W3) +else() + set(CXX_WARNING_FLAGS -Wall -Wpedantic) +endif() + add_library(zwidget STATIC ${ZWIDGET_SOURCES} ${ZWIDGET_INCLUDES}) target_compile_options(zwidget PRIVATE ${ZWIDGET_COMPILE_OPTIONS}) target_compile_definitions(zwidget PRIVATE ${ZWIDGET_DEFINES}) target_include_directories(zwidget PRIVATE ${ZWIDGET_INCLUDE_DIRS}) target_link_options(zwidget PRIVATE ${ZWIDGET_LINK_OPTIONS}) -target_link_libraries(zwidget ${ZWIDGET_LIBS}) -set_target_properties(zwidget PROPERTIES CXX_STANDARD 17) +target_link_libraries(zwidget PRIVATE ${ZWIDGET_LIBS}) +if(SDL2_FOUND) + if(TARGET SDL2::SDL2) + target_link_libraries(zwidget PRIVATE SDL2::SDL2) + else() # needed for gzdoom compat for now + target_include_directories(zwidget PRIVATE ${SDL2_INCLUDE_DIR} ${SDL2_INCLUDE_DIRS}) + target_link_libraries(zwidget PRIVATE ${SDL2_LIBRARY}) + endif() +endif() +set_target_properties(zwidget PROPERTIES CXX_STANDARD 20) +target_compile_options(zwidget PRIVATE ${CXX_WARNING_FLAGS}) if(MSVC) set_property(TARGET zwidget PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() + +option(ZWIDGET_BUILD_EXAMPLE "Build the zwidget example application" ON) + +if(ZWIDGET_BUILD_EXAMPLE) + add_executable(zwidget_example WIN32 + example/example.cpp + example/picopng.cpp + example/picopng.h + ) + target_compile_options(zwidget_example PRIVATE ${CXX_WARNING_FLAGS}) + + add_custom_command( + TARGET zwidget_example POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/example/banner.png" + "${CMAKE_CURRENT_BINARY_DIR}/banner.png" + COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/example/OpenSans.ttf" + "${CMAKE_CURRENT_BINARY_DIR}/OpenSans.ttf" + ) + + target_include_directories(zwidget_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/example) + target_link_libraries(zwidget_example PRIVATE zwidget) + + if(WIN32) + target_compile_definitions(zwidget_example PRIVATE UNICODE _UNICODE) + target_link_libraries(zwidget_example PRIVATE gdi32 user32 shell32 comdlg32) + elseif(APPLE) + target_link_libraries(zwidget_example PRIVATE "-framework Cocoa -framework OpenGL") + else() + target_link_libraries(zwidget_example PRIVATE ${ZWIDGET_LIBS}) + endif() + + if(SDL2_FOUND) + target_link_libraries(zwidget_example PRIVATE SDL2::SDL2) + endif() + + set_target_properties(zwidget_example PROPERTIES CXX_STANDARD 20) + + if(MSVC) + set_property(TARGET zwidget_example PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + endif() +endif() diff --git a/libraries/ZWidget/example/example.cpp b/libraries/ZWidget/example/example.cpp index cd2b1b740..34fbf5450 100644 --- a/libraries/ZWidget/example/example.cpp +++ b/libraries/ZWidget/example/example.cpp @@ -13,136 +13,29 @@ #include #include #include "picopng.h" +#include + +// ************************************************************ +// Prototypes +// ************************************************************ static std::vector ReadAllBytes(const std::string& filename); -class LauncherWindow : public Widget +class LauncherWindowTab1 : public Widget { public: - LauncherWindow() : Widget(nullptr, WidgetType::Window) - { - Logo = new ImageBox(this); - WelcomeLabel = new TextLabel(this); - VersionLabel = new TextLabel(this); - SelectLabel = new TextLabel(this); - GeneralLabel = new TextLabel(this); - ExtrasLabel = new TextLabel(this); - FullscreenCheckbox = new CheckboxLabel(this); - DisableAutoloadCheckbox = new CheckboxLabel(this); - DontAskAgainCheckbox = new CheckboxLabel(this); - LightsCheckbox = new CheckboxLabel(this); - BrightmapsCheckbox = new CheckboxLabel(this); - WidescreenCheckbox = new CheckboxLabel(this); - PlayButton = new PushButton(this); - ExitButton = new PushButton(this); - GamesList = new ListView(this); - Choices = new Dropdown(this); + LauncherWindowTab1(Widget parent); + void OnGeometryChanged() override; +private: + TextEdit* Text = nullptr; +}; - SetWindowBackground(Colorf::fromRgba8(51, 51, 51)); - SetWindowBorderColor(Colorf::fromRgba8(51, 51, 51)); - SetWindowCaptionColor(Colorf::fromRgba8(33, 33, 33)); - SetWindowCaptionTextColor(Colorf::fromRgba8(226, 223, 219)); - SetWindowTitle("VKDoom Launcher"); - - WelcomeLabel->SetText("Welcome to VKDoom"); - VersionLabel->SetText("Version 0xdeadbabe."); - SelectLabel->SetText("Select which game file (IWAD) to run."); - PlayButton->SetText("Play Game"); - ExitButton->SetText("Exit"); - - ExitButton->OnClick = []{ - DisplayWindow::ExitLoop(); - }; - - GeneralLabel->SetText("General"); - ExtrasLabel->SetText("Extra Graphics"); - FullscreenCheckbox->SetText("Fullscreen"); - DisableAutoloadCheckbox->SetText("Disable autoload"); - DontAskAgainCheckbox->SetText("Don't ask me again"); - LightsCheckbox->SetText("Lights"); - BrightmapsCheckbox->SetText("Brightmaps"); - WidescreenCheckbox->SetText("Widescreen"); - - Choices->SetMaxDisplayItems(2); - Choices->AddItem("First"); - Choices->AddItem("Second"); - Choices->AddItem("Third"); - Choices->AddItem("Fourth"); - Choices->AddItem("Fifth"); - Choices->AddItem("Sixth"); - - Choices->OnChanged = [this](int index) { - std::cout << "Selected " << index << ":" << Choices->GetItem(index) << std::endl; - }; - - try - { - auto filedata = ReadAllBytes("banner.png"); - std::vector pixels; - unsigned long width = 0, height = 0; - int result = decodePNG(pixels, width, height, (const unsigned char*)filedata.data(), filedata.size(), true); - if (result == 0) - { - Logo->SetImage(Image::Create(width, height, ImageFormat::R8G8B8A8, pixels.data())); - } - } - catch (...) - { - } - } - - void OnGeometryChanged() override - { - double y = 0.0; - - Logo->SetFrameGeometry(0.0, y, GetWidth(), Logo->GetPreferredHeight()); - y += Logo->GetPreferredHeight(); - - y += 10.0; - - WelcomeLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, WelcomeLabel->GetPreferredHeight()); - y += WelcomeLabel->GetPreferredHeight(); - - VersionLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, VersionLabel->GetPreferredHeight()); - y += VersionLabel->GetPreferredHeight(); - - y += 10.0; - - SelectLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0 - Choices->GetPreferredWidth(), SelectLabel->GetPreferredHeight()); - y += SelectLabel->GetPreferredHeight(); - - Choices->SetFrameGeometry(GetWidth() - 20.0 - Choices->GetPreferredWidth(), y-Choices->GetPreferredHeight(), Choices->GetPreferredWidth(), Choices->GetPreferredHeight()); - - double listViewTop = y + 10.0; - - y = GetHeight() - 15.0 - PlayButton->GetPreferredHeight(); - PlayButton->SetFrameGeometry(20.0, y, 120.0, PlayButton->GetPreferredHeight()); - ExitButton->SetFrameGeometry(GetWidth() - 20.0 - 120.0, y, 120.0, PlayButton->GetPreferredHeight()); - - y -= 20.0; - - double panelWidth = 150.0; - y -= DontAskAgainCheckbox->GetPreferredHeight(); - DontAskAgainCheckbox->SetFrameGeometry(20.0, y, 190.0, DontAskAgainCheckbox->GetPreferredHeight()); - WidescreenCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, WidescreenCheckbox->GetPreferredHeight()); - - y -= DisableAutoloadCheckbox->GetPreferredHeight(); - DisableAutoloadCheckbox->SetFrameGeometry(20.0, y, 190.0, DisableAutoloadCheckbox->GetPreferredHeight()); - BrightmapsCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, BrightmapsCheckbox->GetPreferredHeight()); - - y -= FullscreenCheckbox->GetPreferredHeight(); - FullscreenCheckbox->SetFrameGeometry(20.0, y, 190.0, FullscreenCheckbox->GetPreferredHeight()); - LightsCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, LightsCheckbox->GetPreferredHeight()); - - y -= GeneralLabel->GetPreferredHeight(); - GeneralLabel->SetFrameGeometry(20.0, y, 190.0, GeneralLabel->GetPreferredHeight()); - ExtrasLabel->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, ExtrasLabel->GetPreferredHeight()); - - double listViewBottom = y - 10.0; - GamesList->SetFrameGeometry(20.0, listViewTop, GetWidth() - 40.0, std::max(listViewBottom - listViewTop, 0.0)); - } - - ImageBox* Logo = nullptr; +class LauncherWindowTab2 : public Widget +{ +public: + LauncherWindowTab2(Widget parent); + void OnGeometryChanged() override; +private: TextLabel* WelcomeLabel = nullptr; TextLabel* VersionLabel = nullptr; TextLabel* SelectLabel = nullptr; @@ -154,13 +47,261 @@ public: CheckboxLabel* LightsCheckbox = nullptr; CheckboxLabel* BrightmapsCheckbox = nullptr; CheckboxLabel* WidescreenCheckbox = nullptr; - PushButton* PlayButton = nullptr; - PushButton* ExitButton = nullptr; ListView* GamesList = nullptr; Dropdown* Choices = nullptr; }; -static std::vector ReadAllBytes(const std::string& filename); +class LauncherWindowTab3 : public Widget +{ +public: + LauncherWindowTab3(Widget parent); + void OnGeometryChanged() override; +private: + TextLabel* Label = nullptr; + Dropdown* Choices = nullptr; + PushButton* Popup = nullptr; +}; + +class LauncherWindow : public Widget +{ +public: + LauncherWindow(); +private: + void OnClose() override; + void OnGeometryChanged() override; + + ImageBox* Logo = nullptr; + TabWidget* Pages = nullptr; + PushButton* ExitButton = nullptr; + + LauncherWindowTab1* Tab1 = nullptr; + LauncherWindowTab2* Tab2 = nullptr; + LauncherWindowTab3* Tab3 = nullptr; +}; + +// ************************************************************ +// UI implementation +// ************************************************************ + +LauncherWindow::LauncherWindow(): Widget(nullptr, WidgetType::Window) +{ + SetWindowTitle("ZWidget Demo"); + + try + { + SetWindowIcon({ + Image::LoadResource("surreal-engine-icon-16.png"), + Image::LoadResource("surreal-engine-icon-24.png"), + Image::LoadResource("surreal-engine-icon-32.png"), + Image::LoadResource("surreal-engine-icon-48.png"), + Image::LoadResource("surreal-engine-icon-64.png"), + Image::LoadResource("surreal-engine-icon-128.png"), + Image::LoadResource("surreal-engine-icon-256.png") + }); + } + catch (...) + { + } + + Logo = new ImageBox(this); + ExitButton = new PushButton(this); + Pages = new TabWidget(this); + + Tab1 = new LauncherWindowTab1(this); + Tab2 = new LauncherWindowTab2(this); + Tab3 = new LauncherWindowTab3(this); + + Pages->AddTab(Tab1, "Welcome"); + Pages->AddTab(Tab2, "VKDoom"); + Pages->AddTab(Tab3, "ZWidgets"); + + ExitButton->SetText("Exit"); + + ExitButton->OnClick = []{ + DisplayWindow::ExitLoop(); + }; + + try + { + auto filedata = ReadAllBytes("banner.png"); + std::vector pixels; + unsigned long width = 0, height = 0; + int result = decodePNG(pixels, width, height, (const unsigned char*)filedata.data(), filedata.size(), true); + if (result == 0) + { + Logo->SetImage(Image::Create(width, height, ImageFormat::R8G8B8A8, pixels.data())); + } + } + catch (...) + { + } +} + +void LauncherWindow::OnGeometryChanged() +{ + double y = 0, h; + + h = Logo->GetPreferredHeight(); + Logo->SetFrameGeometry(0, y, GetWidth(), h); + y += h; + + h = GetHeight() - y - ExitButton->GetPreferredHeight() - 40; + Pages->SetFrameGeometry(0, y, GetWidth(), h); + y += h + 20; + + ExitButton->SetFrameGeometry(GetWidth() - 20 - 120, y, 120, ExitButton->GetPreferredHeight()); +} + +void LauncherWindow::OnClose() +{ + DisplayWindow::ExitLoop(); +} + +LauncherWindowTab1::LauncherWindowTab1(Widget parent): Widget(nullptr) +{ + Text = new TextEdit(this); + + Text->SetText( + "Welcome to VKDoom\n\n" + "Click the tabs to look at other widgets\n\n" + "Also, this text is editable\n" + ); +} + +void LauncherWindowTab1::OnGeometryChanged() +{ + Text->SetFrameGeometry(0, 10, GetWidth(), GetHeight()); +} + +LauncherWindowTab2::LauncherWindowTab2(Widget parent): Widget(nullptr) +{ + WelcomeLabel = new TextLabel(this); + VersionLabel = new TextLabel(this); + SelectLabel = new TextLabel(this); + GeneralLabel = new TextLabel(this); + ExtrasLabel = new TextLabel(this); + FullscreenCheckbox = new CheckboxLabel(this); + DisableAutoloadCheckbox = new CheckboxLabel(this); + DontAskAgainCheckbox = new CheckboxLabel(this); + LightsCheckbox = new CheckboxLabel(this); + BrightmapsCheckbox = new CheckboxLabel(this); + WidescreenCheckbox = new CheckboxLabel(this); + GamesList = new ListView(this); + + WelcomeLabel->SetText("Welcome to VKDoom"); + VersionLabel->SetText("Version 0xdeadbabe."); + SelectLabel->SetText("Select which game file (IWAD) to run."); + + GamesList->AddItem("Doom"); + GamesList->AddItem("Doom 2: Electric Boogaloo"); + GamesList->AddItem("Doom 3D"); + GamesList->AddItem("Doom 4: The Quest for Peace"); + GamesList->AddItem("Doom on Ice"); + GamesList->AddItem("The Doom"); + GamesList->AddItem("Doom 2"); + + GeneralLabel->SetText("General"); + ExtrasLabel->SetText("Extra Graphics"); + FullscreenCheckbox->SetText("Fullscreen"); + DisableAutoloadCheckbox->SetText("Disable autoload"); + DontAskAgainCheckbox->SetText("Don't ask me again"); + LightsCheckbox->SetText("Lights"); + BrightmapsCheckbox->SetText("Brightmaps"); + WidescreenCheckbox->SetText("Widescreen"); +} + +void LauncherWindowTab2::OnGeometryChanged() +{ + double y = 0, h; + + h = WelcomeLabel->GetPreferredHeight(); + WelcomeLabel->SetFrameGeometry(20, y, GetWidth() - 40, h); + y += h; + + h = VersionLabel->GetPreferredHeight(); + VersionLabel->SetFrameGeometry(20, y, GetWidth() - 40, h); + y += h + 10; + + h = SelectLabel->GetPreferredHeight(); + SelectLabel->SetFrameGeometry(20, y, GetWidth() - 40, h); + y += h; + + double listViewTop = y + 10, listViewBottom; + + y = GetHeight(); + + h = DontAskAgainCheckbox->GetPreferredHeight(); + y -= h; + DontAskAgainCheckbox->SetFrameGeometry(20, y, 190, h); + WidescreenCheckbox->SetFrameGeometry(GetWidth() - 170, y, 150, WidescreenCheckbox->GetPreferredHeight()); + + h = DisableAutoloadCheckbox->GetPreferredHeight(); + y -= h; + DisableAutoloadCheckbox->SetFrameGeometry(20, y, 190, h); + BrightmapsCheckbox->SetFrameGeometry(GetWidth() - 170, y, 150, BrightmapsCheckbox->GetPreferredHeight()); + + h = FullscreenCheckbox->GetPreferredHeight(); + y -= h; + FullscreenCheckbox->SetFrameGeometry(20, y, 190, h); + LightsCheckbox->SetFrameGeometry(GetWidth() - 170, y, 150, LightsCheckbox->GetPreferredHeight()); + + h = GeneralLabel->GetPreferredHeight(); + y -= h; + GeneralLabel->SetFrameGeometry(20, y, 190, GeneralLabel->GetPreferredHeight()); + ExtrasLabel->SetFrameGeometry(GetWidth() - 170, y, 150, ExtrasLabel->GetPreferredHeight()); + + listViewBottom = y - 10; + GamesList->SetFrameGeometry(20, listViewTop, GetWidth() - 40, std::max(listViewBottom - listViewTop, 0)); +} + +LauncherWindowTab3::LauncherWindowTab3(Widget parent): Widget(nullptr) +{ + Label = new TextLabel(this); + Choices = new Dropdown(this); + Popup = new PushButton(this); + + Label->SetText("Oh my, even more widgets"); + Popup->SetText("Click me."); + + Choices->SetMaxDisplayItems(2); + Choices->AddItem("First"); + Choices->AddItem("Second"); + Choices->AddItem("Third"); + Choices->AddItem("Fourth"); + Choices->AddItem("Fifth"); + Choices->AddItem("Sixth"); + + Choices->OnChanged = [this](int index) { + std::cout << "Selected " << index << ":" << Choices->GetItem(index) << std::endl; + }; + + Popup->OnClick = []{ + std::cout << "TODO: open popup" << std::endl; + }; +} + +void LauncherWindowTab3::OnGeometryChanged() +{ + double y = 0, h; + + y += 10; + + h = Label->GetPreferredHeight(); + Label->SetFrameGeometry(20, y, GetWidth() - 40, h); + y += h + 10; + + h = Choices->GetPreferredHeight(); + Choices->SetFrameGeometry(20, y, Choices->GetPreferredWidth(), h); + y += h + 10; + + h = Popup->GetPreferredHeight(); + Popup->SetFrameGeometry(20, y, 120, h); + y += h; +} + +// ************************************************************ +// Shared code +// ************************************************************ std::vector LoadWidgetFontData(const std::string& name) { @@ -174,13 +315,24 @@ std::vector LoadWidgetData(const std::string& name) return ReadAllBytes(name); } -enum class Backend { +{ Default, Win32, SDL2, X11, Wayland }; -int example(Backend backend = Backend::Default) +enum class Theme { - WidgetTheme::SetTheme(std::make_unique()); + Default, Light, Dark +}; + +int example(Backend backend = Backend::Default, Theme theme = Theme::Default) +{ + // just for testing themes + switch (theme) + { + case Theme::Default: WidgetTheme::SetTheme(std::make_unique()); break; + case Theme::Dark: WidgetTheme::SetTheme(std::make_unique()); break; + case Theme::Light: WidgetTheme::SetTheme(std::make_unique()); break; + } // just for testing backends switch (backend) @@ -192,40 +344,23 @@ int example(Backend backend = Backend::Default) case Backend::Wayland: DisplayBackend::Set(DisplayBackend::TryCreateWayland()); break; } -#if 1 auto launcher = new LauncherWindow(); launcher->SetFrameGeometry(100.0, 100.0, 615.0, 668.0); launcher->Show(); -#else - auto mainwindow = new MainWindow(); - auto textedit = new TextEdit(mainwindow); - textedit->SetText(R"( -#version 460 - -in vec4 AttrPos; -in vec4 AttrColor; -out vec4 Color; - -void main() -{ - gl_Position = AttrPos; - Color = AttrColor; -} -)"); - mainwindow->SetWindowTitle("ZWidget Example"); - mainwindow->SetFrameGeometry(100.0, 100.0, 1700.0, 900.0); - mainwindow->SetCentralWidget(textedit); - textedit->SetFocus(); - mainwindow->Show(); -#endif DisplayWindow::RunLoop(); return 0; } +// ************************************************************ +// Platform-specific code +// ************************************************************ + #ifdef WIN32 +#define WIN32_MEAN_AND_LEAN +#define NOMINMAX #include #include @@ -277,6 +412,7 @@ static std::vector ReadAllBytes(const std::string& filename) int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow) { + SetProcessDPIAware(); example(); } @@ -305,18 +441,25 @@ static std::vector ReadAllBytes(const std::string& filename) int main(int argc, const char** argv) { - std::string backendStr = argc > 1? argv[1]: ""; - std::transform(backendStr.begin(), backendStr.end(), backendStr.begin(), - [](unsigned char c){ return std::tolower(c); }); - Backend backend = Backend::Default; + Theme theme = Theme::Default; - if (backendStr == "sdl2") backend = Backend::SDL2; - if (backendStr == "x11") backend = Backend::X11; - if (backendStr == "wayland") backend = Backend::Wayland; - if (backendStr == "win32") backend = Backend::Win32; // lol + for (auto i = 1; i < argc; i++) + { + std::string s = argv[i]; + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c){ return std::tolower(c); }); - example(backend); + if (s == "light") { theme = Theme::Light; continue; } + if (s == "dark") { theme = Theme::Dark; continue; } + + if (s == "sdl2") { backend = Backend::SDL2; continue; } + if (s == "x11") { backend = Backend::X11; continue; } + if (s == "wayland") { backend = Backend::Wayland; continue; } + if (s == "win32") { backend = Backend::Win32; continue; } // lol + } + + example(backend, theme); } #endif diff --git a/libraries/ZWidget/example/picopng.cpp b/libraries/ZWidget/example/picopng.cpp index c2943eb14..7d3e910f2 100644 --- a/libraries/ZWidget/example/picopng.cpp +++ b/libraries/ZWidget/example/picopng.cpp @@ -260,7 +260,7 @@ int decodePNG(std::vector& out_image, unsigned long& image_width, readPngHeader(&in[0], size); if(error) return; size_t pos = 33; //first byte of the first chunk after the header std::vector idat; //the data from idat chunks - bool IEND = false, known_type = true; + bool IEND = false/*, known_type = true*/; info.key_defined = false; while(!IEND) //loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer { @@ -312,7 +312,7 @@ int decodePNG(std::vector& out_image, unsigned long& image_width, { if(!(in[pos + 0] & 32)) { error = 69; return; } //error: unknown critical chunk (5th bit of first byte of chunk type is 0) pos += (chunkLength + 4); //skip 4 letters and uninterpreted data of unimplemented chunk - known_type = false; + //known_type = false; } pos += 4; //step over CRC (which is ignored) } diff --git a/libraries/ZWidget/example/surreal-engine-icon-128.png b/libraries/ZWidget/example/surreal-engine-icon-128.png new file mode 100644 index 000000000..e79210f1c Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-128.png differ diff --git a/libraries/ZWidget/example/surreal-engine-icon-16.png b/libraries/ZWidget/example/surreal-engine-icon-16.png new file mode 100644 index 000000000..18915d886 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-16.png differ diff --git a/libraries/ZWidget/example/surreal-engine-icon-24.png b/libraries/ZWidget/example/surreal-engine-icon-24.png new file mode 100644 index 000000000..e7d197b54 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-24.png differ diff --git a/libraries/ZWidget/example/surreal-engine-icon-256.png b/libraries/ZWidget/example/surreal-engine-icon-256.png new file mode 100644 index 000000000..813ecc534 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-256.png differ diff --git a/libraries/ZWidget/example/surreal-engine-icon-32.png b/libraries/ZWidget/example/surreal-engine-icon-32.png new file mode 100644 index 000000000..7f7518482 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-32.png differ diff --git a/libraries/ZWidget/example/surreal-engine-icon-48.png b/libraries/ZWidget/example/surreal-engine-icon-48.png new file mode 100644 index 000000000..ca3a90828 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-48.png differ diff --git a/libraries/ZWidget/example/surreal-engine-icon-64.png b/libraries/ZWidget/example/surreal-engine-icon-64.png new file mode 100644 index 000000000..790cb9892 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-64.png differ diff --git a/libraries/ZWidget/include/zwidget/core/colorf.h b/libraries/ZWidget/include/zwidget/core/colorf.h index 935214799..ed3793629 100644 --- a/libraries/ZWidget/include/zwidget/core/colorf.h +++ b/libraries/ZWidget/include/zwidget/core/colorf.h @@ -17,6 +17,21 @@ public: return { r * s, g * s, b * s, a * s }; } + static Colorf fromRgba(uint32_t rgba) + { + return fromRgba8( + 0xff & rgba>>24, + 0xff & rgba>>16, + 0xff & rgba>>8, + 0xff & rgba + ); + } + + static Colorf fromRgb(uint32_t rgb) + { + return fromRgba(rgb<<8 | 0xff); + } + uint32_t toBgra8() const { uint32_t cr = (int)(std::max(std::min(r * 255.0f, 255.0f), 0.0f)); diff --git a/libraries/ZWidget/include/zwidget/core/span_layout.h b/libraries/ZWidget/include/zwidget/core/span_layout.h index 974feca40..5f6a15866 100644 --- a/libraries/ZWidget/include/zwidget/core/span_layout.h +++ b/libraries/ZWidget/include/zwidget/core/span_layout.h @@ -212,7 +212,7 @@ private: Colorf cursor_color; std::string::size_type sel_start = 0, sel_end = 0; - Colorf sel_foreground, sel_background = Colorf::fromRgba8(153, 201, 239); + Colorf sel_foreground, sel_background; std::string text; std::vector objects; diff --git a/libraries/ZWidget/include/zwidget/core/theme.h b/libraries/ZWidget/include/zwidget/core/theme.h index 5a5271bc3..04b1f7acb 100644 --- a/libraries/ZWidget/include/zwidget/core/theme.h +++ b/libraries/ZWidget/include/zwidget/core/theme.h @@ -55,7 +55,23 @@ public: class WidgetTheme { + struct SimpleTheme { + const Colorf bgMain; // background + const Colorf fgMain; // + const Colorf bgLight; // headers / inputs + const Colorf fgLight; // + const Colorf bgAction; // interactive elements + const Colorf fgAction; // + const Colorf bgHover; // hover / highlight + const Colorf fgHover; // + const Colorf bgActive; // click + const Colorf fgActive; // + const Colorf border; // around elements + const Colorf divider; // between elements + }; public: + WidgetTheme() {} + WidgetTheme(const struct SimpleTheme &theme); virtual ~WidgetTheme() = default; WidgetStyle* RegisterStyle(std::unique_ptr widgetStyle, const std::string& widgetClass); diff --git a/libraries/ZWidget/include/zwidget/core/widget.h b/libraries/ZWidget/include/zwidget/core/widget.h index be41697cd..716e6b45a 100644 --- a/libraries/ZWidget/include/zwidget/core/widget.h +++ b/libraries/ZWidget/include/zwidget/core/widget.h @@ -40,8 +40,8 @@ public: std::string GetWindowTitle() const; void SetWindowTitle(const std::string& text); - // Icon GetWindowIcon() const; - // void SetWindowIcon(const Icon& icon); + std::vector> GetWindowIcon() const; + void SetWindowIcon(const std::vector>& images); // Widget content box Size GetSize() const; @@ -233,6 +233,7 @@ private: Colorf WindowBackground = Colorf::fromRgba8(240, 240, 240); std::string WindowTitle; + std::vector> WindowIcon; std::unique_ptr DispWindow; std::unique_ptr DispCanvas; Widget* FocusWidget = nullptr; diff --git a/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h b/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h index f392f85d6..007e7bee3 100644 --- a/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h +++ b/libraries/ZWidget/include/zwidget/widgets/textedit/textedit.h @@ -83,6 +83,11 @@ private: SpanLayout layout; Rect box; bool invalidated = true; + + Line(const TextEdit *self) + { + layout.SetSelectionColors(self->selectionFG, self->selectionBG); + } }; struct ivec2 @@ -96,9 +101,10 @@ private: bool operator!=(const ivec2& b) const { return x != b.x || y != b.y; } }; + Colorf selectionBG, selectionFG; Scrollbar* vert_scrollbar; Timer* timer = nullptr; - std::vector lines = { Line() }; + std::vector lines = { Line{this} }; ivec2 cursor_pos = { 0, 0 }; int max_length = -1; bool mouse_selecting = false; diff --git a/libraries/ZWidget/include/zwidget/window/window.h b/libraries/ZWidget/include/zwidget/window/window.h index ad59f1f02..bb0581d51 100644 --- a/libraries/ZWidget/include/zwidget/window/window.h +++ b/libraries/ZWidget/include/zwidget/window/window.h @@ -26,6 +26,7 @@ class Widget; class OpenFileDialog; class SaveFileDialog; class OpenFolderDialog; +class Image; enum class StandardCursor { @@ -163,6 +164,7 @@ public: virtual ~DisplayWindow() = default; virtual void SetWindowTitle(const std::string& text) = 0; + virtual void SetWindowIcon(const std::vector>& images) = 0; virtual void SetWindowFrame(const Rect& box) = 0; virtual void SetClientFrame(const Rect& box) = 0; virtual void Show() = 0; diff --git a/libraries/ZWidget/src/core/canvas.cpp b/libraries/ZWidget/src/core/canvas.cpp index bc72b8f19..060e9366d 100644 --- a/libraries/ZWidget/src/core/canvas.cpp +++ b/libraries/ZWidget/src/core/canvas.cpp @@ -549,9 +549,7 @@ void BitmapCanvas::plot(float x, float y, float alpha, const Colorf& color) int xx = (int)x; int yy = (int)y; - int dwidth = width; - int dheight = height; - uint32_t* dest = pixels.data() + xx + yy * dwidth; + uint32_t* dest = pixels.data() + xx + yy * width; uint32_t cred = (int32_t)clamp(color.r * 256.0f, 0.0f, 256.0f); uint32_t cgreen = (int32_t)clamp(color.g * 256.0f, 0.0f, 256.0f); @@ -665,7 +663,6 @@ void BitmapCanvas::fillTile(float left, float top, float width, float height, Co return; int dwidth = this->width; - int dheight = this->height; uint32_t* dest = this->pixels.data(); int x0 = (int)left; @@ -771,11 +768,9 @@ void BitmapCanvas::drawTile(CanvasTexture* tex, float left, float top, float wid auto texture = static_cast(tex); int swidth = texture->Width; - int sheight = texture->Height; const uint32_t* src = texture->Data.data(); int dwidth = this->width; - int dheight = this->height; uint32_t* dest = this->pixels.data(); int x0 = (int)left; @@ -880,11 +875,9 @@ void BitmapCanvas::drawGlyph(CanvasTexture* tex, float left, float top, float wi auto texture = static_cast(tex); int swidth = texture->Width; - int sheight = texture->Height; const uint32_t* src = texture->Data.data(); int dwidth = this->width; - int dheight = this->height; uint32_t* dest = this->pixels.data(); int x0 = (int)left; diff --git a/libraries/ZWidget/src/core/pathfill.cpp b/libraries/ZWidget/src/core/pathfill.cpp index cb604ef3f..1d92a61e4 100644 --- a/libraries/ZWidget/src/core/pathfill.cpp +++ b/libraries/ZWidget/src/core/pathfill.cpp @@ -255,7 +255,7 @@ PathFillRasterizer::Extent PathFillRasterizer::FindExtent(const PathScanline* sc void PathFillRasterizer::Clear() { - for (size_t y = first_scanline; y < last_scanline; y++) + for (int y = first_scanline; y < last_scanline; y++) { auto& scanline = scanlines[y]; if (!scanline.edges.empty()) diff --git a/libraries/ZWidget/src/core/picopng/picopng.cpp b/libraries/ZWidget/src/core/picopng/picopng.cpp index 97eb3b500..e2ee1923c 100644 --- a/libraries/ZWidget/src/core/picopng/picopng.cpp +++ b/libraries/ZWidget/src/core/picopng/picopng.cpp @@ -262,7 +262,7 @@ int decodePNG(std::vector& out_image, unsigned long& image_width, readPngHeader(&in[0], size); if(error) return; size_t pos = 33; //first byte of the first chunk after the header std::vector idat; //the data from idat chunks - bool IEND = false, known_type = true; + bool IEND = false/*, known_type = true*/; info.key_defined = false; while(!IEND) //loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer { @@ -314,7 +314,7 @@ int decodePNG(std::vector& out_image, unsigned long& image_width, { if(!(in[pos + 0] & 32)) { error = 69; return; } //error: unknown critical chunk (5th bit of first byte of chunk type is 0) pos += (chunkLength + 4); //skip 4 letters and uninterpreted data of unimplemented chunk - known_type = false; + /*known_type = false;*/ } pos += 4; //step over CRC (which is ignored) } diff --git a/libraries/ZWidget/src/core/span_layout.cpp b/libraries/ZWidget/src/core/span_layout.cpp index 1b68f6ef7..1f1e9e6d9 100644 --- a/libraries/ZWidget/src/core/span_layout.cpp +++ b/libraries/ZWidget/src/core/span_layout.cpp @@ -72,16 +72,12 @@ void SpanLayout::DrawLayout(Canvas* canvas) LineSegment& segment = line.segments.back(); if (cursor_visible && segment.end <= cursor_pos) { - switch (segment.type) - { - case object_text: + if (segment.type == object_text) { double cursor_x = x + segment.x_position + canvas->measureText(segment.font, text.substr(segment.start, segment.end - segment.start)).width; double cursor_width = 1; canvas->fillRect(Rect::ltrb(cursor_x, y + line.ascender - segment.ascender, cursor_width, y + line.ascender + segment.descender), cursor_color); } - break; - } } } diff --git a/libraries/ZWidget/src/core/theme.cpp b/libraries/ZWidget/src/core/theme.cpp index 88e482706..c399eca03 100644 --- a/libraries/ZWidget/src/core/theme.cpp +++ b/libraries/ZWidget/src/core/theme.cpp @@ -141,12 +141,26 @@ WidgetTheme* WidgetTheme::GetTheme() return CurrentTheme.get(); } -///////////////////////////////////////////////////////////////////////////// - -DarkWidgetTheme::DarkWidgetTheme() +WidgetTheme::WidgetTheme(const struct SimpleTheme &theme) { + + auto bgMain = theme.bgMain; // background + auto fgMain = theme.fgMain; // + auto bgLight = theme.bgLight; // headers / inputs + auto fgLight = theme.fgLight; // + auto bgAction = theme.bgAction; // interactive elements + auto fgAction = theme.fgAction; // + auto bgHover = theme.bgHover; // hover / highlight + auto fgHover = theme.fgHover; // + auto bgActive = theme.bgActive; // click + auto fgActive = theme.fgActive; // + auto border = theme.border; // around elements + auto divider = theme.divider; // between elements + + auto none = Colorf::transparent(); + auto widget = RegisterStyle(std::make_unique(), "widget"); - auto textlabel = RegisterStyle(std::make_unique(widget), "textlabel"); + /*auto textlabel =*/ RegisterStyle(std::make_unique(widget), "textlabel"); auto pushbutton = RegisterStyle(std::make_unique(widget), "pushbutton"); auto lineedit = RegisterStyle(std::make_unique(widget), "lineedit"); auto textedit = RegisterStyle(std::make_unique(widget), "textedit"); @@ -167,272 +181,177 @@ DarkWidgetTheme::DarkWidgetTheme() auto statusbar = RegisterStyle(std::make_unique(widget), "statusbar"); widget->SetString("font-family", "NotoSans"); - widget->SetColor("color", Colorf::fromRgba8(226, 223, 219)); - widget->SetColor("window-background", Colorf::fromRgba8(51, 51, 51)); - widget->SetColor("window-border", Colorf::fromRgba8(51, 51, 51)); - widget->SetColor("window-caption-color", Colorf::fromRgba8(33, 33, 33)); - widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219)); + widget->SetColor("color", fgMain); + widget->SetColor("window-background", bgMain); + widget->SetColor("window-border", bgMain); + widget->SetColor("window-caption-color", bgLight); + widget->SetColor("window-caption-text-color", fgLight); pushbutton->SetDouble("noncontent-left", 10.0); pushbutton->SetDouble("noncontent-top", 5.0); pushbutton->SetDouble("noncontent-right", 10.0); pushbutton->SetDouble("noncontent-bottom", 5.0); - pushbutton->SetColor("background-color", Colorf::fromRgba8(68, 68, 68)); - pushbutton->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); - pushbutton->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); - pushbutton->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); - pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); - pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); - pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + pushbutton->SetColor("color", fgAction); + pushbutton->SetColor("background-color", bgAction); + pushbutton->SetColor("border-left-color", border); + pushbutton->SetColor("border-top-color", border); + pushbutton->SetColor("border-right-color", border); + pushbutton->SetColor("border-bottom-color", border); + pushbutton->SetColor("hover", "color", fgHover); + pushbutton->SetColor("hover", "background-color", bgHover); + pushbutton->SetColor("down", "color", fgActive); + pushbutton->SetColor("down", "background-color", bgActive); lineedit->SetDouble("noncontent-left", 5.0); lineedit->SetDouble("noncontent-top", 3.0); lineedit->SetDouble("noncontent-right", 5.0); lineedit->SetDouble("noncontent-bottom", 3.0); - lineedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); - lineedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); - lineedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); - lineedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); - lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); - lineedit->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100)); - lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(68, 68, 68)); + lineedit->SetColor("color", fgLight); + lineedit->SetColor("background-color", bgLight); + lineedit->SetColor("border-left-color", border); + lineedit->SetColor("border-top-color", border); + lineedit->SetColor("border-right-color", border); + lineedit->SetColor("border-bottom-color", border); + lineedit->SetColor("selection-color", bgHover); + lineedit->SetColor("no-focus-selection-color", bgHover); textedit->SetDouble("noncontent-left", 8.0); textedit->SetDouble("noncontent-top", 8.0); textedit->SetDouble("noncontent-right", 8.0); textedit->SetDouble("noncontent-bottom", 8.0); - textedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); - textedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); - textedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); - textedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); - textedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + textedit->SetColor("color", fgLight); + textedit->SetColor("background-color", bgLight); + textedit->SetColor("border-left-color", border); + textedit->SetColor("border-top-color", border); + textedit->SetColor("border-right-color", border); + textedit->SetColor("border-bottom-color", border); + textedit->SetColor("selection-color", bgHover); listview->SetDouble("noncontent-left", 10.0); listview->SetDouble("noncontent-top", 10.0); listview->SetDouble("noncontent-right", 3.0); listview->SetDouble("noncontent-bottom", 10.0); - listview->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); - listview->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); - listview->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); - listview->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); - listview->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); - listview->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100)); + listview->SetColor("color", fgLight); + listview->SetColor("background-color", bgLight); + listview->SetColor("border-left-color", border); + listview->SetColor("border-top-color", border); + listview->SetColor("border-right-color", border); + listview->SetColor("border-bottom-color", border); + listview->SetColor("selection-color", bgHover); dropdown->SetDouble("noncontent-left", 5.0); dropdown->SetDouble("noncontent-top", 5.0); dropdown->SetDouble("noncontent-right", 5.0); dropdown->SetDouble("noncontent-bottom", 5.0); - dropdown->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); - dropdown->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); - dropdown->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); - dropdown->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); - dropdown->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); - dropdown->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100)); - dropdown->SetColor("arrow-color", Colorf::fromRgba8(100, 100, 100)); + dropdown->SetColor("color", fgLight); + dropdown->SetColor("background-color", bgLight); + dropdown->SetColor("border-left-color", border); + dropdown->SetColor("border-top-color", border); + dropdown->SetColor("border-right-color", border); + dropdown->SetColor("border-bottom-color", border); + dropdown->SetColor("arrow-color", border); - scrollbar->SetColor("track-color", Colorf::fromRgba8(33, 33, 33)); - scrollbar->SetColor("thumb-color", Colorf::fromRgba8(58, 58, 58)); + scrollbar->SetColor("track-color", divider); + scrollbar->SetColor("thumb-color", border); tabbar->SetDouble("spacer-left", 20.0); tabbar->SetDouble("spacer-right", 20.0); - tabbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); + tabbar->SetColor("background-color", bgLight); tabbar_tab->SetDouble("noncontent-left", 15.0); tabbar_tab->SetDouble("noncontent-right", 15.0); tabbar_tab->SetDouble("noncontent-top", 1.0); tabbar_tab->SetDouble("noncontent-bottom", 1.0); - tabbar_tab->SetColor("background-color", Colorf::fromRgba8(38, 38, 38)); - tabbar_tab->SetColor("border-left-color", Colorf::fromRgba8(68, 68, 68)); - tabbar_tab->SetColor("border-top-color", Colorf::fromRgba8(68, 68, 68)); - tabbar_tab->SetColor("border-right-color", Colorf::fromRgba8(68, 68, 68)); - tabbar_tab->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); - tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(45, 45, 45)); - tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(51, 51, 51)); - tabbar_tab->SetColor("active", "border-left-color", Colorf::fromRgba8(100, 100, 100)); - tabbar_tab->SetColor("active", "border-top-color", Colorf::fromRgba8(100, 100, 100)); - tabbar_tab->SetColor("active", "border-right-color", Colorf::fromRgba8(100, 100, 100)); - tabbar_tab->SetColor("active", "border-bottom-color", Colorf::transparent()); + tabbar_tab->SetColor("color", fgMain); + tabbar_tab->SetColor("background-color", bgMain); + tabbar_tab->SetColor("border-left-color", divider); + tabbar_tab->SetColor("border-top-color", divider); + tabbar_tab->SetColor("border-right-color", divider); + tabbar_tab->SetColor("border-bottom-color", border); + tabbar_tab->SetColor("hover", "color", fgAction); + tabbar_tab->SetColor("hover", "background-color", bgAction); + tabbar_tab->SetColor("active", "background-color", bgMain); + tabbar_tab->SetColor("active", "border-left-color", border); + tabbar_tab->SetColor("active", "border-top-color", border); + tabbar_tab->SetColor("active", "border-right-color", border); + tabbar_tab->SetColor("active", "border-bottom-color", none); tabbar_spacer->SetDouble("noncontent-bottom", 1.0); - tabbar_spacer->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + tabbar_spacer->SetColor("border-bottom-color", border); tabwidget_stack->SetDouble("noncontent-left", 20.0); tabwidget_stack->SetDouble("noncontent-top", 5.0); tabwidget_stack->SetDouble("noncontent-right", 20.0); tabwidget_stack->SetDouble("noncontent-bottom", 5.0); - checkbox_label->SetColor("checked-outer-border-color", Colorf::fromRgba8(100, 100, 100)); - checkbox_label->SetColor("checked-inner-border-color", Colorf::fromRgba8(51, 51, 51)); - checkbox_label->SetColor("checked-color", Colorf::fromRgba8(226, 223, 219)); - checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(99, 99, 99)); - checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(51, 51, 51)); + checkbox_label->SetColor("checked-outer-border-color", border); + checkbox_label->SetColor("checked-inner-border-color", bgMain); + checkbox_label->SetColor("checked-color", fgMain); + checkbox_label->SetColor("unchecked-outer-border-color", border); + checkbox_label->SetColor("unchecked-inner-border-color", bgMain); - menubar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); - toolbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); - statusbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33)); + menubar->SetColor("background-color", bgLight); + toolbar->SetColor("background-color", bgLight); + statusbar->SetColor("background-color", bgLight); - toolbarbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); - toolbarbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + toolbarbutton->SetColor("hover", "color", fgHover); + toolbarbutton->SetColor("hover", "background-color", bgHover); + toolbarbutton->SetColor("down", "color", fgActive); + toolbarbutton->SetColor("down", "background-color", bgActive); - menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219)); - menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); - menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0)); - menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); - menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0)); + menubaritem->SetColor("color", fgMain); + menubaritem->SetColor("hover", "color", fgHover); + menubaritem->SetColor("hover", "background-color", bgHover); + menubaritem->SetColor("down", "color", fgActive); + menubaritem->SetColor("down", "background-color", bgActive); menu->SetDouble("noncontent-left", 5.0); menu->SetDouble("noncontent-top", 5.0); menu->SetDouble("noncontent-right", 5.0); menu->SetDouble("noncontent-bottom", 5.0); - menu->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100)); - menu->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100)); - menu->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100)); - menu->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100)); + menu->SetColor("color", fgMain); + menu->SetColor("background-color", bgMain); + menu->SetColor("border-left-color", border); + menu->SetColor("border-top-color", border); + menu->SetColor("border-right-color", border); + menu->SetColor("border-bottom-color", border); - menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78)); - menuitem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88)); + menuitem->SetColor("hover", "color", fgHover); + menuitem->SetColor("hover", "background-color", bgHover); + menuitem->SetColor("down", "color", fgActive); + menuitem->SetColor("down", "background-color", bgActive); } ///////////////////////////////////////////////////////////////////////////// -LightWidgetTheme::LightWidgetTheme() -{ - auto widget = RegisterStyle(std::make_unique(), "widget"); - auto textlabel = RegisterStyle(std::make_unique(widget), "textlabel"); - auto pushbutton = RegisterStyle(std::make_unique(widget), "pushbutton"); - auto lineedit = RegisterStyle(std::make_unique(widget), "lineedit"); - auto textedit = RegisterStyle(std::make_unique(widget), "textedit"); - auto listview = RegisterStyle(std::make_unique(widget), "listview"); - auto dropdown = RegisterStyle(std::make_unique(widget), "dropdown"); - auto scrollbar = RegisterStyle(std::make_unique(widget), "scrollbar"); - auto tabbar = RegisterStyle(std::make_unique(widget), "tabbar"); - auto tabbar_tab = RegisterStyle(std::make_unique(widget), "tabbar-tab"); - auto tabbar_spacer = RegisterStyle(std::make_unique(widget), "tabbar-spacer"); - auto tabwidget_stack = RegisterStyle(std::make_unique(widget), "tabwidget-stack"); - auto checkbox_label = RegisterStyle(std::make_unique(widget), "checkbox-label"); - auto menubar = RegisterStyle(std::make_unique(widget), "menubar"); - auto menubaritem = RegisterStyle(std::make_unique(widget), "menubaritem"); - auto menu = RegisterStyle(std::make_unique(widget), "menu"); - auto menuitem = RegisterStyle(std::make_unique(widget), "menuitem"); +DarkWidgetTheme::DarkWidgetTheme(): WidgetTheme({ + Colorf::fromRgb(0x2A2A2A), // background + Colorf::fromRgb(0xE2DFDB), // + Colorf::fromRgb(0x212121), // headers / inputs + Colorf::fromRgb(0xE2DFDB), // + Colorf::fromRgb(0x444444), // interactive elements + Colorf::fromRgb(0xFFFFFF), // + Colorf::fromRgb(0xC83C00), // hover / highlight + Colorf::fromRgb(0xFFFFFF), // + Colorf::fromRgb(0xBBBBBB), // click + Colorf::fromRgb(0x000000), // + Colorf::fromRgb(0x646464), // around elements + Colorf::fromRgb(0x555555) // between elements +}) {}; - widget->SetString("font-family", "NotoSans"); - widget->SetColor("color", Colorf::fromRgba8(0, 0, 0)); - widget->SetColor("window-background", Colorf::fromRgba8(240, 240, 240)); - widget->SetColor("window-border", Colorf::fromRgba8(100, 100, 100)); - widget->SetColor("window-caption-color", Colorf::fromRgba8(70, 70, 70)); - widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219)); +///////////////////////////////////////////////////////////////////////////// - pushbutton->SetDouble("noncontent-left", 10.0); - pushbutton->SetDouble("noncontent-top", 5.0); - pushbutton->SetDouble("noncontent-right", 10.0); - pushbutton->SetDouble("noncontent-bottom", 5.0); - pushbutton->SetColor("background-color", Colorf::fromRgba8(210, 210, 210)); - pushbutton->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); - pushbutton->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); - pushbutton->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); - pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); - pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); - pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); - - lineedit->SetDouble("noncontent-left", 5.0); - lineedit->SetDouble("noncontent-top", 3.0); - lineedit->SetDouble("noncontent-right", 5.0); - lineedit->SetDouble("noncontent-bottom", 3.0); - lineedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255)); - lineedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); - lineedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); - lineedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); - lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); - lineedit->SetColor("selection-color", Colorf::fromRgba8(210, 210, 255)); - lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(240, 240, 255)); - - textedit->SetDouble("noncontent-left", 8.0); - textedit->SetDouble("noncontent-top", 8.0); - textedit->SetDouble("noncontent-right", 8.0); - textedit->SetDouble("noncontent-bottom", 8.0); - textedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255)); - textedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); - textedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); - textedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); - textedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); - - listview->SetDouble("noncontent-left", 10.0); - listview->SetDouble("noncontent-top", 10.0); - listview->SetDouble("noncontent-right", 3.0); - listview->SetDouble("noncontent-bottom", 10.0); - listview->SetColor("background-color", Colorf::fromRgba8(230, 230, 230)); - listview->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); - listview->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); - listview->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); - listview->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); - listview->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200)); - - dropdown->SetDouble("noncontent-left", 5.0); - dropdown->SetDouble("noncontent-top", 5.0); - dropdown->SetDouble("noncontent-right", 5.0); - dropdown->SetDouble("noncontent-bottom", 5.0); - dropdown->SetColor("background-color", Colorf::fromRgba8(230, 230, 230)); - dropdown->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); - dropdown->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); - dropdown->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); - dropdown->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); - dropdown->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200)); - dropdown->SetColor("arrow-color", Colorf::fromRgba8(200, 200, 200)); - - scrollbar->SetColor("track-color", Colorf::fromRgba8(210, 210, 220)); - scrollbar->SetColor("thumb-color", Colorf::fromRgba8(180, 180, 180)); - - tabbar->SetDouble("spacer-left", 20.0); - tabbar->SetDouble("spacer-right", 20.0); - tabbar->SetColor("background-color", Colorf::fromRgba8(210, 210, 210)); - - tabbar_tab->SetDouble("noncontent-left", 15.0); - tabbar_tab->SetDouble("noncontent-right", 15.0); - tabbar_tab->SetDouble("noncontent-top", 1.0); - tabbar_tab->SetDouble("noncontent-bottom", 1.0); - tabbar_tab->SetColor("background-color", Colorf::fromRgba8(220, 220, 220)); - tabbar_tab->SetColor("border-left-color", Colorf::fromRgba8(200, 200, 200)); - tabbar_tab->SetColor("border-top-color", Colorf::fromRgba8(200, 200, 200)); - tabbar_tab->SetColor("border-right-color", Colorf::fromRgba8(200, 200, 200)); - tabbar_tab->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); - tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(210, 210, 210)); - tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(240, 240, 240)); - tabbar_tab->SetColor("active", "border-left-color", Colorf::fromRgba8(155, 155, 155)); - tabbar_tab->SetColor("active", "border-top-color", Colorf::fromRgba8(155, 155, 155)); - tabbar_tab->SetColor("active", "border-right-color", Colorf::fromRgba8(155, 155, 155)); - tabbar_tab->SetColor("active", "border-bottom-color", Colorf::transparent()); - - tabbar_spacer->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); - - tabwidget_stack->SetDouble("noncontent-left", 20.0); - tabwidget_stack->SetDouble("noncontent-top", 5.0); - tabwidget_stack->SetDouble("noncontent-right", 20.0); - tabwidget_stack->SetDouble("noncontent-bottom", 5.0); - - checkbox_label->SetColor("checked-outer-border-color", Colorf::fromRgba8(155, 155, 155)); - checkbox_label->SetColor("checked-inner-border-color", Colorf::fromRgba8(200, 200, 200)); - checkbox_label->SetColor("checked-color", Colorf::fromRgba8(50, 50, 50)); - checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(156, 156, 156)); - checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(200, 200, 200)); - - menubar->SetColor("background-color", Colorf::fromRgba8(70, 70, 70)); - - menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219)); - menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); - menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0)); - menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); - menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0)); - - menu->SetDouble("noncontent-left", 5.0); - menu->SetDouble("noncontent-top", 5.0); - menu->SetDouble("noncontent-right", 5.0); - menu->SetDouble("noncontent-bottom", 5.0); - menu->SetColor("background-color", Colorf::fromRgba8(255, 255, 255)); - menu->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155)); - menu->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155)); - menu->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155)); - menu->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155)); - - menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200)); - menuitem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190)); -} +LightWidgetTheme::LightWidgetTheme(): WidgetTheme({ + Colorf::fromRgb(0xF0F0F0), // background + Colorf::fromRgb(0x191919), // + Colorf::fromRgb(0xFAFAFA), // headers / inputs + Colorf::fromRgb(0x191919), // + Colorf::fromRgb(0xC8C8C8), // interactive elements + Colorf::fromRgb(0x000000), // + Colorf::fromRgb(0xD2D2FF), // hover / highlight + Colorf::fromRgb(0x000000), // + Colorf::fromRgb(0xC7B4FF), // click + Colorf::fromRgb(0x000000), // + Colorf::fromRgb(0xA0A0A0), // around elements + Colorf::fromRgb(0xB9B9B9) // between elements +}) {}; diff --git a/libraries/ZWidget/src/core/timer.cpp b/libraries/ZWidget/src/core/timer.cpp index 5ebe2612f..1e395ad59 100644 --- a/libraries/ZWidget/src/core/timer.cpp +++ b/libraries/ZWidget/src/core/timer.cpp @@ -5,9 +5,9 @@ Timer::Timer(Widget* owner) : OwnerObj(owner) { - PrevTimerObj = owner->FirstTimerObj; - if (PrevTimerObj) - PrevTimerObj->PrevTimerObj = this; + NextTimerObj = owner->FirstTimerObj; + if (NextTimerObj) + NextTimerObj->PrevTimerObj = this; owner->FirstTimerObj = this; } @@ -27,7 +27,7 @@ void Timer::Start(int timeoutMilliseconds, bool repeat) { Stop(); - TimerId = DisplayWindow::StartTimer(timeoutMilliseconds, [=]() { + TimerId = DisplayWindow::StartTimer(timeoutMilliseconds, [this,repeat]() { if (!repeat) Stop(); if (FuncExpired) diff --git a/libraries/ZWidget/src/core/truetypefont.cpp b/libraries/ZWidget/src/core/truetypefont.cpp index 757929e3e..d5886d499 100644 --- a/libraries/ZWidget/src/core/truetypefont.cpp +++ b/libraries/ZWidget/src/core/truetypefont.cpp @@ -303,10 +303,10 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos reader.Seek(loca.offsets[glyphIndex]); ttf_int16 numberOfContours = reader.ReadInt16(); - ttf_int16 xMin = reader.ReadInt16(); - ttf_int16 yMin = reader.ReadInt16(); - ttf_int16 xMax = reader.ReadInt16(); - ttf_int16 yMax = reader.ReadInt16(); + /*ttf_int16 xMin =*/ reader.ReadInt16(); + /*ttf_int16 yMin =*/ reader.ReadInt16(); + /*ttf_int16 xMax =*/ reader.ReadInt16(); + /*ttf_int16 yMax =*/ reader.ReadInt16(); if (numberOfContours > 0) // Simple glyph { @@ -450,7 +450,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos if (transform) { - for (int i = childPointsOffset; i < g.points.size(); i++) + for (int i = childPointsOffset; i < (int)g.points.size(); i++) { float x = g.points[i].x * mat2x2[0] + g.points[i].y * mat2x2[1]; float y = g.points[i].x * mat2x2[2] + g.points[i].y * mat2x2[3]; @@ -492,7 +492,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos dy = g.points[parentPointIndex].y - g.points[childPointIndex].y; } - for (int i = childPointsOffset; i < g.points.size(); i++) + for (int i = childPointsOffset; i < (int)g.points.size(); i++) { g.points[i].x += dx; g.points[i].y += dy; @@ -589,9 +589,9 @@ void TrueTypeFont::LoadCharacterMapEncoding(TrueTypeFileReader& reader) for (ttf_uint16 c = startCode; c <= endCode; c++) { int offset = idRangeOffset / 2 + (c - startCode) - ((int)subformat.segCount - i); - if (offset >= 0 && offset < subformat.glyphIdArray.size()) + if (offset >= 0 && offset < (int)subformat.glyphIdArray.size()) { - int glyphId = subformat.glyphIdArray[offset]; + ttf_uint32 glyphId = subformat.glyphIdArray[offset]; if (firstGlyph) { range.startGlyphID = glyphId; @@ -689,9 +689,9 @@ void TTF_CMapSubtable4::Load(TrueTypeFileReader& reader) language = reader.ReadUInt16(); segCount = reader.ReadUInt16() / 2; - ttf_uint16 searchRange = reader.ReadUInt16(); - ttf_uint16 entrySelector = reader.ReadUInt16(); - ttf_uint16 rangeShift = reader.ReadUInt16(); + /*ttf_uint16 searchRange =*/ reader.ReadUInt16(); + /*ttf_uint16 entrySelector =*/ reader.ReadUInt16(); + /*ttf_uint16 rangeShift =*/ reader.ReadUInt16(); endCode.reserve(segCount); startCode.reserve(segCount); @@ -746,9 +746,9 @@ void TTF_TableDirectory::Load(TrueTypeFileReader& reader) numTables = reader.ReadUInt16(); // opentype spec says we can't use these for security reasons, so we pretend they never was part of the header - ttf_uint16 searchRange = reader.ReadUInt16(); - ttf_uint16 entrySelector = reader.ReadUInt16(); - ttf_uint16 rangeShift = reader.ReadUInt16(); + /*ttf_uint16 searchRange =*/ reader.ReadUInt16(); + /*ttf_uint16 entrySelector =*/ reader.ReadUInt16(); + /*ttf_uint16 rangeShift =*/ reader.ReadUInt16(); for (ttf_uint16 i = 0; i < numTables; i++) { diff --git a/libraries/ZWidget/src/core/utf8reader.cpp b/libraries/ZWidget/src/core/utf8reader.cpp index 200da9fdc..64b9c06af 100644 --- a/libraries/ZWidget/src/core/utf8reader.cpp +++ b/libraries/ZWidget/src/core/utf8reader.cpp @@ -71,7 +71,7 @@ unsigned int UTF8Reader::character() else { unsigned int ucs4 = (data[current_position] & bitmask_leadbyte_for_utf8[trailing_bytes]); - for (std::string::size_type i = 0; i < trailing_bytes; i++) + for (std::string::size_type i = 0; i < (size_t)trailing_bytes; i++) { if ((data[current_position + 1 + i] & 0xC0) == 0x80) ucs4 = (ucs4 << 6) + (data[current_position + 1 + i] & 0x3f); @@ -94,7 +94,7 @@ std::string::size_type UTF8Reader::char_length() if (current_position + 1 + trailing_bytes > length) return 1; - for (std::string::size_type i = 0; i < trailing_bytes; i++) + for (std::string::size_type i = 0; i < (size_t)trailing_bytes; i++) { if ((data[current_position + 1 + i] & 0xC0) != 0x80) return 1; @@ -136,7 +136,7 @@ void UTF8Reader::move_to_leadbyte() lead_position--; int trailing_bytes = trailing_bytes_for_utf8[data[lead_position]]; - if (lead_position + trailing_bytes >= current_position) + if (lead_position + trailing_bytes >= (int)current_position) current_position = lead_position; } diff --git a/libraries/ZWidget/src/core/widget.cpp b/libraries/ZWidget/src/core/widget.cpp index 27f6f7b1c..7018a5045 100644 --- a/libraries/ZWidget/src/core/widget.cpp +++ b/libraries/ZWidget/src/core/widget.cpp @@ -173,6 +173,18 @@ void Widget::SetWindowTitle(const std::string& text) } } +std::vector> Widget::GetWindowIcon() const +{ + return WindowIcon; +} + +void Widget::SetWindowIcon(const std::vector>& images) +{ + WindowIcon = images; + if (DispWindow) + DispWindow->SetWindowIcon(WindowIcon); +} + Size Widget::GetSize() const { return ContentGeometry.size(); @@ -365,7 +377,7 @@ void Widget::Repaint() Widget* w = Window(); if (w->DispCanvas) { - w->DispCanvas->begin(WindowBackground); + w->DispCanvas->begin(w->WindowBackground); w->Paint(w->DispCanvas.get()); w->DispCanvas->end(); } diff --git a/libraries/ZWidget/src/widgets/dropdown/dropdown.cpp b/libraries/ZWidget/src/widgets/dropdown/dropdown.cpp index c40d0c02f..a1298a8e7 100644 --- a/libraries/ZWidget/src/widgets/dropdown/dropdown.cpp +++ b/libraries/ZWidget/src/widgets/dropdown/dropdown.cpp @@ -124,7 +124,7 @@ void Dropdown::ClearItems() void Dropdown::SetSelectedItem(int index) { if (index < 0) index = 0; - if (index >= (int)items.size()) index = items.size() - 1; + if (index >= (int)items.size()) index = (int)items.size() - 1; if (selectedItem == index) return; @@ -270,7 +270,7 @@ void Dropdown::OnGeometryChanged() Point pos = MapTo(Window(), Point(0,0)); double width = GetWidth() + GetNoncontentLeft() + GetNoncontentRight(); - double innerH = GetDisplayItems() * 25.0 + 10.0; + double innerH = GetDisplayItems() * 20.0 + 20; double outerH = GetHeight(); pos.x -= GetNoncontentLeft(); @@ -311,14 +311,14 @@ bool Dropdown::OpenDropdown() listView->SetSelectedItem(selectedItem); - listView->OnActivated = [=]() { OnDropdownActivated(); }; - listView->OnChanged = [=](int index) { OnDropdownActivated(); }; + listView->OnActivated = [this]() { OnDropdownActivated(); }; + listView->OnChanged = [this](int index) { OnDropdownActivated(); }; listView->SetFrameGeometry( 0, 0, GetWidth() + GetNoncontentLeft() + GetNoncontentRight(), - GetDisplayItems() * 25.0 + 10.0 + GetDisplayItems() * 20.0 + 20 ); OnGeometryChanged(); diff --git a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp index 336ac9882..445bb34cd 100644 --- a/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp +++ b/libraries/ZWidget/src/widgets/lineedit/lineedit.cpp @@ -9,10 +9,10 @@ LineEdit::LineEdit(Widget* parent) : Widget(parent) SetStyleClass("lineedit"); timer = new Timer(this); - timer->FuncExpired = [=]() { OnTimerExpired(); }; + timer->FuncExpired = [this]() { OnTimerExpired(); }; scroll_timer = new Timer(this); - scroll_timer->FuncExpired = [=]() { OnScrollTimerExpired(); }; + scroll_timer->FuncExpired = [this]() { OnScrollTimerExpired(); }; SetCursor(StandardCursor::ibeam); } @@ -90,12 +90,16 @@ int LineEdit::GetCursorPos() const Size LineEdit::GetTextSize() { Canvas* canvas = GetCanvas(); + if (!canvas) + return Size(0.0, 0.0); return GetVisualTextSize(canvas); } Size LineEdit::GetTextSize(const std::string& str) { Canvas* canvas = GetCanvas(); + if (!canvas) + return Size(0.0, 0.0); return canvas->measureText(str).size(); } @@ -674,7 +678,7 @@ bool LineEdit::InsertText(int pos, const std::string& str) } // checking if insert exceeds max length - if (UTF8Reader::utf8_length(text) + UTF8Reader::utf8_length(str) > max_length) + if (UTF8Reader::utf8_length(text) + UTF8Reader::utf8_length(str) > (size_t)max_length) { return false; } @@ -757,6 +761,9 @@ int LineEdit::GetCharacterIndex(double mouse_x) } Canvas* canvas = GetCanvas(); + if (!canvas) + return 0; + UTF8Reader utf8_reader(text.data(), text.length()); int seek_start = clip_start_offset; @@ -808,7 +815,7 @@ void LineEdit::UpdateTextClipping() if (!canvas) return; - Size text_size = GetVisualTextSize(canvas, clip_start_offset, (int)text.size() - clip_start_offset); + // Size text_size = GetVisualTextSize(canvas, clip_start_offset, (int)text.size() - clip_start_offset); if (cursor_pos < clip_start_offset) clip_start_offset = cursor_pos; @@ -822,7 +829,7 @@ void LineEdit::UpdateTextClipping() utf8_reader.set_position(clip_start_offset); utf8_reader.next(); clip_start_offset = (int)utf8_reader.position(); - if (clip_start_offset == text.size()) + if (clip_start_offset == (int)text.size()) break; cursor_rect = GetCursorRect(); } @@ -837,7 +844,7 @@ void LineEdit::UpdateTextClipping() utf8_reader.set_position(midpoint); utf8_reader.move_to_leadbyte(); - if (midpoint != utf8_reader.position()) + if (midpoint != (int)utf8_reader.position()) utf8_reader.next(); midpoint = (int)utf8_reader.position(); @@ -862,6 +869,8 @@ void LineEdit::UpdateTextClipping() Rect LineEdit::GetCursorRect() { Canvas* canvas = GetCanvas(); + if (!canvas) + return Rect::xywh(0.0, 0.0, 0.0, 0.0); int substr_end = cursor_pos - clip_start_offset; if (substr_end < 0) @@ -890,6 +899,8 @@ Rect LineEdit::GetCursorRect() Rect LineEdit::GetSelectionRect() { Canvas* canvas = GetCanvas(); + if (!canvas) + return Rect::xywh(0.0, 0.0, 0.0, 0.0); // text before selection: @@ -949,6 +960,8 @@ void LineEdit::OnTimerExpired() void LineEdit::OnGeometryChanged() { Canvas* canvas = GetCanvas(); + if (!canvas) + return; vertical_text_align = canvas->verticalTextAlign(); diff --git a/libraries/ZWidget/src/widgets/listview/listview.cpp b/libraries/ZWidget/src/widgets/listview/listview.cpp index 1f771b3b7..263e789bb 100644 --- a/libraries/ZWidget/src/widgets/listview/listview.cpp +++ b/libraries/ZWidget/src/widgets/listview/listview.cpp @@ -6,7 +6,7 @@ ListView::ListView(Widget* parent) : Widget(parent) SetStyleClass("listview"); scrollbar = new Scrollbar(this); - scrollbar->FuncScroll = [=]() { OnScrollbarScroll(); }; + scrollbar->FuncScroll = [this]() { OnScrollbarScroll(); }; SetColumnWidths({ 0.0 }); } @@ -37,7 +37,7 @@ void ListView::SetColumnWidths(const std::vector& widths) void ListView::AddItem(const std::string& text, int index, int column) { - if (column < 0 || column >= columnwidths.size()) + if (column < 0 || column >= (int)columnwidths.size()) return; std::vector newEntry; @@ -47,10 +47,10 @@ void ListView::AddItem(const std::string& text, int index, int column) newEntry[column] = text; if (index >= 0) { - if (index >= items.size()) + if (index >= (int)items.size()) { newEntry[column] = ""; - while (items.size() < index) + while ((int)items.size() < index) items.push_back(newEntry); newEntry[column] = text; @@ -71,7 +71,7 @@ void ListView::AddItem(const std::string& text, int index, int column) void ListView::UpdateItem(const std::string& text, int index, int column) { - if (index < 0 || index >= items.size() || column < 0 || column >= columnwidths.size()) + if (index < 0 || index >= (int)items.size() || column < 0 || column >= (int)columnwidths.size()) return; items[index][column] = text; @@ -80,7 +80,7 @@ void ListView::UpdateItem(const std::string& text, int index, int column) void ListView::RemoveItem(int index) { - if (!items.size() || index >= items.size()) + if (!items.size() || index >= (int)items.size()) return; if (index < 0) @@ -102,7 +102,7 @@ void ListView::Activate() void ListView::SetSelectedItem(int index) { - if (selectedItem != index && index >= 0 && index < items.size()) + if (selectedItem != index && index >= 0 && index < (int)items.size()) { selectedItem = index; if (OnChanged) OnChanged(selectedItem); diff --git a/libraries/ZWidget/src/widgets/menubar/menubar.cpp b/libraries/ZWidget/src/widgets/menubar/menubar.cpp index 37353756f..0da588996 100644 --- a/libraries/ZWidget/src/widgets/menubar/menubar.cpp +++ b/libraries/ZWidget/src/widgets/menubar/menubar.cpp @@ -36,7 +36,7 @@ void Menubar::ShowMenu(MenubarItem* item) if (item->GetOpenCallback()) { openMenu = new Menu(this); - openMenu->onCloseMenu = [=]() { CloseMenu(); }; + openMenu->onCloseMenu = [this]() { CloseMenu(); }; item->GetOpenCallback()(openMenu); if (item->AlignRight) openMenu->SetRightPosition(item->MapToGlobal(Point(item->GetWidth(), item->GetHeight()))); @@ -215,7 +215,7 @@ void Menubar::OnKeyDown(InputKey key) ///////////////////////////////////////////////////////////////////////////// -MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), menubar(menubar), text(text), AlignRight(alignRight) +MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), AlignRight(alignRight), menubar(menubar), text(text) { SetStyleClass("menubaritem"); } diff --git a/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp b/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp index c4e29329a..20ccc444f 100644 --- a/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp +++ b/libraries/ZWidget/src/widgets/scrollbar/scrollbar.cpp @@ -3,13 +3,16 @@ #include "core/colorf.h" #include +#define HIDE_SMALL_BAR false +#define HIDE_SMALL_THUMB true + Scrollbar::Scrollbar(Widget* parent) : Widget(parent) { SetStyleClass("scrollbar"); UpdatePartPositions(); mouse_down_timer = new Timer(this); - mouse_down_timer->FuncExpired = [=]() { OnTimerExpired(); }; + mouse_down_timer->FuncExpired = [this]() { OnTimerExpired(); }; } Scrollbar::~Scrollbar() @@ -295,7 +298,15 @@ void Scrollbar::OnPaint(Canvas* canvas) part_button_increment.render_box(canvas, rect_button_increment); */ - canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight()), 4.0, 0.0, 4.0, 0.0), GetStyleColor("track-color")); + auto height = GetHeight(); + auto paint = rect_thumb.height < height; + + if (HIDE_SMALL_BAR && !paint) return; + + canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), height), 4.0, 0.0, 4.0, 0.0), GetStyleColor("track-color")); + + if (HIDE_SMALL_THUMB && !paint) return; + canvas->fillRect(Rect::shrink(rect_thumb, 4.0, 0.0, 4.0, 0.0), GetStyleColor("thumb-color")); } diff --git a/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp b/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp index ef935a9e8..c3fd0df51 100644 --- a/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp +++ b/libraries/ZWidget/src/widgets/tabwidget/tabwidget.cpp @@ -9,7 +9,7 @@ TabWidget::TabWidget(Widget* parent) : Widget(parent) Bar = new TabBar(this); PageStack = new TabWidgetStack(this); - Bar->OnCurrentChanged = [=]() { OnBarCurrentChanged(); }; + Bar->OnCurrentChanged = [this]() { OnBarCurrentChanged(); }; } int TabWidget::AddTab(Widget* page, const std::string& label) @@ -127,7 +127,7 @@ int TabBar::AddTab(const std::shared_ptr& icon, const std::string& label) TabBarTab* tab = new TabBarTab(this); tab->SetIcon(icon); tab->SetText(label); - tab->OnClick = [=]() { OnTabClicked(tab); }; + tab->OnClick = [this,tab]() { OnTabClicked(tab); }; int pageIndex = (int)Tabs.size(); Tabs.push_back(tab); if (CurrentIndex == -1) diff --git a/libraries/ZWidget/src/widgets/textedit/textedit.cpp b/libraries/ZWidget/src/widgets/textedit/textedit.cpp index 2381332a1..b6ec196e8 100644 --- a/libraries/ZWidget/src/widgets/textedit/textedit.cpp +++ b/libraries/ZWidget/src/widgets/textedit/textedit.cpp @@ -13,11 +13,14 @@ TextEdit::TextEdit(Widget* parent) : Widget(parent) { SetStyleClass("textedit"); + selectionBG = GetStyleColor("selection-color"); + selectionFG = GetStyleColor("color"); + timer = new Timer(this); - timer->FuncExpired = [=]() { OnTimerExpired(); }; + timer->FuncExpired = [this]() { OnTimerExpired(); }; scroll_timer = new Timer(this); - scroll_timer->FuncExpired = [=]() { OnScrollTimerExpired(); }; + scroll_timer->FuncExpired = [this]() { OnScrollTimerExpired(); }; SetCursor(StandardCursor::ibeam); @@ -175,13 +178,13 @@ void TextEdit::SetText(const std::string& text) std::string::size_type end = text.find('\n'); while (end != std::string::npos) { - TextEdit::Line line; + TextEdit::Line line{this}; line.text = text.substr(start, end - start); lines.push_back(line); start = end + 1; end = text.find('\n', start); } - TextEdit::Line line; + TextEdit::Line line{this}; line.text = text.substr(start); lines.push_back(line); @@ -197,13 +200,13 @@ void TextEdit::AddText(const std::string& text) std::string::size_type end = text.find('\n'); while (end != std::string::npos) { - TextEdit::Line line; + TextEdit::Line line{this}; line.text = text.substr(start, end - start); lines.push_back(line); start = end + 1; end = text.find('\n', start); } - TextEdit::Line line; + TextEdit::Line line{this}; line.text = text.substr(start); lines.push_back(line); @@ -434,7 +437,7 @@ void TextEdit::OnKeyDown(InputKey key) if (GetKeyState(InputKey::Shift) && selection_length == 0) selection_start = cursor_pos; - if (cursor_pos.y < lines.size() - 1) + if (cursor_pos.y < (int)lines.size() - 1) { cursor_pos.y++; cursor_pos.x = std::min(lines[cursor_pos.y].text.size(), (size_t)cursor_pos.x); @@ -578,7 +581,7 @@ void TextEdit::OnLostFocus() void TextEdit::CreateComponents() { vert_scrollbar = new Scrollbar(this); - vert_scrollbar->FuncScroll = [=]() { OnVerticalScroll(); }; + vert_scrollbar->FuncScroll = [this]() { OnVerticalScroll(); }; vert_scrollbar->SetVisible(false); vert_scrollbar->SetVertical(); } @@ -714,10 +717,10 @@ TextEdit::ivec2 TextEdit::FindNextBreakCharacter(ivec2 search_start) if (search_start.x >= int(lines[search_start.y].text.size()) - 1) return ivec2(lines[search_start.y].text.size(), search_start.y); - int pos = lines[search_start.y].text.find_first_of(break_characters, search_start.x); + size_t pos = lines[search_start.y].text.find_first_of(break_characters, search_start.x); if (pos == std::string::npos) return ivec2(lines[search_start.y].text.size(), search_start.y); - return ivec2(pos, search_start.y); + return ivec2((int)pos, search_start.y); } TextEdit::ivec2 TextEdit::FindPreviousBreakCharacter(ivec2 search_start) @@ -725,10 +728,10 @@ TextEdit::ivec2 TextEdit::FindPreviousBreakCharacter(ivec2 search_start) search_start.x--; if (search_start.x <= 0) return ivec2(0, search_start.y); - int pos = lines[search_start.y].text.find_last_of(break_characters, search_start.x); + size_t pos = lines[search_start.y].text.find_last_of(break_characters, search_start.x); if (pos == std::string::npos) return ivec2(0, search_start.y); - return ivec2(pos, search_start.y); + return ivec2((int)pos, search_start.y); } void TextEdit::InsertText(ivec2 pos, const std::string& str) @@ -741,7 +744,7 @@ void TextEdit::InsertText(ivec2 pos, const std::string& str) } // checking if insert exceeds max length - if (ToOffset(ivec2(lines[lines.size() - 1].text.size(), lines.size() - 1)) + str.length() > max_length) + if (ToOffset(ivec2((int)lines[lines.size() - 1].text.size(), (int)lines.size() - 1)) + str.length() > (size_t)max_length) { return; } @@ -759,7 +762,7 @@ void TextEdit::InsertText(ivec2 pos, const std::string& str) pos.x += next_newline - start; - Line line; + Line line{this}; line.text = lines[pos.y].text.substr(pos.x); lines.insert(lines.begin() + pos.y + 1, line); lines[pos.y].text = lines[pos.y].text.substr(0, pos.x); @@ -836,9 +839,9 @@ void TextEdit::Del() lines[cursor_pos.y].invalidated = true; Update(); } - else if (cursor_pos.y + 1 < lines.size()) + else if (cursor_pos.y + 1 < (int)lines.size()) { - selection_start = ivec2(lines[cursor_pos.y].text.length(), cursor_pos.y); + selection_start = ivec2((int)lines[cursor_pos.y].text.length(), cursor_pos.y); selection_length = 1; DeleteSelectedText(); } @@ -904,7 +907,7 @@ bool TextEdit::InputMaskAcceptsInput(ivec2 cursor_pos, const std::string& str) std::string::size_type TextEdit::ToOffset(ivec2 pos) const { - if (pos.y < lines.size()) + if (pos.y < (int)lines.size()) { std::string::size_type offset = 0; for (int line = 0; line < pos.y; line++) @@ -927,7 +930,7 @@ std::string::size_type TextEdit::ToOffset(ivec2 pos) const TextEdit::ivec2 TextEdit::FromOffset(std::string::size_type offset) const { int line_offset = 0; - for (int line = 0; line < lines.size(); line++) + for (int line = 0; line < (int)lines.size(); line++) { if (offset <= line_offset + lines[line].text.size()) { @@ -984,9 +987,9 @@ void TextEdit::LayoutLines(Canvas* canvas) line.invalidated = false; } - if (sel_start != sel_end && sel_start.y <= i && sel_end.y >= i) + if (sel_start != sel_end && sel_start.y <= (int)i && sel_end.y >= (int)i) { - line.layout.SetSelectionRange(sel_start.y < i ? 0 : sel_start.x, sel_end.y > i ? line.text.size() : sel_end.x); + line.layout.SetSelectionRange(sel_start.y < (int)i ? 0 : sel_start.x, sel_end.y > (int)i ? line.text.size() : sel_end.x); } else { @@ -996,7 +999,7 @@ void TextEdit::LayoutLines(Canvas* canvas) line.layout.HideCursor(); if (HasFocus()) { - if (cursor_blink_visible && cursor_pos.y == i) + if (cursor_blink_visible && cursor_pos.y == (int)i) { line.layout.SetCursorPos(cursor_pos.x); line.layout.SetCursorColor(textColor); @@ -1035,6 +1038,7 @@ TextEdit::ivec2 TextEdit::GetCharacterIndex(Point mouse_wincoords) return ivec2(clamp(result.offset, (size_t)0, line.text.size()), i); case SpanLayout::HitTestResult::outside_left: return ivec2(0, i); + default: case SpanLayout::HitTestResult::outside_right: return ivec2(line.text.size(), i); } diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp index 1ebc0668f..22c6c139b 100644 --- a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp +++ b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp @@ -281,8 +281,30 @@ bool DBusOpenFileDialog::Show() for (const std::string& uri : uris) { if (uri.size() > 7 && uri.substr(0, 7) == "file://") - outputFilenames.push_back(uri.substr(7)); + outputFilenames.push_back(unescapeUri(uri.substr(7))); } return !uris.empty(); } + +std::string DBusOpenFileDialog::unescapeUri(const std::string& s) +{ + // This needs to be much more advanced to support any kind of uri. Why did they have to pass it as an uri!? idiots. + std::string result; + result.reserve(s.size()); + size_t i = 0; + while (i < s.length()) + { + if (s[i] == '%' && i + 2 < s.length() && s[i + 1] == '2' && s[i + 2] == '0') + { + result += ' '; + i += 3; + } + else + { + result += s[i]; + i++; + } + } + return result; +} diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h index f4efd2663..bf0737154 100644 --- a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h +++ b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h @@ -20,6 +20,8 @@ public: bool Show() override; private: + static std::string unescapeUri(const std::string& s); + std::string ownerHandle; std::string title; std::string initialDirectory; diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp index 7bcd94953..9153d64e2 100644 --- a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp +++ b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp @@ -1,5 +1,8 @@ #include "dbus_open_folder_dialog.h" +#include +#include +#include DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandle(ownerHandle) { @@ -7,20 +10,271 @@ DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandl bool DBusOpenFolderDialog::Show() { - return false; + // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html + + dbus_bool_t bresult = {}; + DBusError error = {}; + dbus_error_init(&error); + + DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (!connection) + { + dbus_error_free(&error); + return false; + } + + std::string busname = dbus_bus_get_unique_name(connection); + + DBusMessage* request = dbus_message_new_method_call("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.FileChooser", "OpenFile"); + if (!request) + { + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + const char* parentWindow = ownerHandle.c_str(); + const char* title = this->title.c_str(); + + DBusMessageIter requestArgs = {}; + dbus_message_iter_init_append(request, &requestArgs); + + bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &parentWindow); + bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &title); + + DBusMessageIter requestOptions = {}; + bresult = dbus_message_iter_open_container(&requestArgs, DBUS_TYPE_ARRAY, "{sv}", &requestOptions); + + // handle_token - s race condition prevention for signal (/org/freedesktop/portal/desktop/request/SENDER/TOKEN) + // accept_label - s text label for the OK button + // modal - b makes dialog modal. Defaults to true. Not really sure what it means since nothing stayed modal on my computer + // directory - b open folder mode, added in version 3 + // filters - a(sa(us)) [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] + // current_filter - sa(us) filter from filters that should be current filter + // choices - a(ssa(ss)s) list of serialized combo boxes to add to the file chooser + // current_name - s suggested name + + // current_folder - ay + if (!initialDirectory.empty()) + { + // Note: the docs unfortunately says "The portal implementation is free to ignore this option" + + const char* key = "current_folder"; + const char* value = initialDirectory.c_str(); + int valueCount = (int)initialDirectory.size() + 1; + + DBusMessageIter entry = {}; + bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry); + bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + DBusMessageIter variant = {}; + DBusMessageIter array = {}; + bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "ay", &variant); + bresult = dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); + bresult = dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &value, valueCount); + bresult = dbus_message_iter_close_container(&variant, &array); + bresult = dbus_message_iter_close_container(&entry, &variant); + + bresult = dbus_message_iter_close_container(&requestOptions, &entry); + } + + // directory - b + { + const char* key = "directory"; + dbus_bool_t value = true; + + DBusMessageIter entry = {}; + bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry); + bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + DBusMessageIter variant = {}; + bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant); + bresult = dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &value); + bresult = dbus_message_iter_close_container(&entry, &variant); + + bresult = dbus_message_iter_close_container(&requestOptions, &entry); + } + + bresult = dbus_message_iter_close_container(&requestArgs, &requestOptions); + + DBusMessage* response = dbus_connection_send_with_reply_and_block(connection, request, DBUS_TIMEOUT_USE_DEFAULT, &error); + if (!response) + { + dbus_message_unref(request); + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + const char* handle = nullptr; + bresult = dbus_message_get_args(response, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID); + if (!bresult) + { + dbus_message_unref(response); + dbus_message_unref(request); + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + std::string signalObjectPath = handle; + + dbus_message_unref(response); + dbus_message_unref(request); + + std::string rule = "type='signal',interface='org.freedesktop.portal.Request',member='Response',path='" + signalObjectPath + "'"; + dbus_bus_add_match(connection, rule.c_str(), &error); + + // Wait for the response signal + // + // To do: process the run loop while we wait + // + DBusMessage* signalmsg = nullptr; + while (!signalmsg && dbus_connection_read_write(connection, -1)) + { + while (true) + { + DBusMessage* message = dbus_connection_pop_message(connection); + if (!message) + break; + + if (dbus_message_is_signal(message, "org.freedesktop.portal.Request", "Response") && dbus_message_get_path(message) == signalObjectPath) + { + signalmsg = message; + break; + } + else + { + dbus_message_unref(message); + } + } + } + dbus_bus_remove_match(connection, rule.c_str(), &error); + + // Read the response + + dbus_uint32_t responseCode = 0; + std::vector uris; + + DBusMessageIter signalArgs; + bresult = dbus_message_iter_init(signalmsg, &signalArgs); + + // response code - u + if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_UINT32) + { + dbus_message_iter_get_basic(&signalArgs, &responseCode); + } + dbus_message_iter_next(&signalArgs); + + // results - a{sv} + if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_ARRAY) + { + DBusMessageIter resultsArray = {}; + dbus_message_iter_recurse(&signalArgs, &resultsArray); + while (true) + { + int type = dbus_message_iter_get_arg_type(&resultsArray); + if (type != DBUS_TYPE_DICT_ENTRY) + break; + + DBusMessageIter entry = {}; + dbus_message_iter_recurse(&resultsArray, &entry); + + const char* key = nullptr; + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + DBusMessageIter value = {}; + dbus_message_iter_recurse(&entry, &value); + + std::string k = key; + if (k == "uris" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // as + { + DBusMessageIter uriArray = {}; + dbus_message_iter_recurse(&value, &uriArray); + while (true) + { + int type = dbus_message_iter_get_arg_type(&uriArray); + if (type != DBUS_TYPE_STRING) + break; + + const char* uri = nullptr; + dbus_message_iter_get_basic(&uriArray, &uri); + uris.push_back(uri); + + dbus_message_iter_next(&uriArray); + } + } + else if (k == "choices" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // a(ss) + { + // An array of pairs of strings, + // the first string being the ID of a combobox that was passed into this call, + // the second string being the selected option. + } + else if (k == "current_filter" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRUCT) // sa(us) + { + // The filter that was selected. + // This may match a filter in the filter list or another filter that was applied unconditionally. + } + + dbus_message_iter_next(&entry); + dbus_message_iter_next(&resultsArray); + } + } + dbus_message_iter_next(&signalArgs); + + dbus_message_unref(signalmsg); + dbus_connection_unref(connection); + dbus_error_free(&error); + + if (responseCode != 0) // User cancelled + return false; + + for (const std::string& uri : uris) + { + if (uri.size() > 7 && uri.substr(0, 7) == "file://") + { + selectedPath = unescapeUri(uri.substr(7)); + break; + } + } + + return !selectedPath.empty(); } std::string DBusOpenFolderDialog::SelectedPath() const { - return selected_path; + return selectedPath; } void DBusOpenFolderDialog::SetInitialDirectory(const std::string& path) { - initial_directory = path; + initialDirectory = path; } void DBusOpenFolderDialog::SetTitle(const std::string& newtitle) { title = newtitle; } + +std::string DBusOpenFolderDialog::unescapeUri(const std::string& s) +{ + // This needs to be much more advanced to support any kind of uri. Why did they have to pass it as an uri!? idiots. + std::string result; + result.reserve(s.size()); + size_t i = 0; + while (i < s.length()) + { + if (s[i] == '%' && i + 2 < s.length() && s[i + 1] == '2' && s[i + 2] == '0') + { + result += ' '; + i += 3; + } + else + { + result += s[i]; + i++; + } + } + return result; +} diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h index 3e40cc7bc..059443979 100644 --- a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h +++ b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h @@ -13,9 +13,10 @@ public: void SetTitle(const std::string& newtitle) override; private: - std::string ownerHandle; + static std::string unescapeUri(const std::string& s); - std::string selected_path; - std::string initial_directory; + std::string ownerHandle; std::string title; + std::string initialDirectory; + std::string selectedPath; }; diff --git a/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp index a71a4a1ca..dea0b8f0f 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp +++ b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp @@ -1,4 +1,3 @@ - #include "sdl2_display_window.h" #include #include @@ -33,7 +32,7 @@ SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWi if (result != 0) throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); } - + WindowList[SDL_GetWindowID(Handle.window)] = this; } @@ -73,8 +72,7 @@ std::vector SDL2DisplayWindow::GetVulkanInstanceExtensions() VkSurfaceKHR SDL2DisplayWindow::CreateVulkanSurface(VkInstance instance) { VkSurfaceKHR surfaceHandle = {}; - SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle); - if (surfaceHandle) + if (SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle) != SDL_TRUE) throw std::runtime_error("Could not create vulkan surface"); return surfaceHandle; } @@ -84,6 +82,12 @@ void SDL2DisplayWindow::SetWindowTitle(const std::string& text) SDL_SetWindowTitle(Handle.window, text.c_str()); } +void SDL2DisplayWindow::SetWindowIcon(const std::vector>& images) +{ + // To do: call SDL_SetWindowIcon for the highest resolution image (or maybe the first 32x32 or higher? not sure what is best here) + // To do: when this is upgraded to SDL3, call SDL_AddSurfaceAlternateImage for all the images +} + void SDL2DisplayWindow::SetWindowFrame(const Rect& box) { // SDL2 doesn't really seem to have an API for this. @@ -423,14 +427,14 @@ SDL2DisplayWindow* SDL2DisplayWindow::FindEventWindow(const SDL_Event& event) int windowID; switch (event.type) { - case SDL_WINDOWEVENT: windowID = event.window.windowID; break; - case SDL_TEXTINPUT: windowID = event.text.windowID; break; - case SDL_KEYUP: windowID = event.key.windowID; break; - case SDL_KEYDOWN: windowID = event.key.windowID; break; - case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break; + case SDL_WINDOWEVENT: windowID = event.window.windowID; break; + case SDL_TEXTINPUT: windowID = event.text.windowID; break; + case SDL_KEYUP: windowID = event.key.windowID; break; + case SDL_KEYDOWN: windowID = event.key.windowID; break; + case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break; case SDL_MOUSEBUTTONDOWN: windowID = event.button.windowID; break; - case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break; - case SDL_MOUSEMOTION: windowID = event.motion.windowID; break; + case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break; + case SDL_MOUSEMOTION: windowID = event.motion.windowID; break; default: if (event.type == PaintEventNumber) windowID = event.user.windowID; else return nullptr; @@ -451,14 +455,14 @@ void SDL2DisplayWindow::DispatchEvent(const SDL_Event& event) switch (event.type) { - case SDL_WINDOWEVENT: window->OnWindowEvent(event.window); break; - case SDL_TEXTINPUT: window->OnTextInput(event.text); break; - case SDL_KEYUP: window->OnKeyUp(event.key); break; - case SDL_KEYDOWN: window->OnKeyDown(event.key); break; - case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp(event.button); break; + case SDL_WINDOWEVENT: window->OnWindowEvent (event.window); break; + case SDL_TEXTINPUT: window->OnTextInput (event.text); break; + case SDL_KEYUP: window->OnKeyUp (event.key); break; + case SDL_KEYDOWN: window->OnKeyDown (event.key); break; + case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp (event.button); break; case SDL_MOUSEBUTTONDOWN: window->OnMouseButtonDown(event.button); break; - case SDL_MOUSEWHEEL: window->OnMouseWheel(event.wheel); break; - case SDL_MOUSEMOTION: window->OnMouseMotion(event.motion); break; + case SDL_MOUSEWHEEL: window->OnMouseWheel (event.wheel); break; + case SDL_MOUSEMOTION: window->OnMouseMotion (event.motion); break; default: if (event.type == PaintEventNumber) window->OnPaintEvent(); } @@ -468,13 +472,39 @@ void SDL2DisplayWindow::OnWindowEvent(const SDL_WindowEvent& event) { switch (event.event) { - case SDL_WINDOWEVENT_CLOSE: WindowHost->OnWindowClose(); break; - case SDL_WINDOWEVENT_MOVED: WindowHost->OnWindowGeometryChanged(); break; - case SDL_WINDOWEVENT_RESIZED: WindowHost->OnWindowGeometryChanged(); break; - case SDL_WINDOWEVENT_SHOWN: WindowHost->OnWindowPaint(); break; - case SDL_WINDOWEVENT_EXPOSED: WindowHost->OnWindowPaint(); break; - case SDL_WINDOWEVENT_FOCUS_GAINED: WindowHost->OnWindowActivated(); break; - case SDL_WINDOWEVENT_FOCUS_LOST: WindowHost->OnWindowDeactivated(); break; + case SDL_WINDOWEVENT_CLOSE: + WindowHost->OnWindowClose(); + break; + + case SDL_WINDOWEVENT_MOVED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_RESIZED: + WindowHost->OnWindowGeometryChanged(); + break; + + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_EXPOSED: + WindowHost->OnWindowPaint(); + break; + + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_FOCUS_LOST: + WindowHost->OnWindowDeactivated(); + break; + + case SDL_WINDOWEVENT_NONE: + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_ENTER: + case SDL_WINDOWEVENT_LEAVE: + case SDL_WINDOWEVENT_TAKE_FOCUS: + case SDL_WINDOWEVENT_HIT_TEST: + case SDL_WINDOWEVENT_ICCPROF_CHANGED: + case SDL_WINDOWEVENT_DISPLAY_CHANGED: + // nope + break; } } @@ -788,5 +818,5 @@ SDL_Scancode SDL2DisplayWindow::InputKeyToScancode(InputKey inputkey) case InputKey::RControl: return SDL_SCANCODE_RCTRL; case InputKey::Tilde: return SDL_SCANCODE_GRAVE; default: return (SDL_Scancode)0; - } + } } diff --git a/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h index 5a510878e..759980db8 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h +++ b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h @@ -13,6 +13,7 @@ public: ~SDL2DisplayWindow(); void SetWindowTitle(const std::string& text) override; + void SetWindowIcon(const std::vector>& images) override; void SetWindowFrame(const Rect& box) override; void SetClientFrame(const Rect& box) override; void Show() override; diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp b/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp index d99283248..f969b25df 100644 --- a/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp +++ b/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp @@ -1,5 +1,6 @@ #include "wayland_display_backend.h" #include "wayland_display_window.h" +#include #ifdef USE_DBUS #include "window/dbus/dbus_open_file_dialog.h" @@ -8,469 +9,496 @@ #endif WaylandDisplayBackend::WaylandDisplayBackend() + :m_DataOfferMimeTypes(0, hash_proxy) { - if (!s_waylandDisplay) - throw std::runtime_error("Wayland Display initialization failed!"); + if (!s_waylandDisplay) + throw std::runtime_error("Wayland Display initialization failed!"); - s_waylandRegistry = s_waylandDisplay.get_registry(); + s_waylandRegistry = s_waylandDisplay.get_registry(); - s_waylandRegistry.on_global() = [&](uint32_t name, std::string interface, uint32_t version) { - if (interface == wayland::compositor_t::interface_name) - s_waylandRegistry.bind(name, m_waylandCompositor, 3); - if (interface == wayland::shm_t::interface_name) - s_waylandRegistry.bind(name, m_waylandSHM, version); - if (interface == wayland::output_t::interface_name) - s_waylandRegistry.bind(name, m_waylandOutput, version); - if (interface == wayland::seat_t::interface_name) - s_waylandRegistry.bind(name, m_waylandSeat, 8); - if (interface == wayland::data_device_manager_t::interface_name) - s_waylandRegistry.bind(name, m_DataDeviceManager, 3); - if (interface == wayland::xdg_wm_base_t::interface_name) - s_waylandRegistry.bind(name, m_XDGWMBase, 4); - if (interface == wayland::zxdg_output_manager_v1_t::interface_name) - s_waylandRegistry.bind(name, m_XDGOutputManager, version); - if (interface == wayland::zxdg_exporter_v2_t::interface_name) - s_waylandRegistry.bind(name, m_XDGExporter, 1); - if (interface == wayland::zwp_pointer_constraints_v1_t::interface_name) - s_waylandRegistry.bind(name, m_PointerConstraints, 1); - if (interface == wayland::xdg_activation_v1_t::interface_name) - s_waylandRegistry.bind(name, m_XDGActivation, 1); - if (interface == wayland::zxdg_decoration_manager_v1_t::interface_name) - s_waylandRegistry.bind(name, m_XDGDecorationManager, 1); - if (interface == wayland::fractional_scale_manager_v1_t::interface_name) - s_waylandRegistry.bind(name, m_FractionalScaleManager, 1); - if (interface == wayland::zwp_relative_pointer_manager_v1_t::interface_name) - s_waylandRegistry.bind(name, m_RelativePointerManager, 1); - }; + s_waylandRegistry.on_global() = [&](uint32_t name, std::string interface, uint32_t version) { + if (interface == wayland::compositor_t::interface_name) + s_waylandRegistry.bind(name, m_waylandCompositor, 3); + if (interface == wayland::shm_t::interface_name) + s_waylandRegistry.bind(name, m_waylandSHM, version); + if (interface == wayland::output_t::interface_name) + s_waylandRegistry.bind(name, m_waylandOutput, version); + if (interface == wayland::seat_t::interface_name) + s_waylandRegistry.bind(name, m_waylandSeat, 8); + if (interface == wayland::data_device_manager_t::interface_name) + s_waylandRegistry.bind(name, m_DataDeviceManager, 3); + if (interface == wayland::xdg_wm_base_t::interface_name) + s_waylandRegistry.bind(name, m_XDGWMBase, 4); + if (interface == wayland::zxdg_output_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGOutputManager, version); + if (interface == wayland::zxdg_exporter_v2_t::interface_name) + s_waylandRegistry.bind(name, m_XDGExporter, 1); + if (interface == wayland::zwp_pointer_constraints_v1_t::interface_name) + s_waylandRegistry.bind(name, m_PointerConstraints, 1); + if (interface == wayland::xdg_activation_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGActivation, 1); + if (interface == wayland::zxdg_decoration_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGDecorationManager, 1); + if (interface == wayland::fractional_scale_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_FractionalScaleManager, 1); + if (interface == wayland::zwp_relative_pointer_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_RelativePointerManager, 1); + if (interface == wayland::xdg_toplevel_icon_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGToplevelIconManager, 1); + if (interface == wayland::cursor_shape_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_CursorShapeManager, 2); + }; - s_waylandDisplay.roundtrip(); + s_waylandDisplay.roundtrip(); - if (!m_XDGWMBase) - throw std::runtime_error("WaylandDisplayBackend: XDG-Shell is required!"); + if (!m_XDGWMBase) + throw std::runtime_error("WaylandDisplayBackend: XDG-Shell is required!"); - if (!m_XDGOutputManager) - throw std::runtime_error("WaylandDisplayBackend: xdg-output-manager-v1 is required!"); + if (!m_XDGOutputManager) + throw std::runtime_error("WaylandDisplayBackend: xdg-output-manager-v1 is required!"); - if (!m_XDGExporter) - throw std::runtime_error("WaylandDisplayBackend: xdg-foreign-unstable-v2 is required!"); + if (!m_XDGExporter) + throw std::runtime_error("WaylandDisplayBackend: xdg-foreign-unstable-v2 is required!"); - if (!m_PointerConstraints) - throw std::runtime_error("WaylandDisplayBackend: pointer-constrains-unstable-v1 is required!"); + if (!m_PointerConstraints) + throw std::runtime_error("WaylandDisplayBackend: pointer-constrains-unstable-v1 is required!"); - if (!m_RelativePointerManager) - throw std::runtime_error("WaylandDisplayBackend: relative-pointer-unstable-v1 is required!"); + if (!m_RelativePointerManager) + throw std::runtime_error("WaylandDisplayBackend: relative-pointer-unstable-v1 is required!"); - m_waylandOutput.on_mode() = [this] (wayland::output_mode flags, int32_t width, int32_t height, int32_t refresh) { - s_ScreenSize = Size(width, height); - }; + m_waylandOutput.on_mode() = [this] (wayland::output_mode flags, int32_t width, int32_t height, int32_t refresh) { + s_ScreenSize = Size(width, height); + }; - m_XDGWMBase.on_ping() = [this] (uint32_t serial) { - m_XDGWMBase.pong(serial); - }; + m_XDGWMBase.on_ping() = [this] (uint32_t serial) { + m_XDGWMBase.pong(serial); + }; - m_waylandSeat.on_capabilities() = [this] (uint32_t capabilities) { - hasKeyboard = capabilities & wayland::seat_capability::keyboard; - hasPointer = capabilities & wayland::seat_capability::pointer; - }; + m_waylandSeat.on_capabilities() = [this] (uint32_t capabilities) { + hasKeyboard = capabilities & wayland::seat_capability::keyboard; + hasPointer = capabilities & wayland::seat_capability::pointer; + }; - m_XDGOutput = m_XDGOutputManager.get_xdg_output(m_waylandOutput); + m_XDGOutput = m_XDGOutputManager.get_xdg_output(m_waylandOutput); - m_XDGOutput.on_logical_position() = [this] (int32_t x, int32_t y) { - //m_WindowGlobalPos = Point(x, y); - }; + m_XDGOutput.on_logical_position() = [this] (int32_t x, int32_t y) { + //m_WindowGlobalPos = Point(x, y); + }; - m_XDGOutput.on_logical_size() = [this] (int32_t width, int32_t height) { - s_ScreenSize = Size(width, height); - }; + m_XDGOutput.on_logical_size() = [this] (int32_t width, int32_t height) { + s_ScreenSize = Size(width, height); + }; - if (!m_FractionalScaleManager) + if (!m_FractionalScaleManager) { - m_waylandOutput.on_scale() = [this] (int32_t scale) { + m_waylandOutput.on_scale() = [this] (int32_t scale) { for (WaylandDisplayWindow* w : s_Windows) { w->m_ScaleFactor = scale; w->m_NeedsUpdate = true; w->windowHost->OnWindowDpiScaleChanged(); } - }; + }; } - s_waylandDisplay.roundtrip(); + // Data Device for receiving contents + m_DataDevice = m_DataDeviceManager.get_data_device(m_waylandSeat); + + m_DataDevice.on_data_offer() = [&] (wayland::data_offer_t offer) + { + m_DataOfferMimeTypes[offer] = {}; + + offer.on_offer() = [this, offer] (std::string const& mime_type) + { + m_DataOfferMimeTypes[offer].insert(mime_type); + }; + }; + + m_DataDevice.on_selection() = [&] (wayland::data_offer_t offer) + { + // Clipboard is empty + if (!offer) + return; + + // There shouldn't be any previous data_offer events + if (!m_DataOfferMimeTypes.count(offer)) + return; + + // Reject anything that's not text/plain + if (!m_DataOfferMimeTypes[offer].count("text/plain")) + { + m_DataOfferMimeTypes.erase(offer); + return; + } + + int fds[2]; + pipe(fds); + offer.receive("text/plain", fds[1]); + close(fds[1]); + + s_waylandDisplay.roundtrip(); + + m_ClipboardContents.clear(); + + // Assign the read data to m_ClipboardContents + while (true) + { + std::string buf(1024, 0); + auto n = read(fds[0], const_cast(buf.c_str()), buf.size()); + if (n <= 0) + break; + + buf.resize(n); + m_ClipboardContents += buf; + } + close(fds[0]); + + m_DataOfferMimeTypes.erase(offer); + }; + + s_waylandDisplay.roundtrip(); // To do: this shouldn't really be fatal. The user might have forgotten to plug in their keyboard or mouse. - if (!hasKeyboard) - throw std::runtime_error("No keyboard detected!"); - if (!hasPointer) - throw std::runtime_error("No pointer device detected!"); + if (!hasKeyboard) + throw std::runtime_error("No keyboard detected!"); + if (!hasPointer) + throw std::runtime_error("No pointer device detected!"); - m_waylandKeyboard = m_waylandSeat.get_keyboard(); - m_waylandPointer = m_waylandSeat.get_pointer(); - m_RelativePointer = m_RelativePointerManager.get_relative_pointer(m_waylandPointer); + m_waylandKeyboard = m_waylandSeat.get_keyboard(); + m_waylandPointer = m_waylandSeat.get_pointer(); + m_RelativePointer = m_RelativePointerManager.get_relative_pointer(m_waylandPointer); + + if (m_CursorShapeManager && m_waylandPointer) + { + // Opt to Cursor Shape Protocol instead + m_CursorShapeDevice = m_CursorShapeManager.get_pointer(m_waylandPointer); + } ConnectDeviceEvents(); - m_cursorSurface = m_waylandCompositor.create_surface(); - SetCursor(StandardCursor::arrow); - -/* - m_DataDevice = m_DataDeviceManager.get_data_device(m_waylandSeat); - - m_DataSource = m_DataDeviceManager.create_data_source(); - m_DataSource.offer("text/plain"); - - m_DataSource.on_send() = [&] (std::string mime_type, int fd) { - if (mime_type != "text/plain") - return; - - if (!m_ClipboardContents.empty()) - write(fd, m_ClipboardContents.data(), m_ClipboardContents.size()); - close(fd); - }; - - m_DataDevice.on_selection() = [&] (wayland::data_offer_t dataOffer) { - m_ClipboardContents.clear(); - - if (!dataOffer) - // Clipboard is empty - return; - - int fds[2]; - pipe(fds); - - dataOffer.receive("text/plain", fds[1]); - close(fds[1]); - - m_waylandDisplay.roundtrip(); - - while (true) - { - char buf[1024]; - - ssize_t n = read(fds[0], buf, sizeof(buf)); - - if (n <= 0) - break; - - m_ClipboardContents += buf; - } - - close(fds[0]); - - dataOffer.proxy_release(); - }; -*/ + m_cursorSurface = m_waylandCompositor.create_surface(); + SetCursor(StandardCursor::arrow); } WaylandDisplayBackend::~WaylandDisplayBackend() { - if (m_KeymapContext) - xkb_context_unref(m_KeymapContext); + if (m_KeymapContext) + xkb_context_unref(m_KeymapContext); - if (m_KeyboardState) - xkb_state_unref(m_KeyboardState); + if (m_KeyboardState) + xkb_state_unref(m_KeyboardState); } void WaylandDisplayBackend::ConnectDeviceEvents() { - m_KeymapContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + m_KeymapContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - m_waylandKeyboard.on_keymap() = [this] (wayland::keyboard_keymap_format format, int fd, uint32_t size) { - if (format != wayland::keyboard_keymap_format::xkb_v1) - throw std::runtime_error("WaylandDisplayBackend: Unrecognized keymap format!"); + m_waylandKeyboard.on_keymap() = [this] (wayland::keyboard_keymap_format format, int fd, uint32_t size) { + if (format != wayland::keyboard_keymap_format::xkb_v1) + throw std::runtime_error("WaylandDisplayBackend: Unrecognized keymap format!"); - char* mapSHM = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (mapSHM == MAP_FAILED) - throw std::runtime_error("WaylandDisplayBackend: Keymap shared memory allocation failed!"); + char* mapSHM = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapSHM == MAP_FAILED) + throw std::runtime_error("WaylandDisplayBackend: Keymap shared memory allocation failed!"); - if (m_Keymap) - xkb_keymap_unref(m_Keymap); + if (m_Keymap) + xkb_keymap_unref(m_Keymap); - m_Keymap = xkb_keymap_new_from_string(m_KeymapContext, mapSHM, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); - munmap(mapSHM, size); - close(fd); + m_Keymap = xkb_keymap_new_from_string(m_KeymapContext, mapSHM, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(mapSHM, size); + close(fd); - if (m_KeyboardState) - xkb_state_unref(m_KeyboardState); + if (m_KeyboardState) + xkb_state_unref(m_KeyboardState); - m_KeyboardState = xkb_state_new(m_Keymap); - }; + m_KeyboardState = xkb_state_new(m_Keymap); + }; - m_waylandKeyboard.on_modifiers() = [this] (uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - xkb_state_update_mask(m_KeyboardState, mods_depressed, mods_latched, mods_locked, 0, 0, group); - }; + m_waylandKeyboard.on_modifiers() = [this] (uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + xkb_state_update_mask(m_KeyboardState, mods_depressed, mods_latched, mods_locked, 0, 0, group); + }; - m_waylandKeyboard.on_enter() = [this] (uint32_t serial, wayland::surface_t surfaceEntered, wayland::array_t keys) { - std::vector keysVec = keys; + m_waylandKeyboard.on_enter() = [this] (uint32_t serial, wayland::surface_t surfaceEntered, wayland::array_t keys) { + std::vector keysVec = keys; - m_KeyboardSerial = serial; + m_KeyboardSerial = serial; // Find the window to focus on by checking the surface window owns. - if (!m_FocusWindow || m_FocusWindow->GetWindowSurface() != surfaceEntered) - { - for (auto win: s_Windows) - { - if (win->GetWindowSurface() == surfaceEntered) - m_FocusWindow = win; - } - } + if (!m_FocusWindow || m_FocusWindow->GetWindowSurface() != surfaceEntered) + { + for (auto win: s_Windows) + { + if (win->GetWindowSurface() == surfaceEntered) + m_FocusWindow = win; + } + } + for (auto key: keysVec) + { + // keys parameter represents the keys pressed when entering the surface + // key variable is Linux evdev scancode, to translate it to XKB keycode, we must add 8 + xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); + OnKeyboardKeyEvent(sym, wayland::keyboard_key_state::pressed); - for (auto key: keysVec) - { - // keys parameter represents the keys pressed when entering the surface - // key variable is Linux evdev scancode, to translate it to XKB keycode, we must add 8 - xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); - OnKeyboardKeyEvent(sym, wayland::keyboard_key_state::pressed); + // Also cause a Char event + char buf[128]; + xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); - // Also cause a Char event - char buf[128]; - xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); + OnKeyboardCharEvent(buf, wayland::keyboard_key_state::pressed); + } + }; - OnKeyboardCharEvent(buf, wayland::keyboard_key_state::pressed); - } - }; + m_waylandKeyboard.on_key() = [this] (uint32_t serial, uint32_t time, uint32_t key, wayland::keyboard_key_state state) { + // key is Linux evdev scancode, to translate it to XKB keycode, we must add 8 + xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); + OnKeyboardKeyEvent(sym, state); - m_waylandKeyboard.on_key() = [this] (uint32_t serial, uint32_t time, uint32_t key, wayland::keyboard_key_state state) { - // key is Linux evdev scancode, to translate it to XKB keycode, we must add 8 - xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); - OnKeyboardKeyEvent(sym, state); + // Also cause a Char event + char buf[128]; + xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); - // Also cause a Char event - char buf[128]; - xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); + OnKeyboardCharEvent(buf, state); - OnKeyboardCharEvent(buf, state); + //m_DataDevice.set_selection(m_DataSource, m_KeyboardSerial); + }; - //m_DataDevice.set_selection(m_DataSource, m_KeyboardSerial); - }; + m_waylandPointer.on_enter() = [this](uint32_t serial, wayland::surface_t surfaceEntered, double surfaceX, double surfaceY) { + // Keep track of the mouse serial for using it in Cursor Shape protocol + m_MouseSerial = serial; - m_waylandPointer.on_enter() = [this](uint32_t serial, wayland::surface_t surfaceEntered, double surfaceX, double surfaceY) { // Find the window to focus on by checking the surface window owns. - if (!m_MouseFocusWindow || m_MouseFocusWindow->GetWindowSurface() != surfaceEntered) - { - for (auto win: s_Windows) - { - if (win->GetWindowSurface() == surfaceEntered) - { - m_MouseFocusWindow = win; - } - } - } + if (!m_MouseFocusWindow || m_MouseFocusWindow->GetWindowSurface() != surfaceEntered) + { + for (auto win: s_Windows) + { + if (win->GetWindowSurface() == surfaceEntered) + { + m_MouseFocusWindow = win; + } + } + } currentPointerEvent.event_mask |= POINTER_EVENT_ENTER; currentPointerEvent.serial = serial; - currentPointerEvent.surfaceX = surfaceX; - currentPointerEvent.surfaceY = surfaceY; - }; + currentPointerEvent.surfaceX = surfaceX; + currentPointerEvent.surfaceY = surfaceY; + }; - m_waylandPointer.on_leave() = [this](uint32_t serial, wayland::surface_t surfaceLeft) { - currentPointerEvent.event_mask |= POINTER_EVENT_LEAVE; - currentPointerEvent.serial = serial; - }; + m_waylandPointer.on_leave() = [this](uint32_t serial, wayland::surface_t surfaceLeft) { + currentPointerEvent.event_mask |= POINTER_EVENT_LEAVE; + currentPointerEvent.serial = serial; + }; - m_waylandPointer.on_motion() = [this] (uint32_t serial, double surfaceX, double surfaceY) { - currentPointerEvent.event_mask |= POINTER_EVENT_MOTION; - currentPointerEvent.serial = serial; - currentPointerEvent.surfaceX = surfaceX; - currentPointerEvent.surfaceY = surfaceY; - }; + m_waylandPointer.on_motion() = [this] (uint32_t serial, double surfaceX, double surfaceY) { + currentPointerEvent.event_mask |= POINTER_EVENT_MOTION; + currentPointerEvent.serial = serial; + currentPointerEvent.surfaceX = surfaceX; + currentPointerEvent.surfaceY = surfaceY; + }; - m_waylandPointer.on_button() = [this] (uint32_t serial, uint32_t time, uint32_t button, wayland::pointer_button_state state) { - currentPointerEvent.event_mask |= POINTER_EVENT_BUTTON; - currentPointerEvent.serial = serial; - currentPointerEvent.time = time; - currentPointerEvent.button = button; - currentPointerEvent.state = state; - }; + m_waylandPointer.on_button() = [this] (uint32_t serial, uint32_t time, uint32_t button, wayland::pointer_button_state state) { + currentPointerEvent.event_mask |= POINTER_EVENT_BUTTON; + currentPointerEvent.serial = serial; + currentPointerEvent.time = time; + currentPointerEvent.button = button; + currentPointerEvent.state = state; + }; - m_waylandPointer.on_axis() = [this] (uint32_t serial, wayland::pointer_axis axis, double value) { - currentPointerEvent.event_mask |= POINTER_EVENT_AXIS; - currentPointerEvent.serial = serial; - currentPointerEvent.axes[uint32_t(axis)].value = value; - currentPointerEvent.axes[uint32_t(axis)].valid = true; - }; + m_waylandPointer.on_axis() = [this] (uint32_t serial, wayland::pointer_axis axis, double value) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS; + currentPointerEvent.serial = serial; + currentPointerEvent.axes[uint32_t(axis)].value = value; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + }; - // High resolution scroll event - m_waylandPointer.on_axis_value120() = [this] (wayland::pointer_axis axis, int32_t value) { - currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_120; - currentPointerEvent.axes[uint32_t(axis)].valid = true; - currentPointerEvent.axes[uint32_t(axis)].value120 = value; - }; + // High resolution scroll event + m_waylandPointer.on_axis_value120() = [this] (wayland::pointer_axis axis, int32_t value) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_120; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + currentPointerEvent.axes[uint32_t(axis)].value120 = value; + }; - m_waylandPointer.on_axis_source() = [this] (wayland::pointer_axis_source axis) { - currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_SOURCE; - currentPointerEvent.axis_source = axis; - }; + m_waylandPointer.on_axis_source() = [this] (wayland::pointer_axis_source axis) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_SOURCE; + currentPointerEvent.axis_source = axis; + }; - m_waylandPointer.on_axis_stop() = [this] (uint32_t time, wayland::pointer_axis axis) { - currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_STOP; - currentPointerEvent.time = time; - currentPointerEvent.axes[uint32_t(axis)].valid = true; - }; + m_waylandPointer.on_axis_stop() = [this] (uint32_t time, wayland::pointer_axis axis) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_STOP; + currentPointerEvent.time = time; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + }; - m_RelativePointer.on_relative_motion() = [this] (uint32_t utime_hi, uint32_t utime_lo, - double dx, double dy, - double dx_unaccel, double dy_unaccel) { - currentPointerEvent.event_mask |= POINTER_EVENT_RELATIVE_MOTION; - currentPointerEvent.time = utime_lo; - currentPointerEvent.dx = dx; - currentPointerEvent.dy = dy; - }; + m_RelativePointer.on_relative_motion() = [this] (uint32_t utime_hi, uint32_t utime_lo, + double dx, double dy, + double dx_unaccel, double dy_unaccel) { + currentPointerEvent.event_mask |= POINTER_EVENT_RELATIVE_MOTION; + currentPointerEvent.time = utime_lo; + currentPointerEvent.dx = dx; + currentPointerEvent.dy = dy; + currentPointerEvent.dx_unaccel = dx_unaccel; + currentPointerEvent.dy_unaccel = dy_unaccel; + }; - m_waylandPointer.on_frame() = [this] () { - if (currentPointerEvent.event_mask & POINTER_EVENT_ENTER) - OnMouseEnterEvent(currentPointerEvent.serial); + m_waylandPointer.on_frame() = [this] () { + if (currentPointerEvent.event_mask & POINTER_EVENT_ENTER) + OnMouseEnterEvent(currentPointerEvent.serial); - if (currentPointerEvent.event_mask & POINTER_EVENT_LEAVE) - OnMouseLeaveEvent(); + if (currentPointerEvent.event_mask & POINTER_EVENT_LEAVE) + OnMouseLeaveEvent(); - if (currentPointerEvent.event_mask & POINTER_EVENT_MOTION) - { - OnMouseMoveEvent(Point(currentPointerEvent.surfaceX, currentPointerEvent.surfaceY)); - } + if (currentPointerEvent.event_mask & POINTER_EVENT_MOTION) + { + OnMouseMoveEvent(Point(currentPointerEvent.surfaceX, currentPointerEvent.surfaceY)); + } - if (currentPointerEvent.event_mask & POINTER_EVENT_RELATIVE_MOTION) - { - if (hasMouseLock) - OnMouseMoveRawEvent(int(currentPointerEvent.dx), int(currentPointerEvent.dy)); - } + if (currentPointerEvent.event_mask & POINTER_EVENT_RELATIVE_MOTION) + { + if (hasMouseLock) + OnMouseMoveRawEvent(int(currentPointerEvent.dx_unaccel), int(currentPointerEvent.dy_unaccel)); + } - if (currentPointerEvent.event_mask & POINTER_EVENT_BUTTON) - { - InputKey ikey = LinuxInputEventCodeToInputKey(currentPointerEvent.button); - if (currentPointerEvent.state == wayland::pointer_button_state::pressed) - OnMousePressEvent(ikey); - else // released - OnMouseReleaseEvent(ikey); - } + if (currentPointerEvent.event_mask & POINTER_EVENT_BUTTON) + { + InputKey ikey = LinuxInputEventCodeToInputKey(currentPointerEvent.button); + if (currentPointerEvent.state == wayland::pointer_button_state::pressed) + OnMousePressEvent(ikey); + else // released + OnMouseReleaseEvent(ikey); + } - uint32_t axisevents = POINTER_EVENT_AXIS | POINTER_EVENT_AXIS_120 | POINTER_EVENT_AXIS_SOURCE | POINTER_EVENT_AXIS_STOP; + uint32_t axisevents = POINTER_EVENT_AXIS | POINTER_EVENT_AXIS_120 | POINTER_EVENT_AXIS_SOURCE | POINTER_EVENT_AXIS_STOP; - if (currentPointerEvent.event_mask & axisevents) - { - for (size_t idx = 0 ; idx < 2 ; idx++) - { - if (!currentPointerEvent.axes[idx].valid) - continue; + if (currentPointerEvent.event_mask & axisevents) + { + for (size_t idx = 0 ; idx < 2 ; idx++) + { + if (!currentPointerEvent.axes[idx].valid) + continue; - if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS_120) - { - if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value > 0) - OnMouseWheelEvent(InputKey::MouseWheelDown); - if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value < 0) - OnMouseWheelEvent(InputKey::MouseWheelUp); - } - else if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS) - { - if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 > 0) - OnMouseWheelEvent(InputKey::MouseWheelDown); - if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 < 0) - OnMouseWheelEvent(InputKey::MouseWheelUp); - } - } - } + if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS_120) + { + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value > 0) + OnMouseWheelEvent(InputKey::MouseWheelDown); + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value < 0) + OnMouseWheelEvent(InputKey::MouseWheelUp); + } + else if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS) + { + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 > 0) + OnMouseWheelEvent(InputKey::MouseWheelDown); + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 < 0) + OnMouseWheelEvent(InputKey::MouseWheelUp); + } + } + } - // Reset everything once all the events are processed - currentPointerEvent = {0}; - }; + // Reset everything once all the events are processed + currentPointerEvent = {0}; + }; - m_keyboardDelayTimer = ZTimer(); - m_keyboardRepeatTimer = ZTimer(); + m_keyboardDelayTimer = ZTimer(); + m_keyboardRepeatTimer = ZTimer(); - m_waylandKeyboard.on_repeat_info() = [this] (int32_t rate, int32_t delay) { - // rate is characters per second, delay is in milliseconds - m_keyboardDelayTimer.SetDuration(ZTimer::Duration(delay)); - m_keyboardRepeatTimer.SetDuration(ZTimer::Duration(1000.0 / rate)); - }; + m_waylandKeyboard.on_repeat_info() = [this] (int32_t rate, int32_t delay) { + // rate is characters per second, delay is in milliseconds + m_keyboardDelayTimer.SetDuration(ZTimer::Duration(delay)); + m_keyboardRepeatTimer.SetDuration(ZTimer::Duration(1000.0 / rate)); + }; - m_keyboardDelayTimer.SetCallback([this] () { OnKeyboardDelayEnd(); }); - m_keyboardRepeatTimer.SetCallback([this] () { OnKeyboardRepeat(); }); + m_keyboardDelayTimer.SetCallback([this] () { OnKeyboardDelayEnd(); }); + m_keyboardRepeatTimer.SetCallback([this] () { OnKeyboardRepeat(); }); - m_keyboardRepeatTimer.SetRepeating(true); + m_keyboardRepeatTimer.SetRepeating(true); - m_previousTime = ZTimer::Clock::now(); - m_currentTime = ZTimer::Clock::now(); + m_previousTime = ZTimer::Clock::now(); + m_currentTime = ZTimer::Clock::now(); } void WaylandDisplayBackend::OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state) { - InputKey inputKey = XKBKeySymToInputKey(xkbKeySym); - - if (state == wayland::keyboard_key_state::pressed) - { - inputKeyStates[inputKey] = true; - m_FocusWindow->windowHost->OnWindowKeyDown(inputKey); - if (inputKey != previousKey) - { - previousKey = inputKey; - m_keyboardDelayTimer.Stop(); - m_keyboardRepeatTimer.Stop(); - } - m_keyboardDelayTimer.Start(); - } - if (state == wayland::keyboard_key_state::released) - { - inputKeyStates[inputKey] = false; - if (m_FocusWindow) - m_FocusWindow->windowHost->OnWindowKeyUp(inputKey); - m_keyboardDelayTimer.Stop(); - m_keyboardRepeatTimer.Stop(); - } + InputKey inputKey = XKBKeySymToInputKey(xkbKeySym); + if (state == wayland::keyboard_key_state::pressed) + { + inputKeyStates[inputKey] = true; + m_FocusWindow->windowHost->OnWindowKeyDown(inputKey); + if (inputKey != previousKey) + { + previousKey = inputKey; + m_keyboardDelayTimer.Stop(); + m_keyboardRepeatTimer.Stop(); + } + m_keyboardDelayTimer.Start(); + } + if (state == wayland::keyboard_key_state::released) + { + inputKeyStates[inputKey] = false; + if (m_FocusWindow) + m_FocusWindow->windowHost->OnWindowKeyUp(inputKey); + m_keyboardDelayTimer.Stop(); + m_keyboardRepeatTimer.Stop(); + } } void WaylandDisplayBackend::OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state) { - if (state == wayland::keyboard_key_state::pressed) - { - previousChars = std::string(ch); - m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); - } + if (state == wayland::keyboard_key_state::pressed) + { + previousChars = std::string(ch); + m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); + } } void WaylandDisplayBackend::OnKeyboardDelayEnd() { - if (inputKeyStates[previousKey]) - m_keyboardRepeatTimer.Start(); + if (inputKeyStates[previousKey]) + m_keyboardRepeatTimer.Start(); } void WaylandDisplayBackend::OnKeyboardRepeat() { - if (inputKeyStates[previousKey]) - { - m_FocusWindow->windowHost->OnWindowKeyDown(previousKey); - m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); - } + if (inputKeyStates[previousKey]) + { + m_FocusWindow->windowHost->OnWindowKeyDown(previousKey); + m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); + } } void WaylandDisplayBackend::OnMouseEnterEvent(uint32_t serial) { - m_cursorSurface.attach(!hasMouseLock ? m_cursorBuffer : nullptr, 0, 0); - if (!hasMouseLock) - m_cursorSurface.damage(0, 0, m_cursorImage.width(), m_cursorImage.height()); - m_cursorSurface.commit(); - m_waylandPointer.set_cursor(serial, m_cursorSurface, 0, 0); + if (!m_CursorShapeDevice) + { + m_cursorSurface.attach(!hasMouseLock ? m_cursorBuffer : nullptr, 0, 0); + if (!hasMouseLock) + { + m_cursorSurface.damage(0, 0, m_cursorImage.width(), m_cursorImage.height()); + } + + m_cursorSurface.commit(); + } + m_waylandPointer.set_cursor(serial, !hasMouseLock ? m_cursorSurface : nullptr, 0, 0); } void WaylandDisplayBackend::OnMouseLeaveEvent() { - if (m_MouseFocusWindow) - { - m_MouseFocusWindow->windowHost->OnWindowMouseLeave(); - //m_MouseFocusWindow = nullptr; // Borks up the menus - } + if (m_MouseFocusWindow) + { + m_MouseFocusWindow->windowHost->OnWindowMouseLeave(); + //m_MouseFocusWindow = nullptr; // Borks up the menus + } } void WaylandDisplayBackend::OnMousePressEvent(InputKey button) { - if (m_MouseFocusWindow) - m_MouseFocusWindow->windowHost->OnWindowMouseDown(m_MouseFocusWindow->m_SurfaceMousePos, button); + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseDown(m_MouseFocusWindow->m_SurfaceMousePos, button); } void WaylandDisplayBackend::OnMouseReleaseEvent(InputKey button) { - if (m_MouseFocusWindow) - m_MouseFocusWindow->windowHost->OnWindowMouseUp(m_MouseFocusWindow->m_SurfaceMousePos, button); + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseUp(m_MouseFocusWindow->m_SurfaceMousePos, button); } void WaylandDisplayBackend::OnMouseMoveEvent(Point surfacePos) @@ -484,31 +512,39 @@ void WaylandDisplayBackend::OnMouseMoveEvent(Point surfacePos) void WaylandDisplayBackend::OnMouseMoveRawEvent(int dx, int dy) { - if (m_MouseFocusWindow) - m_MouseFocusWindow->windowHost->OnWindowRawMouseMove(dx, dy); + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowRawMouseMove(dx, dy); } void WaylandDisplayBackend::OnMouseWheelEvent(InputKey button) { - if (m_MouseFocusWindow) - m_MouseFocusWindow->windowHost->OnWindowMouseWheel(m_MouseFocusWindow->m_SurfaceMousePos, button); + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseWheel(m_MouseFocusWindow->m_SurfaceMousePos, button); } void WaylandDisplayBackend::SetCursor(StandardCursor cursor) { - std::string cursorName = GetWaylandCursorName(cursor); + if (m_CursorShapeDevice) + { + // Use cursor shapes protocol + m_CursorShapeDevice.set_shape(m_MouseSerial, GetWaylandCursorShape(cursor)); + } + else + { + // Use the older cursor buffer approach + std::string cursorName = GetWaylandCursorName(cursor); - // Perhaps the cursor size can be inferred from the user prefs as well? - wayland::cursor_theme_t cursorTheme = wayland::cursor_theme_t("default", 16, m_waylandSHM); - wayland::cursor_t obtainedCursor = cursorTheme.get_cursor(cursorName); - m_cursorImage = obtainedCursor.image(0); - m_cursorBuffer = m_cursorImage.get_buffer(); + // Perhaps the cursor size can be inferred from the user prefs as well? + wayland::cursor_theme_t cursorTheme = wayland::cursor_theme_t("default", 16, m_waylandSHM); + wayland::cursor_t obtainedCursor = cursorTheme.get_cursor(cursorName); + m_cursorImage = obtainedCursor.image(0); + m_cursorBuffer = m_cursorImage.get_buffer(); + } } void WaylandDisplayBackend::ShowCursor(bool enable) { - m_cursorSurface.attach(enable ? m_cursorBuffer : nullptr, 0, 0); - m_cursorSurface.commit(); + m_waylandPointer.set_cursor(m_MouseSerial, enable ? m_cursorSurface : nullptr, 0, 0); } std::unique_ptr WaylandDisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) @@ -518,101 +554,161 @@ std::unique_ptr WaylandDisplayBackend::Create(DisplayWindowHost* void WaylandDisplayBackend::OnWindowCreated(WaylandDisplayWindow* window) { - s_Windows.push_back(window); - if (!window->m_PopupWindow) - { - m_FocusWindow = window; - m_MouseFocusWindow = window; - } + s_Windows.push_back(window); + if (!window->m_PopupWindow) + { + m_FocusWindow = window; + m_MouseFocusWindow = window; + } } void WaylandDisplayBackend::OnWindowDestroyed(WaylandDisplayWindow* window) { - auto it = std::find(s_Windows.begin(), s_Windows.end(), window); - if (it != s_Windows.end()) - s_Windows.erase(it); + auto it = std::find(s_Windows.begin(), s_Windows.end(), window); + if (it != s_Windows.end()) + s_Windows.erase(it); - if (m_FocusWindow == window) - m_FocusWindow = nullptr; + if (m_FocusWindow == window) + m_FocusWindow = nullptr; - if (m_MouseFocusWindow == window) - m_MouseFocusWindow = nullptr; + if (m_MouseFocusWindow == window) + m_MouseFocusWindow = nullptr; } void WaylandDisplayBackend::ProcessEvents() { - while (wl_display_prepare_read(s_waylandDisplay)) - s_waylandDisplay.dispatch_pending(); + CheckNeedsUpdate(); - while (wl_display_flush(s_waylandDisplay) < 0 && errno == EAGAIN) - poll_single(s_waylandDisplay.get_fd(), POLLOUT, -1); + while (wl_display_prepare_read(s_waylandDisplay)) + s_waylandDisplay.dispatch_pending(); - if (poll_single(s_waylandDisplay.get_fd(), POLLIN, 0) & POLLIN) - { - wl_display_read_events(s_waylandDisplay); - s_waylandDisplay.dispatch_pending(); - } - else - wl_display_cancel_read(s_waylandDisplay); + while (wl_display_flush(s_waylandDisplay) < 0 && errno == EAGAIN) + poll_single(s_waylandDisplay.get_fd(), POLLOUT, -1); - if (s_waylandDisplay.get_error()) - throw std::runtime_error("Wayland Protocol Error"); + if (poll_single(s_waylandDisplay.get_fd(), POLLIN, 0) & POLLIN) + { + wl_display_read_events(s_waylandDisplay); + s_waylandDisplay.dispatch_pending(); + } + else + wl_display_cancel_read(s_waylandDisplay); + + if (s_waylandDisplay.get_error()) + throw std::runtime_error("Wayland Protocol Error"); + + UpdateTimers(); } void WaylandDisplayBackend::RunLoop() { - exitRunLoop = false; + exitRunLoop = false; - while (!exitRunLoop) - { - CheckNeedsUpdate(); - UpdateTimers(); + while (!exitRunLoop && !s_Windows.empty()) + { + ProcessEvents(); + WaitForEvents(GetTimerTimeout()); + } +} - ProcessEvents(); - } +void WaylandDisplayBackend::WaitForEvents(int timeout) +{ + poll_single(s_waylandDisplay.get_fd(), POLLIN, -1); +} + +static int64_t GetTimePoint() +{ + using namespace std::chrono; + return (int64_t)(duration_cast(steady_clock::now().time_since_epoch()).count()); +} + +int WaylandDisplayBackend::GetTimerTimeout() +{ + if (m_timers.empty()) + return 0; + + int64_t nextTime = m_timers.front()->nextTime; + for (auto& timer : m_timers) + { + nextTime = std::min(nextTime, timer->nextTime); + } + + int64_t now = GetTimePoint(); + return (int)std::max(nextTime - now, (int64_t)1); } void WaylandDisplayBackend::UpdateTimers() { - m_currentTime = ZTimer::Clock::now(); + m_currentTime = ZTimer::Clock::now(); - m_keyboardDelayTimer.Update(m_currentTime - m_previousTime); - m_keyboardRepeatTimer.Update(m_currentTime - m_previousTime); + m_keyboardDelayTimer.Update(m_currentTime - m_previousTime); + m_keyboardRepeatTimer.Update(m_currentTime - m_previousTime); - m_previousTime = m_currentTime; + m_previousTime = m_currentTime; + + int64_t now = GetTimePoint(); + + // The callback may stop timers. Iterators might invalidate. + while (true) + { + std::shared_ptr foundTimer; + for (auto& timer : m_timers) + { + if (timer->nextTime < now) + { + foundTimer = timer; + break; + } + } + + if (!foundTimer) + break; + + // Not very precise, but these aren't high precision timers + foundTimer->nextTime = now + foundTimer->timeoutMilliseconds; + foundTimer->onTimer(); + } } void WaylandDisplayBackend::CheckNeedsUpdate() { - for (auto window: s_Windows) - { - if (window->m_NeedsUpdate) - { - window->m_NeedsUpdate = false; - window->windowHost->OnWindowPaint(); - } - } -} - -void WaylandDisplayBackend::ExitLoop() -{ - exitRunLoop = true; -} - -Size WaylandDisplayBackend::GetScreenSize() -{ - return s_ScreenSize; + for (auto window: s_Windows) + { + if (window->m_NeedsUpdate) + { + window->m_NeedsUpdate = false; + window->windowHost->OnWindowPaint(); + } + } } void* WaylandDisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) { - throw std::logic_error("unimplemented: WaylandDisplayBackend::StartTimer"); + timeoutMilliseconds = std::max(timeoutMilliseconds, 1); + m_timers.push_back(std::make_shared(timeoutMilliseconds, onTimer, GetTimePoint() + timeoutMilliseconds)); + return m_timers.back().get(); } void WaylandDisplayBackend::StopTimer(void* timerID) { - throw std::logic_error("unimplemented: WaylandDisplayBackend::StopTimer"); + for (auto it = m_timers.begin(); it != m_timers.end(); ++it) + { + if (it->get() == timerID) + { + m_timers.erase(it); + return; + } + } +} + +void WaylandDisplayBackend::ExitLoop() +{ + exitRunLoop = true; +} + +Size WaylandDisplayBackend::GetScreenSize() +{ + return s_ScreenSize; } #ifdef USE_DBUS @@ -643,509 +739,543 @@ std::unique_ptr WaylandDisplayBackend::CreateOpenFolderDialog( bool WaylandDisplayBackend::GetKeyState(InputKey key) { - auto it = inputKeyStates.find(key); + auto it = inputKeyStates.find(key); - // if the key isn't "registered", then it is not pressed. - if (it == inputKeyStates.end()) - return false; + // if the key isn't "registered", then it is not pressed. + if (it == inputKeyStates.end()) + return false; - return it->second; + return it->second; } InputKey WaylandDisplayBackend::XKBKeySymToInputKey(xkb_keysym_t keySym) { - switch (keySym) - { - case XKB_KEY_Escape: - return InputKey::Escape; - case XKB_KEY_1: - return InputKey::_1; - case XKB_KEY_2: - return InputKey::_2; - case XKB_KEY_3: - return InputKey::_3; - case XKB_KEY_4: - return InputKey::_4; - case XKB_KEY_5: - return InputKey::_5; - case XKB_KEY_6: - return InputKey::_6; - case XKB_KEY_7: - return InputKey::_7; - case XKB_KEY_8: - return InputKey::_8; - case XKB_KEY_9: - return InputKey::_9; - case XKB_KEY_0: - return InputKey::_0; - case XKB_KEY_KP_1: - return InputKey::NumPad1; - case XKB_KEY_KP_2: - return InputKey::NumPad2; - case XKB_KEY_KP_3: - return InputKey::NumPad3; - case XKB_KEY_KP_4: - return InputKey::NumPad4; - case XKB_KEY_KP_5: - return InputKey::NumPad5; - case XKB_KEY_KP_6: - return InputKey::NumPad6; - case XKB_KEY_KP_7: - return InputKey::NumPad7; - case XKB_KEY_KP_8: - return InputKey::NumPad8; - case XKB_KEY_KP_9: - return InputKey::NumPad9; - case XKB_KEY_KP_0: - return InputKey::NumPad0; - case XKB_KEY_F1: - return InputKey::F1; - case XKB_KEY_F2: - return InputKey::F2; - case XKB_KEY_F3: - return InputKey::F3; - case XKB_KEY_F4: - return InputKey::F4; - case XKB_KEY_F5: - return InputKey::F5; - case XKB_KEY_F6: - return InputKey::F6; - case XKB_KEY_F7: - return InputKey::F7; - case XKB_KEY_F8: - return InputKey::F8; - case XKB_KEY_F9: - return InputKey::F9; - case XKB_KEY_F10: - return InputKey::F10; - case XKB_KEY_F11: - return InputKey::F11; - case XKB_KEY_F12: - return InputKey::F12; - case XKB_KEY_F13: - return InputKey::F13; - case XKB_KEY_F14: - return InputKey::F14; - case XKB_KEY_F15: - return InputKey::F15; - case XKB_KEY_F16: - return InputKey::F16; - case XKB_KEY_F17: - return InputKey::F17; - case XKB_KEY_F18: - return InputKey::F18; - case XKB_KEY_F19: - return InputKey::F19; - case XKB_KEY_F20: - return InputKey::F20; - case XKB_KEY_F21: - return InputKey::F21; - case XKB_KEY_F22: - return InputKey::F22; - case XKB_KEY_F23: - return InputKey::F23; - case XKB_KEY_F24: - return InputKey::F24; - case XKB_KEY_minus: - case XKB_KEY_KP_Subtract: - return InputKey::Minus; - case XKB_KEY_equal: - return InputKey::Equals; - case XKB_KEY_BackSpace: - return InputKey::Backspace; - case XKB_KEY_backslash: - return InputKey::Backslash; - case XKB_KEY_Tab: - return InputKey::Tab; - case XKB_KEY_braceleft: - return InputKey::LeftBracket; - case XKB_KEY_braceright: - return InputKey::RightBracket; - case XKB_KEY_Control_L: - case XKB_KEY_Control_R: - return InputKey::Ctrl; - case XKB_KEY_Alt_L: - case XKB_KEY_Alt_R: - return InputKey::Alt; - case XKB_KEY_Delete: - return InputKey::Delete; - case XKB_KEY_semicolon: - return InputKey::Semicolon; - case XKB_KEY_comma: - return InputKey::Comma; - case XKB_KEY_period: - return InputKey::Period; - case XKB_KEY_Num_Lock: - return InputKey::NumLock; - case XKB_KEY_Caps_Lock: - return InputKey::CapsLock; - case XKB_KEY_Scroll_Lock: - return InputKey::ScrollLock; - case XKB_KEY_Shift_L: - return InputKey::LShift; - case XKB_KEY_Shift_R: - return InputKey::RShift; - case XKB_KEY_grave: - return InputKey::Tilde; - case XKB_KEY_apostrophe: - return InputKey::SingleQuote; - case XKB_KEY_KP_Enter: - case XKB_KEY_Return: - return InputKey::Enter; - case XKB_KEY_space: - return InputKey::Space; + switch (keySym) + { + case XKB_KEY_Escape: + return InputKey::Escape; + case XKB_KEY_1: + return InputKey::_1; + case XKB_KEY_2: + return InputKey::_2; + case XKB_KEY_3: + return InputKey::_3; + case XKB_KEY_4: + return InputKey::_4; + case XKB_KEY_5: + return InputKey::_5; + case XKB_KEY_6: + return InputKey::_6; + case XKB_KEY_7: + return InputKey::_7; + case XKB_KEY_8: + return InputKey::_8; + case XKB_KEY_9: + return InputKey::_9; + case XKB_KEY_0: + return InputKey::_0; + case XKB_KEY_KP_1: + return InputKey::NumPad1; + case XKB_KEY_KP_2: + return InputKey::NumPad2; + case XKB_KEY_KP_3: + return InputKey::NumPad3; + case XKB_KEY_KP_4: + return InputKey::NumPad4; + case XKB_KEY_KP_5: + return InputKey::NumPad5; + case XKB_KEY_KP_6: + return InputKey::NumPad6; + case XKB_KEY_KP_7: + return InputKey::NumPad7; + case XKB_KEY_KP_8: + return InputKey::NumPad8; + case XKB_KEY_KP_9: + return InputKey::NumPad9; + case XKB_KEY_KP_0: + return InputKey::NumPad0; + case XKB_KEY_F1: + return InputKey::F1; + case XKB_KEY_F2: + return InputKey::F2; + case XKB_KEY_F3: + return InputKey::F3; + case XKB_KEY_F4: + return InputKey::F4; + case XKB_KEY_F5: + return InputKey::F5; + case XKB_KEY_F6: + return InputKey::F6; + case XKB_KEY_F7: + return InputKey::F7; + case XKB_KEY_F8: + return InputKey::F8; + case XKB_KEY_F9: + return InputKey::F9; + case XKB_KEY_F10: + return InputKey::F10; + case XKB_KEY_F11: + return InputKey::F11; + case XKB_KEY_F12: + return InputKey::F12; + case XKB_KEY_F13: + return InputKey::F13; + case XKB_KEY_F14: + return InputKey::F14; + case XKB_KEY_F15: + return InputKey::F15; + case XKB_KEY_F16: + return InputKey::F16; + case XKB_KEY_F17: + return InputKey::F17; + case XKB_KEY_F18: + return InputKey::F18; + case XKB_KEY_F19: + return InputKey::F19; + case XKB_KEY_F20: + return InputKey::F20; + case XKB_KEY_F21: + return InputKey::F21; + case XKB_KEY_F22: + return InputKey::F22; + case XKB_KEY_F23: + return InputKey::F23; + case XKB_KEY_F24: + return InputKey::F24; + case XKB_KEY_minus: + case XKB_KEY_KP_Subtract: + return InputKey::Minus; + case XKB_KEY_equal: + return InputKey::Equals; + case XKB_KEY_BackSpace: + return InputKey::Backspace; + case XKB_KEY_backslash: + return InputKey::Backslash; + case XKB_KEY_Tab: + return InputKey::Tab; + case XKB_KEY_braceleft: + return InputKey::LeftBracket; + case XKB_KEY_braceright: + return InputKey::RightBracket; + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + return InputKey::Ctrl; + case XKB_KEY_Alt_L: + case XKB_KEY_Alt_R: + return InputKey::Alt; + case XKB_KEY_Delete: + return InputKey::Delete; + case XKB_KEY_semicolon: + return InputKey::Semicolon; + case XKB_KEY_comma: + return InputKey::Comma; + case XKB_KEY_period: + return InputKey::Period; + case XKB_KEY_Num_Lock: + return InputKey::NumLock; + case XKB_KEY_Caps_Lock: + return InputKey::CapsLock; + case XKB_KEY_Scroll_Lock: + return InputKey::ScrollLock; + case XKB_KEY_Shift_L: + case XKB_KEY_Shift_R: + return InputKey::Shift; + case XKB_KEY_grave: + return InputKey::Tilde; + case XKB_KEY_apostrophe: + return InputKey::SingleQuote; + case XKB_KEY_KP_Enter: + case XKB_KEY_Return: + return InputKey::Enter; + case XKB_KEY_space: + return InputKey::Space; - case XKB_KEY_Up: - return InputKey::Up; - case XKB_KEY_Down: - return InputKey::Down; - case XKB_KEY_Left: - return InputKey::Left; - case XKB_KEY_Right: - return InputKey::Right; + case XKB_KEY_Up: + return InputKey::Up; + case XKB_KEY_Down: + return InputKey::Down; + case XKB_KEY_Left: + return InputKey::Left; + case XKB_KEY_Right: + return InputKey::Right; - case XKB_KEY_A: - case XKB_KEY_a: - return InputKey::A; - case XKB_KEY_B: - case XKB_KEY_b: - return InputKey::B; - case XKB_KEY_C: - case XKB_KEY_c: - return InputKey::C; - case XKB_KEY_D: - case XKB_KEY_d: - return InputKey::D; - case XKB_KEY_E: - case XKB_KEY_e: - return InputKey::E; - case XKB_KEY_F: - case XKB_KEY_f: - return InputKey::F; - case XKB_KEY_G: - case XKB_KEY_g: - return InputKey::G; - case XKB_KEY_H: - case XKB_KEY_h: - return InputKey::H; - case XKB_KEY_I: - case XKB_KEY_i: - return InputKey::I; - case XKB_KEY_J: - case XKB_KEY_j: - return InputKey::J; - case XKB_KEY_K: - case XKB_KEY_k: - return InputKey::K; - case XKB_KEY_L: - case XKB_KEY_l: - return InputKey::L; - case XKB_KEY_M: - case XKB_KEY_m: - return InputKey::M; - case XKB_KEY_N: - case XKB_KEY_n: - return InputKey::N; - case XKB_KEY_O: - case XKB_KEY_o: - return InputKey::O; - case XKB_KEY_P: - case XKB_KEY_p: - return InputKey::P; - case XKB_KEY_Q: - case XKB_KEY_q: - return InputKey::Q; - case XKB_KEY_R: - case XKB_KEY_r: - return InputKey::R; - case XKB_KEY_S: - case XKB_KEY_s: - return InputKey::S; - case XKB_KEY_T: - case XKB_KEY_t: - return InputKey::T; - case XKB_KEY_U: - case XKB_KEY_u: - return InputKey::U; - case XKB_KEY_V: - case XKB_KEY_v: - return InputKey::V; - case XKB_KEY_W: - case XKB_KEY_w: - return InputKey::W; - case XKB_KEY_X: - case XKB_KEY_x: - return InputKey::X; - case XKB_KEY_Y: - case XKB_KEY_y: - return InputKey::Y; - case XKB_KEY_Z: - case XKB_KEY_z: - return InputKey::Z; + case XKB_KEY_A: + case XKB_KEY_a: + return InputKey::A; + case XKB_KEY_B: + case XKB_KEY_b: + return InputKey::B; + case XKB_KEY_C: + case XKB_KEY_c: + return InputKey::C; + case XKB_KEY_D: + case XKB_KEY_d: + return InputKey::D; + case XKB_KEY_E: + case XKB_KEY_e: + return InputKey::E; + case XKB_KEY_F: + case XKB_KEY_f: + return InputKey::F; + case XKB_KEY_G: + case XKB_KEY_g: + return InputKey::G; + case XKB_KEY_H: + case XKB_KEY_h: + return InputKey::H; + case XKB_KEY_I: + case XKB_KEY_i: + return InputKey::I; + case XKB_KEY_J: + case XKB_KEY_j: + return InputKey::J; + case XKB_KEY_K: + case XKB_KEY_k: + return InputKey::K; + case XKB_KEY_L: + case XKB_KEY_l: + return InputKey::L; + case XKB_KEY_M: + case XKB_KEY_m: + return InputKey::M; + case XKB_KEY_N: + case XKB_KEY_n: + return InputKey::N; + case XKB_KEY_O: + case XKB_KEY_o: + return InputKey::O; + case XKB_KEY_P: + case XKB_KEY_p: + return InputKey::P; + case XKB_KEY_Q: + case XKB_KEY_q: + return InputKey::Q; + case XKB_KEY_R: + case XKB_KEY_r: + return InputKey::R; + case XKB_KEY_S: + case XKB_KEY_s: + return InputKey::S; + case XKB_KEY_T: + case XKB_KEY_t: + return InputKey::T; + case XKB_KEY_U: + case XKB_KEY_u: + return InputKey::U; + case XKB_KEY_V: + case XKB_KEY_v: + return InputKey::V; + case XKB_KEY_W: + case XKB_KEY_w: + return InputKey::W; + case XKB_KEY_X: + case XKB_KEY_x: + return InputKey::X; + case XKB_KEY_Y: + case XKB_KEY_y: + return InputKey::Y; + case XKB_KEY_Z: + case XKB_KEY_z: + return InputKey::Z; - case XKB_KEY_NoSymbol: - case XKB_KEY_VoidSymbol: - return InputKey::None; - default: - return InputKey::None; - } + case XKB_KEY_NoSymbol: + case XKB_KEY_VoidSymbol: + return InputKey::None; + default: + return InputKey::None; + } } InputKey WaylandDisplayBackend::LinuxInputEventCodeToInputKey(uint32_t inputCode) { - switch (inputCode) - { - // Keyboard - // Probably not needed due to the existence of XKBKeySym - case KEY_ESC: - return InputKey::Escape; - case KEY_1: - return InputKey::_1; - case KEY_2: - return InputKey::_2; - case KEY_3: - return InputKey::_3; - case KEY_4: - return InputKey::_4; - case KEY_5: - return InputKey::_5; - case KEY_6: - return InputKey::_6; - case KEY_7: - return InputKey::_7; - case KEY_8: - return InputKey::_8; - case KEY_9: - return InputKey::_9; - case KEY_0: - return InputKey::_0; - case KEY_KP1: - return InputKey::NumPad1; - case KEY_KP2: - return InputKey::NumPad2; - case KEY_KP3: - return InputKey::NumPad3; - case KEY_KP4: - return InputKey::NumPad4; - case KEY_KP5: - return InputKey::NumPad5; - case KEY_KP6: - return InputKey::NumPad6; - case KEY_KP7: - return InputKey::NumPad7; - case KEY_KP8: - return InputKey::NumPad8; - case KEY_KP9: - return InputKey::NumPad9; - case KEY_KP0: - return InputKey::NumPad0; - case KEY_F1: - return InputKey::F1; - case KEY_F2: - return InputKey::F2; - case KEY_F3: - return InputKey::F3; - case KEY_F4: - return InputKey::F4; - case KEY_F5: - return InputKey::F5; - case KEY_F6: - return InputKey::F6; - case KEY_F7: - return InputKey::F7; - case KEY_F8: - return InputKey::F8; - case KEY_F9: - return InputKey::F9; - case KEY_F10: - return InputKey::F10; - case KEY_F11: - return InputKey::F11; - case KEY_F12: - return InputKey::F12; - case KEY_F13: - return InputKey::F13; - case KEY_F14: - return InputKey::F14; - case KEY_F15: - return InputKey::F15; - case KEY_F16: - return InputKey::F16; - case KEY_F17: - return InputKey::F17; - case KEY_F18: - return InputKey::F18; - case KEY_F19: - return InputKey::F19; - case KEY_F20: - return InputKey::F20; - case KEY_F21: - return InputKey::F21; - case KEY_F22: - return InputKey::F22; - case KEY_F23: - return InputKey::F23; - case KEY_F24: - return InputKey::F24; - case KEY_MINUS: - case KEY_KPMINUS: - return InputKey::Minus; - case KEY_EQUAL: - return InputKey::Equals; - case KEY_BACKSPACE: - return InputKey::Backspace; - case KEY_BACKSLASH: - return InputKey::Backslash; - case KEY_TAB: - return InputKey::Tab; - case KEY_LEFTBRACE: - return InputKey::LeftBracket; - case KEY_RIGHTBRACE: - return InputKey::RightBracket; - case KEY_LEFTCTRL: - return InputKey::LControl; - case KEY_RIGHTCTRL: - return InputKey::RControl; - case KEY_LEFTALT: - case KEY_RIGHTALT: - return InputKey::Alt; - case KEY_DELETE: - return InputKey::Delete; - case KEY_SEMICOLON: - return InputKey::Semicolon; - case KEY_COMMA: - return InputKey::Comma; - case KEY_DOT: - return InputKey::Period; - case KEY_NUMLOCK: - return InputKey::NumLock; - case KEY_CAPSLOCK: - return InputKey::CapsLock; - case KEY_SCROLLDOWN: - return InputKey::ScrollLock; - case KEY_LEFTSHIFT: - return InputKey::LShift; - case KEY_RIGHTSHIFT: - return InputKey::RShift; - case KEY_GRAVE: - return InputKey::Tilde; - case KEY_APOSTROPHE: - return InputKey::SingleQuote; - case KEY_SPACE: - return InputKey::Space; - case KEY_ENTER: - case KEY_KPENTER: - return InputKey::Enter; + switch (inputCode) + { + // Keyboard + // Probably not needed due to the existence of XKBKeySym + case KEY_ESC: + return InputKey::Escape; + case KEY_1: + return InputKey::_1; + case KEY_2: + return InputKey::_2; + case KEY_3: + return InputKey::_3; + case KEY_4: + return InputKey::_4; + case KEY_5: + return InputKey::_5; + case KEY_6: + return InputKey::_6; + case KEY_7: + return InputKey::_7; + case KEY_8: + return InputKey::_8; + case KEY_9: + return InputKey::_9; + case KEY_0: + return InputKey::_0; + case KEY_KP1: + return InputKey::NumPad1; + case KEY_KP2: + return InputKey::NumPad2; + case KEY_KP3: + return InputKey::NumPad3; + case KEY_KP4: + return InputKey::NumPad4; + case KEY_KP5: + return InputKey::NumPad5; + case KEY_KP6: + return InputKey::NumPad6; + case KEY_KP7: + return InputKey::NumPad7; + case KEY_KP8: + return InputKey::NumPad8; + case KEY_KP9: + return InputKey::NumPad9; + case KEY_KP0: + return InputKey::NumPad0; + case KEY_F1: + return InputKey::F1; + case KEY_F2: + return InputKey::F2; + case KEY_F3: + return InputKey::F3; + case KEY_F4: + return InputKey::F4; + case KEY_F5: + return InputKey::F5; + case KEY_F6: + return InputKey::F6; + case KEY_F7: + return InputKey::F7; + case KEY_F8: + return InputKey::F8; + case KEY_F9: + return InputKey::F9; + case KEY_F10: + return InputKey::F10; + case KEY_F11: + return InputKey::F11; + case KEY_F12: + return InputKey::F12; + case KEY_F13: + return InputKey::F13; + case KEY_F14: + return InputKey::F14; + case KEY_F15: + return InputKey::F15; + case KEY_F16: + return InputKey::F16; + case KEY_F17: + return InputKey::F17; + case KEY_F18: + return InputKey::F18; + case KEY_F19: + return InputKey::F19; + case KEY_F20: + return InputKey::F20; + case KEY_F21: + return InputKey::F21; + case KEY_F22: + return InputKey::F22; + case KEY_F23: + return InputKey::F23; + case KEY_F24: + return InputKey::F24; + case KEY_MINUS: + case KEY_KPMINUS: + return InputKey::Minus; + case KEY_EQUAL: + return InputKey::Equals; + case KEY_BACKSPACE: + return InputKey::Backspace; + case KEY_BACKSLASH: + return InputKey::Backslash; + case KEY_TAB: + return InputKey::Tab; + case KEY_LEFTBRACE: + return InputKey::LeftBracket; + case KEY_RIGHTBRACE: + return InputKey::RightBracket; + case KEY_LEFTCTRL: + return InputKey::LControl; + case KEY_RIGHTCTRL: + return InputKey::RControl; + case KEY_LEFTALT: + case KEY_RIGHTALT: + return InputKey::Alt; + case KEY_DELETE: + return InputKey::Delete; + case KEY_SEMICOLON: + return InputKey::Semicolon; + case KEY_COMMA: + return InputKey::Comma; + case KEY_DOT: + return InputKey::Period; + case KEY_NUMLOCK: + return InputKey::NumLock; + case KEY_CAPSLOCK: + return InputKey::CapsLock; + case KEY_SCROLLDOWN: + return InputKey::ScrollLock; + case KEY_LEFTSHIFT: + return InputKey::LShift; + case KEY_RIGHTSHIFT: + return InputKey::RShift; + case KEY_GRAVE: + return InputKey::Tilde; + case KEY_APOSTROPHE: + return InputKey::SingleQuote; + case KEY_SPACE: + return InputKey::Space; + case KEY_ENTER: + case KEY_KPENTER: + return InputKey::Enter; - case KEY_UP: - return InputKey::Up; - case KEY_DOWN: - return InputKey::Down; - case KEY_LEFT: - return InputKey::Left; - case KEY_RIGHT: - return InputKey::Right; + case KEY_UP: + return InputKey::Up; + case KEY_DOWN: + return InputKey::Down; + case KEY_LEFT: + return InputKey::Left; + case KEY_RIGHT: + return InputKey::Right; - case KEY_A: - return InputKey::A; - case KEY_B: - return InputKey::B; - case KEY_C: - return InputKey::C; - case KEY_D: - return InputKey::D; - case KEY_E: - return InputKey::E; - case KEY_F: - return InputKey::F; - case KEY_G: - return InputKey::G; - case KEY_H: - return InputKey::H; - case KEY_I: - return InputKey::I; - case KEY_J: - return InputKey::J; - case KEY_K: - return InputKey::K; - case KEY_L: - return InputKey::L; - case KEY_M: - return InputKey::M; - case KEY_N: - return InputKey::N; - case KEY_O: - return InputKey::O; - case KEY_P: - return InputKey::P; - case KEY_Q: - return InputKey::Q; - case KEY_R: - return InputKey::R; - case KEY_S: - return InputKey::S; - case KEY_T: - return InputKey::T; - case KEY_U: - return InputKey::U; - case KEY_V: - return InputKey::V; - case KEY_W: - return InputKey::W; - case KEY_X: - return InputKey::X; - case KEY_Y: - return InputKey::Y; - case KEY_Z: - return InputKey::Z; + case KEY_A: + return InputKey::A; + case KEY_B: + return InputKey::B; + case KEY_C: + return InputKey::C; + case KEY_D: + return InputKey::D; + case KEY_E: + return InputKey::E; + case KEY_F: + return InputKey::F; + case KEY_G: + return InputKey::G; + case KEY_H: + return InputKey::H; + case KEY_I: + return InputKey::I; + case KEY_J: + return InputKey::J; + case KEY_K: + return InputKey::K; + case KEY_L: + return InputKey::L; + case KEY_M: + return InputKey::M; + case KEY_N: + return InputKey::N; + case KEY_O: + return InputKey::O; + case KEY_P: + return InputKey::P; + case KEY_Q: + return InputKey::Q; + case KEY_R: + return InputKey::R; + case KEY_S: + return InputKey::S; + case KEY_T: + return InputKey::T; + case KEY_U: + return InputKey::U; + case KEY_V: + return InputKey::V; + case KEY_W: + return InputKey::W; + case KEY_X: + return InputKey::X; + case KEY_Y: + return InputKey::Y; + case KEY_Z: + return InputKey::Z; - // Mouse - case BTN_LEFT: - return InputKey::LeftMouse; - case BTN_RIGHT: - return InputKey::RightMouse; - case BTN_MIDDLE: - return InputKey::MiddleMouse; - default: - return InputKey::None; - } + // Mouse + case BTN_LEFT: + return InputKey::LeftMouse; + case BTN_RIGHT: + return InputKey::RightMouse; + case BTN_MIDDLE: + return InputKey::MiddleMouse; + default: + return InputKey::None; + } } std::string WaylandDisplayBackend::GetWaylandCursorName(StandardCursor cursor) { - // Checked out Adwaita and Breeze cursors for the names. - // Other cursor themes should adhere to the names these two have. - switch (cursor) - { - case StandardCursor::arrow: - return "default"; - case StandardCursor::appstarting: - return "progress"; - case StandardCursor::cross: - return "crosshair"; - case StandardCursor::hand: - return "pointer"; - case StandardCursor::ibeam: - return "text"; - case StandardCursor::no: - return "not-allowed"; - case StandardCursor::size_all: - return "fleur"; - case StandardCursor::size_nesw: - return "nesw-resize"; - case StandardCursor::size_ns: - return "ns-resize"; - case StandardCursor::size_nwse: - return "nwse-resize"; - case StandardCursor::size_we: - return "ew-resize"; - case StandardCursor::uparrow: - // Breeze actually has an up-arrow cursor, but Adwaita doesn't, so the default cursor it is - return "default"; - case StandardCursor::wait: - return "wait"; - default: - return "default"; - } + // Checked out Adwaita and Breeze cursors for the names. + // Other cursor themes should adhere to the names these two have. + switch (cursor) + { + case StandardCursor::arrow: + return "default"; + case StandardCursor::appstarting: + return "progress"; + case StandardCursor::cross: + return "crosshair"; + case StandardCursor::hand: + return "pointer"; + case StandardCursor::ibeam: + return "text"; + case StandardCursor::no: + return "not-allowed"; + case StandardCursor::size_all: + return "fleur"; + case StandardCursor::size_nesw: + return "nesw-resize"; + case StandardCursor::size_ns: + return "ns-resize"; + case StandardCursor::size_nwse: + return "nwse-resize"; + case StandardCursor::size_we: + return "ew-resize"; + case StandardCursor::uparrow: + // Breeze actually has an up-arrow cursor, but Adwaita doesn't, so the default cursor it is + return "default"; + case StandardCursor::wait: + return "wait"; + default: + return "default"; + } } +wayland::cursor_shape_device_v1_shape WaylandDisplayBackend::GetWaylandCursorShape(StandardCursor cursor) +{ + switch (cursor) + { + case StandardCursor::arrow: + return wayland::cursor_shape_device_v1_shape::_default; + case StandardCursor::appstarting: + return wayland::cursor_shape_device_v1_shape::progress; + case StandardCursor::cross: + return wayland::cursor_shape_device_v1_shape::crosshair; + case StandardCursor::hand: + return wayland::cursor_shape_device_v1_shape::pointer; + case StandardCursor::ibeam: + return wayland::cursor_shape_device_v1_shape::text; + case StandardCursor::no: + return wayland::cursor_shape_device_v1_shape::not_allowed; + case StandardCursor::size_all: + return wayland::cursor_shape_device_v1_shape::all_resize; + case StandardCursor::size_nesw: + return wayland::cursor_shape_device_v1_shape::nesw_resize; + case StandardCursor::size_ns: + return wayland::cursor_shape_device_v1_shape::ns_resize; + case StandardCursor::size_nwse: + return wayland::cursor_shape_device_v1_shape::nwse_resize; + case StandardCursor::size_we: + return wayland::cursor_shape_device_v1_shape::ew_resize; + case StandardCursor::uparrow: + // Up arrow cursor doesn't seem to be defined in the shapes, so _default it is + return wayland::cursor_shape_device_v1_shape::_default; + case StandardCursor::wait: + return wayland::cursor_shape_device_v1_shape::wait; + default: + return wayland::cursor_shape_device_v1_shape::_default; + } +} diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_backend.h b/libraries/ZWidget/src/window/wayland/wayland_display_backend.h index 26e3e7f64..b9912fdb8 100644 --- a/libraries/ZWidget/src/window/wayland/wayland_display_backend.h +++ b/libraries/ZWidget/src/window/wayland/wayland_display_backend.h @@ -9,49 +9,69 @@ #include #include #include "wl_fractional_scaling_protocol.hpp" +#include "wl_xdg_toplevel_icon.hpp" #include #include #include +#include #include -static short poll_single(int fd, short events, int timeout) { - pollfd pfd { .fd = fd, .events = events, .revents = 0 }; - if (0 > poll(&pfd, 1, timeout)) { - throw std::runtime_error("poll() failed"); - } +#include "wl_cursor_shape.hpp" - return pfd.revents; +inline auto hash_proxy = [](wayland::proxy_t const& proxy) { return reinterpret_cast(proxy.c_ptr()); }; +using hash_proxy_t = decltype(hash_proxy); + +static short poll_single(int fd, short events, int timeout) +{ + pollfd pfd { .fd = fd, .events = events, .revents = 0 }; + if (0 > poll(&pfd, 1, timeout)) + { + throw std::runtime_error("poll() failed"); + } + + return pfd.revents; } -enum pointer_event_mask { - POINTER_EVENT_ENTER = 1 << 0, - POINTER_EVENT_LEAVE = 1 << 1, - POINTER_EVENT_MOTION = 1 << 2, - POINTER_EVENT_BUTTON = 1 << 3, - POINTER_EVENT_AXIS = 1 << 4, - POINTER_EVENT_AXIS_SOURCE = 1 << 5, - POINTER_EVENT_AXIS_STOP = 1 << 6, - POINTER_EVENT_AXIS_DISCRETE = 1 << 7, - POINTER_EVENT_AXIS_120 = 1 << 8, - POINTER_EVENT_RELATIVE_MOTION = 1 << 9, +enum pointer_event_mask +{ + POINTER_EVENT_ENTER = 1 << 0, + POINTER_EVENT_LEAVE = 1 << 1, + POINTER_EVENT_MOTION = 1 << 2, + POINTER_EVENT_BUTTON = 1 << 3, + POINTER_EVENT_AXIS = 1 << 4, + POINTER_EVENT_AXIS_SOURCE = 1 << 5, + POINTER_EVENT_AXIS_STOP = 1 << 6, + POINTER_EVENT_AXIS_DISCRETE = 1 << 7, + POINTER_EVENT_AXIS_120 = 1 << 8, + POINTER_EVENT_RELATIVE_MOTION = 1 << 9, }; struct WaylandPointerEvent { - uint32_t event_mask; - double surfaceX, surfaceY; - double dx, dy; - uint32_t button; - wayland::pointer_button_state state; - uint32_t time; - uint32_t serial; - struct { - bool valid; - double value; - int32_t discrete; - int32_t value120; - } axes[2]; - wayland::pointer_axis_source axis_source; + uint32_t event_mask; + double surfaceX, surfaceY; + double dx, dy, dx_unaccel, dy_unaccel; + uint32_t button; + wayland::pointer_button_state state; + uint32_t time; + uint32_t serial; + struct { + bool valid; + double value; + int32_t discrete; + int32_t value120; + } axes[2]; + wayland::pointer_axis_source axis_source; +}; + +class WaylandTimer +{ +public: + WaylandTimer(int timeoutMilliseconds, std::function onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {} + + int timeoutMilliseconds = 0; + std::function onTimer; + int64_t nextTime = 0; }; class WaylandDisplayWindow; @@ -60,7 +80,7 @@ class WaylandDisplayBackend : public DisplayBackend { public: WaylandDisplayBackend(); - ~WaylandDisplayBackend(); + ~WaylandDisplayBackend(); std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; void ProcessEvents() override; @@ -78,9 +98,13 @@ public: void OnWindowDestroyed(WaylandDisplayWindow* window); void SetCursor(StandardCursor cursor); - void ShowCursor(bool enable); + void ShowCursor(bool enable); bool GetKeyState(InputKey key); + std::string GetClipboardText() { return m_ClipboardContents; } + wayland::data_device_t& GetDataDevice() { return m_DataDevice; } + uint32_t GetKeyboardSerial() const { return m_KeyboardSerial; } + #ifdef USE_DBUS std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner) override; std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner) override; @@ -93,76 +117,92 @@ public: wayland::registry_t s_waylandRegistry; std::vector s_Windows; WaylandDisplayWindow* m_FocusWindow = nullptr; - WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately. + WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately. - wayland::compositor_t m_waylandCompositor; - wayland::shm_t m_waylandSHM; - wayland::seat_t m_waylandSeat; - wayland::output_t m_waylandOutput; - wayland::data_device_manager_t m_DataDeviceManager; - wayland::xdg_wm_base_t m_XDGWMBase; - wayland::zwp_pointer_constraints_v1_t m_PointerConstraints; - wayland::xdg_activation_v1_t m_XDGActivation; - wayland::zxdg_decoration_manager_v1_t m_XDGDecorationManager; - wayland::fractional_scale_manager_v1_t m_FractionalScaleManager; - wayland::zxdg_output_manager_v1_t m_XDGOutputManager; - wayland::zxdg_output_v1_t m_XDGOutput; - wayland::zxdg_exporter_v2_t m_XDGExporter; + wayland::compositor_t m_waylandCompositor; + wayland::shm_t m_waylandSHM; + wayland::seat_t m_waylandSeat; + wayland::output_t m_waylandOutput; + wayland::data_device_manager_t m_DataDeviceManager; + wayland::xdg_wm_base_t m_XDGWMBase; + wayland::zwp_pointer_constraints_v1_t m_PointerConstraints; + wayland::xdg_activation_v1_t m_XDGActivation; + wayland::zxdg_decoration_manager_v1_t m_XDGDecorationManager; + wayland::fractional_scale_manager_v1_t m_FractionalScaleManager; + wayland::zxdg_output_manager_v1_t m_XDGOutputManager; + wayland::zxdg_output_v1_t m_XDGOutput; + wayland::zxdg_exporter_v2_t m_XDGExporter; - wayland::keyboard_t m_waylandKeyboard; - wayland::pointer_t m_waylandPointer; + wayland::keyboard_t m_waylandKeyboard; + wayland::pointer_t m_waylandPointer; - wayland::zwp_relative_pointer_manager_v1_t m_RelativePointerManager; - wayland::zwp_relative_pointer_v1_t m_RelativePointer; + wayland::zwp_relative_pointer_manager_v1_t m_RelativePointerManager; + wayland::zwp_relative_pointer_v1_t m_RelativePointer; - wayland::cursor_image_t m_cursorImage; - wayland::surface_t m_cursorSurface; - wayland::buffer_t m_cursorBuffer; + wayland::xdg_toplevel_icon_manager_v1_t m_XDGToplevelIconManager; - std::map inputKeyStates; // True when the key is pressed, false when isn't + wayland::cursor_shape_manager_v1_t m_CursorShapeManager; + wayland::cursor_shape_device_v1_t m_CursorShapeDevice; - bool IsMouseLocked() { return hasMouseLock; } - void SetMouseLocked(bool val) { hasMouseLock = val; } + wayland::cursor_image_t m_cursorImage; + wayland::surface_t m_cursorSurface; + wayland::buffer_t m_cursorBuffer; + + std::map inputKeyStates; // True when the key is pressed, false when isn't + + bool IsMouseLocked() { return hasMouseLock; } + void SetMouseLocked(bool val) { hasMouseLock = val; } private: - void CheckNeedsUpdate(); + void CheckNeedsUpdate(); void UpdateTimers(); - void ConnectDeviceEvents(); - void OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state); - void OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state); - void OnKeyboardDelayEnd(); - void OnKeyboardRepeat(); - void OnMouseEnterEvent(uint32_t serial); - void OnMouseLeaveEvent(); - void OnMousePressEvent(InputKey button); - void OnMouseReleaseEvent(InputKey button); - void OnMouseMoveEvent(Point surfacePos); - void OnMouseMoveRawEvent(int surfaceX, int surfaceY); - void OnMouseWheelEvent(InputKey button); + void WaitForEvents(int timeout); + int GetTimerTimeout(); + void ConnectDeviceEvents(); + void OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state); + void OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state); + void OnKeyboardDelayEnd(); + void OnKeyboardRepeat(); + void OnMouseEnterEvent(uint32_t serial); + void OnMouseLeaveEvent(); + void OnMousePressEvent(InputKey button); + void OnMouseReleaseEvent(InputKey button); + void OnMouseMoveEvent(Point surfacePos); + void OnMouseMoveRawEvent(int surfaceX, int surfaceY); + void OnMouseWheelEvent(InputKey button); - InputKey XKBKeySymToInputKey(xkb_keysym_t keySym); - InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode); + InputKey XKBKeySymToInputKey(xkb_keysym_t keySym); + InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode); - std::string GetWaylandCursorName(StandardCursor cursor); + wayland::cursor_shape_device_v1_shape GetWaylandCursorShape(StandardCursor cursor); + std::string GetWaylandCursorName(StandardCursor cursor); - bool hasKeyboard = false; - bool hasPointer = false; - bool hasMouseLock = false; + bool hasKeyboard = false; + bool hasPointer = false; + bool hasMouseLock = false; - ZTimer::TimePoint m_previousTime; - ZTimer::TimePoint m_currentTime; + ZTimer::TimePoint m_previousTime; + ZTimer::TimePoint m_currentTime; - ZTimer m_keyboardDelayTimer; - ZTimer m_keyboardRepeatTimer; + ZTimer m_keyboardDelayTimer; + ZTimer m_keyboardRepeatTimer; - InputKey previousKey = {}; - std::string previousChars; + InputKey previousKey = {}; + std::string previousChars; - uint32_t m_KeyboardSerial = 0; + uint32_t m_KeyboardSerial = 0; + uint32_t m_MouseSerial = 0; - xkb_context* m_KeymapContext = nullptr; - xkb_keymap* m_Keymap = nullptr; - xkb_state* m_KeyboardState = nullptr; + xkb_context* m_KeymapContext = nullptr; + xkb_keymap* m_Keymap = nullptr; + xkb_state* m_KeyboardState = nullptr; - WaylandPointerEvent currentPointerEvent = {0}; + WaylandPointerEvent currentPointerEvent = {0}; + + wayland::data_device_t m_DataDevice; + std::unordered_map, hash_proxy_t> m_DataOfferMimeTypes; + + std::string m_ClipboardContents; + + std::vector> m_timers; }; diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp b/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp index f41b3317e..22c1668f0 100644 --- a/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp +++ b/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp @@ -3,288 +3,313 @@ #include #include +#include "core/image.h" + WaylandDisplayWindow::WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI) - : backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI) + : backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI) { - m_AppSurface = backend->m_waylandCompositor.create_surface(); + m_AppSurface = backend->m_waylandCompositor.create_surface(); - m_NativeHandle.display = backend->s_waylandDisplay; - m_NativeHandle.surface = m_AppSurface; + m_NativeHandle.display = backend->s_waylandDisplay; + m_NativeHandle.surface = m_AppSurface; - if (backend->m_FractionalScaleManager) - { - m_FractionalScale = backend->m_FractionalScaleManager.get_fractional_scale(m_AppSurface); + if (backend->m_FractionalScaleManager) + { + m_FractionalScale = backend->m_FractionalScaleManager.get_fractional_scale(m_AppSurface); - m_FractionalScale.on_preferred_scale() = [&](uint32_t scale_numerator) { - // parameter is the numerator of a fraction with the denominator of 120 - m_ScaleFactor = scale_numerator / 120.0; + m_FractionalScale.on_preferred_scale() = [&](uint32_t scale_numerator) { + // parameter is the numerator of a fraction with the denominator of 120 + m_ScaleFactor = scale_numerator / 120.0; - m_NeedsUpdate = true; - windowHost->OnWindowDpiScaleChanged(); - }; - } + m_NeedsUpdate = true; + windowHost->OnWindowDpiScaleChanged(); + }; + } - m_XDGSurface = backend->m_XDGWMBase.get_xdg_surface(m_AppSurface); - m_XDGSurface.on_configure() = [&] (uint32_t serial) { - m_XDGSurface.ack_configure(serial); - }; + m_XDGSurface = backend->m_XDGWMBase.get_xdg_surface(m_AppSurface); + m_XDGSurface.on_configure() = [&] (uint32_t serial) { + m_XDGSurface.ack_configure(serial); + }; - if (popupWindow) - { - InitializePopup(); - } - else - { - InitializeToplevel(); - } + if (popupWindow) + { + InitializePopup(); + } + else + { + InitializeToplevel(); + } - backend->OnWindowCreated(this); + backend->OnWindowCreated(this); - this->DrawSurface(); + this->DrawSurface(); } WaylandDisplayWindow::~WaylandDisplayWindow() { - backend->OnWindowDestroyed(this); + backend->OnWindowDestroyed(this); } void WaylandDisplayWindow::InitializeToplevel() { - m_XDGToplevel = m_XDGSurface.get_toplevel(); - m_XDGToplevel.set_title("ZWidget Window"); + m_XDGToplevel = m_XDGSurface.get_toplevel(); + m_XDGToplevel.set_title("ZWidget Window"); - if (m_owner) - m_XDGToplevel.set_parent(m_owner->m_XDGToplevel); + if (m_owner) + m_XDGToplevel.set_parent(m_owner->m_XDGToplevel); - if (backend->m_XDGDecorationManager) - { - // Force server side decorations if possible - m_XDGToplevelDecoration = backend->m_XDGDecorationManager.get_toplevel_decoration(m_XDGToplevel); - m_XDGToplevelDecoration.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::server_side); - } + if (backend->m_XDGDecorationManager) + { + // Force server side decorations if possible + m_XDGToplevelDecoration = backend->m_XDGDecorationManager.get_toplevel_decoration(m_XDGToplevel); + m_XDGToplevelDecoration.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::server_side); + } - m_AppSurface.commit(); + m_AppSurface.commit(); - backend->s_waylandDisplay.roundtrip(); + backend->s_waylandDisplay.roundtrip(); - // These have to be added after the roundtrip - m_XDGToplevel.on_configure() = [&] (int32_t width, int32_t height, wayland::array_t states) { - OnXDGToplevelConfigureEvent(width, height); - }; + // These have to be added after the roundtrip + m_XDGToplevel.on_configure() = [&] (int32_t width, int32_t height, wayland::array_t states) { + OnXDGToplevelConfigureEvent(width, height); + }; - m_XDGToplevel.on_close() = [&] () { - OnExitEvent(); - }; + m_XDGToplevel.on_close() = [&] () { + OnExitEvent(); + }; - m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height) - { + m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height) + { - }; + }; - m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface); + m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface); - m_XDGExported.on_handle() = [&] (std::string handleStr) { - OnExportHandleEvent(handleStr); - }; + m_XDGExported.on_handle() = [&] (std::string handleStr) { + OnExportHandleEvent(handleStr); + }; } void WaylandDisplayWindow::InitializePopup() { - if (!m_owner) - throw std::runtime_error("Popup window must have an owner!"); + if (!m_owner) + throw std::runtime_error("Popup window must have an owner!"); - wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner(); + wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner(); - popupPositioner.set_anchor(wayland::xdg_positioner_anchor::bottom); - popupPositioner.set_anchor_rect(0, 0, 1, 30); - popupPositioner.set_size(1, 1); + popupPositioner.set_anchor(wayland::xdg_positioner_anchor::bottom); + popupPositioner.set_anchor_rect(0, 0, 1, 30); + popupPositioner.set_size(1, 1); - m_XDGPopup = m_XDGSurface.get_popup(m_owner->m_XDGSurface, popupPositioner); + m_XDGPopup = m_XDGSurface.get_popup(m_owner->m_XDGSurface, popupPositioner); - m_XDGPopup.on_configure() = [&] (int32_t x, int32_t y, int32_t width, int32_t height) { - SetClientFrame(Rect::xywh(x, y, width, height)); - }; + m_XDGPopup.on_configure() = [&] (int32_t x, int32_t y, int32_t width, int32_t height) { + SetClientFrame(Rect::xywh(x, y, width, height)); + }; - //m_XDGPopup.on_repositioned() + //m_XDGPopup.on_repositioned() - m_XDGPopup.on_popup_done() = [&] () { - OnExitEvent(); - }; + m_XDGPopup.on_popup_done() = [&] () { + OnExitEvent(); + }; - m_AppSurface.commit(); + m_AppSurface.commit(); - backend->s_waylandDisplay.roundtrip(); + backend->s_waylandDisplay.roundtrip(); } void WaylandDisplayWindow::SetWindowTitle(const std::string& text) { - if (m_XDGToplevel) - m_XDGToplevel.set_title(text); + if (m_XDGToplevel) + m_XDGToplevel.set_title(text); +} + +void WaylandDisplayWindow::SetWindowIcon(const std::vector>& images) +{ + if (backend->m_XDGToplevelIconManager) + { + if (images.empty()) + { + backend->m_XDGToplevelIconManager.set_icon(m_XDGToplevel, nullptr); + return; + } + + m_XDGToplevelIcon = backend->m_XDGToplevelIconManager.create_icon(); + + CreateAppIconBuffers(images); + + for (auto& icon_buffer : appIconBuffers) + { + m_XDGToplevelIcon.add_buffer(icon_buffer, 1); + } + + backend->m_XDGToplevelIconManager.set_icon(m_XDGToplevel, m_XDGToplevelIcon); + } } void WaylandDisplayWindow::SetWindowFrame(const Rect& box) { - // Resizing will be shown on the next commit - CreateBuffers(box.width, box.height); - windowHost->OnWindowGeometryChanged(); - m_NeedsUpdate = true; - m_AppSurface.commit(); + // Resizing will be shown on the next commit + CreateBuffers(box.width, box.height); + windowHost->OnWindowGeometryChanged(); + m_NeedsUpdate = true; + m_AppSurface.commit(); } void WaylandDisplayWindow::SetClientFrame(const Rect& box) { - SetWindowFrame(box); + SetWindowFrame(box); } void WaylandDisplayWindow::Show() { - m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); - m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); - m_AppSurface.commit(); + m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); + m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); + m_AppSurface.commit(); } void WaylandDisplayWindow::ShowFullscreen() { - if (m_XDGToplevel) - { - m_XDGToplevel.set_fullscreen(backend->m_waylandOutput); - isFullscreen = true; - } + if (m_XDGToplevel) + { + m_XDGToplevel.set_fullscreen(backend->m_waylandOutput); + isFullscreen = true; + } } void WaylandDisplayWindow::ShowMaximized() { - if (m_XDGToplevel) - m_XDGToplevel.set_maximized(); + if (m_XDGToplevel) + m_XDGToplevel.set_maximized(); } void WaylandDisplayWindow::ShowMinimized() { - if (m_XDGToplevel) - m_XDGToplevel.set_minimized(); + if (m_XDGToplevel) + m_XDGToplevel.set_minimized(); } void WaylandDisplayWindow::ShowNormal() { - if (m_XDGToplevel) - m_XDGToplevel.unset_fullscreen(); + if (m_XDGToplevel) + m_XDGToplevel.unset_fullscreen(); } bool WaylandDisplayWindow::IsWindowFullscreen() { - return isFullscreen; + return isFullscreen; } void WaylandDisplayWindow::Hide() { - // Apparently this is how hiding a window works - // By attaching a null buffer to the surface - // See: https://lists.freedesktop.org/archives/wayland-devel/2017-November/035963.html - m_AppSurface.attach(nullptr, 0, 0); - m_AppSurface.commit(); + // Apparently this is how hiding a window works + // By attaching a null buffer to the surface + // See: https://lists.freedesktop.org/archives/wayland-devel/2017-November/035963.html + m_AppSurface.attach(nullptr, 0, 0); + m_AppSurface.commit(); } void WaylandDisplayWindow::Activate() { - // To do: this needs to be in the backend instance if all windows share the activation token - wayland::xdg_activation_token_v1_t xdgActivationToken = backend->m_XDGActivation.get_activation_token(); + // To do: this needs to be in the backend instance if all windows share the activation token + wayland::xdg_activation_token_v1_t xdgActivationToken = backend->m_XDGActivation.get_activation_token(); - std::string tokenString; + std::string tokenString; - xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) { - tokenString = obtainedString; - }; + xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) { + tokenString = obtainedString; + }; - xdgActivationToken.set_surface(m_AppSurface); - xdgActivationToken.commit(); // This will set our token string + xdgActivationToken.set_surface(m_AppSurface); + xdgActivationToken.commit(); // This will set our token string - backend->m_XDGActivation.activate(tokenString, m_AppSurface); - backend->m_FocusWindow = this; - backend->m_MouseFocusWindow = this; - windowHost->OnWindowActivated(); + backend->m_XDGActivation.activate(tokenString, m_AppSurface); + backend->m_FocusWindow = this; + backend->m_MouseFocusWindow = this; + windowHost->OnWindowActivated(); } void WaylandDisplayWindow::ShowCursor(bool enable) { - backend->ShowCursor(enable); + backend->ShowCursor(enable); } void WaylandDisplayWindow::LockCursor() { - m_LockedPointer = backend->m_PointerConstraints.lock_pointer(m_AppSurface, backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); - backend->SetMouseLocked(true); - ShowCursor(false); + m_LockedPointer = backend->m_PointerConstraints.lock_pointer(m_AppSurface, backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); + backend->SetMouseLocked(true); + ShowCursor(false); } void WaylandDisplayWindow::UnlockCursor() { - if (m_LockedPointer) - m_LockedPointer.proxy_release(); - backend->SetMouseLocked(false); - ShowCursor(true); + if (m_LockedPointer) + m_LockedPointer.proxy_release(); + backend->SetMouseLocked(false); + ShowCursor(true); } void WaylandDisplayWindow::CaptureMouse() { - m_ConfinedPointer = backend->m_PointerConstraints.confine_pointer(GetWindowSurface(), backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); - ShowCursor(false); + m_ConfinedPointer = backend->m_PointerConstraints.confine_pointer(GetWindowSurface(), backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); + ShowCursor(false); } void WaylandDisplayWindow::ReleaseMouseCapture() { - if (m_ConfinedPointer) - m_ConfinedPointer.proxy_release(); - ShowCursor(true); + if (m_ConfinedPointer) + m_ConfinedPointer.proxy_release(); + ShowCursor(true); } void WaylandDisplayWindow::Update() { - m_NeedsUpdate = true; + m_NeedsUpdate = true; } bool WaylandDisplayWindow::GetKeyState(InputKey key) { - return backend->GetKeyState(key); + return backend->GetKeyState(key); } void WaylandDisplayWindow::SetCursor(StandardCursor cursor) { - backend->SetCursor(cursor); + backend->SetCursor(cursor); } Rect WaylandDisplayWindow::GetWindowFrame() const { - return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height); + return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height); } Size WaylandDisplayWindow::GetClientSize() const { - return m_WindowSize / m_ScaleFactor; + return m_WindowSize / m_ScaleFactor; } int WaylandDisplayWindow::GetPixelWidth() const { - return m_WindowSize.width; + return m_WindowSize.width; } int WaylandDisplayWindow::GetPixelHeight() const { - return m_WindowSize.height; + return m_WindowSize.height; } double WaylandDisplayWindow::GetDpiScale() const { - return m_ScaleFactor; + return m_ScaleFactor; } void WaylandDisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) { - // Make new buffers if the sizes don't match - if (width != m_WindowSize.width || height != m_WindowSize.height) - CreateBuffers(width, height); + // Make new buffers if the sizes don't match + if (width != m_WindowSize.width || height != m_WindowSize.height) + CreateBuffers(width, height); - std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4); + std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4); } void WaylandDisplayWindow::SetBorderColor(uint32_t bgra8) @@ -304,79 +329,115 @@ void WaylandDisplayWindow::SetCaptionTextColor(uint32_t bgra8) std::string WaylandDisplayWindow::GetClipboardText() { - return m_ClipboardContents; + return backend->GetClipboardText(); } void WaylandDisplayWindow::SetClipboardText(const std::string& text) { - m_ClipboardContents = text; + m_DataSource = backend->m_DataDeviceManager.create_data_source(); + + m_DataSource.on_send() = [text] (const std::string& mime_type, const int fd) + { + write(fd, text.c_str(), text.size()); + close(fd); + }; + + m_DataSource.offer("text/plain"); + backend->GetDataDevice().set_selection(m_DataSource, backend->GetKeyboardSerial()); } Point WaylandDisplayWindow::MapFromGlobal(const Point& pos) const { - return (pos - m_WindowGlobalPos) / m_ScaleFactor; + return (pos - m_WindowGlobalPos) / m_ScaleFactor; } Point WaylandDisplayWindow::MapToGlobal(const Point& pos) const { - return (m_WindowGlobalPos + pos) / m_ScaleFactor; + return (m_WindowGlobalPos + pos) / m_ScaleFactor; } void WaylandDisplayWindow::OnXDGToplevelConfigureEvent(int32_t width, int32_t height) { - Rect rect = GetWindowFrame(); - rect.width = width / m_ScaleFactor; - rect.height = height / m_ScaleFactor; - SetWindowFrame(rect); - windowHost->OnWindowGeometryChanged(); + Rect rect = GetWindowFrame(); + rect.width = width / m_ScaleFactor; + rect.height = height / m_ScaleFactor; + SetWindowFrame(rect); + windowHost->OnWindowGeometryChanged(); } void WaylandDisplayWindow::OnExportHandleEvent(std::string exportedHandle) { - m_windowID = exportedHandle; + m_windowID = exportedHandle; } void WaylandDisplayWindow::OnExitEvent() { - windowHost->OnWindowClose(); + windowHost->OnWindowClose(); } void WaylandDisplayWindow::DrawSurface(uint32_t serial) { - m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); - m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); + m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); + m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); - if (m_renderAPI == RenderAPI::Unspecified || m_renderAPI == RenderAPI::Bitmap) - { - m_FrameCallback = m_AppSurface.frame(); + if (m_renderAPI == RenderAPI::Unspecified || m_renderAPI == RenderAPI::Bitmap) + { + m_FrameCallback = m_AppSurface.frame(); - m_FrameCallback.on_done() = bind_mem_fn(&WaylandDisplayWindow::DrawSurface, this); - } - m_AppSurface.commit(); + m_FrameCallback.on_done() = bind_mem_fn(&WaylandDisplayWindow::DrawSurface, this); + } + m_AppSurface.commit(); } void WaylandDisplayWindow::CreateBuffers(int32_t width, int32_t height) { - if (width == 0 || height == 0) - return; + if (width == 0 || height == 0) + return; - if (shared_mem) - shared_mem.reset(); + if (shared_mem) + shared_mem.reset(); - int scaled_width = width * m_ScaleFactor; - int scaled_height = height * m_ScaleFactor; + int scaled_width = width * m_ScaleFactor; + int scaled_height = height * m_ScaleFactor; - shared_mem = std::make_shared(scaled_width * scaled_height * 4); - auto pool = backend->m_waylandSHM.create_pool(shared_mem->get_fd(), scaled_width * scaled_height * 4); + shared_mem = std::make_shared(scaled_width * scaled_height * 4); + auto pool = backend->m_waylandSHM.create_pool(shared_mem->get_fd(), scaled_width * scaled_height * 4); - m_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888); + m_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888); - m_WindowSize = Size(scaled_width, scaled_height); + m_WindowSize = Size(scaled_width, scaled_height); +} + +void WaylandDisplayWindow::CreateAppIconBuffers(const std::vector>& images) +{ + if (images.empty()) + return; + + appIconBuffers.clear(); + appIconSharedMems.clear(); + + for (auto& image: images) + { + const int image_size = image->GetWidth() * image->GetHeight() * 4; + const int stride = image->GetWidth() * 4; + + auto new_shared_mem = std::make_shared(image_size); + + auto pool = backend->m_waylandSHM.create_pool(new_shared_mem->get_fd(), image_size); + + auto new_buffer = pool.create_buffer(0, image->GetWidth(), image->GetHeight(), stride, wayland::shm_format::argb8888); + + // Fill the buffer with the icon data + std::memcpy(new_shared_mem->get_mem(), image->GetData(), image_size); + + appIconSharedMems.push_back(new_shared_mem); + appIconBuffers.push_back(new_buffer); + } } std::string WaylandDisplayWindow::GetWaylandWindowID() { - return m_windowID; + return m_windowID; } // This is to avoid needing all the Vulkan headers and the volk binding library just for this: @@ -398,11 +459,11 @@ typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance inst typedef VkFlags VkWaylandSurfaceCreateFlagsKHR; typedef struct VkWaylandSurfaceCreateInfoKHR { - VkStructureType sType; - const void* pNext; - VkWaylandSurfaceCreateFlagsKHR flags; - struct wl_display* display; - struct wl_surface* surface; + VkStructureType sType; + const void* pNext; + VkWaylandSurfaceCreateFlagsKHR flags; + struct wl_display* display; + struct wl_surface* surface; } VkWaylandSurfaceCreateInfoKHR; typedef VkResult(VKAPI_PTR* PFN_vkCreateWaylandSurfaceKHR)(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_window.h b/libraries/ZWidget/src/window/wayland/wayland_display_window.h index 5553ece84..8479c303f 100644 --- a/libraries/ZWidget/src/window/wayland/wayland_display_window.h +++ b/libraries/ZWidget/src/window/wayland/wayland_display_window.h @@ -25,70 +25,71 @@ template std::function bind_mem_fn(R(T::* func)(Args...), T *t) { return [func, t] (Args... args) - { - return (t->*func)(args...); - }; + { + return (t->*func)(args...); + }; } class SharedMemHelper { public: - SharedMemHelper(size_t size) - : len(size) - { - std::stringstream ss; - std::random_device device; - std::default_random_engine engine(device()); - std::uniform_int_distribution distribution(0, std::numeric_limits::max()); - ss << distribution(engine); - name = ss.str(); + SharedMemHelper(size_t size) + : len(size) + { + std::stringstream ss; + std::random_device device; + std::default_random_engine engine(device()); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + ss << distribution(engine); + name = ss.str(); - fd = memfd_create(name.c_str(), 0); - if(fd < 0) - throw std::runtime_error("shm_open failed."); + fd = memfd_create(name.c_str(), 0); + if(fd < 0) + throw std::runtime_error("shm_open failed."); - if(ftruncate(fd, size) < 0) - throw std::runtime_error("ftruncate failed."); + if(ftruncate(fd, size) < 0) + throw std::runtime_error("ftruncate failed."); - mem = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if(mem == MAP_FAILED) // NOLINT - throw std::runtime_error("mmap failed with len " + std::to_string(len) + "."); - } + mem = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if(mem == MAP_FAILED) // NOLINT + throw std::runtime_error("mmap failed with len " + std::to_string(len) + "."); + } - ~SharedMemHelper() noexcept - { - if(fd) - { - munmap(mem, len); - close(fd); - shm_unlink(name.c_str()); - } - } + ~SharedMemHelper() noexcept + { + if(fd) + { + munmap(mem, len); + close(fd); + shm_unlink(name.c_str()); + } + } - int get_fd() const - { - return fd; - } + int get_fd() const + { + return fd; + } - void *get_mem() - { - return mem; - } + void *get_mem() + { + return mem; + } private: - std::string name; - int fd = 0; - size_t len = 0; - void *mem = nullptr; + std::string name; + int fd = 0; + size_t len = 0; + void *mem = nullptr; }; class WaylandDisplayWindow : public DisplayWindow { public: - WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI); - ~WaylandDisplayWindow(); + WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI); + ~WaylandDisplayWindow(); - void SetWindowTitle(const std::string& text) override; + void SetWindowTitle(const std::string& text) override; + void SetWindowIcon(const std::vector>& images) override; void SetWindowFrame(const Rect& box) override; void SetClientFrame(const Rect& box) override; void Show() override; @@ -135,64 +136,69 @@ public: bool IsWindowFullscreen() override; private: - // Event handlers as otherwise linking DisplayWindowHost On...() functions with Wayland events directly crashes the app - // Alternatively to avoid crashes one can capture by value ([=]) instead of reference ([&]) - void OnXDGToplevelConfigureEvent(int32_t width, int32_t height); - void OnExportHandleEvent(std::string exportedHandle); - void OnExitEvent(); + // Event handlers as otherwise linking DisplayWindowHost On...() functions with Wayland events directly crashes the app + // Alternatively to avoid crashes one can capture by value ([=]) instead of reference ([&]) + void OnXDGToplevelConfigureEvent(int32_t width, int32_t height); + void OnExportHandleEvent(std::string exportedHandle); + void OnExitEvent(); - void DrawSurface(uint32_t serial = 0); + void DrawSurface(uint32_t serial = 0); - void InitializeToplevel(); - void InitializePopup(); + void InitializeToplevel(); + void InitializePopup(); - WaylandDisplayBackend* backend = nullptr; - WaylandDisplayWindow* m_owner = nullptr; - DisplayWindowHost* windowHost = nullptr; - bool m_PopupWindow = false; + WaylandDisplayBackend* backend = nullptr; + WaylandDisplayWindow* m_owner = nullptr; + DisplayWindowHost* windowHost = nullptr; + bool m_PopupWindow = false; - bool m_NeedsUpdate = true; + bool m_NeedsUpdate = true; - Point m_WindowGlobalPos = Point(0, 0); - Size m_WindowSize = Size(0, 0); - double m_ScaleFactor = 1.0; + Point m_WindowGlobalPos = Point(0, 0); + Size m_WindowSize = Size(0, 0); + double m_ScaleFactor = 1.0; - Point m_SurfaceMousePos = Point(0, 0); + Point m_SurfaceMousePos = Point(0, 0); - WaylandNativeHandle m_NativeHandle; - RenderAPI m_renderAPI; + WaylandNativeHandle m_NativeHandle; + RenderAPI m_renderAPI; - wayland::data_device_t m_DataDevice; - wayland::data_source_t m_DataSource; + wayland::data_source_t m_DataSource; - wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration; + wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration; - wayland::fractional_scale_v1_t m_FractionalScale; + wayland::fractional_scale_v1_t m_FractionalScale; - wayland::surface_t m_AppSurface; - wayland::buffer_t m_AppSurfaceBuffer; + wayland::surface_t m_AppSurface; + wayland::buffer_t m_AppSurfaceBuffer; - wayland::xdg_surface_t m_XDGSurface; - wayland::xdg_toplevel_t m_XDGToplevel; - wayland::xdg_popup_t m_XDGPopup; + wayland::xdg_surface_t m_XDGSurface; + wayland::xdg_toplevel_t m_XDGToplevel; + wayland::xdg_popup_t m_XDGPopup; - wayland::zxdg_exported_v2_t m_XDGExported; + wayland::zxdg_exported_v2_t m_XDGExported; - wayland::zwp_locked_pointer_v1_t m_LockedPointer; - wayland::zwp_confined_pointer_v1_t m_ConfinedPointer; + wayland::zwp_locked_pointer_v1_t m_LockedPointer; + wayland::zwp_confined_pointer_v1_t m_ConfinedPointer; - wayland::callback_t m_FrameCallback; + wayland::xdg_toplevel_icon_v1_t m_XDGToplevelIcon; - std::string m_windowID; - std::string m_ClipboardContents; + wayland::callback_t m_FrameCallback; - std::shared_ptr shared_mem; + std::string m_windowID; - bool isFullscreen = false; + std::shared_ptr shared_mem; - // Helper functions - void CreateBuffers(int32_t width, int32_t height); - std::string GetWaylandWindowID(); + std::vector> appIconSharedMems; + std::vector appIconBuffers; - friend WaylandDisplayBackend; + bool isFullscreen = false; + + // Helper functions + void CreateBuffers(int32_t width, int32_t height); + + void CreateAppIconBuffers(const std::vector>& images); + std::string GetWaylandWindowID(); + + friend WaylandDisplayBackend; }; diff --git a/libraries/ZWidget/src/window/wayland/wl_cursor_shape.cpp b/libraries/ZWidget/src/window/wayland/wl_cursor_shape.cpp new file mode 100644 index 000000000..2884071c0 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_cursor_shape.cpp @@ -0,0 +1,218 @@ +#include "wl_cursor_shape.hpp" + +using namespace wayland; +using namespace wayland::detail; + +const wl_interface* cursor_shape_manager_v1_interface_destroy_request[0] = { +}; + +const wl_interface* cursor_shape_manager_v1_interface_get_pointer_request[2] = { + &cursor_shape_device_v1_interface, + &pointer_interface, +}; + +const wl_interface* cursor_shape_manager_v1_interface_get_tablet_tool_v2_request[2] = { + &cursor_shape_device_v1_interface, + &zwp_tablet_tool_v2_interface, +}; + +const wl_message cursor_shape_manager_v1_interface_requests[3] = { + { + "destroy", + "", + cursor_shape_manager_v1_interface_destroy_request, + }, + { + "get_pointer", + "no", + cursor_shape_manager_v1_interface_get_pointer_request, + }, + { + "get_tablet_tool_v2", + "no", + cursor_shape_manager_v1_interface_get_tablet_tool_v2_request, + }, +}; + +const wl_message cursor_shape_manager_v1_interface_events[0] = { +}; + +const wl_interface wayland::detail::cursor_shape_manager_v1_interface = + { + "wp_cursor_shape_manager_v1", + 2, + 3, + cursor_shape_manager_v1_interface_requests, + 0, + cursor_shape_manager_v1_interface_events, + }; + +const wl_interface* cursor_shape_device_v1_interface_destroy_request[0] = { +}; + +const wl_interface* cursor_shape_device_v1_interface_set_shape_request[2] = { + nullptr, + nullptr, +}; + +const wl_message cursor_shape_device_v1_interface_requests[2] = { + { + "destroy", + "", + cursor_shape_device_v1_interface_destroy_request, + }, + { + "set_shape", + "uu", + cursor_shape_device_v1_interface_set_shape_request, + }, +}; + +const wl_message cursor_shape_device_v1_interface_events[0] = { +}; + +const wl_interface wayland::detail::cursor_shape_device_v1_interface = + { + "wp_cursor_shape_device_v1", + 2, + 2, + cursor_shape_device_v1_interface_requests, + 0, + cursor_shape_device_v1_interface_events, + }; + +cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&cursor_shape_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_manager_v1_t(p); }); +} + +cursor_shape_manager_v1_t::cursor_shape_manager_v1_t() +{ + set_interface(&cursor_shape_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_manager_v1_t(p); }); +} + +cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(wp_cursor_shape_manager_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&cursor_shape_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_manager_v1_t(p); }); +} + +cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&cursor_shape_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_manager_v1_t(p); }); +} + +cursor_shape_manager_v1_t cursor_shape_manager_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string cursor_shape_manager_v1_t::interface_name = "wp_cursor_shape_manager_v1"; + +cursor_shape_manager_v1_t::operator wp_cursor_shape_manager_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +cursor_shape_device_v1_t cursor_shape_manager_v1_t::get_pointer(pointer_t const& pointer) +{ + proxy_t p = marshal_constructor(1U, &cursor_shape_device_v1_interface, nullptr, pointer.proxy_has_object() ? reinterpret_cast(pointer.c_ptr()) : nullptr); + return cursor_shape_device_v1_t(p); +} + + +cursor_shape_device_v1_t cursor_shape_manager_v1_t::get_tablet_tool_v2(zwp_tablet_tool_v2_t const& tablet_tool) +{ + proxy_t p = marshal_constructor(2U, &cursor_shape_device_v1_interface, nullptr, tablet_tool.proxy_has_object() ? reinterpret_cast(tablet_tool.c_ptr()) : nullptr); + return cursor_shape_device_v1_t(p); +} + + +int cursor_shape_manager_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + return 0; +} + +cursor_shape_device_v1_t::cursor_shape_device_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&cursor_shape_device_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_device_v1_t(p); }); +} + +cursor_shape_device_v1_t::cursor_shape_device_v1_t() +{ + set_interface(&cursor_shape_device_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_device_v1_t(p); }); +} + +cursor_shape_device_v1_t::cursor_shape_device_v1_t(wp_cursor_shape_device_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&cursor_shape_device_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_device_v1_t(p); }); +} + +cursor_shape_device_v1_t::cursor_shape_device_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&cursor_shape_device_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_device_v1_t(p); }); +} + +cursor_shape_device_v1_t cursor_shape_device_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string cursor_shape_device_v1_t::interface_name = "wp_cursor_shape_device_v1"; + +cursor_shape_device_v1_t::operator wp_cursor_shape_device_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +void cursor_shape_device_v1_t::set_shape(uint32_t serial, cursor_shape_device_v1_shape const& shape) +{ + marshal(1U, serial, static_cast(shape)); +} + + +int cursor_shape_device_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + return 0; +} + + + + diff --git a/libraries/ZWidget/src/window/wayland/wl_cursor_shape.hpp b/libraries/ZWidget/src/window/wayland/wl_cursor_shape.hpp new file mode 100644 index 000000000..7514b128e --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_cursor_shape.hpp @@ -0,0 +1,258 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct wp_cursor_shape_manager_v1; +struct wp_cursor_shape_device_v1; + +namespace wayland +{ +class cursor_shape_manager_v1_t; +class cursor_shape_device_v1_t; +enum class cursor_shape_device_v1_shape : uint32_t; +enum class cursor_shape_device_v1_error : uint32_t; + +namespace detail +{ + extern const wl_interface cursor_shape_manager_v1_interface; + extern const wl_interface cursor_shape_device_v1_interface; +} + +/** \brief cursor shape manager + + This global offers an alternative, optional way to set cursor images. This + new way uses enumerated cursors instead of a wl_surface like + wl_pointer.set_cursor does. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + +*/ +class cursor_shape_manager_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + cursor_shape_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + cursor_shape_manager_v1_t(); + explicit cursor_shape_manager_v1_t(const proxy_t &proxy); + cursor_shape_manager_v1_t(wp_cursor_shape_manager_v1 *p, wrapper_type t = wrapper_type::standard); + + cursor_shape_manager_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator wp_cursor_shape_manager_v1*() const; + + /** \brief manage the cursor shape of a pointer device + \param pointer + + Obtain a wp_cursor_shape_device_v1 for a wl_pointer object. + + When the pointer capability is removed from the wl_seat, the + wp_cursor_shape_device_v1 object becomes inert. + + */ + cursor_shape_device_v1_t get_pointer(pointer_t const& pointer); + + /** \brief Minimum protocol version required for the \ref get_pointer function + */ + static constexpr std::uint32_t get_pointer_since_version = 1; + + /** \brief manage the cursor shape of a tablet tool device + \param tablet_tool + + Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object. + + When the zwp_tablet_tool_v2 is removed, the wp_cursor_shape_device_v1 + object becomes inert. + + */ + cursor_shape_device_v1_t get_tablet_tool_v2(zwp_tablet_tool_v2_t const& tablet_tool); + + /** \brief Minimum protocol version required for the \ref get_tablet_tool_v2 function + */ + static constexpr std::uint32_t get_tablet_tool_v2_since_version = 1; + +}; + + +/** \brief cursor shape for a device + + This interface allows clients to set the cursor shape. + +*/ +class cursor_shape_device_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + cursor_shape_device_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + cursor_shape_device_v1_t(); + explicit cursor_shape_device_v1_t(const proxy_t &proxy); + cursor_shape_device_v1_t(wp_cursor_shape_device_v1 *p, wrapper_type t = wrapper_type::standard); + + cursor_shape_device_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator wp_cursor_shape_device_v1*() const; + + /** \brief set device cursor to the shape + \param serial serial number of the enter event + \param shape + + Sets the device cursor to the specified shape. The compositor will + change the cursor image based on the specified shape. + + The cursor actually changes only if the input device focus is one of + the requesting client's surfaces. If any, the previous cursor image + (surface or shape) is replaced. + + The "shape" argument must be a valid enum entry, otherwise the + invalid_shape protocol error is raised. + + This is similar to the wl_pointer.set_cursor and + zwp_tablet_tool_v2.set_cursor requests, but this request accepts a + shape instead of contents in the form of a surface. Clients can mix + set_cursor and set_shape requests. + + The serial parameter must match the latest wl_pointer.enter or + zwp_tablet_tool_v2.proximity_in serial number sent to the client. + Otherwise the request will be ignored. + + */ + void set_shape(uint32_t serial, cursor_shape_device_v1_shape const& shape); + + /** \brief Minimum protocol version required for the \ref set_shape function + */ + static constexpr std::uint32_t set_shape_since_version = 1; + +}; + +/** \brief cursor shapes + + This enum describes cursor shapes. + + The names are taken from the CSS W3C specification: + https://w3c.github.io/csswg-drafts/css-ui/#cursor + with a few additions. + + Note that there are some groups of cursor shapes that are related: + The first group is drag-and-drop cursors which are used to indicate + the selected action during dnd operations. The second group is resize + cursors which are used to indicate resizing and moving possibilities + on window borders. It is recommended that the shapes in these groups + should use visually compatible images and metaphors. + + */ +enum class cursor_shape_device_v1_shape : uint32_t + { + /** \brief default cursor */ + _default = 1, + /** \brief a context menu is available for the object under the cursor */ + context_menu = 2, + /** \brief help is available for the object under the cursor */ + help = 3, + /** \brief pointer that indicates a link or another interactive element */ + pointer = 4, + /** \brief progress indicator */ + progress = 5, + /** \brief program is busy, user should wait */ + wait = 6, + /** \brief a cell or set of cells may be selected */ + cell = 7, + /** \brief simple crosshair */ + crosshair = 8, + /** \brief text may be selected */ + text = 9, + /** \brief vertical text may be selected */ + vertical_text = 10, + /** \brief drag-and-drop: alias of/shortcut to something is to be created */ + alias = 11, + /** \brief drag-and-drop: something is to be copied */ + copy = 12, + /** \brief drag-and-drop: something is to be moved */ + move = 13, + /** \brief drag-and-drop: the dragged item cannot be dropped at the current cursor location */ + no_drop = 14, + /** \brief drag-and-drop: the requested action will not be carried out */ + not_allowed = 15, + /** \brief drag-and-drop: something can be grabbed */ + grab = 16, + /** \brief drag-and-drop: something is being grabbed */ + grabbing = 17, + /** \brief resizing: the east border is to be moved */ + e_resize = 18, + /** \brief resizing: the north border is to be moved */ + n_resize = 19, + /** \brief resizing: the north-east corner is to be moved */ + ne_resize = 20, + /** \brief resizing: the north-west corner is to be moved */ + nw_resize = 21, + /** \brief resizing: the south border is to be moved */ + s_resize = 22, + /** \brief resizing: the south-east corner is to be moved */ + se_resize = 23, + /** \brief resizing: the south-west corner is to be moved */ + sw_resize = 24, + /** \brief resizing: the west border is to be moved */ + w_resize = 25, + /** \brief resizing: the east and west borders are to be moved */ + ew_resize = 26, + /** \brief resizing: the north and south borders are to be moved */ + ns_resize = 27, + /** \brief resizing: the north-east and south-west corners are to be moved */ + nesw_resize = 28, + /** \brief resizing: the north-west and south-east corners are to be moved */ + nwse_resize = 29, + /** \brief resizing: that the item/column can be resized horizontally */ + col_resize = 30, + /** \brief resizing: that the item/row can be resized vertically */ + row_resize = 31, + /** \brief something can be scrolled in any direction */ + all_scroll = 32, + /** \brief something can be zoomed in */ + zoom_in = 33, + /** \brief something can be zoomed out */ + zoom_out = 34, + /** \brief drag-and-drop: the user will select which action will be carried out (non-css value) */ + dnd_ask = 35, + /** \brief resizing: something can be moved or resized in any direction (non-css value) */ + all_resize = 36 +}; + +/** \brief + + */ +enum class cursor_shape_device_v1_error : uint32_t + { + /** \brief the specified shape value is invalid */ + invalid_shape = 1 +}; + + + +} diff --git a/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.cpp b/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.cpp new file mode 100644 index 000000000..89fa5b3f7 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.cpp @@ -0,0 +1,267 @@ +#include "wl_xdg_toplevel_icon.hpp" + +using namespace wayland; +using namespace wayland::detail; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_destroy_request[0] = { +}; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_create_icon_request[1] = { + &xdg_toplevel_icon_v1_interface, +}; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_set_icon_request[2] = { + &xdg_toplevel_interface, + &xdg_toplevel_icon_v1_interface, +}; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_icon_size_event[1] = { + nullptr, +}; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_done_event[0] = { +}; + +const wl_message xdg_toplevel_icon_manager_v1_interface_requests[3] = { + { + "destroy", + "", + xdg_toplevel_icon_manager_v1_interface_destroy_request, + }, + { + "create_icon", + "n", + xdg_toplevel_icon_manager_v1_interface_create_icon_request, + }, + { + "set_icon", + "o?o", + xdg_toplevel_icon_manager_v1_interface_set_icon_request, + }, +}; + +const wl_message xdg_toplevel_icon_manager_v1_interface_events[2] = { + { + "icon_size", + "i", + xdg_toplevel_icon_manager_v1_interface_icon_size_event, + }, + { + "done", + "", + xdg_toplevel_icon_manager_v1_interface_done_event, + }, +}; + +const wl_interface wayland::detail::xdg_toplevel_icon_manager_v1_interface = + { + "xdg_toplevel_icon_manager_v1", + 1, + 3, + xdg_toplevel_icon_manager_v1_interface_requests, + 2, + xdg_toplevel_icon_manager_v1_interface_events, + }; + +const wl_interface* xdg_toplevel_icon_v1_interface_destroy_request[0] = { +}; + +const wl_interface* xdg_toplevel_icon_v1_interface_set_name_request[1] = { + nullptr, +}; + +const wl_interface* xdg_toplevel_icon_v1_interface_add_buffer_request[2] = { + &buffer_interface, + nullptr, +}; + +const wl_message xdg_toplevel_icon_v1_interface_requests[3] = { + { + "destroy", + "", + xdg_toplevel_icon_v1_interface_destroy_request, + }, + { + "set_name", + "s", + xdg_toplevel_icon_v1_interface_set_name_request, + }, + { + "add_buffer", + "oi", + xdg_toplevel_icon_v1_interface_add_buffer_request, + }, +}; + +const wl_message xdg_toplevel_icon_v1_interface_events[0] = { +}; + +const wl_interface wayland::detail::xdg_toplevel_icon_v1_interface = + { + "xdg_toplevel_icon_v1", + 1, + 3, + xdg_toplevel_icon_v1_interface_requests, + 0, + xdg_toplevel_icon_v1_interface_events, + }; + +xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&xdg_toplevel_icon_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_manager_v1_t(p); }); +} + +xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t() +{ + set_interface(&xdg_toplevel_icon_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_manager_v1_t(p); }); +} + +xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(xdg_toplevel_icon_manager_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&xdg_toplevel_icon_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_manager_v1_t(p); }); +} + +xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&xdg_toplevel_icon_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_manager_v1_t(p); }); +} + +xdg_toplevel_icon_manager_v1_t xdg_toplevel_icon_manager_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string xdg_toplevel_icon_manager_v1_t::interface_name = "xdg_toplevel_icon_manager_v1"; + +xdg_toplevel_icon_manager_v1_t::operator xdg_toplevel_icon_manager_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +xdg_toplevel_icon_v1_t xdg_toplevel_icon_manager_v1_t::create_icon() +{ + proxy_t p = marshal_constructor(1U, &xdg_toplevel_icon_v1_interface, nullptr); + return xdg_toplevel_icon_v1_t(p); +} + + +void xdg_toplevel_icon_manager_v1_t::set_icon(xdg_toplevel_t const& toplevel, xdg_toplevel_icon_v1_t const& icon) +{ + marshal(2U, toplevel.proxy_has_object() ? reinterpret_cast(toplevel.c_ptr()) : nullptr, icon.proxy_has_object() ? reinterpret_cast(icon.c_ptr()) : nullptr); +} + + +std::function &xdg_toplevel_icon_manager_v1_t::on_icon_size() +{ + return std::static_pointer_cast(get_events())->icon_size; +} + +std::function &xdg_toplevel_icon_manager_v1_t::on_done() +{ + return std::static_pointer_cast(get_events())->done; +} + +int xdg_toplevel_icon_manager_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + std::shared_ptr events = std::static_pointer_cast(e); + switch(opcode) + { + case 0: + if(events->icon_size) events->icon_size(args[0].get()); + break; + case 1: + if(events->done) events->done(); + break; + } + return 0; +} + +xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&xdg_toplevel_icon_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_v1_t(p); }); +} + +xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t() +{ + set_interface(&xdg_toplevel_icon_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_v1_t(p); }); +} + +xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(xdg_toplevel_icon_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&xdg_toplevel_icon_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_v1_t(p); }); +} + +xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&xdg_toplevel_icon_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_v1_t(p); }); +} + +xdg_toplevel_icon_v1_t xdg_toplevel_icon_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string xdg_toplevel_icon_v1_t::interface_name = "xdg_toplevel_icon_v1"; + +xdg_toplevel_icon_v1_t::operator xdg_toplevel_icon_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +void xdg_toplevel_icon_v1_t::set_name(std::string const& icon_name) +{ + marshal(1U, icon_name); +} + + +void xdg_toplevel_icon_v1_t::add_buffer(buffer_t const& buffer, int32_t scale) +{ + marshal(2U, buffer.proxy_has_object() ? reinterpret_cast(buffer.c_ptr()) : nullptr, scale); +} + + +int xdg_toplevel_icon_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + return 0; +} + + + diff --git a/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.hpp b/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.hpp new file mode 100644 index 000000000..748d559a1 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.hpp @@ -0,0 +1,244 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct xdg_toplevel_icon_manager_v1; +struct xdg_toplevel_icon_v1; + +namespace wayland +{ +class xdg_toplevel_icon_manager_v1_t; +class xdg_toplevel_icon_v1_t; +enum class xdg_toplevel_icon_v1_error : uint32_t; + +namespace detail +{ + extern const wl_interface xdg_toplevel_icon_manager_v1_interface; + extern const wl_interface xdg_toplevel_icon_v1_interface; +} + +/** \brief interface to manage toplevel icons + + This interface allows clients to create toplevel window icons and set + them on toplevel windows to be displayed to the user. + +*/ +class xdg_toplevel_icon_manager_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + std::function icon_size; + std::function done; + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + xdg_toplevel_icon_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + xdg_toplevel_icon_manager_v1_t(); + explicit xdg_toplevel_icon_manager_v1_t(const proxy_t &proxy); + xdg_toplevel_icon_manager_v1_t(xdg_toplevel_icon_manager_v1 *p, wrapper_type t = wrapper_type::standard); + + xdg_toplevel_icon_manager_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator xdg_toplevel_icon_manager_v1*() const; + + /** \brief create a new icon instance + + Creates a new icon object. This icon can then be attached to a + xdg_toplevel via the 'set_icon' request. + + */ + xdg_toplevel_icon_v1_t create_icon(); + + /** \brief Minimum protocol version required for the \ref create_icon function + */ + static constexpr std::uint32_t create_icon_since_version = 1; + + /** \brief set an icon on a toplevel window + \param toplevel the toplevel to act on + \param icon + + This request assigns the icon 'icon' to 'toplevel', or clears the + toplevel icon if 'icon' was null. + This state is double-buffered and is applied on the next + wl_surface.commit of the toplevel. + + After making this call, the xdg_toplevel_icon_v1 provided as 'icon' + can be destroyed by the client without 'toplevel' losing its icon. + The xdg_toplevel_icon_v1 is immutable from this point, and any + future attempts to change it must raise the + 'xdg_toplevel_icon_v1.immutable' protocol error. + + The compositor must set the toplevel icon from either the pixel data + the icon provides, or by loading a stock icon using the icon name. + See the description of 'xdg_toplevel_icon_v1' for details. + + If 'icon' is set to null, the icon of the respective toplevel is reset + to its default icon (usually the icon of the application, derived from + its desktop-entry file, or a placeholder icon). + If this request is passed an icon with no pixel buffers or icon name + assigned, the icon must be reset just like if 'icon' was null. + + */ + void set_icon(xdg_toplevel_t const& toplevel, xdg_toplevel_icon_v1_t const& icon); + + /** \brief Minimum protocol version required for the \ref set_icon function + */ + static constexpr std::uint32_t set_icon_since_version = 1; + + /** \brief describes a supported & preferred icon size + \param size the edge size of the square icon in surface-local coordinates, e.g. 64 + + This event indicates an icon size the compositor prefers to be + available if the client has scalable icons and can render to any size. + + When the 'xdg_toplevel_icon_manager_v1' object is created, the + compositor may send one or more 'icon_size' events to describe the list + of preferred icon sizes. If the compositor has no size preference, it + may not send any 'icon_size' event, and it is up to the client to + decide a suitable icon size. + + A sequence of 'icon_size' events must be finished with a 'done' event. + If the compositor has no size preferences, it must still send the + 'done' event, without any preceding 'icon_size' events. + + */ + std::function &on_icon_size(); + + /** \brief all information has been sent + + This event is sent after all 'icon_size' events have been sent. + + */ + std::function &on_done(); + +}; + + +/** \brief a toplevel window icon + + This interface defines a toplevel icon. + An icon can have a name, and multiple buffers. + In order to be applied, the icon must have either a name, or at least + one buffer assigned. Applying an empty icon (with no buffer or name) to + a toplevel should reset its icon to the default icon. + + It is up to compositor policy whether to prefer using a buffer or loading + an icon via its name. See 'set_name' and 'add_buffer' for details. + +*/ +class xdg_toplevel_icon_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + xdg_toplevel_icon_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + xdg_toplevel_icon_v1_t(); + explicit xdg_toplevel_icon_v1_t(const proxy_t &proxy); + xdg_toplevel_icon_v1_t(xdg_toplevel_icon_v1 *p, wrapper_type t = wrapper_type::standard); + + xdg_toplevel_icon_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator xdg_toplevel_icon_v1*() const; + + /** \brief set an icon name + \param icon_name + + This request assigns an icon name to this icon. + Any previously set name is overridden. + + The compositor must resolve 'icon_name' according to the lookup rules + described in the XDG icon theme specification[1] using the + environment's current icon theme. + + If the compositor does not support icon names or cannot resolve + 'icon_name' according to the XDG icon theme specification it must + fall back to using pixel buffer data instead. + + If this request is made after the icon has been assigned to a toplevel + via 'set_icon', a 'immutable' error must be raised. + + [1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + + */ + void set_name(std::string const& icon_name); + + /** \brief Minimum protocol version required for the \ref set_name function + */ + static constexpr std::uint32_t set_name_since_version = 1; + + /** \brief add icon data from a pixel buffer + \param buffer + \param scale the scaling factor of the icon, e.g. 1 + + This request adds pixel data supplied as wl_buffer to the icon. + + The client should add pixel data for all icon sizes and scales that + it can provide, or which are explicitly requested by the compositor + via 'icon_size' events on xdg_toplevel_icon_manager_v1. + + The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm + and must be a square (width and height being equal). + If any of these buffer requirements are not fulfilled, a 'invalid_buffer' + error must be raised. + + If this icon instance already has a buffer of the same size and scale + from a previous 'add_buffer' request, data from the last request + overrides the preexisting pixel data. + + The wl_buffer must be kept alive for as long as the xdg_toplevel_icon + it is associated with is not destroyed, otherwise a 'no_buffer' error + is raised. The buffer contents must not be modified after it was + assigned to the icon. As a result, the region of the wl_shm_pool's + backing storage used for the wl_buffer must not be modified after this + request is sent. The wl_buffer.release event is unused. + + If this request is made after the icon has been assigned to a toplevel + via 'set_icon', a 'immutable' error must be raised. + + */ + void add_buffer(buffer_t const& buffer, int32_t scale); + + /** \brief Minimum protocol version required for the \ref add_buffer function + */ + static constexpr std::uint32_t add_buffer_since_version = 1; + +}; + +/** \brief + + */ +enum class xdg_toplevel_icon_v1_error : uint32_t + { + /** \brief the provided buffer does not satisfy requirements */ + invalid_buffer = 1, + /** \brief the icon has already been assigned to a toplevel and must not be changed */ + immutable = 2, + /** \brief the provided buffer has been destroyed before the toplevel icon */ + no_buffer = 3 +}; + + + +} diff --git a/libraries/ZWidget/src/window/win32/win32_display_window.cpp b/libraries/ZWidget/src/window/win32/win32_display_window.cpp index 30027e1cc..fabb5ec12 100644 --- a/libraries/ZWidget/src/window/win32/win32_display_window.cpp +++ b/libraries/ZWidget/src/window/win32/win32_display_window.cpp @@ -1,5 +1,6 @@ #include "win32_display_window.h" +#include #include #include #include @@ -30,11 +31,11 @@ // Code for delay loading DPI related functions, needed for continued Windows 7 compatibility. typedef BOOL (WINAPI *PFN_AdjustWindowRectExForDpi)( - LPRECT lpRect, - DWORD dwStyle, - BOOL bMenu, - DWORD dwExStyle, - UINT dpi + LPRECT lpRect, + DWORD dwStyle, + BOOL bMenu, + DWORD dwExStyle, + UINT dpi ); typedef UINT (WINAPI *PFN_GetDpiForWindow)(HWND hwnd); @@ -44,39 +45,36 @@ static PFN_GetDpiForWindow pGetDpiForWindow = nullptr; static void DPIDelayLoad() { - HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); - if (hUser32) { - pAdjustWindowRectExForDpi = (PFN_AdjustWindowRectExForDpi) - GetProcAddress(hUser32, "AdjustWindowRectExForDpi"); - pGetDpiForWindow = (PFN_GetDpiForWindow) - GetProcAddress(hUser32, "GetDpiForWindow"); - } + HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); + if (hUser32) + { + pAdjustWindowRectExForDpi = (PFN_AdjustWindowRectExForDpi)GetProcAddress(hUser32, "AdjustWindowRectExForDpi"); + pGetDpiForWindow = (PFN_GetDpiForWindow)GetProcAddress(hUser32, "GetDpiForWindow"); + } } -static BOOL DelayLoadAdjustWindowRectExForDpi( - LPRECT lpRect, - DWORD dwStyle, - BOOL bMenu, - DWORD dwExStyle, - HWND hwnd -) { +static BOOL DelayLoadAdjustWindowRectExForDpi(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, HWND hwnd) +{ DPIDelayLoad(); - if (pAdjustWindowRectExForDpi) { - return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle, - pGetDpiForWindow(hwnd)); - } else { - return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle); - } + if (pAdjustWindowRectExForDpi) + { + return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle, + pGetDpiForWindow(hwnd)); + } + else + { + return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle); + } } static double DelayLoadGetDpiScale(HWND hwnd) { DPIDelayLoad(); - if (pGetDpiForWindow) { - return pGetDpiForWindow(hwnd) / 96.0; - } else { - return 1.0; - } + if (pGetDpiForWindow) { + return pGetDpiForWindow(hwnd) / 96.0; + } else { + return 1.0; + } } Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow) @@ -120,6 +118,11 @@ Win32DisplayWindow::~Win32DisplayWindow() WindowHandle.hwnd = 0; } + if (SmallIcon) + DestroyCursor(SmallIcon); + if (LargeIcon) + DestroyCursor(LargeIcon); + Windows.erase(WindowsIterator); } @@ -128,6 +131,95 @@ void Win32DisplayWindow::SetWindowTitle(const std::string& text) SetWindowText(WindowHandle.hwnd, to_utf16(text).c_str()); } +static HICON CreateIconFromImageList(HDC hdc, const std::vector>& images, int desiredSize) +{ + if (images.empty()) + return 0; + + Image* image = images.front().get(); + for (const auto& i : images) + { + int curdist = std::abs(image->GetWidth() - desiredSize); + int dist = std::abs(i->GetWidth() - desiredSize); + if (dist < curdist) + image = i.get(); + } + + int width = image->GetWidth(); + int height = image->GetHeight(); + std::vector pixels(width * height * 4); + + if (image->GetFormat() == ImageFormat::R8G8B8A8) + { + int count = width * height; + const uint32_t* src = (const uint32_t*)image->GetData(); + uint32_t* dest = pixels.data(); + for (int i = 0; i < count; i++) + { + uint32_t r = src[i] & 0xff; + uint32_t g = (src[i] >> 8) & 0xff; + uint32_t b = (src[i] >> 16) & 0xff; + uint32_t a = (src[i] >> 24) & 0xff; + dest[i] = (a << 24) | (r << 16) | (g << 8) | b; + } + } + else if (image->GetFormat() == ImageFormat::B8G8R8A8) + { + memcpy(pixels.data(), image->GetData(), width * height * 4); + } + else + { + return 0; + } + + BITMAPV5HEADER bmp_header = {}; + bmp_header.bV5Size = sizeof(BITMAPV5HEADER); + bmp_header.bV5Width = width; + bmp_header.bV5Height = height; + bmp_header.bV5Planes = 1; + bmp_header.bV5BitCount = 32; + bmp_header.bV5Compression = BI_RGB; + + HBITMAP bitmap = CreateDIBitmap(hdc, (BITMAPINFOHEADER*)&bmp_header, CBM_INIT, pixels.data(), (BITMAPINFO*)&bmp_header, DIB_RGB_COLORS); + if (!bitmap) + return 0; + + ICONINFO iconinfo = {}; + iconinfo.fIcon = TRUE; + iconinfo.hbmColor = bitmap; + iconinfo.hbmMask = bitmap; + HICON icon = CreateIconIndirect(&iconinfo); + DeleteObject(bitmap); + return icon; +} + +void Win32DisplayWindow::SetWindowIcon(const std::vector>& images) +{ + double dpiScale = GetDpiScale(); + + if (SmallIcon) + { + DestroyCursor(SmallIcon); + SmallIcon = {}; + } + if (LargeIcon) + { + DestroyCursor(LargeIcon); + LargeIcon = {}; + } + + HDC hdc = GetDC(WindowHandle.hwnd); + if (hdc) + { + SmallIcon = CreateIconFromImageList(hdc, images, (int)std::round(16 * dpiScale)); + LargeIcon = CreateIconFromImageList(hdc, images, (int)std::round(32 * dpiScale)); + ReleaseDC(WindowHandle.hwnd, hdc); + } + + SendMessage(WindowHandle.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)SmallIcon); + SendMessage(WindowHandle.hwnd, WM_SETICON, ICON_BIG, (LPARAM)LargeIcon); +} + void Win32DisplayWindow::SetBorderColor(uint32_t bgra8) { bgra8 = bgra8 & 0x00ffffff; diff --git a/libraries/ZWidget/src/window/win32/win32_display_window.h b/libraries/ZWidget/src/window/win32/win32_display_window.h index 7666274bd..431f9ed65 100644 --- a/libraries/ZWidget/src/window/win32/win32_display_window.h +++ b/libraries/ZWidget/src/window/win32/win32_display_window.h @@ -14,6 +14,7 @@ public: ~Win32DisplayWindow(); void SetWindowTitle(const std::string& text) override; + void SetWindowIcon(const std::vector>& images) override; void SetWindowFrame(const Rect& box) override; void SetClientFrame(const Rect& box) override; void Show() override; @@ -90,5 +91,8 @@ public: HDC PaintDC = 0; + HICON SmallIcon = {}; + HICON LargeIcon = {}; + StandardCursor CurrentCursor = StandardCursor::arrow; }; diff --git a/libraries/ZWidget/src/window/win32/win32_open_file_dialog.cpp b/libraries/ZWidget/src/window/win32/win32_open_file_dialog.cpp index 092a78da1..ec2417b78 100644 --- a/libraries/ZWidget/src/window/win32/win32_open_file_dialog.cpp +++ b/libraries/ZWidget/src/window/win32/win32_open_file_dialog.cpp @@ -3,6 +3,7 @@ #include "win32_display_window.h" #include "core/widget.h" #include +#include #include #include #include @@ -14,6 +15,65 @@ Win32OpenFileDialog::Win32OpenFileDialog(Win32DisplayWindow* owner) : owner(owne bool Win32OpenFileDialog::Show() { + // For some reason this can hang deep inside Win32 if we do it on the calling thread! + bool done = false; + std::mutex mutex; + std::condition_variable condvar; + bool showResult = false; + std::exception_ptr exception; + + std::thread thread([&]() { + + try + { + showResult = ShowWorkerThread(); + } + catch (...) + { + exception = std::current_exception(); + } + + std::unique_lock lock(mutex); + done = true; + condvar.notify_all(); + + }); + + std::unique_lock lock(mutex); + while (!done) + { + DisplayBackend::Get()->ProcessEvents(); + using namespace std::chrono_literals; + condvar.wait_for(lock, 50ms, [&]() { return done; }); + } + lock.unlock(); + thread.join(); + + if (exception) + std::rethrow_exception(exception); + + return showResult; +} + +bool Win32OpenFileDialog::ShowWorkerThread() +{ + class InitCOM + { + public: + InitCOM() + { + HRESULT result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (FAILED(result)) + throw std::runtime_error("CoInitializeEx(COINIT_APARTMENTTHREADED) failed"); + } + + ~InitCOM() + { + CoUninitialize(); + } + }; + InitCOM initCOM; + std::wstring title16 = to_utf16(title); std::wstring initial_directory16 = to_utf16(initial_directory); @@ -92,35 +152,10 @@ bool Win32OpenFileDialog::Show() } } - // For some reason this can hang deep inside Win32 if we do it on the calling thread! - { - bool done = false; - std::mutex mutex; - std::condition_variable condvar; - - std::thread thread([&]() { - - if (owner) - result = open_dialog->Show(owner->WindowHandle.hwnd); - else - result = open_dialog->Show(0); - - std::unique_lock lock(mutex); - done = true; - condvar.notify_all(); - - }); - - std::unique_lock lock(mutex); - while (!done) - { - DisplayBackend::Get()->ProcessEvents(); - using namespace std::chrono_literals; - condvar.wait_for(lock, 50ms, [&]() { return done; }); - } - lock.unlock(); - thread.join(); - } + if (owner) + result = open_dialog->Show(owner->WindowHandle.hwnd); + else + result = open_dialog->Show(0); if (SUCCEEDED(result)) { diff --git a/libraries/ZWidget/src/window/win32/win32_open_file_dialog.h b/libraries/ZWidget/src/window/win32/win32_open_file_dialog.h index 52ac858f0..a71e9ae9c 100644 --- a/libraries/ZWidget/src/window/win32/win32_open_file_dialog.h +++ b/libraries/ZWidget/src/window/win32/win32_open_file_dialog.h @@ -23,6 +23,7 @@ public: void SetDefaultExtension(const std::string& extension) override; private: + bool ShowWorkerThread(); void throw_if_failed(HRESULT result, const std::string& error); Win32DisplayWindow* owner = nullptr; diff --git a/libraries/ZWidget/src/window/x11/x11_connection.cpp b/libraries/ZWidget/src/window/x11/x11_connection.cpp new file mode 100644 index 000000000..8c8b25a06 --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_connection.cpp @@ -0,0 +1,261 @@ + +#include "x11_connection.h" +#include "x11_display_window.h" +#include +#include + +X11Connection* GetX11Connection() +{ + static X11Connection connection; + return &connection; +} + +X11Connection::X11Connection() +{ + // This is required by vulkan + XInitThreads(); + + display = XOpenDisplay(nullptr); + if (!display) + throw std::runtime_error("Could not open X11 display"); + + // Make auto-repeat keys detectable + Bool supports_detectable_autorepeat = {}; + XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat); + + // Loads the XMODIFIERS environment variable to see what IME to use + XSetLocaleModifiers(""); + xim = XOpenIM(display, 0, 0, 0); + if (!xim) + { + // fallback to internal input method + XSetLocaleModifiers("@im=none"); + xim = XOpenIM(display, 0, 0, 0); + } + + // Look for XInput support + int event = 0, error = 0; + if (XQueryExtension(display, "XInputExtension", &XInputOpcode, &event, &error)) + { + // We need XInput 2.0 support + int major = 2, minor = 0; + if (XIQueryVersion(display, &major, &minor) != BadRequest) + { + // And we need a master pointer ID + int ndevices = 0; + XIDeviceInfo *devices = XIQueryDevice(display, XIAllDevices, &ndevices); + if (devices) + { + for (int i = 0; i < ndevices; i++) + { + if (devices[i].use == XIMasterPointer) + { + MasterPointerID = devices[i].deviceid; + XInput2Supported = true; + break; + } + } + XIFreeDeviceInfo(devices); + } + } + } + + // This XInput API is the gift that just keeps on giving. Instead of routing events to the + // focus window it sends it to the root window. Thanks! + if (XInput2Supported) + { + unsigned char mask[3] = { 0 }; + XISetMask(mask, XI_RawMotion); + XIEventMask eventmask; + eventmask.deviceid = MasterPointerID; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + Window root = XRootWindow(display, XDefaultScreen(display)); + XISelectEvents(display, root, &eventmask, 1); + } +} + +X11Connection::~X11Connection() +{ + for (auto& it : standardCursors) + XFreeCursor(display, it.second); + if (xim) + XCloseIM(xim); + XCloseDisplay(display); +} + +Atom X11Connection::GetAtom(const std::string& name) +{ + auto it = atoms.find(name); + if (it != atoms.end()) + return it->second; + + Atom atom = XInternAtom(display, name.c_str(), True); + atoms[name] = atom; + return atom; +} + +void X11Connection::ProcessEvents() +{ + CheckNeedsUpdate(); + while (XPending(display) > 0) + { + XEvent event = {}; + XNextEvent(display, &event); + DispatchEvent(&event); + } + CheckTimers(); +} + +void X11Connection::RunLoop() +{ + X11Connection* connection = GetX11Connection(); + connection->ExitRunLoop = false; + while (!connection->ExitRunLoop && !connection->windows.empty()) + { + ProcessEvents(); + WaitForEvents(GetTimerTimeout()); + } +} + +void X11Connection::ExitLoop() +{ + X11Connection* connection = GetX11Connection(); + connection->ExitRunLoop = true; +} + +bool X11Connection::WaitForEvents(int timeout) +{ + int fd = XConnectionNumber(display); + + struct timeval tv; + if (timeout > 0) + { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) / 1000; + } + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr); + return result > 0 && FD_ISSET(fd, &rfds); +} + +void X11Connection::CheckNeedsUpdate() +{ + for (auto& it : windows) + { + if (it.second->needsUpdate) + { + it.second->needsUpdate = false; + it.second->windowHost->OnWindowPaint(); + } + } +} + +void X11Connection::DispatchEvent(XEvent* event) +{ + if (XInput2Supported) + { + // XInput sends all raw input to the root window. We want it routed to the focused window + if (event->xcookie.type == GenericEvent && + event->xcookie.extension == XInputOpcode && + XGetEventData(display, &event->xcookie)) + { + for (auto& it : windows) + { + X11DisplayWindow* window = it.second; + if (window->OnXInputEvent(event)) + break; + } + XFreeEventData(display, &event->xcookie); + } + } + + auto it = windows.find(event->xany.window); + if (it != windows.end()) + { + X11DisplayWindow* window = it->second; + window->OnEvent(event); + } +} + +Size X11Connection::GetScreenSize() +{ + int screen = XDefaultScreen(display); + + int disp_width_px = XDisplayWidth(display, screen); + int disp_height_px = XDisplayHeight(display, screen); + int disp_width_mm = XDisplayWidthMM(display, screen); + double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast(disp_width_px) / static_cast(disp_width_mm)); + double dpiScale = ppi / 96.0; + + return Size(disp_width_px / dpiScale, disp_height_px / dpiScale); +} + +static int64_t GetTimePoint() +{ + using namespace std::chrono; + return (int64_t)(duration_cast(steady_clock::now().time_since_epoch()).count()); +} + +void* X11Connection::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + timeoutMilliseconds = std::max(timeoutMilliseconds, 1); + timers.push_back(std::make_shared(timeoutMilliseconds, onTimer, GetTimePoint() + timeoutMilliseconds)); + return timers.back().get(); +} + +void X11Connection::StopTimer(void* timerID) +{ + for (auto it = timers.begin(); it != timers.end(); ++it) + { + if (it->get() == timerID) + { + timers.erase(it); + return; + } + } +} + +void X11Connection::CheckTimers() +{ + int64_t now = GetTimePoint(); + + // The callback may stop timers. Iterators might invalidate. + while (true) + { + std::shared_ptr foundTimer; + for (auto& timer : timers) + { + if (timer->nextTime < now) + { + foundTimer = timer; + break; + } + } + + if (!foundTimer) + break; + + // Not very precise, but these aren't high precision timers + foundTimer->nextTime = now + foundTimer->timeoutMilliseconds; + foundTimer->onTimer(); + } +} + +int X11Connection::GetTimerTimeout() +{ + if (timers.empty()) + return 0; + + int64_t nextTime = timers.front()->nextTime; + for (auto& timer : timers) + { + nextTime = std::min(nextTime, timer->nextTime); + } + + int64_t now = GetTimePoint(); + return (int)std::max(nextTime - now, (int64_t)1); +} diff --git a/libraries/ZWidget/src/window/x11/x11_connection.h b/libraries/ZWidget/src/window/x11/x11_connection.h new file mode 100644 index 000000000..8c0a46eda --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_connection.h @@ -0,0 +1,65 @@ +#pragma once + +#include "window/window.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class X11DisplayWindow; + +class X11Timer +{ +public: + X11Timer(int timeoutMilliseconds, std::function onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {} + + int timeoutMilliseconds = 0; + std::function onTimer; + int64_t nextTime = 0; +}; + +class X11Connection +{ +public: + X11Connection(); + ~X11Connection(); + + void ProcessEvents(); + void RunLoop(); + void ExitLoop(); + bool WaitForEvents(int timeout); + + void* StartTimer(int timeoutMilliseconds, std::function onTimer); + void StopTimer(void* timerID); + + Size GetScreenSize(); + + Atom GetAtom(const std::string& name); + + Display* display = nullptr; + std::map atoms; + std::map windows; + std::map standardCursors; + bool ExitRunLoop = false; + + XIM xim = nullptr; + bool XInput2Supported = false; + int XInputOpcode = 0; + int MasterPointerID = 0; + +private: + void DispatchEvent(XEvent* event); + void CheckNeedsUpdate(); + void CheckTimers(); + int GetTimerTimeout(); + + std::vector> timers; +}; + +X11Connection* GetX11Connection(); diff --git a/libraries/ZWidget/src/window/x11/x11_display_backend.cpp b/libraries/ZWidget/src/window/x11/x11_display_backend.cpp index b97760b3a..4ca267d90 100644 --- a/libraries/ZWidget/src/window/x11/x11_display_backend.cpp +++ b/libraries/ZWidget/src/window/x11/x11_display_backend.cpp @@ -1,6 +1,8 @@ #include "x11_display_backend.h" #include "x11_display_window.h" +#include "x11_connection.h" +#include #ifdef USE_DBUS #include "window/dbus/dbus_open_file_dialog.h" @@ -15,32 +17,32 @@ std::unique_ptr X11DisplayBackend::Create(DisplayWindowHost* wind void X11DisplayBackend::ProcessEvents() { - X11DisplayWindow::ProcessEvents(); + GetX11Connection()->ProcessEvents(); } void X11DisplayBackend::RunLoop() { - X11DisplayWindow::RunLoop(); + GetX11Connection()->RunLoop(); } void X11DisplayBackend::ExitLoop() { - X11DisplayWindow::ExitLoop(); + GetX11Connection()->ExitLoop(); } Size X11DisplayBackend::GetScreenSize() { - return X11DisplayWindow::GetScreenSize(); + return GetX11Connection()->GetScreenSize(); } void* X11DisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) { - return X11DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); + return GetX11Connection()->StartTimer(timeoutMilliseconds, onTimer); } void X11DisplayBackend::StopTimer(void* timerID) { - X11DisplayWindow::StopTimer(timerID); + GetX11Connection()->StopTimer(timerID); } #ifdef USE_DBUS diff --git a/libraries/ZWidget/src/window/x11/x11_display_window.cpp b/libraries/ZWidget/src/window/x11/x11_display_window.cpp index 00887ff94..16235b30d 100644 --- a/libraries/ZWidget/src/window/x11/x11_display_window.cpp +++ b/libraries/ZWidget/src/window/x11/x11_display_window.cpp @@ -1,5 +1,7 @@ #include "x11_display_window.h" +#include "x11_connection.h" +#include #include #include #include @@ -10,83 +12,24 @@ #include #include -class X11Connection -{ -public: - X11Connection() - { - // If we ever want to support windows on multiple threads: - // XInitThreads(); - - display = XOpenDisplay(nullptr); - if (!display) - throw std::runtime_error("Could not open X11 display"); - - // Make auto-repeat keys detectable - Bool supports_detectable_autorepeat = {}; - XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat); - - // Loads the XMODIFIERS environment variable to see what IME to use - XSetLocaleModifiers(""); - xim = XOpenIM(display, 0, 0, 0); - if (!xim) - { - // fallback to internal input method - XSetLocaleModifiers("@im=none"); - xim = XOpenIM(display, 0, 0, 0); - } - } - - ~X11Connection() - { - for (auto& it : standardCursors) - XFreeCursor(display, it.second); - if (xim) - XCloseIM(xim); - XCloseDisplay(display); - } - - Display* display = nullptr; - std::map atoms; - std::map windows; - std::map standardCursors; - bool ExitRunLoop = false; - - XIM xim = nullptr; -}; - -static X11Connection* GetX11Connection() -{ - static X11Connection connection; - return &connection; -} - -static Atom GetAtom(const std::string& name) -{ - auto connection = GetX11Connection(); - auto it = connection->atoms.find(name); - if (it != connection->atoms.end()) - return it->second; - - Atom atom = XInternAtom(connection->display, name.c_str(), True); - connection->atoms[name] = atom; - return atom; -} - X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, X11DisplayWindow* owner, RenderAPI renderAPI) : windowHost(windowHost), owner(owner) { - display = GetX11Connection()->display; + auto connection = GetX11Connection(); + display = connection->display; screen = XDefaultScreen(display); depth = XDefaultDepth(display, screen); visual = XDefaultVisual(display, screen); colormap = XDefaultColormap(display, screen); - int disp_width_px = XDisplayWidth(display, screen); - int disp_height_px = XDisplayHeight(display, screen); - int disp_width_mm = XDisplayWidthMM(display, screen); - double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast(disp_width_px) / static_cast(disp_width_mm)); - dpiScale = std::round(ppi / 96.0 * 4.0) / 4.0; // 100%, 125%, 150%, 175%, 200%, etc. + if (char* value = XGetDefault(display, "Xft", "dpi")) + { + int dpi = std::atoi(value); + if (dpi != 0) + { + dpiScale = dpi / 96.0; + } + } XSetWindowAttributes attributes = {}; attributes.backing_store = Always; @@ -94,14 +37,32 @@ X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWind attributes.save_under = popupWindow ? True : False; attributes.colormap = colormap; attributes.event_mask = - KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | - EnterWindowMask | LeaveWindowMask | PointerMotionMask | KeymapStateMask | + KeyPressMask | KeyReleaseMask | + EnterWindowMask | LeaveWindowMask | KeymapStateMask | ExposureMask | StructureNotifyMask | FocusChangeMask | PropertyChangeMask; + + if (!connection->XInput2Supported) + { + attributes.event_mask |= ButtonPressMask | ButtonReleaseMask | PointerMotionMask; + } unsigned long mask = CWBackingStore | CWSaveUnder | CWEventMask | CWOverrideRedirect; window = XCreateWindow(display, XRootWindow(display, screen), 0, 0, 100, 100, 0, depth, InputOutput, visual, mask, &attributes); - GetX11Connection()->windows[window] = this; + connection->windows[window] = this; + + if (connection->XInput2Supported) + { + unsigned char mask[1] = { 0 }; + XISetMask(mask, XI_ButtonPress); + XISetMask(mask, XI_ButtonRelease); + XISetMask(mask, XI_Motion); + XIEventMask eventmask; + eventmask.deviceid = connection->MasterPointerID; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + XISelectEvents(display, window, &eventmask, 1); + } if (owner) { @@ -109,59 +70,59 @@ X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWind } // Tell window manager which process this window came from - if (GetAtom("_NET_WM_PID") != None) + if (connection->GetAtom("_NET_WM_PID") != None) { int32_t pid = getpid(); if (pid != 0) { - XChangeProperty(display, window, GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + XChangeProperty(display, window, connection->GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); } } // Tell window manager which machine this window came from - if (GetAtom("WM_CLIENT_MACHINE") != None) + if (connection->GetAtom("WM_CLIENT_MACHINE") != None) { std::vector hostname(256); if (gethostname(hostname.data(), hostname.size()) >= 0) { hostname.push_back(0); - XChangeProperty(display, window, GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data())); + XChangeProperty(display, window, connection->GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data())); } } // Tell window manager we want to listen to close events - if (GetAtom("WM_DELETE_WINDOW") != None) + if (connection->GetAtom("WM_DELETE_WINDOW") != None) { - Atom protocol = GetAtom("WM_DELETE_WINDOW"); + Atom protocol = connection->GetAtom("WM_DELETE_WINDOW"); XSetWMProtocols(display, window, &protocol, 1); } // Tell window manager what type of window we are - if (GetAtom("_NET_WM_WINDOW_TYPE") != None) + if (connection->GetAtom("_NET_WM_WINDOW_TYPE") != None) { Atom type = None; if (popupWindow) { - type = GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"); + type = connection->GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"); if (type == None) - type = GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU"); + type = connection->GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU"); if (type == None) - type = GetAtom("_NET_WM_WINDOW_TYPE_COMBO"); + type = connection->GetAtom("_NET_WM_WINDOW_TYPE_COMBO"); } if (type == None) - type = GetAtom("_NET_WM_WINDOW_TYPE_NORMAL"); + type = connection->GetAtom("_NET_WM_WINDOW_TYPE_NORMAL"); if (type != None) { - XChangeProperty(display, window, GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1); + XChangeProperty(display, window, connection->GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1); } } // Create input context - if (GetX11Connection()->xim) + if (connection->xim) { xic = XCreateIC( - GetX11Connection()->xim, + connection->xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, window, @@ -188,6 +149,87 @@ void X11DisplayWindow::SetWindowTitle(const std::string& text) XSetStandardProperties(display, window, text.c_str(), text.c_str(), None, nullptr, 0, nullptr); } +void X11DisplayWindow::SetWindowIcon(const std::vector>& images) +{ + if (images.empty()) + return; + + double dpiscale = GetDpiScale(); + int desiredSize = (int)std::round(32 * dpiscale); + + Image* image = images.front().get(); + for (const auto& i : images) + { + int curdist = std::abs(image->GetWidth() - desiredSize); + int dist = std::abs(i->GetWidth() - desiredSize); + if (dist < curdist) + image = i.get(); + } + + int width = image->GetWidth(); + int height = image->GetHeight(); + const uint32_t* s = (const uint32_t*)image->GetData(); + + unsigned int size = (width * height) + 2; // header is 2 ints + unsigned long* data = (unsigned long*)malloc(size * sizeof(unsigned long)); + + // set header + data[0] = width; + data[1] = height; + + // on 64bit systems, the destination buffer is 64 bit per pixel + // thus, we have to copy each pixel individually (no memcpy) + + // icon data is expected as ARGB + + if (image->GetFormat() == ImageFormat::R8G8B8A8) + { + for (int y = 0; y < height; ++y) + { + const uint32_t* src = s + y * width; + unsigned long* dst = &data[2 + (y * width)]; + for (int x = 0; x < width; ++x) + { + uint32_t r = src[x] & 0xff; + uint32_t g = (src[x] >> 8) & 0xff; + uint32_t b = (src[x] >> 16) & 0xff; + uint32_t a = (src[x] >> 24) & 0xff; + dst[x] = (a << 24) | (r << 16) | (g << 8) | b; + } + } + } + else if (image->GetFormat() == ImageFormat::B8G8R8A8) + { + for (int y = 0; y < height; ++y) + { + const uint32_t* src = s + y * width; + unsigned long* dst = &data[2 + (y * width)]; + for (int x = 0; x < width; ++x) + { + dst[x] = src[x]; + } + } + } + else + { + free(data); + return; + } + + // set icon geometry + unsigned long* geom = (unsigned long*)malloc(4 * sizeof(unsigned long)); + geom[0] = geom[1] = 0; // x, y + geom[2] = width; + geom[3] = height; + + Atom propertyGeom = XInternAtom(display, "_NET_WM_ICON_GEOMETRY", 0); + XChangeProperty(display, window, propertyGeom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)geom, 4); + + // set icon data + Atom property = XInternAtom(display, "_NET_WM_ICON", 0); + XChangeProperty(display, window, property, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)data, size); +} + void X11DisplayWindow::SetWindowFrame(const Rect& box) { // To do: this requires cooperation with the window manager @@ -226,10 +268,11 @@ void X11DisplayWindow::ShowFullscreen() { Show(); - if (GetAtom("_NET_WM_STATE") != None && GetAtom("_NET_WM_STATE_FULLSCREEN") != None) + auto connection = GetX11Connection(); + if (connection->GetAtom("_NET_WM_STATE") != None && connection->GetAtom("_NET_WM_STATE_FULLSCREEN") != None) { - Atom state = GetAtom("_NET_WM_STATE_FULLSCREEN"); - XChangeProperty(display, window, GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1); + Atom state = connection->GetAtom("_NET_WM_STATE_FULLSCREEN"); + XChangeProperty(display, window, connection->GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1); isFullscreen = true; } } @@ -378,7 +421,7 @@ Rect X11DisplayWindow::GetWindowFrame() const unsigned int height = 0; unsigned int borderwidth = 0; unsigned int depth = 0; - Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + /*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); return Rect::xywh(x / dpiscale, y / dpiscale, width / dpiscale, height / dpiscale); } @@ -394,7 +437,7 @@ Size X11DisplayWindow::GetClientSize() const unsigned int height = 0; unsigned int borderwidth = 0; unsigned int depth = 0; - Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + /*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); return Size(width / dpiscale, height / dpiscale); } @@ -408,7 +451,7 @@ int X11DisplayWindow::GetPixelWidth() const unsigned int height = 0; unsigned int borderwidth = 0; unsigned int depth = 0; - Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + /*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); return width; } @@ -421,7 +464,7 @@ int X11DisplayWindow::GetPixelHeight() const unsigned int height = 0; unsigned int borderwidth = 0; unsigned int depth = 0; - Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); + /*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth); return height; } @@ -486,8 +529,8 @@ void X11DisplayWindow::SetCaptionTextColor(uint32_t bgra8) std::vector X11DisplayWindow::GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count) { long read_bytes = 0; - Atom _actual_type = actual_type; - int _actual_format = actual_format; + //Atom _actual_type = actual_type; + //int _actual_format = actual_format; unsigned long _item_count = item_count; unsigned long bytes_remaining = 0; unsigned char *read_data = nullptr; @@ -516,7 +559,8 @@ std::vector X11DisplayWindow::GetWindowProperty(Atom property, Atom &ac std::string X11DisplayWindow::GetClipboardText() { - Atom clipboard = GetAtom("CLIPBOARD"); + auto connection = GetX11Connection(); + Atom clipboard = connection->GetAtom("CLIPBOARD"); if (clipboard == None) return {}; @@ -529,7 +573,7 @@ std::string X11DisplayWindow::GetClipboardText() { if (XCheckTypedWindowEvent(display, window, SelectionNotify, &event)) break; - if (!WaitForEvents(500)) + if (!connection->WaitForEvents(500)) return {}; } @@ -548,7 +592,8 @@ void X11DisplayWindow::SetClipboardText(const std::string& text) { clipboardText = text; - Atom clipboard = GetAtom("CLIPBOARD"); + auto connection = GetX11Connection(); + Atom clipboard = connection->GetAtom("CLIPBOARD"); if (clipboard == None) return; @@ -565,7 +610,7 @@ Point X11DisplayWindow::MapFromGlobal(const Point& pos) const int srcy = (int)std::round(pos.y * dpiscale); int destx = 0; int desty = 0; - Bool result = XTranslateCoordinates(display, root, window, srcx, srcy, &destx, &desty, &child); + /*Bool result =*/ XTranslateCoordinates(display, root, window, srcx, srcy, &destx, &desty, &child); return Point(destx / dpiscale, desty / dpiscale); } @@ -578,7 +623,7 @@ Point X11DisplayWindow::MapToGlobal(const Point& pos) const int srcy = (int)std::round(pos.y * dpiscale); int destx = 0; int desty = 0; - Bool result = XTranslateCoordinates(display, window, root, srcx, srcy, &destx, &desty, &child); + /*Bool result =*/ XTranslateCoordinates(display, window, root, srcx, srcy, &destx, &desty, &child); return Point(destx / dpiscale, desty / dpiscale); } @@ -587,79 +632,6 @@ void* X11DisplayWindow::GetNativeHandle() return reinterpret_cast(window); } -bool X11DisplayWindow::WaitForEvents(int timeout) -{ - Display* display = GetX11Connection()->display; - int fd = XConnectionNumber(display); - - struct timeval tv; - if (timeout > 0) - { - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) / 1000; - } - - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(fd, &rfds); - int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr); - return result > 0 && FD_ISSET(fd, &rfds); -} - -void X11DisplayWindow::CheckNeedsUpdate() -{ - for (auto& it : GetX11Connection()->windows) - { - if (it.second->needsUpdate) - { - it.second->needsUpdate = false; - it.second->windowHost->OnWindowPaint(); - } - } -} - -void X11DisplayWindow::ProcessEvents() -{ - CheckNeedsUpdate(); - Display* display = GetX11Connection()->display; - while (XPending(display) > 0) - { - XEvent event = {}; - XNextEvent(display, &event); - DispatchEvent(&event); - } -} - -void X11DisplayWindow::RunLoop() -{ - X11Connection* connection = GetX11Connection(); - connection->ExitRunLoop = false; - while (!connection->ExitRunLoop && !connection->windows.empty()) - { - CheckNeedsUpdate(); - XEvent event = {}; - XNextEvent(connection->display, &event); - DispatchEvent(&event); - } -} - -void X11DisplayWindow::ExitLoop() -{ - X11Connection* connection = GetX11Connection(); - connection->ExitRunLoop = true; -} - -void X11DisplayWindow::DispatchEvent(XEvent* event) -{ - X11Connection* connection = GetX11Connection(); - auto it = connection->windows.find(event->xany.window); - if (it != connection->windows.end()) - { - X11DisplayWindow* window = it->second; - window->OnEvent(event); - } -} - void X11DisplayWindow::OnEvent(XEvent* event) { if (event->type == ConfigureNotify) @@ -703,11 +675,12 @@ void X11DisplayWindow::OnConfigureNotify(XEvent* event) void X11DisplayWindow::OnClientMessage(XEvent* event) { - Atom protocolsAtom = GetAtom("WM_PROTOCOLS"); + auto connection = GetX11Connection(); + Atom protocolsAtom = connection->GetAtom("WM_PROTOCOLS"); if (protocolsAtom != None && event->xclient.message_type == protocolsAtom) { - Atom deleteAtom = GetAtom("WM_DELETE_WINDOW"); - Atom pingAtom = GetAtom("_NET_WM_PING"); + Atom deleteAtom = connection->GetAtom("WM_DELETE_WINDOW"); + Atom pingAtom = connection->GetAtom("_NET_WM_PING"); Atom protocol = event->xclient.data.l[0]; if (deleteAtom != None && protocol == deleteAtom) @@ -731,11 +704,13 @@ void X11DisplayWindow::OnFocusIn(XEvent* event) if (xic) XSetICFocus(xic); + RawInput.Focused = true; windowHost->OnWindowActivated(); } void X11DisplayWindow::OnFocusOut(XEvent* event) { + RawInput.Focused = false; windowHost->OnWindowDeactivated(); } @@ -949,6 +924,8 @@ void X11DisplayWindow::OnKeyRelease(XEvent* event) void X11DisplayWindow::OnButtonPress(XEvent* event) { + // Note: this only gets called if XInput is not available + InputKey key = GetInputKey(event); keyState[key] = true; windowHost->OnWindowMouseDown(GetMousePos(event), key); @@ -958,6 +935,8 @@ void X11DisplayWindow::OnButtonPress(XEvent* event) void X11DisplayWindow::OnButtonRelease(XEvent* event) { + // Note: this only gets called if XInput is not available + InputKey key = GetInputKey(event); keyState[key] = false; windowHost->OnWindowMouseUp(GetMousePos(event), key); @@ -965,6 +944,8 @@ void X11DisplayWindow::OnButtonRelease(XEvent* event) void X11DisplayWindow::OnMotionNotify(XEvent* event) { + // Note: this only gets called if XInput is not available + double dpiScale = GetDpiScale(); int x = event->xmotion.x; int y = event->xmotion.y; @@ -974,18 +955,134 @@ void X11DisplayWindow::OnMotionNotify(XEvent* event) } else { - MouseX = ClientSizeX / 2; - MouseY = ClientSizeY / 2; - - if (MouseX != -1 && MouseY != -1) + int dx = x - RawInput.LastX; + int dy = y - RawInput.LastY; + RawInput.LastX = x; + RawInput.LastY = y; + int centerX = ClientSizeX / 2; + int centerY = ClientSizeY / 2; + if (x != centerX || y != centerY) { - windowHost->OnWindowRawMouseMove(x - MouseX, y - MouseY); + // We still have to contain the mouse even if we are using XInput. + // Maybe we can do that in a simpler way if XInput is available? + XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, centerX, centerY); + + // Use the accelerated mouse cursor if there's no XInput 2.0 support + if (!GetX11Connection()->XInput2Supported) + { + if (dx != 0 || dy != 0) + windowHost->OnWindowRawMouseMove(dx, dy); + } + } + } +} + +bool X11DisplayWindow::OnXInputEvent(XEvent* event) +{ + // This API is so horrible it makes Win32 look attractive! + if (event->xcookie.evtype == XI_ButtonPress || event->xcookie.evtype == XI_ButtonRelease) + { + auto deviceEvent = (XIDeviceEvent*)event->xcookie.data; + if (deviceEvent->event != window) + return false; + + InputKey key = {}; + switch (deviceEvent->detail) + { + case 1: key = InputKey::LeftMouse; break; + case 2: key = InputKey::MiddleMouse; break; + case 3: key = InputKey::RightMouse; break; + case 4: key = InputKey::MouseWheelUp; break; + case 5: key = InputKey::MouseWheelDown; break; + // case 6: key = InputKey::XButton1; break; + // case 7: key = InputKey::XButton2; break; + default: return false; } - // Warp pointer to the center of the window - XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, ClientSizeX / 2, ClientSizeY / 2); - } + double dpiScale = GetDpiScale(); + int x = (int)std::round(deviceEvent->event_x); + int y = (int)std::round(deviceEvent->event_y); + Point mousePos(x / dpiScale, y / dpiScale); + if (!isCursorEnabled) + { + // Raw input gets blocked until we ungrab the pointer. Worst design EVER. + XUngrabPointer(display, deviceEvent->time); + } + + if (event->xcookie.evtype == XI_ButtonPress) + { + keyState[key] = true; + windowHost->OnWindowMouseDown(mousePos, key); + // if (lastClickWithin400ms) + // windowHost->OnWindowMouseDoubleclick(GetMousePos(event), InputKey::LeftMouse); + } + else + { + keyState[key] = false; + windowHost->OnWindowMouseUp(mousePos, key); + } + + return true; + } + else if (event->xcookie.evtype == XI_Motion) + { + auto deviceEvent = (XIDeviceEvent*)event->xcookie.data; + if (deviceEvent->event != window) + return false; + + if (isCursorEnabled) + { + double dpiScale = GetDpiScale(); + int x = (int)std::round(deviceEvent->event_x); + int y = (int)std::round(deviceEvent->event_y); + Point mousePos(x / dpiScale, y / dpiScale); + windowHost->OnWindowMouseMove(Point(x / dpiScale, y / dpiScale)); + } + else if (RawInput.Focused) + { + int x = (int)std::round(deviceEvent->event_x); + int y = (int)std::round(deviceEvent->event_y); + int centerX = ClientSizeX / 2; + int centerY = ClientSizeY / 2; + if (x != centerX || y != centerY) + { + // We still have to contain the mouse even if we are using XInput. + // Maybe we can do that in a simpler way? + XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, centerX, centerY); + } + } + + return true; + } + else if (!isCursorEnabled && RawInput.Focused && event->xcookie.evtype == XI_RawMotion) + { + std::vector values; + values.reserve(2); + + auto rawEvent = (XIRawEvent*)event->xcookie.data; + double *rawValuator = rawEvent->raw_values; + for (int i = 0; i < rawEvent->valuators.mask_len * 8; i++) + { + if (XIMaskIsSet(rawEvent->valuators.mask, i)) + { + values.push_back(*rawValuator); + rawValuator++; + } + } + + if (values.size() >= 2) + { + // Values seems to be integers for my mouse. Is that always the case? + int dx = (int)std::round(values[0]); + int dy = (int)std::round(values[1]); + if (dx != 0 || dy != 0) + windowHost->OnWindowRawMouseMove(dx, dy); + } + + return true; + } + return false; } void X11DisplayWindow::OnLeaveNotify(XEvent* event) @@ -1009,8 +1106,9 @@ void X11DisplayWindow::OnSelectionRequest(XEvent* event) if (requestor == window) return; - Atom targetsAtom = GetAtom("TARGETS"); - Atom multipleAtom = GetAtom("MULTIPLE"); + X11Connection* connection = GetX11Connection(); + Atom targetsAtom = connection->GetAtom("TARGETS"); + Atom multipleAtom = connection->GetAtom("MULTIPLE"); struct Request { Window target; Atom property; }; std::vector requests; @@ -1067,31 +1165,6 @@ void X11DisplayWindow::OnSelectionRequest(XEvent* event) } } -Size X11DisplayWindow::GetScreenSize() -{ - X11Connection* connection = GetX11Connection(); - Display* display = connection->display; - int screen = XDefaultScreen(display); - - int disp_width_px = XDisplayWidth(display, screen); - int disp_height_px = XDisplayHeight(display, screen); - int disp_width_mm = XDisplayWidthMM(display, screen); - double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast(disp_width_px) / static_cast(disp_width_mm)); - double dpiScale = ppi / 96.0; - - return Size(disp_width_px / dpiScale, disp_height_px / dpiScale); -} - -void* X11DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) -{ - throw std::logic_error("unimplemented: X11DisplayWindow::StartTimer"); -} - -void X11DisplayWindow::StopTimer(void* timerID) -{ - throw std::logic_error("unimplemented: X11DisplayWindow::StopTimer"); -} - // This is to avoid needing all the Vulkan headers and the volk binding library just for this: #ifndef VK_VERSION_1_0 diff --git a/libraries/ZWidget/src/window/x11/x11_display_window.h b/libraries/ZWidget/src/window/x11/x11_display_window.h index 62a63f151..188fc2b18 100644 --- a/libraries/ZWidget/src/window/x11/x11_display_window.h +++ b/libraries/ZWidget/src/window/x11/x11_display_window.h @@ -7,6 +7,7 @@ #include #include #include +#include #include class X11DisplayWindow : public DisplayWindow @@ -16,6 +17,7 @@ public: ~X11DisplayWindow(); void SetWindowTitle(const std::string& text) override; + void SetWindowIcon(const std::vector>& images) override; void SetWindowFrame(const Rect& box) override; void SetClientFrame(const Rect& box) override; void Show() override; @@ -59,13 +61,6 @@ public: std::vector GetVulkanInstanceExtensions() override; VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; - static void ProcessEvents(); - static void RunLoop(); - static void ExitLoop(); - static Size GetScreenSize(); - static void* StartTimer(int timeoutMilliseconds, std::function onTimer); - static void StopTimer(void* timerID); - private: void UpdateCursor(); @@ -85,6 +80,7 @@ private: void OnSelectionClear(XEvent* event); void OnSelectionNotify(XEvent* event); void OnSelectionRequest(XEvent* event); + bool OnXInputEvent(XEvent* event); void CreateBackbuffer(int width, int height); void DestroyBackbuffer(); @@ -92,11 +88,6 @@ private: InputKey GetInputKey(XEvent* event); Point GetMousePos(XEvent* event); - static bool WaitForEvents(int timeout); - static void DispatchEvent(XEvent* event); - - static void CheckNeedsUpdate(); - std::vector GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count); DisplayWindowHost* windowHost = nullptr; @@ -117,8 +108,13 @@ private: int ClientSizeX = 0; int ClientSizeY = 0; - int MouseX = -1; - int MouseY = -1; + + struct + { + int LastX = -1; + int LastY = -1; + bool Focused = false; + } RawInput; Pixmap cursor_bitmap = None; Cursor hidden_cursor = None; @@ -138,5 +134,6 @@ private: bool needsUpdate = false; + friend class X11Connection; friend class X11DisplayBackend; }; diff --git a/libraries/asmjit/asmjit/base/cpuinfo.h b/libraries/asmjit/asmjit/base/cpuinfo.h index 268d37e8d..6398f993e 100644 --- a/libraries/asmjit/asmjit/base/cpuinfo.h +++ b/libraries/asmjit/asmjit/base/cpuinfo.h @@ -37,15 +37,17 @@ public: // [Construction / Destruction] // -------------------------------------------------------------------------- - ASMJIT_INLINE CpuFeatures() noexcept { reset(); } - ASMJIT_INLINE CpuFeatures(const CpuFeatures& other) noexcept { init(other); } + ASMJIT_INLINE CpuFeatures() noexcept : _bits{} {} + + ASMJIT_INLINE CpuFeatures(const CpuFeatures& other) noexcept = default; + ASMJIT_INLINE CpuFeatures& operator=(const CpuFeatures& other) noexcept = default; // -------------------------------------------------------------------------- // [Init / Reset] // -------------------------------------------------------------------------- - ASMJIT_INLINE void init(const CpuFeatures& other) noexcept { ::memcpy(this, &other, sizeof(*this)); } - ASMJIT_INLINE void reset() noexcept { ::memset(this, 0, sizeof(*this)); } + ASMJIT_INLINE void init(const CpuFeatures& other) noexcept { *this = other; } + ASMJIT_INLINE void reset() noexcept { *this = CpuFeatures{}; } // -------------------------------------------------------------------------- // [Ops] @@ -248,8 +250,21 @@ public: // [Construction / Destruction] // -------------------------------------------------------------------------- - ASMJIT_INLINE CpuInfo() noexcept { reset(); } - ASMJIT_INLINE CpuInfo(const CpuInfo& other) noexcept { init(other); } + ASMJIT_INLINE CpuInfo() noexcept: + _archInfo{}, + _vendorId{}, + _family{}, + _model{}, + _stepping{}, + _hwThreadsCount{}, + _features{}, + _vendorString{}, + _brandString{}, + _x86Data{} + {} + + ASMJIT_INLINE CpuInfo(const CpuInfo& other) noexcept = default; + ASMJIT_INLINE CpuInfo& operator=(const CpuInfo& other) noexcept = default; // -------------------------------------------------------------------------- // [Init / Reset] @@ -260,8 +275,8 @@ public: _archInfo.init(archType, archMode); } - ASMJIT_INLINE void init(const CpuInfo& other) noexcept { ::memcpy(this, &other, sizeof(*this)); } - ASMJIT_INLINE void reset() noexcept { ::memset(this, 0, sizeof(*this)); } + ASMJIT_INLINE void init(const CpuInfo& other) noexcept { *this = other; } + ASMJIT_INLINE void reset() noexcept { *this = CpuInfo{}; } // -------------------------------------------------------------------------- // [Detect] diff --git a/libraries/asmjit/asmjit/base/func.h b/libraries/asmjit/asmjit/base/func.h index c9ab0529d..8139eee96 100644 --- a/libraries/asmjit/asmjit/base/func.h +++ b/libraries/asmjit/asmjit/base/func.h @@ -734,10 +734,18 @@ public: // [Construction / Destruction] // -------------------------------------------------------------------------- - ASMJIT_INLINE FuncDetail() noexcept { reset(); } - ASMJIT_INLINE FuncDetail(const FuncDetail& other) noexcept { - ::memcpy(this, &other, sizeof(*this)); - } + ASMJIT_INLINE FuncDetail() noexcept: + _callConv{}, + _argCount{}, + _retCount{}, + _usedRegs{}, + _argStackSize{}, + _rets{}, + _args{} + {} + + ASMJIT_INLINE FuncDetail(const FuncDetail& other) noexcept = default; + ASMJIT_INLINE FuncDetail& operator=(const FuncDetail& other) noexcept = default; // -------------------------------------------------------------------------- // [Init / Reset] @@ -745,7 +753,7 @@ public: //! Initialize this `FuncDetail` to the given signature. ASMJIT_API Error init(const FuncSignature& sign); - ASMJIT_INLINE void reset() noexcept { ::memset(this, 0, sizeof(*this)); } + ASMJIT_INLINE void reset() noexcept { *this = FuncDetail{}; } // -------------------------------------------------------------------------- // [Accessors - Calling Convention] @@ -878,20 +886,23 @@ struct FuncFrameInfo { // [Construction / Destruction] // -------------------------------------------------------------------------- - ASMJIT_INLINE FuncFrameInfo() noexcept { reset(); } + ASMJIT_INLINE FuncFrameInfo() noexcept: + _attributes{}, + _dirtyRegs{}, + _stackFrameAlignment{}, + _callFrameAlignment{}, + _stackArgsRegId{Globals::kInvalidRegId}, + _stackFrameSize{}, + _callFrameSize{} + {} - ASMJIT_INLINE FuncFrameInfo(const FuncFrameInfo& other) noexcept { - ::memcpy(this, &other, sizeof(*this)); - } + ASMJIT_INLINE FuncFrameInfo(const FuncFrameInfo& other) noexcept = default; // -------------------------------------------------------------------------- // [Init / Reset] // -------------------------------------------------------------------------- - ASMJIT_INLINE void reset() noexcept { - ::memset(this, 0, sizeof(*this)); - _stackArgsRegId = Globals::kInvalidRegId; - } + ASMJIT_INLINE void reset() noexcept { *this = FuncFrameInfo{}; } // -------------------------------------------------------------------------- // [Accessors] @@ -1180,19 +1191,18 @@ public: // [Construction / Destruction] // -------------------------------------------------------------------------- - explicit ASMJIT_INLINE FuncArgsMapper(const FuncDetail* fd) noexcept { reset(fd); } - ASMJIT_INLINE FuncArgsMapper(const FuncArgsMapper& other) noexcept { - ::memcpy(this, &other, sizeof(*this)); - } + explicit ASMJIT_INLINE FuncArgsMapper(const FuncDetail* fd = nullptr) noexcept: + _funcDetail(fd), + _args{} + {} + + ASMJIT_INLINE FuncArgsMapper(const FuncArgsMapper& other) noexcept = default; // -------------------------------------------------------------------------- // [Reset] // -------------------------------------------------------------------------- - ASMJIT_INLINE void reset(const FuncDetail* fd = nullptr) noexcept { - _funcDetail = fd; - ::memset(_args, 0, sizeof(_args)); - } + ASMJIT_INLINE void reset(const FuncDetail* fd = nullptr) noexcept { *this = FuncArgsMapper(fd); } // -------------------------------------------------------------------------- // [Accessors] diff --git a/libraries/asmjit/asmjit/base/regalloc_p.h b/libraries/asmjit/asmjit/base/regalloc_p.h index 53c7aebe6..46187898b 100644 --- a/libraries/asmjit/asmjit/base/regalloc_p.h +++ b/libraries/asmjit/asmjit/base/regalloc_p.h @@ -101,7 +101,7 @@ struct TiedReg { // -------------------------------------------------------------------------- ASMJIT_INLINE TiedReg& operator=(const TiedReg& other) { - ::memcpy(this, &other, sizeof(TiedReg)); + ::memcpy((void*)this, &other, sizeof(TiedReg)); return *this; } diff --git a/libraries/asmjit/asmjit/base/zone.cpp b/libraries/asmjit/asmjit/base/zone.cpp index 6dd535de8..95deb98fa 100644 --- a/libraries/asmjit/asmjit/base/zone.cpp +++ b/libraries/asmjit/asmjit/base/zone.cpp @@ -221,7 +221,8 @@ void ZoneHeap::reset(Zone* zone) noexcept { } // Zero the entire class and initialize to the given `zone`. - ::memset(this, 0, sizeof(*this)); + _dynamicBlocks = nullptr; + ::memset(_slots, 0, sizeof(_slots)); _zone = zone; } diff --git a/libraries/asmjit/asmjit/base/zone.h b/libraries/asmjit/asmjit/base/zone.h index 5a461a29c..818436084 100644 --- a/libraries/asmjit/asmjit/base/zone.h +++ b/libraries/asmjit/asmjit/base/zone.h @@ -297,14 +297,19 @@ class ZoneHeap { //! Create a new `ZoneHeap`. //! //! NOTE: To use it, you must first `init()` it. - ASMJIT_INLINE ZoneHeap() noexcept { - ::memset(this, 0, sizeof(*this)); - } + ASMJIT_INLINE ZoneHeap() noexcept: + _zone{}, + _slots{}, + _dynamicBlocks{} + {} + //! Create a new `ZoneHeap` initialized to use `zone`. - explicit ASMJIT_INLINE ZoneHeap(Zone* zone) noexcept { - ::memset(this, 0, sizeof(*this)); - _zone = zone; - } + explicit ASMJIT_INLINE ZoneHeap(Zone* zone) noexcept: + _zone(zone), + _slots{}, + _dynamicBlocks{} + {} + //! Destroy the `ZoneHeap`. ASMJIT_INLINE ~ZoneHeap() noexcept { reset(); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f3e5d0116..102eaf1fd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,6 +60,7 @@ if( WIN32 ) wsock32 winmm dinput8 + Xinput ole32 user32 gdi32 @@ -133,6 +134,7 @@ else() if( NOT APPLE OR NOT OSX_COCOA_BACKEND ) find_package( SDL2 REQUIRED ) include_directories( SYSTEM "${SDL2_INCLUDE_DIR}" ) + link_directories(${SDL2_LINK_DIR}) set( PROJECT_LIBRARIES ${PROJECT_LIBRARIES} "${SDL2_LIBRARY}" ) endif() @@ -1079,6 +1081,7 @@ set (PCH_SOURCES common/engine/renderstyle.cpp common/engine/v_colortables.cpp common/engine/serializer.cpp + common/engine/m_haptics.cpp common/engine/m_joy.cpp common/engine/m_random.cpp common/objects/autosegs.cpp @@ -1445,11 +1448,18 @@ add_custom_command(TARGET zdoom POST_BUILD ${CMAKE_SOURCE_DIR}/fm_banks/gs-by-papiezak-and-sneakernets.wopn $/fm_banks/gs-by-papiezak-and-sneakernets.wopn ) -if ( NOT APPLE AND NOT ZMUSIC_SYSTEM_INSTALL) +if ( NOT ZMUSIC_SYSTEM_INSTALL AND (NOT DEFINED BUILD_SHARED_LIBS OR BUILD_SHARED_LIBS)) # bring zmusic in add_custom_command(TARGET zdoom POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ DEPENDS zmusic ) + if ( NOT WIN32 ) + add_custom_command(TARGET zdoom POST_BUILD + COMMAND ${CMAKE_COMMAND} -E create_symlink $ $ + COMMAND ${CMAKE_COMMAND} -E create_symlink $ $ + WORKING_DIRECTORY $ + DEPENDS zmusic ) + endif() endif() if( WIN32 ) @@ -1516,6 +1526,17 @@ elseif(UNIX) set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic" ) endif() +if ( NOT ZMUSIC_SYSTEM_INSTALL ) + if ( WIN32 OR APPLE ) + # nothing to do + else() + install(TARGETS zmusic + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT "Game executable" + ) + endif() +endif() + if( WIN32 ) set( INSTALL_PATH . CACHE STRING "Directory where the executable will be placed during install." ) else() diff --git a/src/common/audio/sound/i_soundinternal.h b/src/common/audio/sound/i_soundinternal.h index 52dabeea3..408344ebd 100644 --- a/src/common/audio/sound/i_soundinternal.h +++ b/src/common/audio/sound/i_soundinternal.h @@ -1,11 +1,11 @@ #ifndef __SNDINT_H #define __SNDINT_H -#include #include +#include -#include "vectors.h" #include "tflags.h" +#include "vectors.h" enum EChanFlag { @@ -32,6 +32,8 @@ enum EChanFlag CHANF_TRANSIENT = 32768, // Do not record in savegames - used for sounds that get restarted outside the sound system (e.g. ambients in SW and Blood) CHANF_FORCE = 65536, // Start, even if sound is paused. CHANF_SINGULAR = 0x20000, // Only start if no sound of this name is already playing. + CHANF_RUMBLE = 0x40000, // Hint to rumble trigger rumble from sound + CHANF_NORUMBLE = 0x80000, // Disable rumble even if it would normally happen }; typedef TFlags EChanFlags; @@ -146,5 +148,4 @@ struct FISoundChannel void S_SetSoundPaused(int state); - #endif diff --git a/src/common/audio/sound/s_sound.cpp b/src/common/audio/sound/s_sound.cpp index d8ae7fe00..c8d71da2b 100644 --- a/src/common/audio/sound/s_sound.cpp +++ b/src/common/audio/sound/s_sound.cpp @@ -5,6 +5,7 @@ **--------------------------------------------------------------------------- ** Copyright 1998-2016 Randy Heit ** Copyright 2002-2019 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -36,15 +37,15 @@ #include #include - -#include "s_soundinternal.h" -#include "m_swap.h" -#include "superfasthash.h" -#include "s_music.h" -#include "m_random.h" -#include "printf.h" #include "c_cvars.h" #include "gamestate.h" +#include "i_soundinternal.h" +#include "m_haptics.h" +#include "m_random.h" +#include "m_swap.h" +#include "printf.h" +#include "s_music.h" +#include "s_soundinternal.h" CVARD(Bool, snd_enabled, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "enables/disables sound effects") CVAR(Bool, i_soundinbackground, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) @@ -475,6 +476,11 @@ FSoundChan *SoundEngine::StartSound(int type, const void *source, // Attenuate the attenuation based on the sound. attenuation *= sfx->Attenuation; +#if 0 + // sound debug printout + Printf("sound: '%s' -> '%s' %g\n", soundEngine->GetSoundName(org_id), soundEngine->GetSoundName(sound_id), attenuation); +#endif + // The passed rolloff overrides any sound-specific rolloff. if (forcedrolloff != NULL && forcedrolloff->MinDistance != 0) { @@ -608,6 +614,14 @@ FSoundChan *SoundEngine::StartSound(int type, const void *source, { chanflags |= CHANF_LISTENERZ | CHANF_JUSTSTARTED; } + if (chanflags & CHANF_RUMBLE && !(chanflags & CHANF_NORUMBLE)) + { + // I would love to use attenuation here, but it seems weird. + // Pistol's attenuation is always 1, but it's not silent. + // I'm not sure why that is. + // Joy_Rumble(soundEngine->GetSoundName(org_id), (chanflags&CHANF_IS3D)? attenuation: 0); + Joy_Rumble(soundEngine->GetSoundName(org_id)); + } if (chan != NULL) { chan->SoundID = sound_id; @@ -1009,7 +1023,6 @@ void SoundEngine::RelinkSound (int sourcetype, const void *from, const void *to, } } - //========================================================================== // // S_ChangeSoundVolume @@ -1450,7 +1463,6 @@ void SoundEngine::Reset() RestoreEvictedChannels(); } - //========================================================================== // // S_FindSound @@ -1560,7 +1572,6 @@ FSoundID SoundEngine::AddSoundLump(const char* logicalname, int lump, int Curren return id; } - //========================================================================== // // S_FindSoundTentative @@ -1581,7 +1592,6 @@ FSoundID SoundEngine::FindSoundTentative(const char* name, int nearlimit) return id; } - //========================================================================== // // S_CacheRandomSound @@ -1694,7 +1704,6 @@ void SoundEngine::HashSounds() } S_rnd.ShrinkToFit(); - } void SoundEngine::AddRandomSound(FSoundID Owner, TArray list) @@ -1728,7 +1737,6 @@ void S_SoundReset() #include "i_net.h" #include "i_interface.h" - CCMD(cachesound) { if (argv.argc() < 2) @@ -1746,7 +1754,6 @@ CCMD(cachesound) } } - CCMD(listsoundchannels) { Printf("%s", soundEngine->ListSoundChannels().GetChars()); @@ -1844,8 +1851,6 @@ void S_SetSoundPaused(int state) } } - - CCMD(snd_status) { GSnd->PrintStatus(); @@ -1876,4 +1881,3 @@ ADD_STAT(sound) return GSnd->GatherStats(); } - diff --git a/src/common/console/c_bind.cpp b/src/common/console/c_bind.cpp index 078a9a2b1..150e5b631 100644 --- a/src/common/console/c_bind.cpp +++ b/src/common/console/c_bind.cpp @@ -334,11 +334,23 @@ void FKeyBindings::DoBind (const char *key, const char *bind) // //============================================================================= -void FKeyBindings::UnbindAll () +void FKeyBindings::UnbindAll (const TArray *filter_ptr) { - for (int i = 0; i < NUM_KEYS; ++i) + if (filter_ptr != nullptr) { - Binds[i] = ""; + const TArray &filter = *filter_ptr; + for (auto key : filter) + { + assert(key >= 0 && key < NUM_KEYS); + Binds[key] = ""; + } + } + else + { + for (int i = 0; i < NUM_KEYS; ++i) + { + Binds[i] = ""; + } } } @@ -555,11 +567,11 @@ void FKeyBindings::DefaultBind(const char *keyname, const char *cmd) // //============================================================================= -void C_UnbindAll () +void C_UnbindAll (const TArray *filter) { - Bindings.UnbindAll(); - DoubleBindings.UnbindAll(); - AutomapBindings.UnbindAll(); + Bindings.UnbindAll(filter); + DoubleBindings.UnbindAll(filter); + AutomapBindings.UnbindAll(filter); } UNSAFE_CCMD (unbindall) @@ -677,7 +689,7 @@ CCMD(rebind) // //============================================================================= -void ReadBindings(int lump, bool override) +void ReadBindings(int lump, bool override, const TArray *filter = nullptr) { FScanner sc(lump); @@ -692,6 +704,15 @@ void ReadBindings(int lump, bool override) if (override) { // This is only for games to clear unsuitable base defaults, not for mods. + if (filter != nullptr) + { + key = GetKeyFromName(sc.String); + if (!filter->Contains(key)) + { + continue; + } + } + dest->UnbindKey(sc.String); } continue; @@ -712,13 +733,17 @@ void ReadBindings(int lump, bool override) dest = &AutomapBindings; sc.MustGetString(); } - else if (sc.Compare("unbind")) - { - sc.MustGetString(); - dest->UnbindKey(sc.String); - continue; - } + key = GetConfigKeyFromName(sc.String); + + if (filter != nullptr) + { + if (!filter->Contains(key)) + { + continue; + } + } + sc.MustGetString(); dest->SetBind(key, sc.String, override); } @@ -730,7 +755,7 @@ void ReadBindings(int lump, bool override) // //============================================================================= -void C_SetDefaultKeys(const char* baseconfig) +void C_SetDefaultKeys(const char* baseconfig, const TArray *filter = nullptr) { auto lump = fileSystem.CheckNumForFullName("engine/commonbinds.txt"); if (lump >= 0) @@ -743,7 +768,7 @@ void C_SetDefaultKeys(const char* baseconfig) fileSystem.GetResourceFileFullName(fileno2), "engine/commonbinds.txt"); } - ReadBindings(lump, true); + ReadBindings(lump, true, filter); } int lastlump = 0; @@ -751,7 +776,7 @@ void C_SetDefaultKeys(const char* baseconfig) { // Read this only from the main game resources. if (fileSystem.GetFileContainer(lump) <= fileSystem.GetMaxIwadNum()) - ReadBindings(lump, true); + ReadBindings(lump, true, filter); } lastlump = 0; @@ -761,9 +786,9 @@ void C_SetDefaultKeys(const char* baseconfig) // Comes from an IWAD/IPK3/IPK7 allow it to override the users settings... // If it comes from a user mod however, don't. if (fileSystem.GetFileContainer(lump) > fileSystem.GetMaxIwadNum()) - ReadBindings(lump, false); + ReadBindings(lump, false, filter); else - ReadBindings(lump, true); + ReadBindings(lump, true, filter); } } @@ -775,15 +800,18 @@ void C_SetDefaultKeys(const char* baseconfig) CVAR(Int, cl_defaultconfiguration, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -void C_BindDefaults() +void C_BindDefaults(const TArray *filter = nullptr) { - C_SetDefaultKeys(cl_defaultconfiguration == 1 ? "engine/origbinds.txt" : cl_defaultconfiguration == 2 ? "engine/leftbinds.txt" : "engine/defbinds.txt"); + C_SetDefaultKeys( + cl_defaultconfiguration == 1 ? "engine/origbinds.txt" : cl_defaultconfiguration == 2 ? "engine/leftbinds.txt" : "engine/defbinds.txt", + filter + ); } -void C_SetDefaultBindings() +void C_SetDefaultBindings(const TArray *filter) { - C_UnbindAll(); - C_BindDefaults(); + C_UnbindAll(filter); + C_BindDefaults(filter); } diff --git a/src/common/console/c_bind.h b/src/common/console/c_bind.h index ada3cb84e..d3af082d2 100644 --- a/src/common/console/c_bind.h +++ b/src/common/console/c_bind.h @@ -56,7 +56,7 @@ public: int GetKeysForCommand (const char *cmd, int *first, int *second); TArray GetKeysForCommand (const char *cmd); void UnbindACommand (const char *str); - void UnbindAll (); + void UnbindAll (const TArray *filter = nullptr); void UnbindKey(const char *key); void DoBind (const char *key, const char *bind); void DefaultBind(const char *keyname, const char *cmd); @@ -92,8 +92,8 @@ extern FKeyBindings AutomapBindings; bool C_DoKey (event_t *ev, FKeyBindings *binds, FKeyBindings *doublebinds); // Stuff used by the customize controls menu -void C_SetDefaultBindings (); -void C_UnbindAll (); +void C_SetDefaultBindings (const TArray *filter = nullptr); +void C_UnbindAll (const TArray *filter = nullptr); extern const char *KeyNames[]; diff --git a/src/common/console/c_buttons.cpp b/src/common/console/c_buttons.cpp index 3464553e3..2c6e23dbb 100644 --- a/src/common/console/c_buttons.cpp +++ b/src/common/console/c_buttons.cpp @@ -39,6 +39,8 @@ #include "printf.h" #include "cmdlib.h" #include "c_console.h" +#include "m_joy.h" +#include "c_bind.h" ButtonMap buttonMap; @@ -150,6 +152,26 @@ void ButtonMap::ResetButtonStates () // //============================================================================= +void ButtonMap::GetAxes () +{ + float joyaxes[NUM_AXIS_CODES]; + I_GetAxes(joyaxes); + + for (int i = 0; i < Buttons.Size(); i++) + { + FButtonStatus &btn = Buttons[i]; + FString &btn_name = NumToName[i]; + + btn.AddAxes(btn_name, joyaxes); + } +} + +//============================================================================= +// +// +// +//============================================================================= + bool FButtonStatus::PressKey (int keynum) { int i, open; @@ -246,6 +268,53 @@ bool FButtonStatus::ReleaseKey (int keynum) // //============================================================================= +void FButtonStatus::AddAxes (FString &btn_name, float joyaxes[NUM_AXIS_CODES]) +{ + int i; + + bIsAxis = false; + Axis = 0.0f; + + char cmd_name[16]; + strcpy(&cmd_name[1], btn_name.GetChars()); + + cmd_name[0] = '+'; + TArray positive_keys = Bindings.GetKeysForCommand(cmd_name); + + cmd_name[0] = '-'; + TArray negative_keys = Bindings.GetKeysForCommand(cmd_name); + + for (i = 0; i < NUM_AXIS_CODES; i++) + { + float axis_value = joyaxes[i]; + + if (axis_value > 0.0) + { + int key_code = KeyAxisMapping[i]; + + if (positive_keys.Contains(key_code)) + { + Axis += axis_value; + bIsAxis = true; + } + + if (negative_keys.Contains(key_code)) + { + Axis -= axis_value; + bIsAxis = true; + } + } + } + + Axis = clamp(Axis, 0.0f, 1.0f); +} + +//============================================================================= +// +// +// +//============================================================================= + void ButtonMap::AddButtonTabCommands() { // Add all the action commands for tab completion diff --git a/src/common/console/c_buttons.h b/src/common/console/c_buttons.h index 3bfbbd0fb..fd5040a84 100644 --- a/src/common/console/c_buttons.h +++ b/src/common/console/c_buttons.h @@ -3,6 +3,7 @@ #include #include "tarray.h" #include "name.h" +#include "keydef.h" // Actions struct FButtonStatus @@ -14,13 +15,18 @@ struct FButtonStatus bool bWentDown; // Button went down this tic bool bWentUp; // Button went up this tic bool bReleaseLock; // Lock ReleaseKey call in ResetButtonStates + + bool bIsAxis; // Whenever or not this button is being controlled by any axis. + float Axis; // How far the button has been pressed. Updated by I_GetAxes. + void (*PressHandler)(); // for optional game-side customization void (*ReleaseHandler)(); bool PressKey (int keynum); // Returns true if this key caused the button to be pressed. bool ReleaseKey (int keynum); // Returns true if this key is no longer pressed. + void AddAxes (FString &btn_name, float joyaxes[NUM_AXIS_CODES]); // Update joystick axis information. void ResetTriggers () { bWentDown = bWentUp = false; } - void Reset () { bDown = bWentDown = bWentUp = false; } + void Reset () { bDown = bWentDown = bWentUp = bIsAxis = false; Axis = 0.0f; } }; class ButtonMap @@ -53,6 +59,7 @@ public: void ResetButtonTriggers(); // Call ResetTriggers for all buttons void ResetButtonStates(); // Same as above, but also clear bDown + void GetAxes(); // Call AddAxes for all buttons int ListActionCommands(const char* pattern); void AddButtonTabCommands(); @@ -62,6 +69,28 @@ public: return Buttons[x].bDown; } + bool ButtonDownDigital(int x) const + { + // Like ButtonDown, but only for digital buttons. + // This is necessary, because of how the game runs different + // code for both digital and analog, and they can't be rolled + // together without introducing some subtle gameplay differences. + return (Buttons[x].bIsAxis == false && Buttons[x].bDown == true); + } + + float ButtonAnalog(int x) const + { + // Get the analog value of a button. + // This is 0.0 (not 1.0) for digital presses, because again, + // of how the game logic is split between the two. + if (Buttons[x].bIsAxis == true) + { + return Buttons[x].Axis; + } + + return 0.0f; + } + bool ButtonPressed(int x) const { return Buttons[x].bWentDown; diff --git a/src/common/console/c_commandbuffer.cpp b/src/common/console/c_commandbuffer.cpp index 0c570da14..c041987fe 100644 --- a/src/common/console/c_commandbuffer.cpp +++ b/src/common/console/c_commandbuffer.cpp @@ -4,6 +4,7 @@ **--------------------------------------------------------------------------- ** Copyright 1998-2006 Randy Heit ** Copyright 2010-2020 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -31,11 +32,12 @@ **--------------------------------------------------------------------------- ** */ + #include "c_commandbuffer.h" -#include "v_draw.h" -#include "v_2ddrawer.h" -#include "v_font.h" #include "utf8.h" +#include "v_2ddrawer.h" +#include "v_draw.h" +#include "v_font.h" FCommandBuffer CmdLine; @@ -155,18 +157,23 @@ void FCommandBuffer::MakeStartPosGood() } } -void FCommandBuffer::CursorStart() +bool FCommandBuffer::CursorStart() { + bool moved = CursorPos != 0; CursorPos = 0; StartPos = 0; CursorPosCells = 0; StartPosCells = 0; + return moved; } -void FCommandBuffer::CursorEnd() +bool FCommandBuffer::CursorEnd() { - CursorPos = (unsigned)Text.length(); + unsigned len = Text.length(); + bool moved = CursorPos != len; + CursorPos = len; MakeStartPosGood(); + return moved; } void FCommandBuffer::CursorLeft() diff --git a/src/common/console/c_commandbuffer.h b/src/common/console/c_commandbuffer.h index 637027b27..b12ea2a29 100644 --- a/src/common/console/c_commandbuffer.h +++ b/src/common/console/c_commandbuffer.h @@ -1,5 +1,7 @@ #pragma once + #include + #include "zstring.h" struct FCommandBuffer @@ -31,8 +33,8 @@ public: unsigned CalcCellSize(unsigned length); unsigned CharsForCells(unsigned cellin, bool *overflow); void MakeStartPosGood(); - void CursorStart(); - void CursorEnd(); + bool CursorStart(); + bool CursorEnd(); private: void MoveCursorLeft() diff --git a/src/common/console/c_console.cpp b/src/common/console/c_console.cpp index 9ac7bdaa7..fe66e0818 100644 --- a/src/common/console/c_console.cpp +++ b/src/common/console/c_console.cpp @@ -4,6 +4,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 1998-2006 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -32,38 +33,33 @@ ** */ -#include - - -#include "version.h" #include "c_bind.h" +#include "c_commandbuffer.h" #include "c_console.h" +#include "c_consolebuffer.h" #include "c_cvars.h" #include "c_dispatch.h" -#include "gamestate.h" -#include "v_text.h" -#include "filesystem.h" -#include "d_gui.h" +#include "c_notifybufferbase.h" #include "cmdlib.h" +#include "common/scripting/dap/GameEventEmit.h" #include "d_eventbase.h" -#include "c_consolebuffer.h" +#include "d_gui.h" +#include "g_input.h" +#include "gamestate.h" +#include "i_interface.h" +#include "i_system.h" +#include "i_time.h" +#include "menu.h" +#include "menustate.h" +#include "printf.h" +#include "texturemanager.h" #include "utf8.h" #include "v_2ddrawer.h" #include "v_draw.h" #include "v_font.h" -#include "printf.h" -#include "i_time.h" -#include "texturemanager.h" -#include "v_draw.h" -#include "i_interface.h" +#include "v_text.h" #include "v_video.h" -#include "i_system.h" -#include "menu.h" -#include "menustate.h" -#include "v_2ddrawer.h" -#include "c_notifybufferbase.h" -#include "g_input.h" -#include "c_commandbuffer.h" +#include "version.h" #include "vm.h" #include "common/widgets/errorwindow.h" #include "common/scripting/dap/GameEventEmit.h" @@ -142,6 +138,9 @@ CUSTOM_CVAR(Float, con_alpha, 0.75f, CVAR_ARCHIVE) if (self > 1.f) self = 1.f; } +CUSTOM_CVARD(Bool, con_quick_home_end, true, CVAR_ARCHIVE, "Use HOME/END keys to scroll when cursor is at start/end of line already") +{} + // Show developer messages if true. CUSTOM_CVAR(Int, developer, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) { @@ -702,12 +701,12 @@ void C_DrawConsole () if (textScale == 1) DrawText(twod, CurrentConsoleFont, CR_ORANGE, twod->GetWidth() - 8 - CurrentConsoleFont->StringWidth (GetVersionString()), - ConBottom / textScale - CurrentConsoleFont->GetHeight() - 4, + round((float)ConBottom / textScale) - CurrentConsoleFont->GetHeight() - 4, GetVersionString(), TAG_DONE); else - DrawText(twod, CurrentConsoleFont, CR_ORANGE, twod->GetWidth() / textScale - 8 - + DrawText(twod, CurrentConsoleFont, CR_ORANGE, (float)twod->GetWidth() / textScale - 8 - CurrentConsoleFont->StringWidth(GetVersionString()), - ConBottom / textScale - CurrentConsoleFont->GetHeight() - 4, + round((float)ConBottom / textScale) - CurrentConsoleFont->GetHeight() - 4, GetVersionString(), DTA_VirtualWidth, twod->GetWidth() / textScale, DTA_VirtualHeight, twod->GetHeight() / textScale, @@ -834,6 +833,11 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer) int data1 = ev->data1; bool keepappending = false; + int page_height = (twod->GetHeight()-4)/active_con_scale(twod) / + ((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? CurrentConsoleFont->GetHeight() : CurrentConsoleFont->GetHeight()*2) - 3; + int total_lines = conbuffer->GetFormattedLineCount(); + int top_row = total_lines - page_height - 1; + switch (ev->subtype) { default: @@ -883,75 +887,48 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer) break; case GK_PGUP: - if (ev->data3 & (GKM_SHIFT|GKM_CTRL)) - { // Scroll console buffer up one page - RowAdjust += (twod->GetHeight()-4)/active_con_scale(twod) / - ((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? CurrentConsoleFont->GetHeight() : CurrentConsoleFont->GetHeight()*2) - 3; - } - else if (RowAdjust < conbuffer->GetFormattedLineCount()) + if (ev->subtype == EV_GUI_WheelUp) { // Scroll console buffer up - if (ev->subtype == EV_GUI_WheelUp) - { - RowAdjust += 3; - } - else - { - RowAdjust++; - } - if (RowAdjust > conbuffer->GetFormattedLineCount()) - { - RowAdjust = conbuffer->GetFormattedLineCount(); - } + if (ev->data3 & (GKM_SHIFT|GKM_CTRL)) RowAdjust += 1; + else RowAdjust += 3; + + if (RowAdjust > total_lines) RowAdjust = total_lines; + } + else + { // Scroll console buffer up one page + RowAdjust += page_height; + + if (RowAdjust > top_row) RowAdjust = top_row; // intentionally different than scroll behavior } break; case GK_PGDN: - if (ev->data3 & (GKM_SHIFT|GKM_CTRL)) - { // Scroll console buffer down one page - const int scrollamt = (twod->GetHeight()-4)/active_con_scale(twod) / - ((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? CurrentConsoleFont->GetHeight() : CurrentConsoleFont->GetHeight()*2) - 3; - if (RowAdjust < scrollamt) - { - RowAdjust = 0; - } - else - { - RowAdjust -= scrollamt; - } - } - else if (RowAdjust > 0) + if (ev->subtype == EV_GUI_WheelDown) { // Scroll console buffer down - if (ev->subtype == EV_GUI_WheelDown) - { - RowAdjust = max (0, RowAdjust - 3); - } - else - { - RowAdjust--; - } + if (ev->data3 & (GKM_SHIFT|GKM_CTRL)) RowAdjust -= 1; + else RowAdjust -= 3; } + else + { // Scroll console buffer down one page + RowAdjust -= page_height; + } + if (RowAdjust < 0) RowAdjust = 0; break; case GK_HOME: - if (ev->data3 & GKM_CTRL) + if ((ev->data3 & (GKM_CTRL|GKM_SHIFT)) + || (!buffer.CursorStart() && con_quick_home_end)) // Try to move cursor to start of line { // Move to top of console buffer - RowAdjust = conbuffer->GetFormattedLineCount(); - } - else - { // Move cursor to start of line - buffer.CursorStart(); + RowAdjust = top_row; } break; case GK_END: - if (ev->data3 & GKM_CTRL) + if ((ev->data3 & (GKM_CTRL|GKM_SHIFT)) + || (!buffer.CursorEnd() && con_quick_home_end)) // Try to move cursor to end of line { // Move to bottom of console buffer RowAdjust = 0; } - else - { // Move cursor to end of line - buffer.CursorEnd(); - } break; case GK_LEFT: @@ -979,39 +956,53 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer) break; case GK_UP: - // Move to previous entry in the command history - if (HistPos == NULL) + if (ev->data3 & GKM_SHIFT) { - HistPos = HistHead; - } - else if (HistPos->Older) - { - HistPos = HistPos->Older; - } - - if (HistPos) - { - buffer.SetString(HistPos->String); - } - - TabbedLast = false; - TabbedList = false; - break; - - case GK_DOWN: - // Move to next entry in the command history - if (HistPos && HistPos->Newer) - { - HistPos = HistPos->Newer; - buffer.SetString(HistPos->String); + RowAdjust = min (total_lines, RowAdjust + 1); // match mousewheel } else { - HistPos = NULL; - buffer.SetString(""); + // Move to previous entry in the command history + if (HistPos == NULL) + { + HistPos = HistHead; + } + else if (HistPos->Older) + { + HistPos = HistPos->Older; + } + + if (HistPos) + { + buffer.SetString(HistPos->String); + } + + TabbedLast = false; + TabbedList = false; + } + break; + + case GK_DOWN: + if (ev->data3 & GKM_SHIFT) + { + RowAdjust = max (0, RowAdjust - 1); + } + else + { + // Move to next entry in the command history + if (HistPos && HistPos->Newer) + { + HistPos = HistPos->Newer; + buffer.SetString(HistPos->String); + } + else + { + HistPos = NULL; + buffer.SetString(""); + } + TabbedLast = false; + TabbedList = false; } - TabbedLast = false; - TabbedList = false; break; case 'X': @@ -1261,4 +1252,3 @@ CCMD(toggleconsole) { C_ToggleConsole(); } - diff --git a/src/common/console/keydef.h b/src/common/console/keydef.h index 620b5f5e7..ca9179ca7 100644 --- a/src/common/console/keydef.h +++ b/src/common/console/keydef.h @@ -147,3 +147,77 @@ enum EKeyCodes NUM_JOYAXISBUTTONS = 8, }; + +// Axes are provided per-key now, but passing around +// an entire NUM_KEYS-sized array is overkill. These +// codes are used for translating between axes and keys. +enum EAxisCodes +{ + AXIS_CODE_NULL = -1, + + AXIS_CODE_JOY1_PLUS = 0, + AXIS_CODE_JOY1_MINUS, + AXIS_CODE_JOY2_PLUS, + AXIS_CODE_JOY2_MINUS, + AXIS_CODE_JOY3_PLUS, + AXIS_CODE_JOY3_MINUS, + AXIS_CODE_JOY4_PLUS, + AXIS_CODE_JOY4_MINUS, + AXIS_CODE_JOY5_PLUS, + AXIS_CODE_JOY5_MINUS, + AXIS_CODE_JOY6_PLUS, + AXIS_CODE_JOY6_MINUS, + AXIS_CODE_JOY7_PLUS, + AXIS_CODE_JOY7_MINUS, + AXIS_CODE_JOY8_PLUS, + AXIS_CODE_JOY8_MINUS, + + AXIS_CODE_PAD_LTHUMB_RIGHT, + AXIS_CODE_PAD_LTHUMB_LEFT, + AXIS_CODE_PAD_LTHUMB_DOWN, + AXIS_CODE_PAD_LTHUMB_UP, + + AXIS_CODE_PAD_RTHUMB_RIGHT, + AXIS_CODE_PAD_RTHUMB_LEFT, + AXIS_CODE_PAD_RTHUMB_DOWN, + AXIS_CODE_PAD_RTHUMB_UP, + + AXIS_CODE_PAD_LTRIGGER, + AXIS_CODE_PAD_RTRIGGER, + + NUM_AXIS_CODES, +}; + + +static const int KeyAxisMapping[NUM_AXIS_CODES] = { + KEY_JOYAXIS1PLUS, + KEY_JOYAXIS1MINUS, + KEY_JOYAXIS2PLUS, + KEY_JOYAXIS2MINUS, + KEY_JOYAXIS3PLUS, + KEY_JOYAXIS3MINUS, + KEY_JOYAXIS4PLUS, + KEY_JOYAXIS4MINUS, + KEY_JOYAXIS5PLUS, + KEY_JOYAXIS5MINUS, + KEY_JOYAXIS6PLUS, + KEY_JOYAXIS6MINUS, + KEY_JOYAXIS7PLUS, + KEY_JOYAXIS7MINUS, + KEY_JOYAXIS8PLUS, + KEY_JOYAXIS8MINUS, + + KEY_PAD_LTHUMB_RIGHT, + KEY_PAD_LTHUMB_LEFT, + KEY_PAD_LTHUMB_DOWN, + KEY_PAD_LTHUMB_UP, + + KEY_PAD_RTHUMB_RIGHT, + KEY_PAD_RTHUMB_LEFT, + KEY_PAD_RTHUMB_DOWN, + KEY_PAD_RTHUMB_UP, + + KEY_PAD_LTRIGGER, + KEY_PAD_RTRIGGER, +}; + diff --git a/src/common/engine/d_event.cpp b/src/common/engine/d_event.cpp index ce11d3b1e..3aa2d3f8f 100644 --- a/src/common/engine/d_event.cpp +++ b/src/common/engine/d_event.cpp @@ -34,15 +34,19 @@ */ #include "c_bind.h" -#include "d_eventbase.h" #include "c_console.h" +#include "c_cvars.h" +#include "d_eventbase.h" #include "d_gui.h" -#include "menu.h" -#include "utf8.h" -#include "m_joy.h" -#include "vm.h" #include "gamestate.h" #include "i_interface.h" +#include "keydef.h" +#include "m_joy.h" +#include "menu.h" +#include "utf8.h" +#include "vm.h" + +extern bool ToggleFullscreen; int eventhead; int eventtail; @@ -73,6 +77,7 @@ void D_ProcessEvents (void) while (eventtail != eventhead) { event_t *ev = &events[eventtail]; + eventtail = (eventtail + 1) & (MAXEVENTS - 1); if (ev->type == EV_KeyUp && keywasdown[ev->data1]) @@ -86,6 +91,16 @@ void D_ProcessEvents (void) if (ev->type == EV_DeviceChange) UpdateJoystickMenu(I_UpdateDeviceList()); +#if defined(__linux__) || defined(_WIN32) + // I cannot test on macos, so it is disabled for now + if ((ev->type == EV_KeyDown && ev->data1 == KEY_ENTER && (ev->data3 & GKM_ALT)) + || (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_KeyDown && ev->data1 == GK_RETURN && (ev->data3 & GKM_ALT))) + { + ToggleFullscreen = !ToggleFullscreen; + continue; + } +#endif + // allow the game to intercept Escape before dispatching it. if (ev->type != EV_KeyDown || ev->data1 != KEY_ESCAPE || !sysCallbacks.WantEscape || !sysCallbacks.WantEscape()) { diff --git a/src/common/engine/i_interface.cpp b/src/common/engine/i_interface.cpp index eb9c18361..d7b6cc498 100644 --- a/src/common/engine/i_interface.cpp +++ b/src/common/engine/i_interface.cpp @@ -43,7 +43,7 @@ CVAR(Int, defaultnetplayers, 8, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Int, defaultnethostport, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Int, defaultnetticdup, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Int, defaultnetmode, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -CVAR(Int, defaultnetgamemode, -1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnetgamemode, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Bool, defaultnetaltdm, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(String, defaultnetaddress, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Int, defaultnetjoinport, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) diff --git a/src/common/engine/i_interface.h b/src/common/engine/i_interface.h index 87d12b53a..0101c8b64 100644 --- a/src/common/engine/i_interface.h +++ b/src/common/engine/i_interface.h @@ -93,8 +93,8 @@ struct FStartupSelectionInfo int DefaultNetHostPort = 0; int DefaultNetTicDup = 0; bool DefaultNetExtraTic = false; - int DefaultNetMode = -1; - int DefaultNetGameMode = -1; + int DefaultNetMode = 0; + int DefaultNetGameMode = 0; bool DefaultNetAltDM = false; FString DefaultNetAddress = {}; diff --git a/src/common/engine/m_haptics.cpp b/src/common/engine/m_haptics.cpp new file mode 100644 index 000000000..e078bdca1 --- /dev/null +++ b/src/common/engine/m_haptics.cpp @@ -0,0 +1,832 @@ +/* +** m_haptics.cpp +** +** Haptic feedback implementation +** +**--------------------------------------------------------------------------- +** +** Copyright 2025 Marcus Minhorst +** Copyright 2025 GZDoom Maintainers and Contributors +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see http://www.gnu.org/licenses/ +** +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include + +#include "c_cvars.h" +#include "c_dispatch.h" +#include "doomdef.h" +#include "doomstat.h" +#include "m_haptics.h" +#include "name.h" +#include "printf.h" +#include "s_soundinternal.h" +#include "tarray.h" +#include "vm.h" +#include "zstring.h" + +// MACROS ------------------------------------------------------------------ + +#ifndef MAX_TRY_DEPTH +#define MAX_TRY_DEPTH 8 +#endif + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +inline void _Rumble(const int); +inline void _RumbleDirect(int, int, double, double, double, double); + +const FName * Joy_GetMapping(const FName); +const FName * Joy_GuessMapping(const FName); +const struct Haptics * Joy_GetRumble(FName); +void Joy_Rumble(const FName, const struct Haptics, double); + +void RumbleDump(); +void RumblePrint(const FName, const FName *, const struct Haptics *, double); + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern bool AppActive; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +struct { + int tic = gametic; // last tic processed + bool dirty = false; // do we need to do something next tick ? + bool enabled = true; // do we need to do anything ever ? + bool active = true; // is the game currently not paused ? + struct { + double base = 1.0; // [0,1] -> number <1 turns down rumble strength + double high_frequency = 1.0; // [0,inf) + double low_frequency = 1.0; // [0,inf) + double left_trigger = 1.0; // [0,inf) + double right_trigger = 1.0; // [0,inf) + } strength; + struct Haptics current = {0,0,0,0,0}; // current state of the controller + TMap channels; // active rumbles (that will be mixed) +} Haptics; + +TMap RumbleDefinition = {}; +TMap RumbleMapping = {}; +TMap RumbleAlias = {}; +TArray RumbleMissed = {}; + +// fallback names. these exist in base sndinfo +const FName HapticIntense = "INTENSE", + HapticHeavy = "HEAVY", + HapticMedium = "MEDIUM", + HapticLight = "LIGHT", + HapticSubtle = "SUBTLE", + HapticNone = "NONE", + HapticDirect = "DIRECT"; // except for this one + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// for fine-control, if user wants/needs +// added because trigger haptics are much stronger on xbone controller than expected +CUSTOM_CVARD(Float, haptics_strength_lf, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "low frequency motor fine-control") +{ + if (self < 0) self = 0; + Haptics.strength.low_frequency = self * Haptics.strength.base; +}; +CUSTOM_CVARD(Float, haptics_strength_hf, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "high frequency motor fine-control") +{ + if (self < 0) self = 0; + Haptics.strength.high_frequency = self * Haptics.strength.base; +}; +CUSTOM_CVARD(Float, haptics_strength_lt, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "left trigger motor fine-control") +{ + if (self < 0) self = 0; + Haptics.strength.left_trigger = self * Haptics.strength.base; +}; +CUSTOM_CVARD(Float, haptics_strength_rt, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "right trigger motor fine-control") +{ + if (self < 0) self = 0; + Haptics.strength.right_trigger = self * Haptics.strength.base; +}; + +CUSTOM_CVARD(Int, haptics_strength, 10, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "Translate linear haptics to audio taper") +{ + double l1 = self / 10.0; + double l2 = l1 * l1; + double l3 = l2 * l1; + double m3 = 2; // cubed portion + double m2 = -2; // squared portion + double m1 = 1 - m2 - m3; // linear portion + + Haptics.enabled = self > 0; + Haptics.strength.base = l1*m1 + l2*m2 + l3*m3; + Haptics.strength.high_frequency = haptics_strength_hf * Haptics.strength.base; + Haptics.strength.low_frequency = haptics_strength_lf * Haptics.strength.base; + Haptics.strength.left_trigger = haptics_strength_lt * Haptics.strength.base; + Haptics.strength.right_trigger = haptics_strength_rt * Haptics.strength.base; + + if (!Haptics.enabled) I_Rumble(0, 0, 0, 0); +} + +CVARD(Bool, haptics_debug, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "print diagnostics for haptic feedback"); + +CUSTOM_CVARD(Int, haptics_compat, HAPTCOMPAT_MATCH, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "haptic feedback compatibility level") +{ + if (self < 0) self = 0; + if (self >= NUM_HAPTCOMPAT) self = NUM_HAPTCOMPAT-1; +} + +CVARD(Bool, haptics_do_menus, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for menus"); +CVARD(Bool, haptics_do_world, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for things acting on player"); +CVARD(Bool, haptics_do_damage, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for things hurting player"); +CVARD(Bool, haptics_do_action, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for player doing things"); + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// Joy_GuessMapping +// +// Takes a sound name, and tried to figure out a similar mapping. +// These are cached, so performance is probably not a huge deal +// This can absolutely be expanded/improved, and probably should. +// +//========================================================================== + +const FName * Joy_GuessMapping(const FName identifier) +{ + FString text = identifier.GetChars(); + // I would like to slugify here + // Maybe one day + text.ToLower(); + + struct MapSet { const FName * mapping; const TArray tokens; }; + static struct MapSet mappings[] = { + { &HapticNone, { "ricochet", "casing" } }, + { &HapticIntense, { "quake", "death", "gibbed" } }, + { &HapticHeavy, { "teleport", "activate", "secret" } }, + { &HapticMedium, { "success", "grunt", "land", "pain", "pkup", "pickup", "fist", "weapon", + "fire", "shoot", "blast", "attack", "launch", "punch" } }, + { &HapticLight, { "push", "menu", "use", "fail", "open", "close", "eject", "reload", + "charge", "try" } }, + { &HapticNone, { "step", "floor" } }, + }; + + for (auto mapping: mappings) + for (auto token: mapping.tokens) + if (text.IndexOf(token) != -1) + return mapping.mapping; + + return nullptr; +} + +//========================================================================== +// +// Joy_GetMapping +// +// Takes a sound name, and find its corresponding rumble identifier. +// +//========================================================================== + +const FName * Joy_GetMapping(const FName identifier) +{ + const FName * mapping = RumbleMapping.CheckKey(identifier); + FName actual = identifier; + + // try to grab an aliased sound + // todo: mapping candidate for aliases + if (!mapping) + { + auto id = soundEngine->FindSoundTentative(identifier.GetChars()); + + // loop a couple layers deep, trying to find a candidate + for (auto i = 0; i < MAX_TRY_DEPTH; i++) + { + if (!id.isvalid()) break; + + actual = soundEngine->GetSoundName(id); + mapping = RumbleMapping.CheckKey(actual); + + if (mapping) break; + + // writable to be able to get random sound + auto sfx = soundEngine->GetWritableSfx(id); + // is there a way to get the actual random sound? Selecting the first is probably fine + id = sfx->bRandomHeader + ? soundEngine->ResolveRandomSound(sfx)->Choices[0] + : sfx->link; + } + } + + // convert unknown skinned sound to sound + // is there a better way to do this? + if (!mapping) + { + FString idString = identifier.GetChars(); + + auto skindex = idString.IndexOf("*"); + + if (skindex >= 0) + { + idString.Remove(0, skindex); + } + + actual = FName(idString); + + mapping = RumbleMapping.CheckKey(actual); + } + + bool replaced = actual != identifier && actual.IsValidName(); + + auto warn = [&identifier]() + { + if (RumbleMissed.Contains(identifier)) return; + RumbleMissed.Push(identifier); + Printf(DMSG_WARNING|PRINT_NONOTIFY, "Unknown rumble mapping '%s'\n", identifier.GetChars()); + }; + + if (!mapping && identifier != "") + { + switch (haptics_compat) + { + case HAPTCOMPAT_NONE: // sometimes warn for devs + if (!RumbleMissed.Contains(identifier)) warn(); + break; + case HAPTCOMPAT_WARN: // always warn for authors + replaced = nullptr != (mapping = Joy_GuessMapping(identifier)); + if (replaced) Printf("Rumble compat mapped '%s' to '%s'\n", identifier.GetChars(), mapping->GetChars()); + else Printf(TEXTCOLOR_ORANGE "Unknown rumble mapping '%s'\n", identifier.GetChars()); + break; + case HAPTCOMPAT_MATCH: // try our best to choose correct (default) + replaced = nullptr != (mapping = Joy_GuessMapping(identifier)); + if (!replaced) warn(); + break; + case HAPTCOMPAT_ALL: // simple fallback + mapping = &HapticMedium; + replaced = true; + break; + } + } + + if (mapping && replaced && haptics_compat != HAPTCOMPAT_WARN) + { + // cache replacement + RumbleMapping.Insert(identifier, *mapping); + } + + return mapping; +} + +//========================================================================== +// +// Joy_GetRumble +// +// Takes a rumble identifier, and returns its haptics definition. +// +//========================================================================== + +const struct Haptics * Joy_GetRumble(FName identifier) +{ + struct Haptics * rumble = RumbleDefinition.CheckKey(identifier); + + if (!rumble && !RumbleMissed.Contains(identifier)) + { + RumbleMissed.Push(identifier); + Printf(DMSG_ERROR, TEXTCOLOR_RED "Rumble mapping not found! '%s'\n", identifier.GetChars()); + return nullptr; + } + + return rumble; +} + +//========================================================================== +// +// Joy_AddRumbleType +// +// Ties a rumble identifier to a haptics definition. +// +//========================================================================== + +void Joy_AddRumbleType(const FName identifier, const struct Haptics data) +{ + if (haptics_debug) + { + if (data.ticks == 0) Printf("rumble disabled %s\n", identifier.GetChars()); + else Printf("rumble add %s T%d H%.1g L%.1g\n", identifier.GetChars(), data.ticks, data.high_frequency, data.low_frequency); + } + RumbleDefinition.Insert(identifier, data); +} + +//========================================================================== +// +// Joy_AddRumbleAlias +// +// Adds an alternative rumble identifier for a haptics definition. +// Joy_ReadyRumbleMapping must be called after adding any aliases. +// +//========================================================================== + +void Joy_AddRumbleAlias(const FName alias, const FName actual) +{ + if (haptics_debug) Printf("rumble alias %s -> %s\n", alias.GetChars(), actual.GetChars()); + RumbleAlias.Insert(alias, actual); +} + +//========================================================================== +// +// Joy_MapRumbleType +// +// Ties a sound to a rumble identifier. +// +//========================================================================== + +void Joy_MapRumbleType(const FName sound, const FName identifier) +{ + if (haptics_debug) Printf("rumble map %s -> %s\n", sound.GetChars(), identifier.GetChars()); + RumbleMapping.Insert(sound, identifier); +} + +//========================================================================== +// +// Joy_ResetRumbleMapping +// +// Resets entire rumble state +// +//========================================================================== + +void Joy_ResetRumbleMapping() +{ + if (haptics_debug) Printf("rumble reset\n"); + RumbleMapping.Clear(); + RumbleAlias.Clear(); + RumbleMissed.Clear(); + RumbleDefinition.Clear(); + Haptics.channels.Clear(); + Haptics.current.high_frequency = Haptics.current.low_frequency = + Haptics.current.left_trigger = Haptics.current.right_trigger = + Haptics.current.ticks = 0; + I_Rumble(0, 0, 0, 0); +} + +//========================================================================== +// +// Joy_ReadyRumbleMapping +// +// Resets entire rumble state +// +//========================================================================== + +void Joy_ReadyRumbleMapping() +{ + if (haptics_debug) Printf("rumble ready\n"); + TArray found; + TMapIterator it(RumbleAlias); + TMap::Pair* pair; + + while (RumbleAlias.CountUsed()) + { + while (it.NextPair(pair)) + { + auto predefined = RumbleDefinition.CheckKey(pair->Key); + if (predefined) + { + Printf(DMSG_ERROR, TEXTCOLOR_RED "Rumble alias trying to redefine mapping! '%s'\n", pair->Key.GetChars()); + continue; + } + + auto mapping = RumbleDefinition.CheckKey(pair->Value); + if (!mapping) continue; + + found.AddUnique(pair->Key); + RumbleDefinition.Insert(pair->Key, *mapping); + } + it.Reset(); + + if (found.Size() == 0) + { + FString list = "["; + while (it.NextPair(pair)) + list.AppendFormat(" '%s'->'%s'", pair->Key.GetChars(), pair->Value.GetChars()); + Printf(DMSG_ERROR, TEXTCOLOR_RED "Circular rumble alias found! (%d) %s ]\n", RumbleAlias.CountUsed(), list.GetChars()); + break; + } + + while (found.size() > 0) + { + RumbleAlias.Remove(found.Last()); + found.Pop(); + } + } + if (haptics_debug) RumbleDump(); +} + +//========================================================================== +// +// Joy_RumbleTick +// +// Called once per gametic. Manages rumble state. +// +//========================================================================== + +void Joy_RumbleTick() +{ + if (!Haptics.enabled) return; + + // pause detection + if (AppActive != Haptics.active) + { + Haptics.active = AppActive; + + if (Haptics.active && Haptics.current.ticks != 0) + { + I_Rumble( + Haptics.current.high_frequency, + Haptics.current.low_frequency, + Haptics.current.left_trigger, + Haptics.current.right_trigger + ); + } + else + { + I_Rumble(0, 0, 0, 0); + } + } + + if (Haptics.tic >= gametic) return; + + Haptics.tic = gametic; + + // new value OR time elapsed + Haptics.dirty |= Haptics.current.ticks != 0 && Haptics.current.ticks < Haptics.tic; + + if (!Haptics.dirty) return; + + // init + Haptics.current.ticks = 0; + Haptics.current.high_frequency = 0; + Haptics.current.low_frequency = 0; + Haptics.current.left_trigger = 0; + Haptics.current.right_trigger = 0; + + TMapIterator it(Haptics.channels); + TMap::Pair* pair; + TArray stale; + + // remove old rumble data + while (it.NextPair(pair)) + { + if (pair->Value.ticks < Haptics.tic) + { + stale.Push(pair->Key); + } + } + for (auto key: stale) + { + Haptics.channels.Remove(key); + } + + it.Reset(); + while (it.NextPair(pair)) + { + // grab upcoming event time + Haptics.current.ticks = Haptics.current.ticks == 0 + ? pair->Value.ticks + : std::min(Haptics.current.ticks, pair->Value.ticks); + + // simple intensity mixing + Haptics.current.high_frequency += pair->Value.high_frequency; + Haptics.current.low_frequency += pair->Value.low_frequency; + Haptics.current.left_trigger += pair->Value.left_trigger; + Haptics.current.right_trigger += pair->Value.right_trigger; + } + + // should this be clamped to [0,1]? Maybe a controller api will support "hdr" haptics lol + // Haptics.current.high_frequency = std::min(std::max(0.0, Haptics.current.high_frequency), 1.0); + // Haptics.current.low_frequency = std::min(std::max(0.0, Haptics.current.low_frequency), 1.0); + // Haptics.current.left_trigger = std::min(std::max(0.0, Haptics.current.left_trigger), 1.0); + // Haptics.current.right_trigger = std::min(std::max(0.0, Haptics.current.right_trigger), 1.0); + + I_Rumble( + Haptics.current.high_frequency, + Haptics.current.low_frequency, + Haptics.current.left_trigger, + Haptics.current.right_trigger + ); + + Haptics.dirty = false; +} + +void Joy_Rumble(const FName source, const struct Haptics data, double attenuation) +{ + if (!Haptics.enabled) return; + if (data.ticks <= 0) return; + if (attenuation >= 1) return; + + float strength = 1 - (attenuation < 0? 0: attenuation); + + // this will overwrite stuff from same source mapping (weapons/pistol not W_BULLET) + Haptics.channels.Insert(source, { + Haptics.tic + data.ticks + 1, + data.high_frequency * Haptics.strength.high_frequency * strength, + data.low_frequency * Haptics.strength.low_frequency * strength, + data.left_trigger * Haptics.strength.left_trigger * strength, + data.right_trigger * Haptics.strength.right_trigger * strength, + }); + + Haptics.dirty = true; +} + +//========================================================================== +// +// Joy_Rumble +// +// Requests haptic feedback based on provided data. Attenuation of >=1 results in no-op. +// +//========================================================================== + +void Joy_Rumble( + const FName source, + int tic_count, + double high_frequency, + double low_frequency, + double left_trigger, + double right_trigger, + double attenuation +) +{ + const struct Haptics rumble {tic_count, high_frequency, low_frequency, left_trigger, right_trigger}; + + if (haptics_debug) RumblePrint(source, &HapticDirect, &rumble, attenuation); + + Joy_Rumble(source, rumble, attenuation); +} + +//========================================================================== +// +// Joy_Rumble +// +// Requests haptic feedback. Attenuation of >=1 results in no-op. +// +//========================================================================== + +void Joy_Rumble(const FName identifier, double attenuation) +{ + const FName * mapping; + const struct Haptics * rumble; + + rumble = (nullptr != (mapping = Joy_GetMapping(identifier))) + ? Joy_GetRumble(*mapping) + : nullptr; + + if (haptics_debug) RumblePrint(identifier, mapping, rumble, attenuation); + + if (rumble) Joy_Rumble(identifier, * rumble, attenuation); +} + +//========================================================================== +// +// rumble +// +// test command +// +//========================================================================== + +CCMD (rumble) +{ + int count = argv.argc()-1; + int ticks; + double high_freq, low_freq, left_trig, right_trig; + + switch (count) + { + case 0: { + RumbleDump(); + Printf("Testing rumble for 5s\n"); + Joy_Rumble("", 5 * TICRATE, 1.0, 1.0, 1.0, 1.0); + } + break; + case 1: + Printf("Testing rumble for action '%s'\n", argv[1]); + Joy_Rumble(argv[1]); + break; + case 5: + try + { + ticks = std::stoi(argv[1], nullptr, 10); + high_freq = static_cast (std::stof(argv[2], nullptr)); + low_freq = static_cast (std::stof(argv[3], nullptr)); + left_trig = static_cast (std::stof(argv[4], nullptr)); + right_trig = static_cast (std::stof(argv[5], nullptr)); + } catch (...) + { + Printf("Failed to parse args\n"); + return; + } + Printf("testing rumble with params (%d, %f, %f, %f, %f)\n", ticks, high_freq, low_freq, left_trig, right_trig); + Joy_Rumble("", ticks, high_freq, low_freq, left_trig, right_trig); + break; + default: + Printf( + "usage:\n %s\n %s\n %s\n", + "rumble", + "rumble string_id", + "rumble int_duration float_high_freq float_low_freq float_left_trig float_right_trigger" + ); + break; + } +} + +//========================================================================== +// +// _Rumble +// +// VM wrapper for Joy_Rumble +// +//========================================================================== + +inline void _Rumble(const int identifier) +{ + Joy_Rumble(ENamedName(identifier)); +} + +//========================================================================== +// +// Rumble +// +// VM function for named Joy_Rumble +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_NATIVE(DHaptics, Rumble, _Rumble) +{ + PARAM_PROLOGUE; + PARAM_INT(identifier); + _Rumble(ENamedName(identifier)); + return 0; +} + +//========================================================================== +// +// _RumbleDirect +// +// VM wrapper for Joy_Rumble +// +//========================================================================== + +inline void _RumbleDirect( + int source, + int tic_count, + double high_frequency, + double low_frequency, + double left_trigger, + double right_trigger +) +{ + Joy_Rumble( + ENamedName(source), + tic_count, + high_frequency, + low_frequency, + left_trigger, + right_trigger + ); +} + +//========================================================================== +// +// Rumble +// +// VM function for direct Joy_Rumble +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_NATIVE(DHaptics, RumbleDirect, _RumbleDirect) +{ + PARAM_PROLOGUE; + PARAM_INT(source); + PARAM_INT(tic_count); + PARAM_FLOAT(high_frequency); + PARAM_FLOAT(low_frequency); + PARAM_FLOAT(left_trigger); + PARAM_FLOAT(right_trigger); + _RumbleDirect(source, tic_count, high_frequency, low_frequency, left_trigger, right_trigger); + return 0; +} + + +//========================================================================== +// +// RumblePrint +// +// Debug printout +// +//========================================================================== + +void RumblePrint(const FName identifier, const FName * mapping, const struct Haptics * rumble, double attenuation) +{ + const char * color; + FString text = FStringf("r '%s'", identifier.GetChars()); + if (!mapping) + { color = TEXTCOLOR_ORANGE; text.AppendFormat(" -"); } + else + { + text.AppendFormat(" %s", mapping->GetChars()); + if (!rumble) + { color = TEXTCOLOR_RED; text.AppendFormat(" -"); } + else if (rumble->ticks == 0) + { color = TEXTCOLOR_BLACK; text.AppendFormat(" 0"); } + else + { color = TEXTCOLOR_CYAN; text.AppendFormat(" T%d H%.1g L%.1g A%.1g", + rumble->ticks, rumble->high_frequency, rumble->low_frequency, attenuation); } + } + Printf("%s%s\n", color, text.GetChars()); +} + +//========================================================================== +// +// RumbleDump +// +// Debug printout +// +//========================================================================== + +void RumbleDump() +{ + TArray unused, used; + + { + TMapIterator it(RumbleDefinition); + TMap::Pair* pair; + while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars()); + } + { + TMapIterator it(RumbleAlias); + TMap::Pair* pair; + while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars()); + } + { + TMapIterator it(RumbleAlias); + TMap::Pair* pair; + while (it.NextPair(pair)) + { + if (unused.Contains(pair->Value.GetChars())) + unused.Delete(unused.Find(pair->Value.GetChars())); + } + } + { + TMapIterator it(RumbleMapping); + TMap::Pair* pair; + Printf("Mappings:\n"); + while (it.NextPair(pair)) + { + used.AddUnique(pair->Value.GetChars()); + if (unused.Contains(pair->Value.GetChars())) + unused.Delete(unused.Find(pair->Value.GetChars())); + auto mapping = Joy_GetRumble(pair->Value); + FString key = pair->Key.GetChars(); + FString val = pair->Value.GetChars(); + key.ToLower(); + val.ToUpper(); + FString a = FStringf("'%s'\t->\t'%s'", key.GetChars(), val.GetChars()); + FString b = mapping + ? FStringf( + "{ %d %g %g %g %g }", + mapping->ticks, + mapping->high_frequency, + mapping->low_frequency, + mapping->left_trigger, + mapping->right_trigger + ) : "[undefined]"; + Printf("\t%s\t->\t%s\n", a.GetChars(), b.GetChars()); + } + } + + if (unused.Size() > 0) + { + Printf("Unused:\n"); + for (auto i:unused) + { + FString s = i.GetChars(); + s.ToUpper(); + Printf("\t'%s'\n", s.GetChars()); + } + } +} diff --git a/src/common/engine/m_haptics.h b/src/common/engine/m_haptics.h new file mode 100644 index 000000000..6a27dfb33 --- /dev/null +++ b/src/common/engine/m_haptics.h @@ -0,0 +1,39 @@ +#ifndef M_HAPTICS_H +#define M_HAPTICS_H + +#include "name.h" + +enum EHapticCompat { + HAPTCOMPAT_WARN, + HAPTCOMPAT_NONE, + HAPTCOMPAT_MATCH, + HAPTCOMPAT_ALL, + + NUM_HAPTCOMPAT +}; + +struct Haptics { + int ticks; + double high_frequency; + double low_frequency; + double left_trigger; + double right_trigger; +}; + +void I_Rumble(double high_freq, double low_freq, double left_trig, double right_trig); + +void Joy_AddRumbleType(const FName idenifier, const struct Haptics data); +void Joy_AddRumbleAlias(const FName alias, const FName actual); +void Joy_MapRumbleType(const FName sound, const FName idenifier); +void Joy_ResetRumbleMapping(); +void Joy_ReadyRumbleMapping(); +void Joy_RumbleTick(); +void Joy_Rumble( + const FName source, int tic_count, + double high_frequency, double low_frequency, + double left_trigger = 0, double right_trigger = 0, + double attenuation = 0 +); +void Joy_Rumble(const FName identifier, double attenuation = 0); + +#endif diff --git a/src/common/engine/m_joy.cpp b/src/common/engine/m_joy.cpp index 4ea016879..04f2a175b 100644 --- a/src/common/engine/m_joy.cpp +++ b/src/common/engine/m_joy.cpp @@ -1,8 +1,12 @@ /* +** m_joy.cpp ** +** Cross-platform joystick/gamepad management ** **--------------------------------------------------------------------------- +** ** Copyright 2005-2016 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -27,20 +31,26 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ + // HEADER FILES ------------------------------------------------------------ #include + +#include "c_cvars.h" #include "c_dispatch.h" -#include "vectors.h" -#include "m_joy.h" -#include "configfile.h" -#include "i_interface.h" -#include "d_eventbase.h" #include "cmdlib.h" +#include "configfile.h" +#include "d_eventbase.h" +#include "i_interface.h" +#include "m_joy.h" +#include "name.h" #include "printf.h" +#include "vectors.h" +#include "zstring.h" // MACROS ------------------------------------------------------------------ @@ -62,6 +72,8 @@ extern const float JOYDEADZONE_DEFAULT = 0.1; // reduced from 0.25 extern const float JOYSENSITIVITY_DEFAULT = 1.0; +extern const float JOYHAPSTRENGTH_DEFAULT = 1.0; + extern const float JOYTHRESH_DEFAULT = 0.05; extern const float JOYTHRESH_TRIGGER = 0.05; extern const float JOYTHRESH_STICK_X = 0.65; @@ -162,6 +174,12 @@ bool M_LoadJoystickConfig(IJoystickConfig *joy) joy->SetSensitivity((float)atof(value)); } + value = GameConfig->GetValueForKey("Haptics"); + if (value) + { + joy->SetHapticsStrength((float)atof(value)); + } + numaxes = joy->GetNumAxes(); for (int i = 0; i < numaxes; ++i) { @@ -222,18 +240,6 @@ bool M_LoadJoystickConfig(IJoystickConfig *joy) { joy->SetAxisResponseCurvePoint(i, 3, (float)atof(value)); } - - mysnprintf(key + axislen, countof(key) - axislen, "map"); - value = GameConfig->GetValueForKey(key); - if (value) - { - EJoyAxis gameaxis = (EJoyAxis)atoi(value); - if (gameaxis < JOYAXIS_None || gameaxis >= NUM_JOYAXIS) - { - gameaxis = JOYAXIS_None; - } - joy->SetAxisMap(i, gameaxis); - } } return true; } @@ -264,12 +270,19 @@ void M_SaveJoystickConfig(IJoystickConfig *joy) { GameConfig->SetValueForKey("EnabledInBackground", "1"); } - + if (!joy->IsSensitivityDefault()) { mysnprintf(value, countof(value), "%g", joy->GetSensitivity()); GameConfig->SetValueForKey("Sensitivity", value); } + + if (!joy->IsHapticsStrengthDefault()) + { + mysnprintf(value, countof(value), "%g", joy->GetHapticsStrength()); + GameConfig->SetValueForKey("Haptics", value); + } + numaxes = joy->GetNumAxes(); for (int i = 0; i < numaxes; ++i) { @@ -314,12 +327,6 @@ void M_SaveJoystickConfig(IJoystickConfig *joy) mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 3)); GameConfig->SetValueForKey(key, value); } - if (!joy->IsAxisMapDefault(i)) - { - mysnprintf(key + axislen, countof(key) - axislen, "map"); - mysnprintf(value, countof(value), "%d", joy->GetAxisMap(i)); - GameConfig->SetValueForKey(key, value); - } } // If the joystick is entirely at its defaults, delete this section // so that we don't write out a lone section header. @@ -355,7 +362,6 @@ CCMD (gamepad) "\n\tgamepad curve-y1 pad.axis [float]" "\n\tgamepad curve-x2 pad.axis [float]" "\n\tgamepad curve-y2 pad.axis [float]" - "\n\tgamepad map pad.axis [-1|0|1|2|3|4]" "\n" ); }; @@ -429,10 +435,7 @@ CCMD (gamepad) if (command == "reset") { if (set) return usage(); - sticks[pad]->SetDefaultConfig(); - sticks[pad]->SetEnabled(true); - sticks[pad]->SetEnabledInBackground(sticks[pad]->AllowsEnabledInBackground()); - sticks[pad]->SetSensitivity(1); + sticks[pad]->Reset(); return; } if (command == "enabled") @@ -490,49 +493,10 @@ CCMD (gamepad) if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 3, value); return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 3)); } - if (command == "map") - { - if (set) sticks[pad]->SetAxisMap(axis, (EJoyAxis)value); - return (void) Printf("%d\n", sticks[pad]->GetAxisMap(axis)); - } return usage(); } -//=========================================================================== -// -// Joy_RemoveDeadZone -// -//=========================================================================== - -double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons) -{ - uint8_t butt; - - // Cancel out deadzone. - if (fabs(axisval) < deadzone) - { - axisval = 0; - butt = 0; - } - // Make the dead zone the new 0. - else if (axisval < 0) - { - axisval = (axisval + deadzone) / (1.0 - deadzone); - butt = 2; // button minus - } - else - { - axisval = (axisval - deadzone) / (1.0 - deadzone); - butt = 1; // button plus - } - if (buttons != NULL) - { - *buttons = butt; - } - return axisval; -} - //=========================================================================== // // Joy_ApplyResponseCurveBezier @@ -575,6 +539,54 @@ double Joy_ApplyResponseCurveBezier(const CubicBezier &curve, double input) return sign*t; } +//=========================================================================== +// +// Joy_ManageSingleAxis +// +//=========================================================================== + +double Joy_ManageSingleAxis(double axisval, double deadzone, double threshold, const CubicBezier &curve, uint8_t *buttons) +{ + uint8_t butt; + + // Cancel out deadzone. + if (fabs(axisval) < deadzone) + { + axisval = 0; + butt = 0; + } + else + { + // Make the dead zone the new 0. + if (axisval < 0) + { + axisval = (axisval + deadzone) / (1.0 - deadzone); + butt = 2; // button minus + } + else + { + axisval = (axisval - deadzone) / (1.0 - deadzone); + butt = 1; // button plus + } + + // Apply input response curve + axisval = Joy_ApplyResponseCurveBezier(curve, axisval); + + if (abs(axisval) < threshold) + { + // Needs to meet digital threshold to send button + butt = 0; + } + } + + if (buttons != NULL) + { + *buttons = butt; + } + + return axisval; +} + //=========================================================================== // // Joy_XYAxesToButtons @@ -612,6 +624,86 @@ int Joy_XYAxesToButtons(double x, double y) return JoyAngleButtons[int(rad) & 7]; } +//=========================================================================== +// +// Joy_ManageThumbstick +// +// Given two axes and their settings, handle applying deadzone, digital +// threshold, input response curve, and setting button state. This is +// necessary, because doing the two axes individually creates awkward +// behavior (such as cross shaped deadzones, sluggish input response when +// pressing diagonally, so on). +// +//=========================================================================== + +double Joy_ManageThumbstick( + double *axis_x, double *axis_y, + double deadzone_x, double deadzone_y, + double threshold_x, double threshold_y, + const CubicBezier &curve_x, const CubicBezier &curve_y, + uint8_t *buttons) +{ + double ret_x = *axis_x; + double ret_y = *axis_y; + + double x_abs = abs(*axis_x); + double y_abs = abs(*axis_y); + double magnitude = sqrt((x_abs * x_abs) + (y_abs * y_abs)); + + double ret_dist = 0; + uint8_t ret_butt = 0; + + double xy_lerp = 0.5; + if (magnitude > 0) + { + // Later it would be nice to have a single deadzone / threshold / curve setting + // for the whole thumbstick instead of awkwardly trying to combine them, but + // that requires re-considering how axes are exposed to the rest of the engine. + // This will do for now. + xy_lerp = abs(cos(atan2(ret_y, ret_x))); + } + + double deadzone = (xy_lerp * deadzone_x) + ((1.0 - xy_lerp) * deadzone_y); + if (magnitude < deadzone) + { + ret_x = 0; + ret_y = 0; + } + else + { + // Make the dead zone the new 0. + ret_dist = (magnitude - deadzone) / (1.0 - deadzone); + + const CubicBezier curve = {{ + (float)((xy_lerp * curve_x.x1) + ((1.0 - xy_lerp) * curve_y.x1)), + (float)((xy_lerp * curve_x.y1) + ((1.0 - xy_lerp) * curve_y.y1)), + (float)((xy_lerp * curve_x.x2) + ((1.0 - xy_lerp) * curve_y.x2)), + (float)((xy_lerp * curve_x.y2) + ((1.0 - xy_lerp) * curve_y.y2)) + }}; + + ret_dist = Joy_ApplyResponseCurveBezier(curve, ret_dist); + + ret_x = (ret_x / magnitude) * ret_dist; + ret_y = (ret_y / magnitude) * ret_dist; + + double threshold = (xy_lerp * threshold_x) + ((1.0 - xy_lerp) * threshold_y); + if (ret_dist >= threshold) + { + ret_butt = Joy_XYAxesToButtons(ret_x, ret_y); + } + } + + *axis_x = ret_x; + *axis_y = ret_y; + + if (buttons != NULL) + { + *buttons = ret_butt; + } + + return ret_dist; +} + //=========================================================================== // // Joy_GenerateButtonEvent diff --git a/src/common/engine/m_joy.h b/src/common/engine/m_joy.h index 87dca60a3..16a962db2 100644 --- a/src/common/engine/m_joy.h +++ b/src/common/engine/m_joy.h @@ -1,10 +1,9 @@ #ifndef M_JOY_H #define M_JOY_H -#include "basics.h" +#include "c_cvars.h" #include "keydef.h" #include "tarray.h" -#include "c_cvars.h" union CubicBezier { struct { @@ -26,22 +25,12 @@ enum EJoyCurve { NUM_JOYCURVE }; -enum EJoyAxis -{ - JOYAXIS_None = -1, - JOYAXIS_Yaw, - JOYAXIS_Pitch, - JOYAXIS_Forward, - JOYAXIS_Side, - JOYAXIS_Up, -// JOYAXIS_Roll, // Ha ha. No roll for you. - NUM_JOYAXIS, -}; - extern const float JOYDEADZONE_DEFAULT; extern const float JOYSENSITIVITY_DEFAULT; +extern const float JOYHAPSTRENGTH_DEFAULT; + extern const float JOYTHRESH_DEFAULT; extern const float JOYTHRESH_TRIGGER; @@ -59,9 +48,12 @@ struct IJoystickConfig virtual float GetSensitivity() = 0; virtual void SetSensitivity(float scale) = 0; + virtual bool HasHaptics() = 0; + virtual float GetHapticsStrength() = 0; + virtual void SetHapticsStrength(float strength) = 0; + virtual int GetNumAxes() = 0; virtual float GetAxisDeadZone(int axis) = 0; - virtual EJoyAxis GetAxisMap(int axis) = 0; virtual const char *GetAxisName(int axis) = 0; virtual float GetAxisScale(int axis) = 0; virtual float GetAxisDigitalThreshold(int axis) = 0; @@ -69,7 +61,6 @@ struct IJoystickConfig virtual float GetAxisResponseCurvePoint(int axis, int point) = 0; virtual void SetAxisDeadZone(int axis, float zone) = 0; - virtual void SetAxisMap(int axis, EJoyAxis gameaxis) = 0; virtual void SetAxisScale(int axis, float scale) = 0; virtual void SetAxisDigitalThreshold(int axis, float threshold) = 0; virtual void SetAxisResponseCurve(int axis, EJoyCurve preset) = 0; @@ -84,14 +75,22 @@ struct IJoystickConfig // Used by the saver to not save properties that are at their defaults. virtual bool IsSensitivityDefault() = 0; + virtual bool IsHapticsStrengthDefault() = 0; virtual bool IsAxisDeadZoneDefault(int axis) = 0; - virtual bool IsAxisMapDefault(int axis) = 0; virtual bool IsAxisScaleDefault(int axis) = 0; virtual bool IsAxisDigitalThresholdDefault(int axis) = 0; virtual bool IsAxisResponseCurveDefault(int axis) = 0; virtual void SetDefaultConfig() = 0; virtual FString GetIdentifier() = 0; + + void Reset() + { + SetDefaultConfig(); + SetEnabled(true); + SetEnabledInBackground(AllowsEnabledInBackground()); + SetSensitivity(1); + } }; EXTERN_CVAR(Bool, use_joystick); @@ -102,12 +101,16 @@ void M_SaveJoystickConfig(IJoystickConfig *joy); void Joy_GenerateButtonEvent(bool down, EKeyCodes which); void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, int base); void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys); -int Joy_XYAxesToButtons(double x, double y); -double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons); + double Joy_ApplyResponseCurveBezier(const CubicBezier &curve, double input); +double Joy_ManageSingleAxis(double axisval, double deadzone, double threshold, const CubicBezier &curve, uint8_t *buttons); +int Joy_XYAxesToButtons(double x, double y); +double Joy_ManageThumbstick(double *axis_x, double *axis_y, double deadzone_x, double deadzone_y, + double threshold_x, double threshold_y, const CubicBezier &curve_x, const CubicBezier &curve_y, uint8_t *buttons); + // These ought to be provided by a system-specific i_input.cpp. -void I_GetAxes(float axes[NUM_JOYAXIS]); +void I_GetAxes(float axes[NUM_AXIS_CODES]); void I_GetJoysticks(TArray &sticks); IJoystickConfig *I_UpdateDeviceList(); extern void UpdateJoystickMenu(IJoystickConfig *); diff --git a/src/common/filesystem/source/wildcards.hpp b/src/common/filesystem/source/wildcards.hpp new file mode 100644 index 000000000..b3b695e29 --- /dev/null +++ b/src/common/filesystem/source/wildcards.hpp @@ -0,0 +1,1833 @@ +// THIS FILE HAS BEEN GENERATED AUTOMATICALLY. DO NOT EDIT DIRECTLY. +// Generated: 2019-03-08 09:59:35.958950200 +// Copyright Tomas Zeman 2018. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +#ifndef WILDCARDS_HPP +#define WILDCARDS_HPP +#define WILDCARDS_VERSION_MAJOR 1 +#define WILDCARDS_VERSION_MINOR 5 +#define WILDCARDS_VERSION_PATCH 0 +#ifndef WILDCARDS_CARDS_HPP +#define WILDCARDS_CARDS_HPP +#include +namespace wildcards +{ +template +struct cards +{ +constexpr cards(T a, T s, T e) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{false}, +alt_enabled{false} +{ +} +constexpr cards(T a, T s, T e, T so, T sc, T sn, T ao, T ac, T ar) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{true}, +set_open{std::move(so)}, +set_close{std::move(sc)}, +set_not{std::move(sn)}, +alt_enabled{true}, +alt_open{std::move(ao)}, +alt_close{std::move(ac)}, +alt_or{std::move(ar)} +{ +} +T anything; +T single; +T escape; +bool set_enabled; +T set_open; +T set_close; +T set_not; +bool alt_enabled; +T alt_open; +T alt_close; +T alt_or; +}; +enum class cards_type +{ +standard, +extended +}; +template <> +struct cards +{ +constexpr cards(cards_type type = cards_type::extended) +: set_enabled{type == cards_type::extended}, alt_enabled{type == cards_type::extended} +{ +} +constexpr cards(char a, char s, char e) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{false}, +alt_enabled{false} +{ +} +constexpr cards(char a, char s, char e, char so, char sc, char sn, char ao, char ac, char ar) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{true}, +set_open{std::move(so)}, +set_close{std::move(sc)}, +set_not{std::move(sn)}, +alt_enabled{true}, +alt_open{std::move(ao)}, +alt_close{std::move(ac)}, +alt_or{std::move(ar)} +{ +} +char anything{'*'}; +char single{'?'}; +char escape{'\\'}; +bool set_enabled{true}; +char set_open{'['}; +char set_close{']'}; +char set_not{'!'}; +bool alt_enabled{true}; +char alt_open{'('}; +char alt_close{')'}; +char alt_or{'|'}; +}; +template <> +struct cards +{ +constexpr cards(cards_type type = cards_type::extended) +: set_enabled{type == cards_type::extended}, alt_enabled{type == cards_type::extended} +{ +} +constexpr cards(char16_t a, char16_t s, char16_t e) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{false}, +alt_enabled{false} +{ +} +constexpr cards(char16_t a, char16_t s, char16_t e, char16_t so, char16_t sc, char16_t sn, +char16_t ao, char16_t ac, char16_t ar) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{true}, +set_open{std::move(so)}, +set_close{std::move(sc)}, +set_not{std::move(sn)}, +alt_enabled{true}, +alt_open{std::move(ao)}, +alt_close{std::move(ac)}, +alt_or{std::move(ar)} +{ +} +char16_t anything{u'*'}; +char16_t single{u'?'}; +char16_t escape{u'\\'}; +bool set_enabled{true}; +char16_t set_open{u'['}; +char16_t set_close{u']'}; +char16_t set_not{u'!'}; +bool alt_enabled{true}; +char16_t alt_open{u'('}; +char16_t alt_close{u')'}; +char16_t alt_or{u'|'}; +}; +template <> +struct cards +{ +constexpr cards(cards_type type = cards_type::extended) +: set_enabled{type == cards_type::extended}, alt_enabled{type == cards_type::extended} +{ +} +constexpr cards(char32_t a, char32_t s, char32_t e) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{false}, +alt_enabled{false} +{ +} +constexpr cards(char32_t a, char32_t s, char32_t e, char32_t so, char32_t sc, char32_t sn, +char32_t ao, char32_t ac, char32_t ar) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{true}, +set_open{std::move(so)}, +set_close{std::move(sc)}, +set_not{std::move(sn)}, +alt_enabled{true}, +alt_open{std::move(ao)}, +alt_close{std::move(ac)}, +alt_or{std::move(ar)} +{ +} +char32_t anything{U'*'}; +char32_t single{U'?'}; +char32_t escape{U'\\'}; +bool set_enabled{true}; +char32_t set_open{U'['}; +char32_t set_close{U']'}; +char32_t set_not{U'!'}; +bool alt_enabled{true}; +char32_t alt_open{U'('}; +char32_t alt_close{U')'}; +char32_t alt_or{U'|'}; +}; +template <> +struct cards +{ +constexpr cards(cards_type type = cards_type::extended) +: set_enabled{type == cards_type::extended}, alt_enabled{type == cards_type::extended} +{ +} +constexpr cards(wchar_t a, wchar_t s, wchar_t e) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{false}, +alt_enabled{false} +{ +} +constexpr cards(wchar_t a, wchar_t s, wchar_t e, wchar_t so, wchar_t sc, wchar_t sn, wchar_t ao, +wchar_t ac, wchar_t ar) +: anything{std::move(a)}, +single{std::move(s)}, +escape{std::move(e)}, +set_enabled{true}, +set_open{std::move(so)}, +set_close{std::move(sc)}, +set_not{std::move(sn)}, +alt_enabled{true}, +alt_open{std::move(ao)}, +alt_close{std::move(ac)}, +alt_or{std::move(ar)} +{ +} +wchar_t anything{L'*'}; +wchar_t single{L'?'}; +wchar_t escape{L'\\'}; +bool set_enabled{true}; +wchar_t set_open{L'['}; +wchar_t set_close{L']'}; +wchar_t set_not{L'!'}; +bool alt_enabled{true}; +wchar_t alt_open{L'('}; +wchar_t alt_close{L')'}; +wchar_t alt_or{L'|'}; +}; +template +constexpr cards make_cards(T&& a, T&& s, T&& e) +{ +return {std::forward(a), std::forward(s), std::forward(e)}; +} +template +constexpr cards make_cards(T&& a, T&& s, T&& e, T&& so, T&& sc, T&& sn, T&& ao, T&& ac, T&& ar) +{ +return {std::forward(a), std::forward(s), std::forward(e), +std::forward(so), std::forward(sc), std::forward(sn), +std::forward(ao), std::forward(ac), std::forward(ar)}; +} +} +#endif +#ifndef WILDCARDS_MATCH_HPP +#define WILDCARDS_MATCH_HPP +#include +#include +#include +#ifndef CONFIG_HPP +#define CONFIG_HPP +#ifndef QUICKCPPLIB_HAS_FEATURE_H +#define QUICKCPPLIB_HAS_FEATURE_H +#if __cplusplus >= 201103L +#if !defined(__cpp_alias_templates) +#define __cpp_alias_templates 190000 +#endif +#if !defined(__cpp_attributes) +#define __cpp_attributes 190000 +#endif +#if !defined(__cpp_constexpr) +#if __cplusplus >= 201402L +#define __cpp_constexpr 201304 +#else +#define __cpp_constexpr 190000 +#endif +#endif +#if !defined(__cpp_decltype) +#define __cpp_decltype 190000 +#endif +#if !defined(__cpp_delegating_constructors) +#define __cpp_delegating_constructors 190000 +#endif +#if !defined(__cpp_explicit_conversion) +#define __cpp_explicit_conversion 190000 +#endif +#if !defined(__cpp_inheriting_constructors) +#define __cpp_inheriting_constructors 190000 +#endif +#if !defined(__cpp_initializer_lists) +#define __cpp_initializer_lists 190000 +#endif +#if !defined(__cpp_lambdas) +#define __cpp_lambdas 190000 +#endif +#if !defined(__cpp_nsdmi) +#define __cpp_nsdmi 190000 +#endif +#if !defined(__cpp_range_based_for) +#define __cpp_range_based_for 190000 +#endif +#if !defined(__cpp_raw_strings) +#define __cpp_raw_strings 190000 +#endif +#if !defined(__cpp_ref_qualifiers) +#define __cpp_ref_qualifiers 190000 +#endif +#if !defined(__cpp_rvalue_references) +#define __cpp_rvalue_references 190000 +#endif +#if !defined(__cpp_static_assert) +#define __cpp_static_assert 190000 +#endif +#if !defined(__cpp_unicode_characters) +#define __cpp_unicode_characters 190000 +#endif +#if !defined(__cpp_unicode_literals) +#define __cpp_unicode_literals 190000 +#endif +#if !defined(__cpp_user_defined_literals) +#define __cpp_user_defined_literals 190000 +#endif +#if !defined(__cpp_variadic_templates) +#define __cpp_variadic_templates 190000 +#endif +#endif +#if __cplusplus >= 201402L +#if !defined(__cpp_aggregate_nsdmi) +#define __cpp_aggregate_nsdmi 190000 +#endif +#if !defined(__cpp_binary_literals) +#define __cpp_binary_literals 190000 +#endif +#if !defined(__cpp_decltype_auto) +#define __cpp_decltype_auto 190000 +#endif +#if !defined(__cpp_generic_lambdas) +#define __cpp_generic_lambdas 190000 +#endif +#if !defined(__cpp_init_captures) +#define __cpp_init_captures 190000 +#endif +#if !defined(__cpp_return_type_deduction) +#define __cpp_return_type_deduction 190000 +#endif +#if !defined(__cpp_sized_deallocation) +#define __cpp_sized_deallocation 190000 +#endif +#if !defined(__cpp_variable_templates) +#define __cpp_variable_templates 190000 +#endif +#endif +#if defined(_MSC_VER) && !defined(__clang__) +#if !defined(__cpp_exceptions) && defined(_CPPUNWIND) +#define __cpp_exceptions 190000 +#endif +#if !defined(__cpp_rtti) && defined(_CPPRTTI) +#define __cpp_rtti 190000 +#endif +#if !defined(__cpp_alias_templates) && _MSC_VER >= 1800 +#define __cpp_alias_templates 190000 +#endif +#if !defined(__cpp_attributes) +#define __cpp_attributes 190000 +#endif +#if !defined(__cpp_constexpr) && _MSC_FULL_VER >= 190023506 +#define __cpp_constexpr 190000 +#endif +#if !defined(__cpp_decltype) && _MSC_VER >= 1600 +#define __cpp_decltype 190000 +#endif +#if !defined(__cpp_delegating_constructors) && _MSC_VER >= 1800 +#define __cpp_delegating_constructors 190000 +#endif +#if !defined(__cpp_explicit_conversion) && _MSC_VER >= 1800 +#define __cpp_explicit_conversion 190000 +#endif +#if !defined(__cpp_inheriting_constructors) && _MSC_VER >= 1900 +#define __cpp_inheriting_constructors 190000 +#endif +#if !defined(__cpp_initializer_lists) && _MSC_VER >= 1900 +#define __cpp_initializer_lists 190000 +#endif +#if !defined(__cpp_lambdas) && _MSC_VER >= 1600 +#define __cpp_lambdas 190000 +#endif +#if !defined(__cpp_nsdmi) && _MSC_VER >= 1900 +#define __cpp_nsdmi 190000 +#endif +#if !defined(__cpp_range_based_for) && _MSC_VER >= 1700 +#define __cpp_range_based_for 190000 +#endif +#if !defined(__cpp_raw_strings) && _MSC_VER >= 1800 +#define __cpp_raw_strings 190000 +#endif +#if !defined(__cpp_ref_qualifiers) && _MSC_VER >= 1900 +#define __cpp_ref_qualifiers 190000 +#endif +#if !defined(__cpp_rvalue_references) && _MSC_VER >= 1600 +#define __cpp_rvalue_references 190000 +#endif +#if !defined(__cpp_static_assert) && _MSC_VER >= 1600 +#define __cpp_static_assert 190000 +#endif +#if !defined(__cpp_user_defined_literals) && _MSC_VER >= 1900 +#define __cpp_user_defined_literals 190000 +#endif +#if !defined(__cpp_variadic_templates) && _MSC_VER >= 1800 +#define __cpp_variadic_templates 190000 +#endif +#if !defined(__cpp_binary_literals) && _MSC_VER >= 1900 +#define __cpp_binary_literals 190000 +#endif +#if !defined(__cpp_decltype_auto) && _MSC_VER >= 1900 +#define __cpp_decltype_auto 190000 +#endif +#if !defined(__cpp_generic_lambdas) && _MSC_VER >= 1900 +#define __cpp_generic_lambdas 190000 +#endif +#if !defined(__cpp_init_captures) && _MSC_VER >= 1900 +#define __cpp_init_captures 190000 +#endif +#if !defined(__cpp_return_type_deduction) && _MSC_VER >= 1900 +#define __cpp_return_type_deduction 190000 +#endif +#if !defined(__cpp_sized_deallocation) && _MSC_VER >= 1900 +#define __cpp_sized_deallocation 190000 +#endif +#if !defined(__cpp_variable_templates) && _MSC_FULL_VER >= 190023506 +#define __cpp_variable_templates 190000 +#endif +#endif +#if(defined(__GNUC__) && !defined(__clang__)) +#define QUICKCPPLIB_GCC (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#if !defined(__cpp_exceptions) && defined(__EXCEPTIONS) +#define __cpp_exceptions 190000 +#endif +#if !defined(__cpp_rtti) && defined(__GXX_RTTI) +#define __cpp_rtti 190000 +#endif +#if defined(__GXX_EXPERIMENTAL_CXX0X__) +#if !defined(__cpp_alias_templates) && (QUICKCPPLIB_GCC >= 40700) +#define __cpp_alias_templates 190000 +#endif +#if !defined(__cpp_attributes) && (QUICKCPPLIB_GCC >= 40800) +#define __cpp_attributes 190000 +#endif +#if !defined(__cpp_constexpr) && (QUICKCPPLIB_GCC >= 40600) +#define __cpp_constexpr 190000 +#endif +#if !defined(__cpp_decltype) && (QUICKCPPLIB_GCC >= 40300) +#define __cpp_decltype 190000 +#endif +#if !defined(__cpp_delegating_constructors) && (QUICKCPPLIB_GCC >= 40700) +#define __cpp_delegating_constructors 190000 +#endif +#if !defined(__cpp_explicit_conversion) && (QUICKCPPLIB_GCC >= 40500) +#define __cpp_explicit_conversion 190000 +#endif +#if !defined(__cpp_inheriting_constructors) && (QUICKCPPLIB_GCC >= 40800) +#define __cpp_inheriting_constructors 190000 +#endif +#if !defined(__cpp_initializer_lists) && (QUICKCPPLIB_GCC >= 40800) +#define __cpp_initializer_lists 190000 +#endif +#if !defined(__cpp_lambdas) && (QUICKCPPLIB_GCC >= 40500) +#define __cpp_lambdas 190000 +#endif +#if !defined(__cpp_nsdmi) && (QUICKCPPLIB_GCC >= 40700) +#define __cpp_nsdmi 190000 +#endif +#if !defined(__cpp_range_based_for) && (QUICKCPPLIB_GCC >= 40600) +#define __cpp_range_based_for 190000 +#endif +#if !defined(__cpp_raw_strings) && (QUICKCPPLIB_GCC >= 40500) +#define __cpp_raw_strings 190000 +#endif +#if !defined(__cpp_ref_qualifiers) && (QUICKCPPLIB_GCC >= 40801) +#define __cpp_ref_qualifiers 190000 +#endif +#if !defined(__cpp_rvalue_references) && defined(__cpp_rvalue_reference) +#define __cpp_rvalue_references __cpp_rvalue_reference +#endif +#if !defined(__cpp_static_assert) && (QUICKCPPLIB_GCC >= 40300) +#define __cpp_static_assert 190000 +#endif +#if !defined(__cpp_unicode_characters) && (QUICKCPPLIB_GCC >= 40500) +#define __cpp_unicode_characters 190000 +#endif +#if !defined(__cpp_unicode_literals) && (QUICKCPPLIB_GCC >= 40500) +#define __cpp_unicode_literals 190000 +#endif +#if !defined(__cpp_user_defined_literals) && (QUICKCPPLIB_GCC >= 40700) +#define __cpp_user_defined_literals 190000 +#endif +#if !defined(__cpp_variadic_templates) && (QUICKCPPLIB_GCC >= 40400) +#define __cpp_variadic_templates 190000 +#endif +#endif +#endif +#if defined(__clang__) +#define QUICKCPPLIB_CLANG (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) +#if !defined(__cpp_exceptions) && (defined(__EXCEPTIONS) || defined(_CPPUNWIND)) +#define __cpp_exceptions 190000 +#endif +#if !defined(__cpp_rtti) && (defined(__GXX_RTTI) || defined(_CPPRTTI)) +#define __cpp_rtti 190000 +#endif +#if defined(__GXX_EXPERIMENTAL_CXX0X__) +#if !defined(__cpp_alias_templates) && (QUICKCPPLIB_CLANG >= 30000) +#define __cpp_alias_templates 190000 +#endif +#if !defined(__cpp_attributes) && (QUICKCPPLIB_CLANG >= 30300) +#define __cpp_attributes 190000 +#endif +#if !defined(__cpp_constexpr) && (QUICKCPPLIB_CLANG >= 30100) +#define __cpp_constexpr 190000 +#endif +#if !defined(__cpp_decltype) && (QUICKCPPLIB_CLANG >= 20900) +#define __cpp_decltype 190000 +#endif +#if !defined(__cpp_delegating_constructors) && (QUICKCPPLIB_CLANG >= 30000) +#define __cpp_delegating_constructors 190000 +#endif +#if !defined(__cpp_explicit_conversion) && (QUICKCPPLIB_CLANG >= 30000) +#define __cpp_explicit_conversion 190000 +#endif +#if !defined(__cpp_inheriting_constructors) && (QUICKCPPLIB_CLANG >= 30300) +#define __cpp_inheriting_constructors 190000 +#endif +#if !defined(__cpp_initializer_lists) && (QUICKCPPLIB_CLANG >= 30100) +#define __cpp_initializer_lists 190000 +#endif +#if !defined(__cpp_lambdas) && (QUICKCPPLIB_CLANG >= 30100) +#define __cpp_lambdas 190000 +#endif +#if !defined(__cpp_nsdmi) && (QUICKCPPLIB_CLANG >= 30000) +#define __cpp_nsdmi 190000 +#endif +#if !defined(__cpp_range_based_for) && (QUICKCPPLIB_CLANG >= 30000) +#define __cpp_range_based_for 190000 +#endif +#if !defined(__cpp_raw_strings) && defined(__cpp_raw_string_literals) +#define __cpp_raw_strings __cpp_raw_string_literals +#endif +#if !defined(__cpp_raw_strings) && (QUICKCPPLIB_CLANG >= 30000) +#define __cpp_raw_strings 190000 +#endif +#if !defined(__cpp_ref_qualifiers) && (QUICKCPPLIB_CLANG >= 20900) +#define __cpp_ref_qualifiers 190000 +#endif +#if !defined(__cpp_rvalue_references) && defined(__cpp_rvalue_reference) +#define __cpp_rvalue_references __cpp_rvalue_reference +#endif +#if !defined(__cpp_rvalue_references) && (QUICKCPPLIB_CLANG >= 20900) +#define __cpp_rvalue_references 190000 +#endif +#if !defined(__cpp_static_assert) && (QUICKCPPLIB_CLANG >= 20900) +#define __cpp_static_assert 190000 +#endif +#if !defined(__cpp_unicode_characters) && (QUICKCPPLIB_CLANG >= 30000) +#define __cpp_unicode_characters 190000 +#endif +#if !defined(__cpp_unicode_literals) && (QUICKCPPLIB_CLANG >= 30000) +#define __cpp_unicode_literals 190000 +#endif +#if !defined(__cpp_user_defined_literals) && defined(__cpp_user_literals) +#define __cpp_user_defined_literals __cpp_user_literals +#endif +#if !defined(__cpp_user_defined_literals) && (QUICKCPPLIB_CLANG >= 30100) +#define __cpp_user_defined_literals 190000 +#endif +#if !defined(__cpp_variadic_templates) && (QUICKCPPLIB_CLANG >= 20900) +#define __cpp_variadic_templates 190000 +#endif +#endif +#endif +#endif +#define cfg_HAS_CONSTEXPR14 (__cpp_constexpr >= 201304) +#if cfg_HAS_CONSTEXPR14 +#define cfg_constexpr14 constexpr +#else +#define cfg_constexpr14 +#endif +#if cfg_HAS_CONSTEXPR14 && defined(__clang__) +#define cfg_HAS_FULL_FEATURED_CONSTEXPR14 1 +#else +#define cfg_HAS_FULL_FEATURED_CONSTEXPR14 1 +#endif +#endif +#ifndef CX_FUNCTIONAL_HPP +#define CX_FUNCTIONAL_HPP +#include +namespace cx +{ +template +struct less +{ +constexpr auto operator()(const T& lhs, const T& rhs) const -> decltype(lhs < rhs) +{ +return lhs < rhs; +} +}; +template <> +struct less +{ +template +constexpr auto operator()(T&& lhs, U&& rhs) const +-> decltype(std::forward(lhs) < std::forward(rhs)) +{ +return std::forward(lhs) < std::forward(rhs); +} +}; +template +struct equal_to +{ +constexpr auto operator()(const T& lhs, const T& rhs) const -> decltype(lhs == rhs) +{ +return lhs == rhs; +} +}; +template <> +struct equal_to +{ +template +constexpr auto operator()(T&& lhs, U&& rhs) const +-> decltype(std::forward(lhs) == std::forward(rhs)) +{ +return std::forward(lhs) == std::forward(rhs); +} +}; +} +#endif +#ifndef CX_ITERATOR_HPP +#define CX_ITERATOR_HPP +#include +#include +namespace cx +{ +template +constexpr It next(It it) +{ +return it + 1; +} +template +constexpr It prev(It it) +{ +return it - 1; +} +template +constexpr auto size(const C& c) -> decltype(c.size()) +{ +return c.size(); +} +template +constexpr std::size_t size(const T (&)[N]) +{ +return N; +} +template +constexpr auto empty(const C& c) -> decltype(c.empty()) +{ +return c.empty(); +} +template +constexpr bool empty(const T (&)[N]) +{ +return false; +} +template +constexpr bool empty(std::initializer_list il) +{ +return il.size() == 0; +} +template +constexpr auto begin(const C& c) -> decltype(c.begin()) +{ +return c.begin(); +} +template +constexpr auto begin(C& c) -> decltype(c.begin()) +{ +return c.begin(); +} +template +constexpr T* begin(T (&array)[N]) +{ +return &array[0]; +} +template +constexpr const E* begin(std::initializer_list il) +{ +return il.begin(); +} +template +constexpr auto cbegin(const C& c) -> decltype(cx::begin(c)) +{ +return cx::begin(c); +} +template +constexpr auto end(const C& c) -> decltype(c.end()) +{ +return c.end(); +} +template +constexpr auto end(C& c) -> decltype(c.end()) +{ +return c.end(); +} +template +constexpr T* end(T (&array)[N]) +{ +return &array[N]; +} +template +constexpr const E* end(std::initializer_list il) +{ +return il.end(); +} +template +constexpr auto cend(const C& c) -> decltype(cx::end(c)) +{ +return cx::end(c); +} +} +#endif +#ifndef WILDCARDS_UTILITY_HPP +#define WILDCARDS_UTILITY_HPP +#include +#include +namespace wildcards +{ +template +struct const_iterator +{ +using type = typename std::remove_cv< +typename std::remove_reference()))>::type>::type; +}; +template +using const_iterator_t = typename const_iterator::type; +template +struct iterator +{ +using type = typename std::remove_cv< +typename std::remove_reference()))>::type>::type; +}; +template +using iterator_t = typename iterator::type; +template +struct iterated_item +{ +using type = typename std::remove_cv< +typename std::remove_reference())>::type>::type; +}; +template +using iterated_item_t = typename iterated_item::type; +template +struct container_item +{ +using type = typename std::remove_cv< +typename std::remove_reference()))>::type>::type; +}; +template +using container_item_t = typename container_item::type; +} +#endif +namespace wildcards +{ +template +struct full_match_result +{ +bool res; +SequenceIterator s, send, s1; +PatternIterator p, pend, p1; +constexpr operator bool() const +{ +return res; +} +}; +namespace detail +{ +template +struct match_result +{ +bool res; +SequenceIterator s; +PatternIterator p; +constexpr operator bool() const +{ +return res; +} +}; +template +constexpr match_result make_match_result(bool res, +SequenceIterator s, +PatternIterator p) +{ +return {std::move(res), std::move(s), std::move(p)}; +} +template +constexpr full_match_result make_full_match_result( +SequenceIterator s, SequenceIterator send, PatternIterator p, PatternIterator pend, +match_result mr) +{ +return {std::move(mr.res), std::move(s), std::move(send), std::move(mr.s), +std::move(p), std::move(pend), std::move(mr.p)}; +} +#if !cfg_HAS_FULL_FEATURED_CONSTEXPR14 +constexpr bool throw_invalid_argument(const char* what_arg) +{ +return what_arg == nullptr ? false : throw std::invalid_argument(what_arg); +} +template +constexpr T throw_invalid_argument(T t, const char* what_arg) +{ +return what_arg == nullptr ? t : throw std::invalid_argument(what_arg); +} +constexpr bool throw_logic_error(const char* what_arg) +{ +return what_arg == nullptr ? false : throw std::logic_error(what_arg); +} +template +constexpr T throw_logic_error(T t, const char* what_arg) +{ +return what_arg == nullptr ? t : throw std::logic_error(what_arg); +} +#endif +enum class is_set_state +{ +open, +not_or_first, +first, +next +}; +template +constexpr bool is_set( +PatternIterator p, PatternIterator pend, +const cards>& c = cards>(), +is_set_state state = is_set_state::open) +{ +#if cfg_HAS_CONSTEXPR14 +if (!c.set_enabled) +{ +return false; +} +while (p != pend) +{ +switch (state) +{ +case is_set_state::open: +if (*p != c.set_open) +{ +return false; +} +state = is_set_state::not_or_first; +break; +case is_set_state::not_or_first: +if (*p == c.set_not) +{ +state = is_set_state::first; +} +else +{ +state = is_set_state::next; +} +break; +case is_set_state::first: +state = is_set_state::next; +break; +case is_set_state::next: +if (*p == c.set_close) +{ +return true; +} +break; +default: +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::logic_error( +"The program execution should never end up here throwing this exception"); +#else +return throw_logic_error( +"The program execution should never end up here throwing this exception"); +#endif +} +p = cx::next(p); +} +return false; +#else +return c.set_enabled && p != pend && +(state == is_set_state::open +? *p == c.set_open && is_set(cx::next(p), pend, c, is_set_state::not_or_first) +: +state == is_set_state::not_or_first +? *p == c.set_not ? is_set(cx::next(p), pend, c, is_set_state::first) +: is_set(cx::next(p), pend, c, is_set_state::next) +: state == is_set_state::first +? is_set(cx::next(p), pend, c, is_set_state::next) +: state == is_set_state::next +? *p == c.set_close || +is_set(cx::next(p), pend, c, is_set_state::next) +: throw std::logic_error("The program execution should never end up " +"here throwing this exception")); +#endif +} +enum class set_end_state +{ +open, +not_or_first, +first, +next +}; +template +constexpr PatternIterator set_end( +PatternIterator p, PatternIterator pend, +const cards>& c = cards>(), +set_end_state state = set_end_state::open) +{ +#if cfg_HAS_CONSTEXPR14 +if (!c.set_enabled) +{ +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The use of sets is disabled"); +#else +return throw_invalid_argument(p, "The use of sets is disabled"); +#endif +} +while (p != pend) +{ +switch (state) +{ +case set_end_state::open: +if (*p != c.set_open) +{ +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The given pattern is not a valid set"); +#else +return throw_invalid_argument(p, "The given pattern is not a valid set"); +#endif +} +state = set_end_state::not_or_first; +break; +case set_end_state::not_or_first: +if (*p == c.set_not) +{ +state = set_end_state::first; +} +else +{ +state = set_end_state::next; +} +break; +case set_end_state::first: +state = set_end_state::next; +break; +case set_end_state::next: +if (*p == c.set_close) +{ +return cx::next(p); +} +break; +default: +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::logic_error( +"The program execution should never end up here throwing this exception"); +#else +return throw_logic_error( +p, "The program execution should never end up here throwing this exception"); +#endif +} +p = cx::next(p); +} +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The given pattern is not a valid set"); +#else +return throw_invalid_argument(p, "The given pattern is not a valid set"); +#endif +#else +return !c.set_enabled +? throw std::invalid_argument("The use of sets is disabled") +: p == pend +? throw std::invalid_argument("The given pattern is not a valid set") +: +state == set_end_state::open +? *p == c.set_open +? set_end(cx::next(p), pend, c, set_end_state::not_or_first) +: throw std::invalid_argument("The given pattern is not a valid set") +: +state == set_end_state::not_or_first +? *p == c.set_not ? set_end(cx::next(p), pend, c, set_end_state::first) +: set_end(cx::next(p), pend, c, set_end_state::next) +: state == set_end_state::first +? set_end(cx::next(p), pend, c, set_end_state::next) +: state == set_end_state::next +? *p == c.set_close +? cx::next(p) +: set_end(cx::next(p), pend, c, set_end_state::next) +: throw std::logic_error( +"The program execution should never end up " +"here throwing this exception"); +#endif +} +enum class match_set_state +{ +open, +not_or_first_in, +first_out, +next_in, +next_out +}; +template > +constexpr match_result match_set( +SequenceIterator s, SequenceIterator send, PatternIterator p, PatternIterator pend, +const cards>& c = cards>(), +const EqualTo& equal_to = EqualTo(), match_set_state state = match_set_state::open) +{ +#if cfg_HAS_CONSTEXPR14 +if (!c.set_enabled) +{ +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The use of sets is disabled"); +#else +return throw_invalid_argument(make_match_result(false, s, p), "The use of sets is disabled"); +#endif +} +while (p != pend) +{ +switch (state) +{ +case match_set_state::open: +if (*p != c.set_open) +{ +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The given pattern is not a valid set"); +#else +return throw_invalid_argument(make_match_result(false, s, p), +"The given pattern is not a valid set"); +#endif +} +state = match_set_state::not_or_first_in; +break; +case match_set_state::not_or_first_in: +if (*p == c.set_not) +{ +state = match_set_state::first_out; +} +else +{ +if (s == send) +{ +return make_match_result(false, s, p); +} +if (equal_to(*s, *p)) +{ +return make_match_result(true, s, p); +} +state = match_set_state::next_in; +} +break; +case match_set_state::first_out: +if (s == send || equal_to(*s, *p)) +{ +return make_match_result(false, s, p); +} +state = match_set_state::next_out; +break; +case match_set_state::next_in: +if (*p == c.set_close || s == send) +{ +return make_match_result(false, s, p); +} +if (equal_to(*s, *p)) +{ +return make_match_result(true, s, p); +} +break; +case match_set_state::next_out: +if (*p == c.set_close) +{ +return make_match_result(true, s, p); +} +if (s == send || equal_to(*s, *p)) +{ +return make_match_result(false, s, p); +} +break; +default: +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::logic_error( +"The program execution should never end up here throwing this exception"); +#else +return throw_logic_error( +make_match_result(false, s, p), +"The program execution should never end up here throwing this exception"); +#endif +} +p = cx::next(p); +} +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The given pattern is not a valid set"); +#else +return throw_invalid_argument(make_match_result(false, s, p), +"The given pattern is not a valid set"); +#endif +#else +return !c.set_enabled +? throw std::invalid_argument("The use of sets is disabled") +: p == pend +? throw std::invalid_argument("The given pattern is not a valid set") +: state == match_set_state::open +? *p == c.set_open +? match_set(s, send, cx::next(p), pend, c, equal_to, +match_set_state::not_or_first_in) +: +throw std::invalid_argument("The given pattern is not a valid set") +: +state == match_set_state::not_or_first_in +? *p == c.set_not +? match_set(s, send, cx::next(p), pend, c, equal_to, +match_set_state::first_out) +: +s == send ? make_match_result(false, s, p) +: equal_to(*s, *p) +? make_match_result(true, s, p) +: match_set(s, send, cx::next(p), pend, c, +equal_to, match_set_state::next_in) +: +state == match_set_state::first_out +? s == send || equal_to(*s, *p) +? make_match_result(false, s, p) +: match_set(s, send, cx::next(p), pend, c, equal_to, +match_set_state::next_out) +: +state == match_set_state::next_in +? *p == c.set_close || s == send +? make_match_result(false, s, p) +: equal_to(*s, *p) ? make_match_result(true, s, p) +: match_set(s, send, cx::next(p), +pend, c, equal_to, state) +: +state == match_set_state::next_out +? *p == c.set_close +? make_match_result(true, s, p) +: s == send || equal_to(*s, *p) +? make_match_result(false, s, p) +: match_set(s, send, cx::next(p), pend, c, +equal_to, state) +: throw std::logic_error( +"The program execution should never end up " +"here " +"throwing this exception"); +#endif +} +enum class is_alt_state +{ +open, +next, +escape +}; +template +constexpr bool is_alt( +PatternIterator p, PatternIterator pend, +const cards>& c = cards>(), +is_alt_state state = is_alt_state::open, int depth = 0) +{ +#if cfg_HAS_CONSTEXPR14 +if (!c.alt_enabled) +{ +return false; +} +while (p != pend) +{ +switch (state) +{ +case is_alt_state::open: +if (*p != c.alt_open) +{ +return false; +} +state = is_alt_state::next; +++depth; +break; +case is_alt_state::next: +if (*p == c.escape) +{ +state = is_alt_state::escape; +} +else if (c.set_enabled && *p == c.set_open && +is_set(cx::next(p), pend, c, is_set_state::not_or_first)) +{ +p = cx::prev(set_end(cx::next(p), pend, c, set_end_state::not_or_first)); +} +else if (*p == c.alt_open) +{ +++depth; +} +else if (*p == c.alt_close) +{ +--depth; +if (depth == 0) +{ +return true; +} +} +break; +case is_alt_state::escape: +state = is_alt_state::next; +break; +default: +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::logic_error( +"The program execution should never end up here throwing this exception"); +#else +return throw_logic_error( +p, "The program execution should never end up here throwing this exception"); +#endif +} +p = cx::next(p); +} +return false; +#else +return c.alt_enabled && p != pend && +(state == is_alt_state::open +? *p == c.alt_open && is_alt(cx::next(p), pend, c, is_alt_state::next, depth + 1) +: state == is_alt_state::next +? *p == c.escape +? is_alt(cx::next(p), pend, c, is_alt_state::escape, depth) +: c.set_enabled && *p == c.set_open && +is_set(cx::next(p), pend, c, is_set_state::not_or_first) +? is_alt(set_end(cx::next(p), pend, c, set_end_state::not_or_first), +pend, c, state, depth) +: *p == c.alt_open +? is_alt(cx::next(p), pend, c, state, depth + 1) +: *p == c.alt_close +? depth == 1 || +is_alt(cx::next(p), pend, c, state, depth - 1) +: is_alt(cx::next(p), pend, c, state, depth) +: +state == is_alt_state::escape +? is_alt(cx::next(p), pend, c, is_alt_state::next, depth) +: throw std::logic_error( +"The program execution should never end up here throwing this " +"exception")); +#endif +} +enum class alt_end_state +{ +open, +next, +escape +}; +template +constexpr PatternIterator alt_end( +PatternIterator p, PatternIterator pend, +const cards>& c = cards>(), +alt_end_state state = alt_end_state::open, int depth = 0) +{ +#if cfg_HAS_CONSTEXPR14 +if (!c.alt_enabled) +{ +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The use of alternatives is disabled"); +#else +return throw_invalid_argument(p, "The use of alternatives is disabled"); +#endif +} +while (p != pend) +{ +switch (state) +{ +case alt_end_state::open: +if (*p != c.alt_open) +{ +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The given pattern is not a valid alternative"); +#else +return throw_invalid_argument(p, "The given pattern is not a valid alternative"); +#endif +} +state = alt_end_state::next; +++depth; +break; +case alt_end_state::next: +if (*p == c.escape) +{ +state = alt_end_state::escape; +} +else if (c.set_enabled && *p == c.set_open && +is_set(cx::next(p), pend, c, is_set_state::not_or_first)) +{ +p = cx::prev(set_end(cx::next(p), pend, c, set_end_state::not_or_first)); +} +else if (*p == c.alt_open) +{ +++depth; +} +else if (*p == c.alt_close) +{ +--depth; +if (depth == 0) +{ +return cx::next(p); +} +} +break; +case alt_end_state::escape: +state = alt_end_state::next; +break; +default: +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::logic_error( +"The program execution should never end up here throwing this exception"); +#else +return throw_logic_error( +p, "The program execution should never end up here throwing this exception"); +#endif +} +p = cx::next(p); +} +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The given pattern is not a valid alternative"); +#else +return throw_invalid_argument(p, "The given pattern is not a valid alternative"); +#endif +#else +return !c.alt_enabled +? throw std::invalid_argument("The use of alternatives is disabled") +: p == pend +? throw std::invalid_argument("The given pattern is not a valid alternative") +: state == alt_end_state::open +? *p == c.alt_open +? alt_end(cx::next(p), pend, c, alt_end_state::next, depth + 1) +: throw std::invalid_argument( +"The given pattern is not a valid alternative") +: state == alt_end_state::next +? *p == c.escape +? alt_end(cx::next(p), pend, c, alt_end_state::escape, depth) +: c.set_enabled && *p == c.set_open && +is_set(cx::next(p), pend, c, +is_set_state::not_or_first) +? alt_end(set_end(cx::next(p), pend, c, +set_end_state::not_or_first), +pend, c, state, depth) +: *p == c.alt_open +? alt_end(cx::next(p), pend, c, state, depth + 1) +: *p == c.alt_close +? depth == 1 ? cx::next(p) +: alt_end(cx::next(p), pend, c, +state, depth - 1) +: alt_end(cx::next(p), pend, c, state, depth) +: +state == alt_end_state::escape +? alt_end(cx::next(p), pend, c, alt_end_state::next, depth) +: throw std::logic_error( +"The program execution should never end up here throwing " +"this " +"exception"); +#endif +} +enum class alt_sub_end_state +{ +next, +escape +}; +template +constexpr PatternIterator alt_sub_end( +PatternIterator p, PatternIterator pend, +const cards>& c = cards>(), +alt_sub_end_state state = alt_sub_end_state::next, int depth = 1) +{ +#if cfg_HAS_CONSTEXPR14 +if (!c.alt_enabled) +{ +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The use of alternatives is disabled"); +#else +return throw_invalid_argument(p, "The use of alternatives is disabled"); +#endif +} +while (p != pend) +{ +switch (state) +{ +case alt_sub_end_state::next: +if (*p == c.escape) +{ +state = alt_sub_end_state::escape; +} +else if (c.set_enabled && *p == c.set_open && +is_set(cx::next(p), pend, c, is_set_state::not_or_first)) +{ +p = cx::prev(set_end(cx::next(p), pend, c, set_end_state::not_or_first)); +} +else if (*p == c.alt_open) +{ +++depth; +} +else if (*p == c.alt_close) +{ +--depth; +if (depth == 0) +{ +return p; +} +} +else if (*p == c.alt_or) +{ +if (depth == 1) +{ +return p; +} +} +break; +case alt_sub_end_state::escape: +state = alt_sub_end_state::next; +break; +default: +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::logic_error( +"The program execution should never end up here throwing this exception"); +#else +return throw_logic_error( +p, "The program execution should never end up here throwing this exception"); +#endif +} +p = cx::next(p); +} +#if cfg_HAS_FULL_FEATURED_CONSTEXPR14 +throw std::invalid_argument("The given pattern is not a valid alternative"); +#else +return throw_invalid_argument(p, "The given pattern is not a valid alternative"); +#endif +#else +return !c.alt_enabled +? throw std::invalid_argument("The use of alternatives is disabled") +: p == pend +? throw std::invalid_argument("The given pattern is not a valid alternative") +: state == alt_sub_end_state::next +? *p == c.escape +? alt_sub_end(cx::next(p), pend, c, alt_sub_end_state::escape, depth) +: c.set_enabled && *p == c.set_open && +is_set(cx::next(p), pend, c, is_set_state::not_or_first) +? alt_sub_end(set_end(cx::next(p), pend, c, +set_end_state::not_or_first), +pend, c, state, depth) +: *p == c.alt_open +? alt_sub_end(cx::next(p), pend, c, state, depth + 1) +: *p == c.alt_close +? depth == 1 ? p : alt_sub_end(cx::next(p), pend, +c, state, depth - 1) +: *p == c.alt_or +? depth == 1 ? p +: alt_sub_end(cx::next(p), pend, +c, state, depth) +: alt_sub_end(cx::next(p), pend, c, state, +depth) +: +state == alt_sub_end_state::escape +? alt_sub_end(cx::next(p), pend, c, alt_sub_end_state::next, depth) +: throw std::logic_error( +"The program execution should never end up here throwing " +"this " +"exception"); +#endif +} +template > +constexpr match_result match( +SequenceIterator s, SequenceIterator send, PatternIterator p, PatternIterator pend, +const cards>& c = cards>(), +const EqualTo& equal_to = EqualTo(), bool partial = false, bool escape = false); +template > +constexpr match_result match_alt( +SequenceIterator s, SequenceIterator send, PatternIterator p1, PatternIterator p1end, +PatternIterator p2, PatternIterator p2end, +const cards>& c = cards>(), +const EqualTo& equal_to = EqualTo(), bool partial = false) +{ +#if cfg_HAS_CONSTEXPR14 +auto result1 = match(s, send, p1, p1end, c, equal_to, true); +if (result1) +{ +auto result2 = match(result1.s, send, p2, p2end, c, equal_to, partial); +if (result2) +{ +return result2; +} +} +p1 = cx::next(p1end); +if (p1 == p2) +{ +return make_match_result(false, s, p1end); +} +return match_alt(s, send, p1, alt_sub_end(p1, p2, c), p2, p2end, c, equal_to, partial); +#else +return match(s, send, p1, p1end, c, equal_to, true) && +match(match(s, send, p1, p1end, c, equal_to, true).s, send, p2, p2end, c, equal_to, +partial) +? match(match(s, send, p1, p1end, c, equal_to, true).s, send, p2, p2end, c, equal_to, +partial) +: cx::next(p1end) == p2 +? make_match_result(false, s, p1end) +: match_alt(s, send, cx::next(p1end), alt_sub_end(cx::next(p1end), p2, c), p2, +p2end, c, equal_to, partial); +#endif +} +template +constexpr match_result match( +SequenceIterator s, SequenceIterator send, PatternIterator p, PatternIterator pend, +const cards>& c, const EqualTo& equal_to, bool partial, +bool escape) +{ +#if cfg_HAS_CONSTEXPR14 +if (p == pend) +{ +return make_match_result(partial || s == send, s, p); +} +if (escape) +{ +if (s == send || !equal_to(*s, *p)) +{ +return make_match_result(false, s, p); +} +return match(cx::next(s), send, cx::next(p), pend, c, equal_to, partial); +} +if (*p == c.anything) +{ +auto result = match(s, send, cx::next(p), pend, c, equal_to, partial); +if (result) +{ +return result; +} +if (s == send) +{ +return make_match_result(false, s, p); +} +return match(cx::next(s), send, p, pend, c, equal_to, partial); +} +if (*p == c.single) +{ +if (s == send) +{ +return make_match_result(false, s, p); +} +return match(cx::next(s), send, cx::next(p), pend, c, equal_to, partial); +} +if (*p == c.escape) +{ +return match(s, send, cx::next(p), pend, c, equal_to, partial, true); +} +if (c.set_enabled && *p == c.set_open && is_set(cx::next(p), pend, c, is_set_state::not_or_first)) +{ +auto result = +match_set(s, send, cx::next(p), pend, c, equal_to, match_set_state::not_or_first_in); +if (!result) +{ +return result; +} +return match(cx::next(s), send, set_end(cx::next(p), pend, c, set_end_state::not_or_first), +pend, c, equal_to, partial); +} +if (c.alt_enabled && *p == c.alt_open && is_alt(cx::next(p), pend, c, is_alt_state::next, 1)) +{ +auto p_alt_end = alt_end(cx::next(p), pend, c, alt_end_state::next, 1); +return match_alt(s, send, cx::next(p), alt_sub_end(cx::next(p), p_alt_end, c), p_alt_end, pend, +c, equal_to, partial); +} +if (s == send || !equal_to(*s, *p)) +{ +return make_match_result(false, s, p); +} +return match(cx::next(s), send, cx::next(p), pend, c, equal_to, partial); +#else +return p == pend +? make_match_result(partial || s == send, s, p) +: escape +? s == send || !equal_to(*s, *p) +? make_match_result(false, s, p) +: match(cx::next(s), send, cx::next(p), pend, c, equal_to, partial) +: *p == c.anything +? match(s, send, cx::next(p), pend, c, equal_to, partial) +? match(s, send, cx::next(p), pend, c, equal_to, partial) +: s == send ? make_match_result(false, s, p) +: match(cx::next(s), send, p, pend, c, equal_to, partial) +: *p == c.single +? s == send ? make_match_result(false, s, p) +: match(cx::next(s), send, cx::next(p), pend, c, +equal_to, partial) +: *p == c.escape +? match(s, send, cx::next(p), pend, c, equal_to, partial, true) +: c.set_enabled && *p == c.set_open && +is_set(cx::next(p), pend, c, +is_set_state::not_or_first) +? !match_set(s, send, cx::next(p), pend, c, equal_to, +match_set_state::not_or_first_in) +? match_set(s, send, cx::next(p), pend, c, +equal_to, +match_set_state::not_or_first_in) +: match(cx::next(s), send, +set_end(cx::next(p), pend, c, +set_end_state::not_or_first), +pend, c, equal_to, partial) +: c.alt_enabled && *p == c.alt_open && +is_alt(cx::next(p), pend, c, +is_alt_state::next, 1) +? match_alt( +s, send, cx::next(p), +alt_sub_end(cx::next(p), +alt_end(cx::next(p), pend, c, +alt_end_state::next, 1), +c), +alt_end(cx::next(p), pend, c, +alt_end_state::next, 1), +pend, c, equal_to, partial) +: s == send || !equal_to(*s, *p) +? make_match_result(false, s, p) +: match(cx::next(s), send, cx::next(p), pend, +c, equal_to, partial); +#endif +} +} +template > +constexpr full_match_result, const_iterator_t> match( +Sequence&& sequence, Pattern&& pattern, +const cards>& c = cards>(), +const EqualTo& equal_to = EqualTo()) +{ +return detail::make_full_match_result( +cx::cbegin(sequence), cx::cend(sequence), cx::cbegin(pattern), cx::cend(pattern), +detail::match(cx::cbegin(sequence), cx::cend(std::forward(sequence)), +cx::cbegin(pattern), cx::cend(std::forward(pattern)), c, equal_to)); +} +template , +typename = typename std::enable_if::value>::type> +constexpr full_match_result, const_iterator_t> match( +Sequence&& sequence, Pattern&& pattern, const EqualTo& equal_to) +{ +return match(std::forward(sequence), std::forward(pattern), +cards>(), equal_to); +} +} +#endif +#ifndef WILDCARDS_MATCHER_HPP +#define WILDCARDS_MATCHER_HPP +#include +#include +#include +#ifndef CX_STRING_VIEW_HPP +#define CX_STRING_VIEW_HPP +#include +#include +#ifndef CX_ALGORITHM_HPP +#define CX_ALGORITHM_HPP +namespace cx +{ +template +constexpr bool equal(Iterator1 first1, Iterator1 last1, Iterator2 first2, Iterator2 last2) +{ +#if cfg_HAS_CONSTEXPR14 +while (first1 != last1 && first2 != last2 && *first1 == *first2) +{ +++first1, ++first2; +} +return first1 == last1 && first2 == last2; +#else +return first1 != last1 && first2 != last2 && *first1 == *first2 +? equal(first1 + 1, last1, first2 + 1, last2) +: first1 == last1 && first2 == last2; +#endif +} +} +#endif +namespace cx +{ +template +class basic_string_view +{ +public: +using value_type = T; +constexpr basic_string_view() = default; +template +constexpr basic_string_view(const T (&str)[N]) : data_{&str[0]}, size_{N - 1} +{ +} +constexpr basic_string_view(const T* str, std::size_t s) : data_{str}, size_{s} +{ +} +constexpr const T* data() const +{ +return data_; +} +constexpr std::size_t size() const +{ +return size_; +} +constexpr bool empty() const +{ +return size() == 0; +} +constexpr const T* begin() const +{ +return data_; +} +constexpr const T* cbegin() const +{ +return begin(); +} +constexpr const T* end() const +{ +return data_ + size_; +} +constexpr const T* cend() const +{ +return end(); +} +private: +const T* data_{nullptr}; +std::size_t size_{0}; +}; +template +constexpr bool operator==(const basic_string_view& lhs, const basic_string_view& rhs) +{ +return equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} +template +constexpr bool operator!=(const basic_string_view& lhs, const basic_string_view& rhs) +{ +return !(lhs == rhs); +} +template +std::basic_ostream& operator<<(std::basic_ostream& o, const basic_string_view& s) +{ +o << s.data(); +return o; +} +template +constexpr basic_string_view make_string_view(const T (&str)[N]) +{ +return {str, N - 1}; +} +template +constexpr basic_string_view make_string_view(const T* str, std::size_t s) +{ +return {str, s}; +} +using string_view = basic_string_view; +using u16string_view = basic_string_view; +using u32string_view = basic_string_view; +using wstring_view = basic_string_view; +namespace literals +{ +constexpr string_view operator ""_sv(const char* str, std::size_t s) +{ +return {str, s}; +} +constexpr u16string_view operator ""_sv(const char16_t* str, std::size_t s) +{ +return {str, s}; +} +constexpr u32string_view operator ""_sv(const char32_t* str, std::size_t s) +{ +return {str, s}; +} +constexpr wstring_view operator ""_sv(const wchar_t* str, std::size_t s) +{ +return {str, s}; +} +} +} +#endif +namespace wildcards +{ +template > +class matcher +{ +public: +constexpr explicit matcher(Pattern&& pattern, const cards>& c = +cards>(), +const EqualTo& equal_to = EqualTo()) +: p_{cx::cbegin(pattern)}, +pend_{cx::cend(std::forward(pattern))}, +c_{c}, +equal_to_{equal_to} +{ +} +constexpr matcher(Pattern&& pattern, const EqualTo& equal_to) +: p_{cx::cbegin(pattern)}, +pend_{cx::cend(std::forward(pattern))}, +c_{cards>()}, +equal_to_{equal_to} +{ +} +template +constexpr full_match_result, const_iterator_t> matches( +Sequence&& sequence) const +{ +return detail::make_full_match_result( +cx::cbegin(sequence), cx::cend(sequence), p_, pend_, +detail::match(cx::cbegin(sequence), cx::cend(std::forward(sequence)), p_, pend_, +c_, equal_to_)); +} +private: +const_iterator_t p_; +const_iterator_t pend_; +cards> c_; +EqualTo equal_to_; +}; +template > +constexpr matcher make_matcher( +Pattern&& pattern, +const cards>& c = cards>(), +const EqualTo& equal_to = EqualTo()) +{ +return matcher{std::forward(pattern), c, equal_to}; +} +template , +typename = typename std::enable_if::value>::type> +constexpr matcher make_matcher(Pattern&& pattern, const EqualTo& equal_to) +{ +return make_matcher(std::forward(pattern), cards>(), equal_to); +} +namespace literals +{ +constexpr auto operator ""_wc(const char* str, std::size_t s) +-> decltype(make_matcher(cx::make_string_view(str, s + 1))) +{ +return make_matcher(cx::make_string_view(str, s + 1)); +} +constexpr auto operator ""_wc(const char16_t* str, std::size_t s) +-> decltype(make_matcher(cx::make_string_view(str, s + 1))) +{ +return make_matcher(cx::make_string_view(str, s + 1)); +} +constexpr auto operator ""_wc(const char32_t* str, std::size_t s) +-> decltype(make_matcher(cx::make_string_view(str, s + 1))) +{ +return make_matcher(cx::make_string_view(str, s + 1)); +} +constexpr auto operator ""_wc(const wchar_t* str, std::size_t s) +-> decltype(make_matcher(cx::make_string_view(str, s + 1))) +{ +return make_matcher(cx::make_string_view(str, s + 1)); +} +} +} +#endif +#endif diff --git a/src/common/menu/joystickmenu.cpp b/src/common/menu/joystickmenu.cpp index e3bb8544b..d65049e47 100644 --- a/src/common/menu/joystickmenu.cpp +++ b/src/common/menu/joystickmenu.cpp @@ -1,9 +1,12 @@ /* ** joystickmenu.cpp +** ** The joystick configuration menus ** **--------------------------------------------------------------------------- +** ** Copyright 2010 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -28,12 +31,13 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ -#include "menu.h" #include "m_joy.h" +#include "menu.h" #include "vm.h" static TArray Joysticks; @@ -52,6 +56,26 @@ DEFINE_ACTION_FUNCTION(IJoystickConfig, SetSensitivity) return 0; } +DEFINE_ACTION_FUNCTION(IJoystickConfig, HasHaptics) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + ACTION_RETURN_BOOL(self->HasHaptics()); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, GetHapticsStrength) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + ACTION_RETURN_FLOAT(self->GetHapticsStrength()); +} + +DEFINE_ACTION_FUNCTION(IJoystickConfig, SetHapticsStrength) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + PARAM_FLOAT(strength); + self->SetHapticsStrength((float)strength); + return 0; +} + DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisScale) { PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); @@ -134,22 +158,6 @@ DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisResponseCurvePoint) return 0; } -DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisMap) -{ - PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); - PARAM_INT(axis); - ACTION_RETURN_INT(self->GetAxisMap(axis)); -} - -DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisMap) -{ - PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); - PARAM_INT(axis); - PARAM_INT(map); - self->SetAxisMap(axis, (EJoyAxis)map); - return 0; -} - DEFINE_ACTION_FUNCTION(IJoystickConfig, GetName) { PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); @@ -203,6 +211,12 @@ DEFINE_ACTION_FUNCTION(IJoystickConfig, SetEnabledInBackground) return 0; } +DEFINE_ACTION_FUNCTION(IJoystickConfig, Reset) +{ + PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig); + self->Reset(); + return 0; +} void UpdateJoystickMenu(IJoystickConfig *selected) { @@ -279,4 +293,3 @@ void UpdateJoystickMenu(IJoystickConfig *selected) } } } - diff --git a/src/common/menu/messagebox.cpp b/src/common/menu/messagebox.cpp index 7c5938243..6f6d8214e 100644 --- a/src/common/menu/messagebox.cpp +++ b/src/common/menu/messagebox.cpp @@ -70,7 +70,7 @@ DMenu *CreateMessageBoxMenu(DMenu *parent, const char *message, int messagemode, IFVIRTUALPTRNAME(p, NAME_MessageBoxMenu, Init) { p->PointerVar(NAME_Handler) = reinterpret_cast(handler); - VMValue params[] = { p, parent, &namestr, messagemode, playsound, action.GetIndex() }; + VMValue params[] = { p, parent, &namestr, messagemode, playsound, action.GetIndex(), (void*)nullptr }; VMCall(func, params, countof(params), nullptr, 0); return (DMenu*)p; } diff --git a/src/common/menu/optionmenu.cpp b/src/common/menu/optionmenu.cpp index 03ce3c526..2d82515a3 100644 --- a/src/common/menu/optionmenu.cpp +++ b/src/common/menu/optionmenu.cpp @@ -4,6 +4,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 2010-2017 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -33,14 +34,14 @@ */ #include "c_cvars.h" -#include "v_video.h" #include "menu.h" #include "vm.h" // by request of Nash -CVARD(Bool, silence_menu_scroll, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Silences cursor movement when using mouse wheel"); -CVARD(Bool, silence_menu_hover, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Silences cursor movement when implicitly selecting with mouse"); +CVARD(Bool, silence_menu_scroll, true, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Silences cursor movement when using mouse wheel"); +CVARD(Bool, silence_menu_hover, true, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Silences cursor movement when implicitly selecting with mouse"); +CVARD(Int, menu_overscroll, 8, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Number of lines you can scroll past the bottom of a menu"); //============================================================================= // diff --git a/src/common/objects/dobjtype.cpp b/src/common/objects/dobjtype.cpp index d08947903..b3a815ebc 100644 --- a/src/common/objects/dobjtype.cpp +++ b/src/common/objects/dobjtype.cpp @@ -5,6 +5,7 @@ **--------------------------------------------------------------------------- ** Copyright 1998-2016 Randy Heit ** Copyright 2005-2016 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -816,7 +817,7 @@ void PClass::BuildFlatPointers() const else { pairType *flat = (pairType*)ClassDataAllocator.Alloc(sizeof(pairType) * NativePointers.Size()); - memcpy(flat, NativePointers.Data(), sizeof(pairType) * NativePointers.Size()); + std::copy(NativePointers.Data(), NativePointers.Data() + NativePointers.Size(), flat); FlatPointers = flat; FlatPointersSize = NativePointers.Size(); @@ -849,15 +850,15 @@ void PClass::BuildFlatPointers() const if (ParentClass->FlatPointersSize > 0) { - memcpy (flat, ParentClass->FlatPointers, sizeof(pairType) * ParentClass->FlatPointersSize); + std::copy(ParentClass->FlatPointers, ParentClass->FlatPointers + ParentClass->FlatPointersSize, flat); } if (NativePointers.Size() > 0) { - memcpy(flat + ParentClass->FlatPointersSize, NativePointers.Data(), sizeof(pairType) * NativePointers.Size()); + std::copy(NativePointers.Data(), NativePointers.Data() + NativePointers.Size(), flat + ParentClass->FlatPointersSize); } if (ScriptPointers.Size() > 0) { - memcpy(flat + ParentClass->FlatPointersSize + NativePointers.Size(), &ScriptPointers[0], sizeof(pairType) * ScriptPointers.Size()); + std::copy(&ScriptPointers[0], &ScriptPointers[0] + ScriptPointers.Size(), flat + ParentClass->FlatPointersSize + NativePointers.Size()); } FlatPointers = flat; FlatPointersSize = ParentClass->FlatPointersSize + NativePointers.Size() + ScriptPointers.Size(); @@ -913,12 +914,12 @@ void PClass::BuildArrayPointers() const pairType *flat = (pairType*)ClassDataAllocator.Alloc(sizeof(pairType) * (ParentClass->ArrayPointersSize + ScriptPointers.Size())); if (ParentClass->ArrayPointersSize > 0) { - memcpy(flat, ParentClass->ArrayPointers, sizeof(pairType) * ParentClass->ArrayPointersSize); + std::copy(ParentClass->ArrayPointers, ParentClass->ArrayPointers + ParentClass->ArrayPointersSize, flat); } if (ScriptPointers.Size() > 0) { - memcpy(flat + ParentClass->ArrayPointersSize, ScriptPointers.Data(), sizeof(pairType) * ScriptPointers.Size()); + std::copy(ScriptPointers.Data(), ScriptPointers.Data() + ScriptPointers.Size(), flat + ParentClass->ArrayPointersSize); } ArrayPointers = flat; @@ -974,12 +975,12 @@ void PClass::BuildMapPointers() const pairType *flat = (pairType*)ClassDataAllocator.Alloc(sizeof(pairType) * (ParentClass->MapPointersSize + ScriptPointers.Size())); if (ParentClass->MapPointersSize > 0) { - memcpy(flat, ParentClass->MapPointers, sizeof(pairType) * ParentClass->MapPointersSize); + std::copy(ParentClass->MapPointers, ParentClass->MapPointers + ParentClass->MapPointersSize, flat); } if (ScriptPointers.Size() > 0) { - memcpy(flat + ParentClass->MapPointersSize, ScriptPointers.Data(), sizeof(pairType) * ScriptPointers.Size()); + std::copy(ScriptPointers.Data(), ScriptPointers.Data() + ScriptPointers.Size(), flat + ParentClass->MapPointersSize); } MapPointers = flat; MapPointersSize = ParentClass->MapPointersSize + ScriptPointers.Size(); diff --git a/src/common/platform/posix/cocoa/i_joystick.cpp b/src/common/platform/posix/cocoa/i_joystick.cpp index ffb9f90a0..adf1daa55 100644 --- a/src/common/platform/posix/cocoa/i_joystick.cpp +++ b/src/common/platform/posix/cocoa/i_joystick.cpp @@ -1,35 +1,38 @@ /* - ** i_joystick.cpp - ** - **--------------------------------------------------------------------------- - ** Copyright 2012-2015 Alexey Lysiuk - ** All rights reserved. - ** - ** Redistribution and use in source and binary forms, with or without - ** modification, are permitted provided that the following conditions - ** are met: - ** - ** 1. Redistributions of source code must retain the above copyright - ** notice, this list of conditions and the following disclaimer. - ** 2. Redistributions in binary form must reproduce the above copyright - ** notice, this list of conditions and the following disclaimer in the - ** documentation and/or other materials provided with the distribution. - ** 3. The name of the author may not be used to endorse or promote products - ** derived from this software without specific prior written permission. - ** - ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **--------------------------------------------------------------------------- - ** - */ +** i_joystick.cpp +** +**--------------------------------------------------------------------------- +** +** Copyright 2012-2015 Alexey Lysiuk +** Copyright 2017-2025 GZDoom Maintainers and Contributors +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +**--------------------------------------------------------------------------- +** +*/ #include #include @@ -44,9 +47,8 @@ #include "printf.h" #include "keydef.h" - EXTERN_CVAR(Bool, joy_axespolling) - +EXTERN_CVAR(Bool, use_joystick) namespace { @@ -75,10 +77,8 @@ FString ToFString(const CFStringRef string) return FString(buffer); } - // --------------------------------------------------------------------------- - class IOKitJoystick : public IJoystickConfig { public: @@ -89,9 +89,12 @@ public: virtual float GetSensitivity(); virtual void SetSensitivity(float scale); + virtual bool HasHaptics(); + virtual float GetHapticsStrength(); + virtual void SetHapticsStrength(float strength); + virtual int GetNumAxes(); virtual float GetAxisDeadZone(int axis); - virtual EJoyAxis GetAxisMap(int axis); virtual const char* GetAxisName(int axis); virtual float GetAxisScale(int axis); float GetAxisDigitalThreshold(int axis); @@ -99,15 +102,14 @@ public: float GetAxisResponseCurvePoint(int axis, int point); virtual void SetAxisDeadZone(int axis, float deadZone); - virtual void SetAxisMap(int axis, EJoyAxis gameAxis); virtual void SetAxisScale(int axis, float scale); void SetAxisDigitalThreshold(int axis, float threshold); void SetAxisResponseCurve(int axis, EJoyCurve preset); void SetAxisResponseCurvePoint(int axis, int point, float value); virtual bool IsSensitivityDefault(); + virtual bool IsHapticsStrengthDefault(); virtual bool IsAxisDeadZoneDefault(int axis); - virtual bool IsAxisMapDefault(int axis); virtual bool IsAxisScaleDefault(int axis); bool IsAxisDigitalThresholdDefault(int axis); bool IsAxisResponseCurveDefault(int axis); @@ -122,7 +124,7 @@ public: virtual void SetDefaultConfig(); virtual FString GetIdentifier(); - void AddAxes(float axes[NUM_JOYAXIS]) const; + void AddAxes(float axes[NUM_AXIS_CODES]) const; void Update(); @@ -160,8 +162,7 @@ private: EJoyCurve defaultResponseCurvePreset; CubicBezier responseCurve; - EJoyAxis gameAxis; - EJoyAxis defaultGameAxis; + uint8_t buttonValue; AnalogAxis() { @@ -208,7 +209,6 @@ private: void RemoveFromQueue(IOHIDElementCookie cookie); }; - IOHIDDeviceInterface** CreateDeviceInterface(const io_object_t device) { IOCFPlugInInterface** plugInInterface = NULL; @@ -287,7 +287,6 @@ IOHIDQueueInterface** CreateDeviceQueue(IOHIDDeviceInterface** const interface) return queue; } - IOKitJoystick::IOKitJoystick(const io_object_t device) : m_interface(CreateDeviceInterface(device)) , m_queue(CreateDeviceQueue(m_interface)) @@ -346,13 +345,11 @@ IOKitJoystick::~IOKitJoystick() } } - FString IOKitJoystick::GetName() { return m_name; } - float IOKitJoystick::GetSensitivity() { return m_sensitivity; @@ -363,6 +360,20 @@ void IOKitJoystick::SetSensitivity(float scale) m_sensitivity = scale; } +bool IOKitJoystick::HasHaptics() +{ + return false; +} + +float IOKitJoystick::GetHapticsStrength() +{ + return JOYHAPSTRENGTH_DEFAULT; +} + +void IOKitJoystick::SetHapticsStrength(float strength) +{ + // nope +} int IOKitJoystick::GetNumAxes() { @@ -376,11 +387,6 @@ float IOKitJoystick::GetAxisDeadZone(int axis) return IS_AXIS_VALID ? m_axes[axis].deadZone : 0.0f; } -EJoyAxis IOKitJoystick::GetAxisMap(int axis) -{ - return IS_AXIS_VALID ? m_axes[axis].gameAxis : JOYAXIS_None; -} - const char* IOKitJoystick::GetAxisName(int axis) { return IS_AXIS_VALID ? m_axes[axis].name : "Invalid"; @@ -414,16 +420,6 @@ void IOKitJoystick::SetAxisDeadZone(int axis, float deadZone) } } -void IOKitJoystick::SetAxisMap(int axis, EJoyAxis gameAxis) -{ - if (IS_AXIS_VALID) - { - m_axes[axis].gameAxis = (gameAxis> JOYAXIS_None && gameAxis = 3) { - m_axes[0].gameAxis = JOYAXIS_Side; m_axes[0].digitalThreshold = JOYTHRESH_STICK_X; - - m_axes[1].gameAxis = JOYAXIS_Forward; m_axes[1].digitalThreshold = JOYTHRESH_STICK_Y; - - m_axes[2].gameAxis = JOYAXIS_Yaw; m_axes[2].digitalThreshold = JOYTHRESH_STICK_X; // Four axes? First two are movement, last two are looking around. if (axisCount >= 4) { - m_axes[3].gameAxis = JOYAXIS_Pitch; // ??? m_axes[3].sensitivity = 0.75f; m_axes[3].digitalThreshold = JOYTHRESH_STICK_Y; @@ -563,7 +547,6 @@ void IOKitJoystick::SetDefaultConfig() if (axisCount >= 5) { - m_axes[4].gameAxis = JOYAXIS_Up; m_axes[4].digitalThreshold = JOYTHRESH_STICK_Y; } } @@ -578,35 +561,43 @@ void IOKitJoystick::SetDefaultConfig() { m_axes[i].defaultDeadZone = m_axes[i].deadZone; m_axes[i].defaultSensitivity = m_axes[i].sensitivity; - m_axes[i].defaultGameAxis = m_axes[i].gameAxis; m_axes[i].defaultDigitalThreshold = m_axes[i].digitalThreshold; m_axes[i].defaultResponseCurvePreset = m_axes[i].responseCurvePreset; } } - FString IOKitJoystick::GetIdentifier() { return m_identifier; } - -void IOKitJoystick::AddAxes(float axes[NUM_JOYAXIS]) const +void IOKitJoystick::AddAxes(float axes[NUM_AXIS_CODES]) const { for (size_t i = 0, count = m_axes.Size(); i < count; ++i) { - const EJoyAxis axis = m_axes[i].gameAxis; + // Add to the game axis. + float axis_value = m_axes[i].value; + int code = AXIS_CODE_NULL; - if (JOYAXIS_None == axis) + if (i < NUM_JOYAXISBUTTONS) { - continue; + if (axis_value > 0.0f) + { + code = AXIS_CODE_JOY1_PLUS + (i * 2); + } + else if (axis_value < 0.0f) + { + code = AXIS_CODE_JOY1_PLUS + (i * 2) + 1; + } } - axes[axis] -= m_axes[i].value; + if (code != AXIS_CODE_NULL) + { + axes[code] += fabs(axis_value); + } } } - void IOKitJoystick::UseAxesPolling(const bool axesPolling) { m_useAxesPolling = axesPolling; @@ -626,7 +617,6 @@ void IOKitJoystick::UseAxesPolling(const bool axesPolling) } } - void IOKitJoystick::Update() { if (NULL == m_queue) @@ -654,7 +644,6 @@ void IOKitJoystick::Update() if(m_enabled) ProcessAxes(); } - void IOKitJoystick::ProcessAxes() { if (NULL == m_interface || !m_useAxesPolling) @@ -664,30 +653,83 @@ void IOKitJoystick::ProcessAxes() for (size_t i = 0, count = m_axes.Size(); i < count; ++i) { - AnalogAxis& axis = m_axes[i]; - static const double scaledMin = -1; static const double scaledMax = 1; - IOHIDEventStruct event; - - if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis.cookie, &event)) + if (i < NUM_JOYAXISBUTTONS && (i > 2 || m_axes.Size() == 1)) { - const double scaledValue = scaledMin + - (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); - const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL); - const double smoothedValue = Joy_ApplyResponseCurveBezier(axis.responseCurve, filteredValue); + AnalogAxis& axis = m_axes[i]; + uint8_t buttonstate = 0; - axis.value = static_cast(smoothedValue * m_sensitivity * axis.sensitivity); + IOHIDEventStruct event; + + if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis.cookie, &event)) + { + const double scaledValue = scaledMin + + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); + + const double filteredValue = Joy_ManageSingleAxis( + scaledValue, + axis.deadZone, + axis.digitalThreshold, + axis.responseCurve, + &buttonstate + ); + + axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); + } + else + { + axis.value = 0.0f; + } + + Joy_GenerateButtonEvents(axis.buttonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); + axis.buttonValue = buttonstate; } - else + else if (i == 1) { - axis.value = 0.0f; + // Since we sorted the axes, we know that the first two are definitely X and Y. + // They are probably a single stick, so treat them as one. + + AnalogAxis& axis_x = m_axes[i - 1]; + AnalogAxis& axis_y = m_axes[i]; + + uint8_t buttonstate = 0; + double axisval_x = 0.0f; + double axisval_y = 0.0f; + + IOHIDEventStruct event; + + if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis_x.cookie, &event)) + { + axisval_x = scaledMin + + (event.value - axis_x.minValue) * (scaledMax - scaledMin) / (axis_x.maxValue - axis_x.minValue); + } + + if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis_y.cookie, &event)) + { + axisval_y = scaledMin + + (event.value - axis_y.minValue) * (scaledMax - scaledMin) / (axis_y.maxValue - axis_y.minValue); + } + + Joy_ManageThumbstick( + &axisval_x, &axisval_y, + axis_x.deadZone, axis_y.deadZone, + axis_x.digitalThreshold, axis_y.digitalThreshold, + axis_x.responseCurve, axis_y.responseCurve, + &buttonstate + ); + + axis_x.value = static_cast(axisval_x * m_sensitivity * axis_x.sensitivity); + axis_y.value = static_cast(axisval_y * m_sensitivity * axis_y.sensitivity); + + // We store all four buttons in the first axis and ignore the second. + Joy_GenerateButtonEvents(axis_x.buttonValue, buttonstate, 4, KEY_JOYAXIS1PLUS + (i-1)*2); + axis_x.buttonValue = buttonstate; } } } - bool IOKitJoystick::ProcessAxis(const IOHIDEventStruct& event) { if (m_useAxesPolling) @@ -703,17 +745,29 @@ bool IOKitJoystick::ProcessAxis(const IOHIDEventStruct& event) } AnalogAxis& axis = m_axes[i]; + uint8_t buttonstate = 0; static const double scaledMin = -1; static const double scaledMax = 1; const double scaledValue = scaledMin + (event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue); - const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL); - const double smoothedValue = Joy_ApplyResponseCurveBezier(axis.responseCurve, filteredValue); - axis.value = static_cast(smoothedValue * m_sensitivity * axis.sensitivity); + const double filteredValue = Joy_ManageSingleAxis( + scaledValue, + axis.deadZone, + axis.digitalThreshold, + axis.responseCurve, + &buttonstate + ); + axis.value = static_cast(filteredValue * m_sensitivity * axis.sensitivity); + + if (i < NUM_JOYAXISBUTTONS) + { + Joy_GenerateButtonEvents(axis.buttonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); + } + axis.buttonValue = buttonstate; return true; } @@ -767,7 +821,6 @@ bool IOKitJoystick::ProcessPOV(const IOHIDEventStruct& event) return false; } - void IOKitJoystick::GatherDeviceInfo(const io_object_t device, const CFDictionaryRef properties) { assert(NULL != properties); @@ -872,7 +925,6 @@ void IOKitJoystick::GatherDeviceInfo(const io_object_t device, const CFDictionar } } - long GetElementValue(const CFDictionaryRef element, const CFStringRef key) { const CFNumberRef number = @@ -949,7 +1001,6 @@ void IOKitJoystick::GatherCollectionElements(const CFDictionaryRef properties) CFArrayApplyFunction(topElement, range, GatherElementsHandler, this); } - IOHIDElementCookie GetElementCookie(const CFDictionaryRef element) { // Use C-style cast to avoid 32/64-bit IOHIDElementCookie type issue @@ -997,7 +1048,6 @@ void IOKitJoystick::AddPOV(CFDictionaryRef element) AddToQueue(pov.cookie); } - void IOKitJoystick::AddToQueue(const IOHIDElementCookie cookie) { if (NULL == m_queue) @@ -1024,16 +1074,13 @@ void IOKitJoystick::RemoveFromQueue(const IOHIDElementCookie cookie) } } - io_object_t* IOKitJoystick::GetNotificationPtr() { return &m_notification; } - // --------------------------------------------------------------------------- - class IOKitJoystickManager { public: @@ -1042,7 +1089,7 @@ public: void GetJoysticks(TArray& joysticks) const; - void AddAxes(float axes[NUM_JOYAXIS]) const; + void AddAxes(float axes[NUM_AXIS_CODES]) const; // Updates axes/buttons states void Update(); @@ -1067,10 +1114,8 @@ private: natural_t messageType, void* messageArgument); }; - IOKitJoystickManager* s_joystickManager; - IOKitJoystickManager::IOKitJoystickManager() { memset(m_notifications, 0, sizeof m_notifications); @@ -1118,7 +1163,6 @@ IOKitJoystickManager::~IOKitJoystickManager() } } - void IOKitJoystickManager::GetJoysticks(TArray& joysticks) const { const size_t joystickCount = m_joysticks.Size(); @@ -1133,7 +1177,7 @@ void IOKitJoystickManager::GetJoysticks(TArray& joysticks) con } } -void IOKitJoystickManager::AddAxes(float axes[NUM_JOYAXIS]) const +void IOKitJoystickManager::AddAxes(float axes[NUM_AXIS_CODES]) const { for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i) { @@ -1141,7 +1185,6 @@ void IOKitJoystickManager::AddAxes(float axes[NUM_JOYAXIS]) const } } - void IOKitJoystickManager::Update() { for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i) @@ -1150,7 +1193,6 @@ void IOKitJoystickManager::Update() } } - void IOKitJoystickManager::UseAxesPolling(const bool axesPolling) { for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i) @@ -1159,14 +1201,12 @@ void IOKitJoystickManager::UseAxesPolling(const bool axesPolling) } } - void PostDeviceChangeEvent() { event_t event = { EV_DeviceChange }; D_PostEvent(&event); } - void IOKitJoystickManager::Rescan(const int usagePage, const int usage, const size_t notificationPortIndex) { CFMutableDictionaryRef deviceMatching = IOServiceMatching(kIOHIDDeviceKey); @@ -1228,7 +1268,6 @@ void IOKitJoystickManager::AddDevices(const IONotificationPortRef notificationPo } } - void IOKitJoystickManager::OnDeviceAttached(void* const refcon, const io_iterator_t iterator) { assert(NULL != refcon); @@ -1267,10 +1306,8 @@ void IOKitJoystickManager::OnDeviceRemoved(void* const refcon, io_service_t, con } // unnamed namespace - // --------------------------------------------------------------------------- - void I_ShutdownInput() { delete s_joystickManager; @@ -1296,9 +1333,9 @@ void I_GetJoysticks(TArray& sticks) } } -void I_GetAxes(float axes[NUM_JOYAXIS]) +void I_GetAxes(float axes[NUM_AXIS_CODES]) { - for (size_t i = 0; i < NUM_JOYAXIS; ++i) + for (size_t i = 0; i < NUM_AXIS_CODES; ++i) { axes[i] = 0.0f; } @@ -1316,10 +1353,8 @@ IJoystickConfig* I_UpdateDeviceList() return NULL; } - // --------------------------------------------------------------------------- - void I_ProcessJoysticks() { if (NULL != s_joystickManager) @@ -1328,10 +1363,8 @@ void I_ProcessJoysticks() } } - // --------------------------------------------------------------------------- - CUSTOM_CVAR(Bool, joy_axespolling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) { if (NULL != s_joystickManager) @@ -1339,3 +1372,11 @@ CUSTOM_CVAR(Bool, joy_axespolling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR s_joystickManager->UseAxesPolling(self); } } + +void I_Rumble(double high_freq, double low_freq, double left_trig, double right_trig) +{ + if (!use_joystick) return; + + // stub +} + diff --git a/src/common/platform/posix/cocoa/i_main.mm b/src/common/platform/posix/cocoa/i_main.mm index 5ab7c4b11..2d994b7b3 100644 --- a/src/common/platform/posix/cocoa/i_main.mm +++ b/src/common/platform/posix/cocoa/i_main.mm @@ -3,6 +3,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 2012-2018 Alexey Lysiuk + ** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -34,28 +35,26 @@ #include "i_common.h" #include "s_soundinternal.h" -#include +#include #include +#include #include "c_console.h" #include "c_cvars.h" #include "cmdlib.h" +#include "engineerrors.h" #include "i_system.h" #include "m_argv.h" -#include "st_console.h" -#include "version.h" #include "printf.h" #include "s_music.h" -#include "engineerrors.h" +#include "st_console.h" +#include "version.h" #include "zstring.h" - #define ZD_UNUSED(VARIABLE) ((void)(VARIABLE)) - // --------------------------------------------------------------------------- - EXTERN_CVAR(Int, vid_defwidth ) EXTERN_CVAR(Int, vid_defheight) EXTERN_CVAR(Bool, vid_vsync ) @@ -65,7 +64,9 @@ int GameMain(); extern bool RunningAsTool; // --------------------------------------------------------------------------- +void SignalHandler(int signal); +// --------------------------------------------------------------------------- void Mac_I_FatalError(const char* const message) { @@ -163,18 +164,12 @@ void I_DetectOS() case 16: name = "Big Sur"; break; } break; - case 11: - name = "Big Sur"; - break; - case 12: - name = "Monterey"; - break; - case 13: - name = "Ventura"; - break; - case 14: - name = "Sonoma"; - break; + case 11: name = "Big Sur"; break; + case 12: name = "Monterey"; break; + case 13: name = "Ventura"; break; + case 14: name = "Sonoma"; break; + case 15: name = "Sequoia"; break; + case 26: name = "Tahoe"; break; } char release[16] = "unknown"; @@ -212,6 +207,11 @@ TArray s_argv; int DoMain(int argc, char** argv) { + signal(SIGINT, SignalHandler); + signal(SIGTERM, SignalHandler); + // signal(SIGHUP, SignalHandler); + // signal(SIGQUIT, SignalHandler); + printf(GAMENAME" %s - %s - Cocoa version\nCompiled on %s\n\n", GetVersionString(), GetGitTime(), __DATE__); diff --git a/src/common/platform/posix/cocoa/i_video.mm b/src/common/platform/posix/cocoa/i_video.mm index ec94a1f92..a406ea410 100644 --- a/src/common/platform/posix/cocoa/i_video.mm +++ b/src/common/platform/posix/cocoa/i_video.mm @@ -3,6 +3,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 2012-2018 Alexey Lysiuk + ** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -32,22 +33,24 @@ */ #ifdef HAVE_VULKAN -#include #include +#include #endif #include "i_common.h" #include "i_interface.h" -#include "v_video.h" #include "bitmap.h" #include "c_dispatch.h" +#include "gl_framebuffer.h" #include "hardware.h" #include "i_system.h" #include "m_argv.h" #include "m_png.h" +#include "printf.h" #include "st_console.h" #include "v_text.h" +#include "v_video.h" #include "version.h" #include "printf.h" @@ -56,6 +59,7 @@ #endif bool I_CreateVulkanSurface(VkInstance instance, VkSurfaceKHR *surface); +#endif extern bool ToggleFullscreen; @@ -305,7 +309,9 @@ public: ~CocoaVideo() { +#ifdef HAVE_VULKAN m_vulkanSurface.reset(); +#endif ms_window = nil; } diff --git a/src/common/platform/posix/sdl/base_sysfb.cpp b/src/common/platform/posix/sdl/base_sysfb.cpp index 05b84de7d..bce2605d4 100644 --- a/src/common/platform/posix/sdl/base_sysfb.cpp +++ b/src/common/platform/posix/sdl/base_sysfb.cpp @@ -3,6 +3,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 2005-2016 Christoph Oelckers et.al. +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -33,17 +34,17 @@ // HEADER FILES ------------------------------------------------------------ +#include "c_console.h" +#include "c_dispatch.h" #include "i_module.h" #include "i_soundinternal.h" #include "i_system.h" #include "i_video.h" #include "i_interface.h" #include "m_argv.h" +#include "printf.h" #include "v_video.h" #include "version.h" -#include "c_console.h" -#include "c_dispatch.h" -#include "printf.h" #include "hardware.h" @@ -58,7 +59,7 @@ // MACROS ------------------------------------------------------------------ #if defined HAVE_VULKAN -#include +#include #endif // HAVE_VULKAN // TYPES ------------------------------------------------------------------- @@ -377,11 +378,6 @@ IVideo *gl_CreateVideo() SystemBaseFrameBuffer::SystemBaseFrameBuffer (void *, bool fullscreen) : DFrameBuffer (vid_defwidth, vid_defheight) { - if (Priv::window != nullptr) - { - SDL_SetWindowFullscreen(Priv::window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); - SDL_ShowWindow(Priv::window); - } } int SystemBaseFrameBuffer::GetClientWidth() @@ -414,6 +410,7 @@ bool SystemBaseFrameBuffer::IsFullscreen () void SystemBaseFrameBuffer::ToggleFullscreen(bool yes) { + SDL_ShowWindow(Priv::window); SDL_SetWindowFullscreen(Priv::window, yes ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); if ( !yes ) { @@ -458,6 +455,7 @@ void SystemBaseFrameBuffer::SetWindowSize(int w, int h) } + void ProcessSDLWindowEvent(const SDL_WindowEvent &event) { switch (event.event) diff --git a/src/common/platform/posix/sdl/base_sysfb.h b/src/common/platform/posix/sdl/base_sysfb.h index 918b6801b..54e54de6d 100644 --- a/src/common/platform/posix/sdl/base_sysfb.h +++ b/src/common/platform/posix/sdl/base_sysfb.h @@ -1,7 +1,7 @@ #ifndef __POSIX_SDL_GL_SYSFB_H__ #define __POSIX_SDL_GL_SYSFB_H__ -#include +#include #include "v_video.h" @@ -26,4 +26,3 @@ protected: }; #endif // __POSIX_SDL_GL_SYSFB_H__ - diff --git a/src/common/platform/posix/sdl/hardware.cpp b/src/common/platform/posix/sdl/hardware.cpp index 73279ea8a..9b5b26978 100644 --- a/src/common/platform/posix/sdl/hardware.cpp +++ b/src/common/platform/posix/sdl/hardware.cpp @@ -4,6 +4,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 1998-2006 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -32,21 +33,20 @@ ** */ -#include +#include #include -#include "i_system.h" -#include "hardware.h" -#include "c_dispatch.h" -#include "v_text.h" -#include "m_argv.h" #include "c_console.h" +#include "c_dispatch.h" +#include "hardware.h" +#include "i_system.h" +#include "m_argv.h" #include "printf.h" +#include "v_text.h" IVideo *Video; - void I_ShutdownGraphics () { if (screen) diff --git a/src/common/platform/posix/sdl/i_gui.cpp b/src/common/platform/posix/sdl/i_gui.cpp index 9290f063f..3fa7b777b 100644 --- a/src/common/platform/posix/sdl/i_gui.cpp +++ b/src/common/platform/posix/sdl/i_gui.cpp @@ -3,6 +3,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 2008 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -33,7 +34,7 @@ #include -#include +#include #include "bitmap.h" #include "textures.h" diff --git a/src/common/platform/posix/sdl/i_input.cpp b/src/common/platform/posix/sdl/i_input.cpp index 5e92dda13..fa2b6c48a 100644 --- a/src/common/platform/posix/sdl/i_input.cpp +++ b/src/common/platform/posix/sdl/i_input.cpp @@ -1,8 +1,11 @@ /* ** i_input.cpp ** +** Handles input from keyboard, mouse, and joystick +** **--------------------------------------------------------------------------- ** Copyright 2005-2016 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -30,25 +33,23 @@ **--------------------------------------------------------------------------- ** */ -#include -#include -#include "i_input.h" -#include "c_cvars.h" -#include "dobject.h" -#include "m_argv.h" -#include "m_joy.h" -#include "v_video.h" -#include "d_gui.h" +#include +#include + #include "c_buttons.h" #include "c_console.h" -#include "c_dispatch.h" +#include "c_cvars.h" +#include "d_gui.h" #include "dikeys.h" -#include "utf8.h" -#include "keydef.h" -#include "i_interface.h" #include "engineerrors.h" +#include "i_input.h" #include "i_interface.h" +#include "keydef.h" +#include "m_haptics.h" +#include "m_joy.h" +#include "utf8.h" +#include "v_video.h" bool GUICapture; static bool NativeMouse = true; @@ -451,6 +452,10 @@ void MessagePump (const SDL_Event &sev) { event.data2 = sev.key.keysym.sym; } + SDL_Keymod kmod = SDL_GetModState(); + event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) | + ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) | + ((kmod & KMOD_ALT) ? GKM_ALT : 0); D_PostEvent (&event); } } @@ -607,6 +612,7 @@ void I_StartTic () I_CheckGUICapture (); I_CheckNativeMouse (); I_GetEvent (); + Joy_RumbleTick(); } void I_ProcessJoysticks (); diff --git a/src/common/platform/posix/sdl/i_joystick.cpp b/src/common/platform/posix/sdl/i_joystick.cpp index d37b4f3a6..112658578 100644 --- a/src/common/platform/posix/sdl/i_joystick.cpp +++ b/src/common/platform/posix/sdl/i_joystick.cpp @@ -1,8 +1,12 @@ /* ** i_joystick.cpp ** +** Handles sdl joysticks and gamepads +** **--------------------------------------------------------------------------- +** ** Copyright 2005-2016 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -27,20 +31,37 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ -#include -#include + +#include +#include +#include #include #include "basics.h" #include "cmdlib.h" - #include "d_eventbase.h" #include "i_input.h" #include "m_joy.h" +static const EAxisCodes ControllerAxisCodes[][2] = +{ + { AXIS_CODE_PAD_LTHUMB_RIGHT, AXIS_CODE_PAD_LTHUMB_LEFT }, + { AXIS_CODE_PAD_LTHUMB_DOWN, AXIS_CODE_PAD_LTHUMB_UP }, + { AXIS_CODE_PAD_RTHUMB_RIGHT, AXIS_CODE_PAD_RTHUMB_LEFT }, + { AXIS_CODE_PAD_RTHUMB_DOWN, AXIS_CODE_PAD_RTHUMB_UP }, + { AXIS_CODE_PAD_LTRIGGER, AXIS_CODE_NULL }, + { AXIS_CODE_PAD_RTRIGGER, AXIS_CODE_NULL } +}; + +#define HAPTICS 0b0001 +#define HAPTICS_TRIGGERS 0b0010 + +EXTERN_CVAR(Bool, use_joystick) + class SDLInputJoystick: public IJoystickConfig { public: @@ -49,6 +70,8 @@ public: InstanceID(SDL_JoystickGetDeviceInstanceID(DeviceIndex)), Multiplier(JOYSENSITIVITY_DEFAULT), Enabled(true), + Haptics(0), + HapticsStrength(JOYHAPSTRENGTH_DEFAULT), SettingsChanged(false) { if (SDL_IsGameController(DeviceIndex)) @@ -63,6 +86,7 @@ public: { NumAxes = SDL_CONTROLLER_AXIS_MAX; NumHats = 0; + Haptics = SDL_GameControllerHasRumble(Mapping) | SDL_GameControllerHasRumbleTriggers(Mapping) << 1; SetDefaultConfig(); } @@ -116,6 +140,25 @@ public: Multiplier = scale; } + bool HasHaptics() + { + return Haptics != 0; + } + + float GetHapticsStrength() + { + return HasHaptics() + ? HapticsStrength + : 0; + } + + void SetHapticsStrength(float strength) + { + if (!HasHaptics()) return; + SettingsChanged = true; + HapticsStrength = clamp(strength, 0.f, 2.f); + } + int GetNumAxes() { return NumAxes + NumHats*2; @@ -124,10 +167,6 @@ public: { return Axes[axis].DeadZone; } - EJoyAxis GetAxisMap(int axis) - { - return Axes[axis].GameAxis; - } const char *GetAxisName(int axis) { return Axes[axis].Name.GetChars(); @@ -156,11 +195,6 @@ public: SettingsChanged = true; Axes[axis].DeadZone = clamp(zone, 0.f, 1.f); } - void SetAxisMap(int axis, EJoyAxis gameaxis) - { - SettingsChanged = true; - Axes[axis].GameAxis = gameaxis; - } void SetAxisScale(int axis, float scale) { SettingsChanged = true; @@ -194,18 +228,16 @@ public: { return Multiplier == JOYSENSITIVITY_DEFAULT; } + bool IsHapticsStrengthDefault() + { + return HapticsStrength == JOYHAPSTRENGTH_DEFAULT; + } bool IsAxisDeadZoneDefault(int axis) { if(axis >= DefaultAxesCount) return Axes[axis].DeadZone == JOYDEADZONE_DEFAULT; return Axes[axis].DeadZone == DefaultAxes[axis].DeadZone; } - bool IsAxisMapDefault(int axis) - { - if(axis >= DefaultAxesCount) - return Axes[axis].GameAxis == JOYAXIS_None; - return Axes[axis].GameAxis == DefaultAxes[axis].GameAxis; - } bool IsAxisScaleDefault(int axis) { if(axis >= DefaultAxesCount) @@ -225,6 +257,29 @@ public: return Axes[axis].ResponseCurvePreset == DefaultAxes[axis].ResponseCurvePreset; } + void Rumble(float high_freq, float low_freq, float left_trig, float right_trig) + { + uint16_t duration_ms = -1; // turn on for max time (we'll turn it off later ourselves) + + if (Haptics & HAPTICS) + { + SDL_GameControllerRumble( + Mapping, + static_cast (0xffff * clamp(high_freq*HapticsStrength, 0.f, 1.f)), + static_cast (0xffff * clamp(low_freq*HapticsStrength, 0.f, 1.f)), + duration_ms); + } + + if (Haptics & HAPTICS_TRIGGERS) + { + SDL_GameControllerRumbleTriggers( + Mapping, + static_cast (0xffff * clamp(left_trig*HapticsStrength, 0.f, 1.f)), + static_cast (0xffff * clamp(right_trig*HapticsStrength, 0.f, 1.f)), + duration_ms); + } + } + void SetDefaultConfig() { if (Axes.size() == 0) @@ -259,7 +314,6 @@ public: if (i < DefaultAxesCount) { - Axes[i].GameAxis = DefaultAxes[i].GameAxis; Axes[i].DeadZone = DefaultAxes[i].DeadZone; Axes[i].Multiplier = DefaultAxes[i].Multiplier; Axes[i].DigitalThreshold = DefaultAxes[i].DigitalThreshold; @@ -268,7 +322,6 @@ public: } else { - Axes[i].GameAxis = JOYAXIS_None; Axes[i].DeadZone = JOYDEADZONE_DEFAULT; Axes[i].Multiplier = JOYSENSITIVITY_DEFAULT; Axes[i].DigitalThreshold = JOYTHRESH_DEFAULT; @@ -300,112 +353,200 @@ public: return id; } - void AddAxes(float axes[NUM_JOYAXIS]) + void AddAxes(float joyaxes[NUM_AXIS_CODES]) { // Add to game axes. for (int i = 0; i < GetNumAxes(); ++i) { - if(Axes[i].GameAxis != JOYAXIS_None) - axes[Axes[i].GameAxis] -= float(Axes[i].Value * Multiplier * Axes[i].Multiplier); + float axis_value = float(Axes[i].Value * Multiplier * Axes[i].Multiplier); + + int axis_code_pos = AXIS_CODE_NULL; + int axis_code_neg = AXIS_CODE_NULL; + int ret_code = AXIS_CODE_NULL; + + if (Mapping) + { + if (i < SDL_CONTROLLER_AXIS_MAX) + { + axis_code_pos = ControllerAxisCodes[i][0]; + axis_code_neg = ControllerAxisCodes[i][1]; + } + } + else + { + if (i < NUM_JOYAXISBUTTONS) + { + axis_code_pos = AXIS_CODE_JOY1_PLUS + (i * 2); + axis_code_neg = AXIS_CODE_JOY1_PLUS + (i * 2) + 1; + } + } + + if (axis_value > 0.0f) + { + ret_code = axis_code_pos; + } + else if (axis_value < 0.0f) + { + ret_code = axis_code_neg; + } + + if (ret_code != AXIS_CODE_NULL) + { + joyaxes[ret_code] += fabs(axis_value); + } } } - void ProcessInput() { + void ProcessGameControllerThumbstick(int index1, int index2, int base) + { + AxisInfo &axis1 = Axes[index1]; + AxisInfo &axis2 = Axes[index2]; + + uint8_t buttonstate; + double axisval1, axisval2; + + axisval1 = SDL_GameControllerGetAxis(Mapping, static_cast(index1)) / 32767.0; + axisval2 = SDL_GameControllerGetAxis(Mapping, static_cast(index2)) / 32767.0; + + Joy_ManageThumbstick( + &axisval1, &axisval2, + axis1.DeadZone, axis2.DeadZone, + axis1.DigitalThreshold, axis2.DigitalThreshold, + axis1.ResponseCurve, axis2.ResponseCurve, + &buttonstate + ); + + axis1.Value = float(axisval1); + axis2.Value = float(axisval2); + + // We store all four buttons in the first axis and ignore the second. + Joy_GenerateButtonEvents(axis1.ButtonValue, buttonstate, 4, base); + axis1.ButtonValue = buttonstate; + } + + void ProcessGameControllerTrigger(int index, int base) + { + AxisInfo &axis = Axes[index]; + + uint8_t buttonstate; + double axisval; + + axisval = SDL_GameControllerGetAxis(Mapping, static_cast(index)) / 32767.0; + axisval = Joy_ManageSingleAxis( + axisval, + axis.DeadZone, axis.DigitalThreshold, axis.ResponseCurve, + &buttonstate + ); + + axis.Value = float(axisval); + Joy_GenerateButtonEvents(axis.ButtonValue, buttonstate, 1, base); + axis.ButtonValue = buttonstate; + } + + void ProcessGameControllerAxes() + { + // Refactored to match more closely with XInput / raw PS2 on Windows + ProcessGameControllerThumbstick(SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, KEY_PAD_LTHUMB_RIGHT); + ProcessGameControllerThumbstick(SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY, KEY_PAD_RTHUMB_RIGHT); + ProcessGameControllerTrigger(SDL_CONTROLLER_AXIS_TRIGGERLEFT, KEY_PAD_LTRIGGER); + ProcessGameControllerTrigger(SDL_CONTROLLER_AXIS_TRIGGERRIGHT, KEY_PAD_RTRIGGER); + } + + void ProcessOldJoystickAxes() + { uint8_t buttonstate; + for (int i = 0; i < NumAxes; ++i) + { + buttonstate = 0; + + if (i < NUM_JOYAXISBUTTONS && (i > 2 || Axes.Size() == 1)) + { + AxisInfo &info = Axes[i]; + double axisval; + + axisval = SDL_JoystickGetAxis(Device, i) / 32767.0; + + info.Value = Joy_ManageSingleAxis( + axisval, + info.DeadZone, + info.DigitalThreshold, + info.ResponseCurve, + &buttonstate + ); + + Joy_GenerateButtonEvents(info.ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); + info.ButtonValue = buttonstate; + } + else if (i == 1) + { + AxisInfo &info_x = Axes[i - 1]; + AxisInfo &info_y = Axes[i]; + double axisval_x, axisval_y; + + axisval_x = SDL_JoystickGetAxis(Device, i - 1) / 32767.0; + axisval_y = SDL_JoystickGetAxis(Device, i) / 32767.0; + Joy_ManageThumbstick( + &axisval_x, &axisval_y, + info_x.DeadZone, info_y.DeadZone, + info_x.DigitalThreshold, info_y.DigitalThreshold, + info_x.ResponseCurve, info_y.ResponseCurve, + &buttonstate + ); + + info_x.Value = axisval_x; + info_y.Value = axisval_y; + + Joy_GenerateButtonEvents(info_x.ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS + (i-1)*2); + info_x.ButtonValue = buttonstate; + } + } + + // Map POV hats to buttons and axes. Why axes? Well apparently I have + // a gamepad where the left control stick is a POV hat (instead of the + // d-pad like you would expect, no that's pressure sensitive). Also + // KDE's joystick dialog maps them to axes as well. + for (int i = 0; i < NumHats; ++i) + { + AxisInfo &x = Axes[NumAxes + i*2]; + AxisInfo &y = Axes[NumAxes + i*2 + 1]; + + buttonstate = SDL_JoystickGetHat(Device, i); + + // If we're going to assume that we can pass SDL's value into + // Joy_GenerateButtonEvents then we might as well assume the format here. + if(buttonstate & 0x1) // Up + y.Value = -1.0; + else if(buttonstate & 0x4) // Down + y.Value = 1.0; + else + y.Value = 0.0; + if(buttonstate & 0x2) // Left + x.Value = 1.0; + else if(buttonstate & 0x8) // Right + x.Value = -1.0; + else + x.Value = 0.0; + + if (i < 4) + { + Joy_GenerateButtonEvents(x.ButtonValue, buttonstate, 4, KEY_JOYPOV1_UP + i*4); + x.ButtonValue = buttonstate; + } + } + } + + void ProcessInput() + { if (Mapping) { // GameController API available - - auto lastTriggerL = Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].DigitalThreshold; - auto lastTriggerR = Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].DigitalThreshold; - - for (auto i = 0; i < SDL_CONTROLLER_AXIS_MAX && i < NumAxes; ++i) - { - buttonstate = 0; - - Axes[i].Value = SDL_GameControllerGetAxis(Mapping, static_cast(i))/32767.0; - Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate); - Axes[i].Value = Joy_ApplyResponseCurveBezier(Axes[i].ResponseCurve, Axes[i].Value); - } - - auto currTriggerL = Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].DigitalThreshold; - auto currTriggerR = Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].DigitalThreshold; - - if (lastTriggerL != currTriggerL) Joy_GenerateButtonEvent(currTriggerL, KEY_PAD_LTRIGGER); - if (lastTriggerR != currTriggerR) Joy_GenerateButtonEvent(currTriggerR, KEY_PAD_RTRIGGER); - - // todo: right stick - buttonstate = Joy_XYAxesToButtons( - abs(Axes[0].Value) < Axes[0].DigitalThreshold ? 0 : Axes[0].Value, - abs(Axes[1].Value) < Axes[1].DigitalThreshold ? 0 : Axes[1].Value - ); - Joy_GenerateButtonEvents(Axes[0].ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); - Axes[0].ButtonValue = buttonstate; + ProcessGameControllerAxes(); } else { // Joystick API fallback - - for (int i = 0; i < NumAxes; ++i) - { - buttonstate = 0; - - Axes[i].Value = SDL_JoystickGetAxis(Device, i)/32767.0; - Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate); - Axes[i].Value = Joy_ApplyResponseCurveBezier(Axes[i].ResponseCurve, Axes[i].Value); - - // Map button to axis - // X and Y are handled differently so if we have 2 or more axes then we'll use that code instead. - if (NumAxes == 1 || (i >= 2 && i < NUM_JOYAXISBUTTONS)) - { - Joy_GenerateButtonEvents(Axes[i].ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); - Axes[i].ButtonValue = buttonstate; - } - } - - if(NumAxes > 1) - { - buttonstate = Joy_XYAxesToButtons( - abs(Axes[0].Value) < Axes[0].DigitalThreshold ? 0 : Axes[0].Value, - abs(Axes[1].Value) < Axes[1].DigitalThreshold ? 0 : Axes[1].Value - ); - Joy_GenerateButtonEvents(Axes[0].ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); - Axes[0].ButtonValue = buttonstate; - } - - // Map POV hats to buttons and axes. Why axes? Well apparently I have - // a gamepad where the left control stick is a POV hat (instead of the - // d-pad like you would expect, no that's pressure sensitive). Also - // KDE's joystick dialog maps them to axes as well. - for (int i = 0; i < NumHats; ++i) - { - AxisInfo &x = Axes[NumAxes + i*2]; - AxisInfo &y = Axes[NumAxes + i*2 + 1]; - - buttonstate = SDL_JoystickGetHat(Device, i); - - // If we're going to assume that we can pass SDL's value into - // Joy_GenerateButtonEvents then we might as well assume the format here. - if(buttonstate & 0x1) // Up - y.Value = -1.0; - else if(buttonstate & 0x4) // Down - y.Value = 1.0; - else - y.Value = 0.0; - if(buttonstate & 0x2) // Left - x.Value = 1.0; - else if(buttonstate & 0x8) // Right - x.Value = -1.0; - else - x.Value = 0.0; - - if(i < 4) - { - Joy_GenerateButtonEvents(x.ButtonValue, buttonstate, 4, KEY_JOYPOV1_UP + i*4); - x.ButtonValue = buttonstate; - } - } + ProcessOldJoystickAxes(); } } @@ -418,14 +559,12 @@ protected: float DigitalThreshold; EJoyCurve ResponseCurvePreset; CubicBezier ResponseCurve; - EJoyAxis GameAxis; double Value; uint8_t ButtonValue; }; struct DefaultAxisConfig { float DeadZone; - EJoyAxis GameAxis; float Multiplier; float DigitalThreshold; EJoyCurve ResponseCurvePreset; @@ -445,6 +584,8 @@ protected: TArray Axes; int NumAxes; int NumHats; + int Haptics; + float HapticsStrength; bool SettingsChanged; friend class SDLInputJoystickManager; @@ -452,21 +593,21 @@ protected: // [Nash 4 Feb 2024] seems like on Linux, the third axis is actually the Left Trigger, resulting in the player uncontrollably looking upwards. const SDLInputJoystick::DefaultAxisConfig SDLInputJoystick::DefaultJoystickAxes[5] = { - {JOYDEADZONE_DEFAULT, JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, - {JOYDEADZONE_DEFAULT, JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}, - {JOYDEADZONE_DEFAULT, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_DEFAULT, JOYCURVE_DEFAULT}, - {JOYDEADZONE_DEFAULT, JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, - {JOYDEADZONE_DEFAULT, JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT} + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_DEFAULT, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT} }; // Defaults if we have access to the GameController API for this device const SDLInputJoystick::DefaultAxisConfig SDLInputJoystick::DefaultControllerAxes[6] = { - {JOYDEADZONE_DEFAULT, JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, - {JOYDEADZONE_DEFAULT, JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}, - {JOYDEADZONE_DEFAULT, JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, - {JOYDEADZONE_DEFAULT, JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}, - {JOYDEADZONE_DEFAULT, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT}, - {JOYDEADZONE_DEFAULT, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT}, + {JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT}, }; class SDLInputJoystickManager @@ -490,7 +631,7 @@ public: } } - void AddAxes(float axes[NUM_JOYAXIS]) + void AddAxes(float axes[NUM_AXIS_CODES]) { for(unsigned int i = 0;i < Joysticks.Size();i++) Joysticks[i]->AddAxes(axes); @@ -505,6 +646,16 @@ public: } } + void Rumble(float high_freq, float low_freq, float left_trig, float right_trig) + { + for(unsigned int i = 0;i < Joysticks.Size();i++) + { + if (Joysticks[i]->Enabled) { + Joysticks[i]->Rumble(high_freq, low_freq, left_trig, right_trig); + } + } + } + void ProcessInput() const { for(unsigned int i = 0;i < Joysticks.Size();++i) @@ -532,7 +683,7 @@ static SDLInputJoystickManager *JoystickManager; void I_StartupJoysticks() { #ifndef NO_SDL_JOYSTICK - if(SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) >= 0) + if(SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER) >= 0) JoystickManager = new SDLInputJoystickManager(); #endif } @@ -553,18 +704,26 @@ void I_GetJoysticks(TArray &sticks) JoystickManager->GetDevices(sticks); } -void I_GetAxes(float axes[NUM_JOYAXIS]) +void I_GetAxes(float axes[NUM_AXIS_CODES]) { - for (int i = 0; i < NUM_JOYAXIS; ++i) + for (int i = 0; i < NUM_AXIS_CODES; ++i) { - axes[i] = 0; + axes[i] = 0.0f; } + if (use_joystick && JoystickManager) { JoystickManager->AddAxes(axes); } } +void I_Rumble(double high_freq, double low_freq, double left_trig, double right_trig) +{ + if (!use_joystick) return; + + JoystickManager->Rumble(high_freq, low_freq, left_trig, right_trig); +} + void I_ProcessJoysticks() { if (use_joystick && JoystickManager) diff --git a/src/common/platform/posix/sdl/i_main.cpp b/src/common/platform/posix/sdl/i_main.cpp index ef8784f64..e892a5932 100644 --- a/src/common/platform/posix/sdl/i_main.cpp +++ b/src/common/platform/posix/sdl/i_main.cpp @@ -4,6 +4,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 1998-2007 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -34,24 +35,24 @@ // HEADER FILES ------------------------------------------------------------ -#include -#include -#include -#include -#include +#include +#include #include +#include +#include +#include #include #include +#include -#include "engineerrors.h" -#include "m_argv.h" #include "c_console.h" -#include "version.h" #include "cmdlib.h" #include "engineerrors.h" -#include "i_system.h" #include "i_interface.h" +#include "i_system.h" +#include "m_argv.h" #include "printf.h" +#include "version.h" extern "C" int cc_install_handlers(int, char**, int, int*, const char*, int(*)(char*, char*)); extern bool RunningAsTool; @@ -65,6 +66,7 @@ void Linux_I_FatalError(const char* errortext); #endif int GameMain(); +void SignalHandler(int signal); FString sys_ostype; FArgs *Args; @@ -180,6 +182,11 @@ int I_GameMain(int argc, char** argv) { InitCrashReporter(argc, argv); SetEffectiveUser(); + signal(SIGINT, SignalHandler); + signal(SIGTERM, SignalHandler); + // signal(SIGHUP, SignalHandler); + // signal(SIGQUIT, SignalHandler); + InitLocale(); printf(GAMENAME" %s - %s - SDL version\nCompiled on %s\n", GetVersionString(), GetGitTime(), __DATE__); diff --git a/src/common/platform/posix/sdl/i_system.cpp b/src/common/platform/posix/sdl/i_system.cpp index 5567b8a65..4f63aa86a 100644 --- a/src/common/platform/posix/sdl/i_system.cpp +++ b/src/common/platform/posix/sdl/i_system.cpp @@ -5,6 +5,7 @@ **--------------------------------------------------------------------------- ** Copyright 1999-2016 Randy Heit ** Copyright 2019-2020 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -34,18 +35,18 @@ */ #include -#include -#include -#include -#include #include +#include +#include +#include +#include #include -#include #include +#include +#include #include #include -#include #ifdef __linux__ #include @@ -58,18 +59,20 @@ #include #endif -#include +#include -#include "version.h" -#include "cmdlib.h" -#include "m_argv.h" -#include "i_sound.h" -#include "i_interface.h" -#include "v_font.h" #include "c_cvars.h" #include "palutil.h" #include "printf.h" +#include "cmdlib.h" +#include "i_interface.h" +#include "i_sound.h" #include "launcherwindow.h" +#include "m_argv.h" +#include "palutil.h" +#include "printf.h" +#include "v_font.h" +#include "version.h" #ifndef NO_GTK bool I_GtkAvailable (); diff --git a/src/common/platform/posix/sdl/st_start.cpp b/src/common/platform/posix/sdl/st_start.cpp new file mode 100644 index 000000000..e9d2de706 --- /dev/null +++ b/src/common/platform/posix/sdl/st_start.cpp @@ -0,0 +1,342 @@ +/* +** st_start.cpp +** Handles the startup screen. +** +**--------------------------------------------------------------------------- +** Copyright 2006-2007 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include +#include +#include +#include +#include + +#include "basics.h" +#include "st_start.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +class FTTYStartupScreen : public FStartupScreen +{ + public: + FTTYStartupScreen(int max_progress); + ~FTTYStartupScreen(); + + void Progress(int amount = 1) override; + + void NetInit(const char* message, bool host) override; + void NetMessage(const char* message) override; + void NetConnect(int client, const char* name, unsigned flags, int status) override; + void NetUpdate(int client, int status) override; + void NetDisconnect(int client) override; + void NetProgress(int cur, int limit) override; + void NetDone() override; + void NetClose() override; + bool ShouldStartNet() override; + int GetNetKickClient() override; + int GetNetBanClient() override; + bool NetLoop(bool (*loopCallback)(void*), void* data) override; + + protected: + bool DidNetInit; + int NetMaxPos, NetCurPos; + const char *TheNetMessage; + termios OldTermIOS; +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +extern void RedrawProgressBar(int CurPos, int MaxPos); +extern void CleanProgressBar(); +extern volatile sig_atomic_t gameloop_abort; + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +static const char SpinnyProgressChars[4] = { '|', '/', '-', '\\' }; + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// FStartupScreen :: CreateInstance +// +// Initializes the startup screen for the detected game. +// Sets the size of the progress bar and displays the startup screen. +// +//========================================================================== + +FStartupScreen *FStartupScreen::CreateInstance(int max_progress) +{ + return new FTTYStartupScreen(max_progress); +} + +//=========================================================================== +// +// FTTYStartupScreen Constructor +// +// Sets the size of the progress bar and displays the startup screen. +// +//=========================================================================== + +FTTYStartupScreen::FTTYStartupScreen(int max_progress) + : FStartupScreen(max_progress) +{ + DidNetInit = false; + NetMaxPos = 0; + NetCurPos = 0; + TheNetMessage = NULL; +} + +//=========================================================================== +// +// FTTYStartupScreen Destructor +// +// Called just before entering graphics mode to deconstruct the startup +// screen. +// +//=========================================================================== + +FTTYStartupScreen::~FTTYStartupScreen() +{ + NetDone(); // Just in case it wasn't called yet and needs to be. +} + +//=========================================================================== +// +// FTTYStartupScreen :: Progress +// +//=========================================================================== + +void FTTYStartupScreen::Progress(int advance) +{ + CurPos = min(CurPos + advance, MaxPos); + RedrawProgressBar(CurPos, MaxPos); +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetInit +// +// Sets stdin for unbuffered I/O, displays the given message, and shows +// a progress meter. +// +//=========================================================================== + +void FTTYStartupScreen::NetInit(const char* message, bool host) +{ + if (!DidNetInit) + { + termios rawtermios; + + CleanProgressBar(); + fprintf (stderr, "Press 'Q' to abort network game synchronization."); + // Set stdin to raw mode so we can get keypresses in ST_CheckNetAbort() + // immediately without waiting for an EOL. + tcgetattr (STDIN_FILENO, &OldTermIOS); + rawtermios = OldTermIOS; + rawtermios.c_lflag &= ~(ICANON | ECHO); + tcsetattr (STDIN_FILENO, TCSANOW, &rawtermios); + DidNetInit = true; + } + fprintf(stderr, "\n%s.", message); + fflush (stderr); + TheNetMessage = message; + NetCurPos = 0; +} + +void FTTYStartupScreen::NetMessage(const char* message) +{ + TheNetMessage = message; +} + +void FTTYStartupScreen::NetConnect(int client, const char* name, unsigned flags, int status) +{ + +} + +void FTTYStartupScreen::NetUpdate(int client, int status) +{ + +} + +void FTTYStartupScreen::NetDisconnect(int client) +{ + +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetProgress +// +// Sets the network progress meter. +// +//=========================================================================== + +void FTTYStartupScreen::NetProgress(int cur, int limit) +{ + int i; + + NetMaxPos = limit; + NetCurPos = cur; + if (NetMaxPos == 0) + { + // Spinny-type progress meter, because we're a guest waiting for the host. + fprintf(stderr, "\r%s: %c", TheNetMessage, SpinnyProgressChars[NetCurPos & 3]); + fflush(stderr); + } + else if (NetMaxPos > 1) + { + // Dotty-type progress meter. + fprintf(stderr, "\r%s: ", TheNetMessage); + for (i = 0; i < NetCurPos; ++i) + { + fputc('.', stderr); + } + fprintf(stderr, "%*c[%2d/%2d]", NetMaxPos + 1 - NetCurPos, ' ', NetCurPos, NetMaxPos); + fflush(stderr); + } +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetDone +// +// Restores the old stdin tty settings. +// +//=========================================================================== + +void FTTYStartupScreen::NetDone() +{ + CurPos = MaxPos; + CleanProgressBar(); + // Restore stdin settings + if (DidNetInit) + { + tcsetattr (STDIN_FILENO, TCSANOW, &OldTermIOS); + printf ("\n"); + DidNetInit = false; + } +} + +void FTTYStartupScreen::NetClose() +{ + // TODO: Implement this +} + +bool FTTYStartupScreen::ShouldStartNet() +{ + return false; +} + +int FTTYStartupScreen::GetNetKickClient() +{ + return -1; +} + +int FTTYStartupScreen::GetNetBanClient() +{ + return -1; +} + +//=========================================================================== +// +// FTTYStartupScreen :: NetLoop +// +// The timer_callback function is called at least two times per second +// and passed the userdata value. It should return true to stop the loop and +// return control to the caller or false to continue the loop. +// +// ST_NetLoop will return true if the loop was halted by the callback and +// false if the loop was halted because the user wants to abort the +// network synchronization. +// +//=========================================================================== + +bool FTTYStartupScreen::NetLoop(bool (*loopCallback)(void *), void *data) +{ + fd_set rfds; + struct timeval tv; + int retval; + char k; + bool stdin_eof = false; + + while (!gameloop_abort) + { + // Don't flood the network with packets on startup. + tv.tv_sec = 0; + tv.tv_usec = 500000; + + FD_ZERO (&rfds); + if (!stdin_eof) + { + FD_SET (STDIN_FILENO, &rfds); + } + + retval = select (1, &rfds, NULL, NULL, &tv); + + if (retval == -1) + { + // Error + } + else if (retval == 0) + { + if (loopCallback (data)) + { + fputc ('\n', stderr); + return true; + } + } + else + { + ssize_t amt = read (STDIN_FILENO, &k, 1); // Check input on stdin + if (amt == 0) + { + // EOF. Stop reading + stdin_eof = true; + } + else if (amt == 1) + { + if (k == 'q' || k == 'Q') + { + break; + } + } + } + } + + fprintf (stderr, "\nNetwork game synchronization aborted.\n"); + return false; +} + diff --git a/src/common/platform/win32/i_dijoy.cpp b/src/common/platform/win32/i_dijoy.cpp index 07c16eb88..523e18391 100644 --- a/src/common/platform/win32/i_dijoy.cpp +++ b/src/common/platform/win32/i_dijoy.cpp @@ -1,8 +1,12 @@ /* +** i_dijoy.cpp ** +** Handles direct input joysticks ** **--------------------------------------------------------------------------- +** ** Copyright 2005-2016 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -27,13 +31,13 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ // HEADER FILES ------------------------------------------------------------ -#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #define DIRECTINPUT_VERSION 0x800 @@ -42,19 +46,19 @@ #ifndef __GNUC__ #include #endif + #include #include #include "i_input.h" #include "d_eventbase.h" - #include "gameconfigfile.h" #include "cmdlib.h" #include "v_text.h" #include "m_argv.h" #include "keydef.h" #include "printf.h" - +#include "m_joy.h" #include "i_mainwindow.h" #define SAFE_RELEASE(x) { if (x != NULL) { x->Release(); x = NULL; } } @@ -148,7 +152,6 @@ public: // MACROS ------------------------------------------------------------------ - // TYPES ------------------------------------------------------------------- class FDInputJoystick : public FInputDevice, IJoystickConfig @@ -159,16 +162,19 @@ public: bool GetDevice(); void ProcessInput(); - void AddAxes(float axes[NUM_JOYAXIS]); + void AddAxes(float axes[NUM_AXIS_CODES]); // IJoystickConfig interface FString GetName(); float GetSensitivity(); virtual void SetSensitivity(float scale); + bool HasHaptics(); + float GetHapticsStrength(); + void SetHapticsStrength(float strength); + int GetNumAxes(); float GetAxisDeadZone(int axis); - EJoyAxis GetAxisMap(int axis); const char *GetAxisName(int axis); float GetAxisScale(int axis); float GetAxisDigitalThreshold(int axis); @@ -176,15 +182,14 @@ public: float GetAxisResponseCurvePoint(int axis, int point); void SetAxisDeadZone(int axis, float deadzone); - void SetAxisMap(int axis, EJoyAxis gameaxis); void SetAxisScale(int axis, float scale); void SetAxisDigitalThreshold(int axis, float threshold); void SetAxisResponseCurve(int axis, EJoyCurve preset); void SetAxisResponseCurvePoint(int axis, int point, float value); bool IsSensitivityDefault(); + bool IsHapticsStrengthDefault(); bool IsAxisDeadZoneDefault(int axis); - bool IsAxisMapDefault(int axis); bool IsAxisScaleDefault(int axis); bool IsAxisDigitalThresholdDefault(int axis); bool IsAxisResponseCurveDefault(int axis); @@ -213,7 +218,6 @@ protected: float DigitalThreshold, DefaultDigitalThreshold; EJoyCurve ResponseCurvePreset, DefaultResponseCurvePreset; CubicBezier ResponseCurve; - EJoyAxis GameAxis, DefaultGameAxis; uint8_t ButtonValue; }; struct ButtonInfo @@ -256,7 +260,7 @@ public: bool GetDevice(); void ProcessInput(); - void AddAxes(float axes[NUM_JOYAXIS]); + void AddAxes(float axes[NUM_AXIS_CODES]); void GetDevices(TArray &sticks); IJoystickConfig *Rescan(); @@ -457,33 +461,58 @@ void FDInputJoystick::ProcessInput() // into button presses. for (i = 0; i < Axes.Size(); ++i) { - AxisInfo *info = &Axes[i]; - LONG value = *(LONG *)(state + info->Ofs); - double axisval; uint8_t buttonstate = 0; - // Scale to [-1.0, 1.0] - axisval = (value - info->Min) * 2.0 / (info->Max - info->Min) - 1.0; - // Cancel out dead zone - axisval = Joy_RemoveDeadZone(axisval, info->DeadZone, &buttonstate); - axisval = Joy_ApplyResponseCurveBezier(info->ResponseCurve, axisval); - info->Value = float(axisval); if (i < NUM_JOYAXISBUTTONS && (i > 2 || Axes.Size() == 1)) { - if (abs(axisval) < info->DigitalThreshold) buttonstate = 0; + AxisInfo *info = &Axes[i]; + LONG value = *(LONG *)(state + info->Ofs); + double axisval; + + // Scale to [-1.0, 1.0] + axisval = (value - info->Min) * 2.0 / (info->Max - info->Min) - 1.0; + + // Cancel out dead zone + axisval = Joy_ManageSingleAxis( + axisval, + info->DeadZone, + info->DigitalThreshold, + info->ResponseCurve, + &buttonstate + ); + Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); + info->ButtonValue = buttonstate; } else if (i == 1) { // Since we sorted the axes, we know that the first two are definitely X and Y. - // They are probably a single stick, so use angular position to determine buttons. - buttonstate = Joy_XYAxesToButtons( - (abs(Axes[0].Value) < Axes[0].DigitalThreshold) ? 0 : Axes[0].Value, - (abs(axisval) < info->DigitalThreshold) ? 0 : axisval + // They are probably a single stick, so treat them as one. + AxisInfo *info_x = &Axes[i - 1]; + AxisInfo *info_y = &Axes[i]; + + LONG value_x = *(LONG *)(state + info_x->Ofs); + LONG value_y = *(LONG *)(state + info_y->Ofs); + + double axisval_x, axisval_y; + + // Scale to [-1.0, 1.0] + axisval_x = (value_x - info_x->Min) * 2.0 / (info_x->Max - info_x->Min) - 1.0; + axisval_y = (value_y - info_y->Min) * 2.0 / (info_y->Max - info_y->Min) - 1.0; + + // Cancel out dead zone + Joy_ManageThumbstick( + &axisval_x, &axisval_y, + info_x->DeadZone, info_y->DeadZone, + info_x->DigitalThreshold, info_y->DigitalThreshold, + info_x->ResponseCurve, info_y->ResponseCurve, + &buttonstate ); - Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); + + // We store all four buttons in the first axis and ignore the second. + Joy_GenerateButtonEvents(info_x->ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS + (i-1)*2); + info_x->ButtonValue = buttonstate; } - info->ButtonValue = buttonstate; } // Compare button states and generate events for buttons that have changed. @@ -529,12 +558,30 @@ void FDInputJoystick::ProcessInput() // //=========================================================================== -void FDInputJoystick::AddAxes(float axes[NUM_JOYAXIS]) +void FDInputJoystick::AddAxes(float axes[NUM_AXIS_CODES]) { for (unsigned i = 0; i < Axes.Size(); ++i) { // Add to the game axis. - axes[Axes[i].GameAxis] -= float(Axes[i].Value * Multiplier * Axes[i].Multiplier); + float axis_value = float(Axes[i].Value * Multiplier * Axes[i].Multiplier); + int code = AXIS_CODE_NULL; + + if (i < NUM_JOYAXISBUTTONS) + { + if (axis_value > 0.0f) + { + code = AXIS_CODE_JOY1_PLUS + (i * 2); + } + else if (axis_value < 0.0f) + { + code = AXIS_CODE_JOY1_PLUS + (i * 2) + 1; + } + } + + if (code != AXIS_CODE_NULL) + { + axes[code] += fabs(axis_value); + } } } @@ -610,7 +657,6 @@ BOOL CALLBACK FDInputJoystick::EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpd info.Ofs = 0; info.Min = diprg.lMin; info.Max = diprg.lMax; - info.GameAxis = JOYAXIS_None; info.Value = 0; info.ButtonValue = 0; joy->Axes.Push(info); @@ -783,7 +829,6 @@ void FDInputJoystick::SetDefaultConfig() { Axes[i].DeadZone = JOYDEADZONE_DEFAULT; Axes[i].Multiplier = JOYSENSITIVITY_DEFAULT; - Axes[i].GameAxis = JOYAXIS_None; Axes[i].DigitalThreshold = JOYTHRESH_DEFAULT; Axes[i].ResponseCurvePreset = JOYCURVE_DEFAULT; Axes[i].ResponseCurve = JOYCURVE[JOYCURVE_DEFAULT]; @@ -797,34 +842,24 @@ void FDInputJoystick::SetDefaultConfig() // Two axes? Horizontal is yaw and vertical is forward. if (Axes.Size() == 2) { - Axes[0].GameAxis = JOYAXIS_Yaw; Axes[0].DigitalThreshold = JOYTHRESH_STICK_X; - - Axes[1].GameAxis = JOYAXIS_Forward; Axes[1].DigitalThreshold = JOYTHRESH_STICK_Y; } // Three axes? First two are movement, third is yaw. else if (Axes.Size() >= 3) { - Axes[0].GameAxis = JOYAXIS_Side; Axes[0].DigitalThreshold = JOYTHRESH_STICK_X; - - Axes[1].GameAxis = JOYAXIS_Forward; Axes[1].DigitalThreshold = JOYTHRESH_STICK_Y; - - Axes[2].GameAxis = JOYAXIS_Yaw; Axes[2].DigitalThreshold = JOYTHRESH_STICK_X; // Four axes? First two are movement, last two are looking around. if (Axes.Size() >= 4) { - Axes[3].GameAxis = JOYAXIS_Pitch; // Axes[3].Multiplier = 0.75f; Axes[3].DigitalThreshold = JOYTHRESH_STICK_Y; // Five axes? Use the fifth one for moving up and down. if (Axes.Size() >= 5) { - Axes[4].GameAxis = JOYAXIS_Up; Axes[4].DigitalThreshold = JOYTHRESH_STICK_Y; } } @@ -837,7 +872,6 @@ void FDInputJoystick::SetDefaultConfig() { Axes[i].DefaultDeadZone = Axes[i].DeadZone; Axes[i].DefaultMultiplier = Axes[i].Multiplier; - Axes[i].DefaultGameAxis = Axes[i].GameAxis; Axes[i].DefaultDigitalThreshold = Axes[i].DigitalThreshold; Axes[i].DefaultResponseCurvePreset = Axes[i].ResponseCurvePreset; } @@ -887,6 +921,50 @@ bool FDInputJoystick::IsSensitivityDefault() return Multiplier == JOYSENSITIVITY_DEFAULT; } +//=========================================================================== +// +// FDInputJoystick :: HasHaptics +// +//=========================================================================== + +bool FDInputJoystick::HasHaptics() +{ + return false; +} + +//=========================================================================== +// +// FDInputJoystick :: GetHapticsStrength +// +//=========================================================================== + +float FDInputJoystick::GetHapticsStrength() +{ + return JOYHAPSTRENGTH_DEFAULT; +} + +//=========================================================================== +// +// FDInputJoystick :: SetHapticsStrength +// +//=========================================================================== + +void FDInputJoystick::SetHapticsStrength(float strength) +{ + // nope +} + +//=========================================================================== +// +// FDInputJoystick :: IsHapticsStrengthDefault +// +//=========================================================================== + +bool FDInputJoystick::IsHapticsStrengthDefault() +{ + return true; +} + //=========================================================================== // // FDInputJoystick :: GetNumAxes @@ -913,21 +991,6 @@ float FDInputJoystick::GetAxisDeadZone(int axis) return Axes[axis].DeadZone; } -//=========================================================================== -// -// FDInputJoystick :: GetAxisMap -// -//=========================================================================== - -EJoyAxis FDInputJoystick::GetAxisMap(int axis) -{ - if (unsigned(axis) >= Axes.Size()) - { - return JOYAXIS_None; - } - return Axes[axis].GameAxis; -} - //=========================================================================== // // FDInputJoystick :: GetAxisName @@ -1017,20 +1080,6 @@ void FDInputJoystick::SetAxisDeadZone(int axis, float deadzone) } } -//=========================================================================== -// -// FDInputJoystick :: SetAxisMap -// -//=========================================================================== - -void FDInputJoystick::SetAxisMap(int axis, EJoyAxis gameaxis) -{ - if (unsigned(axis) < Axes.Size()) - { - Axes[axis].GameAxis = (unsigned(gameaxis) < NUM_JOYAXIS) ? gameaxis : JOYAXIS_None; - } -} - //=========================================================================== // // FDInputJoystick :: SetAxisScale @@ -1173,21 +1222,6 @@ void FDInputJoystick::SetEnabled(bool enabled) Enabled = enabled; } -//=========================================================================== -// -// FDInputJoystick :: IsAxisMapDefault -// -//=========================================================================== - -bool FDInputJoystick::IsAxisMapDefault(int axis) -{ - if (unsigned(axis) < Axes.Size()) - { - return Axes[axis].GameAxis == Axes[axis].DefaultGameAxis; - } - return true; -} - //=========================================================================== // // FDInputJoystickManager - Constructor @@ -1255,7 +1289,7 @@ void FDInputJoystickManager::ProcessInput() // //=========================================================================== -void FDInputJoystickManager :: AddAxes(float axes[NUM_JOYAXIS]) +void FDInputJoystickManager :: AddAxes(float axes[NUM_AXIS_CODES]) { for (unsigned i = 0; i < Devices.Size(); ++i) { diff --git a/src/common/platform/win32/i_input.cpp b/src/common/platform/win32/i_input.cpp index 4fee4e11a..0d9a18759 100644 --- a/src/common/platform/win32/i_input.cpp +++ b/src/common/platform/win32/i_input.cpp @@ -1,9 +1,11 @@ /* ** i_input.cpp +** ** Handles input from keyboard, mouse, and joystick ** **--------------------------------------------------------------------------- ** Copyright 1998-2009 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -56,7 +58,6 @@ #define GET_RAWINPUT_CODE_WPARAM(wParam) ((wParam) & 0xff) #endif - #include "c_dispatch.h" #include "m_argv.h" #include "i_input.h" @@ -76,13 +77,13 @@ #include "c_buttons.h" #include "cmdlib.h" #include "i_mainwindow.h" +#include "m_haptics.h" // Compensate for w32api's lack #ifndef GET_XBUTTON_WPARAM #define GET_XBUTTON_WPARAM(wParam) (HIWORD(wParam)) #endif - #ifdef _DEBUG #define INGAME_PRIORITY_CLASS NORMAL_PRIORITY_CLASS #else @@ -92,7 +93,6 @@ FJoystickCollection *JoyDevices[NUM_JOYDEVICES]; - extern HINSTANCE g_hInst; bool GUICapture; @@ -130,7 +130,6 @@ bool win32EnableInput = true; EXTERN_CVAR(Bool, i_pauseinbackground); - CVAR (Bool, k_allowfullscreentoggle, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) static void I_CheckGUICapture () @@ -393,7 +392,6 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } } - switch (message) { case WM_DESTROY: @@ -443,11 +441,6 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; case WM_SYSKEYDOWN: - // Pressing Alt+Enter can toggle between fullscreen and windowed. - if (wParam == VK_RETURN && k_allowfullscreentoggle && !(lParam & 0x40000000)) - { - ToggleFullscreen = !ToggleFullscreen; - } // Pressing Alt+F4 quits the program. if (wParam == VK_F4 && !(lParam & 0x40000000)) { @@ -554,7 +547,6 @@ bool I_InitInput (void *hwnd) return TRUE; } - // Free all input resources void I_ShutdownInput () { @@ -604,7 +596,6 @@ void I_GetWindowEvent() } } - void I_GetEvent () { I_GetWindowEvent(); @@ -630,6 +621,7 @@ void I_StartTic () EventHandlerResultForNativeMouse = sysCallbacks.WantNativeMouse && sysCallbacks.WantNativeMouse(); I_CheckNativeMouse (false, EventHandlerResultForNativeMouse); I_GetEvent (); + Joy_RumbleTick(); } // @@ -649,14 +641,15 @@ void I_StartFrame () } } -void I_GetAxes(float axes[NUM_JOYAXIS]) +void I_GetAxes(float axes[NUM_AXIS_CODES]) { int i; - for (i = 0; i < NUM_JOYAXIS; ++i) + for (i = 0; i < NUM_AXIS_CODES; ++i) { - axes[i] = 0; + axes[i] = 0.0f; } + if (use_joystick) { for (i = 0; i < NUM_JOYDEVICES; ++i) diff --git a/src/common/platform/win32/i_input.h b/src/common/platform/win32/i_input.h index 553a1cd21..21ce81a45 100644 --- a/src/common/platform/win32/i_input.h +++ b/src/common/platform/win32/i_input.h @@ -120,7 +120,7 @@ protected: class FJoystickCollection : public FInputDevice { public: - virtual void AddAxes(float axes[NUM_JOYAXIS]) = 0; + virtual void AddAxes(float axes[NUM_AXIS_CODES]) = 0; virtual void GetDevices(TArray &sticks) = 0; virtual IJoystickConfig *Rescan() = 0; }; diff --git a/src/common/platform/win32/i_keyboard.cpp b/src/common/platform/win32/i_keyboard.cpp index 24d5fd586..bad154dd7 100644 --- a/src/common/platform/win32/i_keyboard.cpp +++ b/src/common/platform/win32/i_keyboard.cpp @@ -279,7 +279,9 @@ void FKeyboard::PostKeyEvent(int key, INTBOOL down, bool foreground) ev.data1 = key; ev.data2 = Convert[key]; ev.data3 = 0; - if (CheckKey(DIK_LSHIFT) || CheckKey(DIK_RSHIFT)) ev.data3 |= 1; + if (CheckKey(DIK_LSHIFT) || CheckKey(DIK_RSHIFT)) ev.data3 |= GKM_SHIFT; + if (CheckKey(DIK_LCONTROL) || CheckKey(DIK_RCONTROL)) ev.data3 |= GKM_CTRL; + if (CheckKey(DIK_LALT) || CheckKey(DIK_RALT)) ev.data3 |= GKM_ALT; D_PostEvent(&ev); } diff --git a/src/common/platform/win32/i_main.cpp b/src/common/platform/win32/i_main.cpp index b456edab7..889f63b3b 100644 --- a/src/common/platform/win32/i_main.cpp +++ b/src/common/platform/win32/i_main.cpp @@ -4,6 +4,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 1998-2009 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -41,6 +42,8 @@ #include #include #include +#include +#include #include #include @@ -94,6 +97,7 @@ FString GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool cre void DestroyCustomCursor(); int GameMain(); +void SignalHandler(int signal); extern UINT TimerPeriod; extern bool RunningAsTool; diff --git a/src/common/platform/win32/i_rawps2.cpp b/src/common/platform/win32/i_rawps2.cpp index 544c498b4..23ff6f791 100644 --- a/src/common/platform/win32/i_rawps2.cpp +++ b/src/common/platform/win32/i_rawps2.cpp @@ -2,6 +2,7 @@ ** ** **--------------------------------------------------------------------------- +** ** Copyright 2005-2016 Randy Heit ** All rights reserved. ** @@ -27,6 +28,7 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ @@ -92,7 +94,7 @@ public: ~FRawPS2Controller(); bool ProcessInput(RAWHID *raw, int code); - void AddAxes(float axes[NUM_JOYAXIS]); + void AddAxes(float axes[NUM_AXIS_CODES]); bool IsConnected() { return Connected; } // IJoystickConfig interface @@ -100,9 +102,12 @@ public: float GetSensitivity(); virtual void SetSensitivity(float scale); + bool HasHaptics(); + float GetHapticsStrength(); + void SetHapticsStrength(float strength); + int GetNumAxes(); float GetAxisDeadZone(int axis); - EJoyAxis GetAxisMap(int axis); const char *GetAxisName(int axis); float GetAxisScale(int axis); float GetAxisDigitalThreshold(int axis); @@ -110,15 +115,14 @@ public: float GetAxisResponseCurvePoint(int axis, int point); void SetAxisDeadZone(int axis, float deadzone); - void SetAxisMap(int axis, EJoyAxis gameaxis); void SetAxisScale(int axis, float scale); void SetAxisDigitalThreshold(int axis, float threshold); void SetAxisResponseCurve(int axis, EJoyCurve preset); void SetAxisResponseCurvePoint(int axis, int point, float value); bool IsSensitivityDefault(); + bool IsHapticsStrengthDefault(); bool IsAxisDeadZoneDefault(int axis); - bool IsAxisMapDefault(int axis); bool IsAxisScaleDefault(int axis); bool IsAxisDigitalThresholdDefault(int axis); bool IsAxisResponseCurveDefault(int axis); @@ -142,12 +146,10 @@ protected: float DigitalThreshold; EJoyCurve ResponseCurvePreset; CubicBezier ResponseCurve; - EJoyAxis GameAxis; uint8_t ButtonValue; }; struct DefaultAxisConfig { - EJoyAxis GameAxis; float Multiplier; float DigitalThreshold; EJoyCurve ResponseCurvePreset; @@ -193,7 +195,7 @@ public: bool GetDevice(); bool ProcessRawInput(RAWINPUT *raw, int code); - void AddAxes(float axes[NUM_JOYAXIS]); + void AddAxes(float axes[NUM_AXIS_CODES]); void GetDevices(TArray &sticks); IJoystickConfig *Rescan(); @@ -369,13 +371,21 @@ static const char *AxisNames[] = "Right Thumb Y Axis", }; +static const EAxisCodes AxisCodes[][2] = +{ + { AXIS_CODE_PAD_LTHUMB_RIGHT, AXIS_CODE_PAD_LTHUMB_LEFT }, + { AXIS_CODE_PAD_LTHUMB_DOWN, AXIS_CODE_PAD_LTHUMB_UP }, + { AXIS_CODE_PAD_RTHUMB_RIGHT, AXIS_CODE_PAD_RTHUMB_LEFT }, + { AXIS_CODE_PAD_RTHUMB_DOWN, AXIS_CODE_PAD_RTHUMB_UP } +}; + FRawPS2Controller::DefaultAxisConfig FRawPS2Controller::DefaultAxes[NUM_AXES] = { - // Game axis, multiplier, digital threshold, response curve A, response curve B - { JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbLX - { JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbLY - { JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbRX - { JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbRY + // multiplier, digital threshold, response curve A, response curve B + { JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbLX + { JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbLY + { JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbRX + { JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbRY }; // CODE -------------------------------------------------------------------- @@ -564,18 +574,19 @@ void FRawPS2Controller::ProcessThumbstick(int value1, AxisInfo *axis1, int value axisval1 = value1 * (2.0 / 255) - 1.0; axisval2 = value2 * (2.0 / 255) - 1.0; - axisval1 = Joy_RemoveDeadZone(axisval1, axis1->DeadZone, NULL); - axisval2 = Joy_RemoveDeadZone(axisval2, axis2->DeadZone, NULL); - axisval1 = Joy_ApplyResponseCurveBezier(axis1->ResponseCurve, axisval1); - axisval2 = Joy_ApplyResponseCurveBezier(axis2->ResponseCurve, axisval2); + + Joy_ManageThumbstick( + &axisval1, &axisval2, + axis1->DeadZone, axis2->DeadZone, + axis1->DigitalThreshold, axis2->DigitalThreshold, + axis1->ResponseCurve, axis2->ResponseCurve, + &buttonstate + ); + axis1->Value = float(axisval1); axis2->Value = float(axisval2); // We store all four buttons in the first axis and ignore the second. - buttonstate = Joy_XYAxesToButtons( - (abs(axisval1) < axis1->DigitalThreshold) ? 0 : axisval1, - (abs(axisval2) < axis2->DigitalThreshold) ? 0 : axisval2 - ); Joy_GenerateButtonEvents(axis1->ButtonValue, buttonstate, 4, base); axis1->ButtonValue = buttonstate; } @@ -646,12 +657,27 @@ void FRawPS2Controller::NeutralInput() // //========================================================================== -void FRawPS2Controller::AddAxes(float axes[NUM_JOYAXIS]) +void FRawPS2Controller::AddAxes(float axes[NUM_AXIS_CODES]) { - // Add to game axes. for (int i = 0; i < NUM_AXES; ++i) { - axes[Axes[i].GameAxis] -= float(Axes[i].Value * Multiplier * Axes[i].Multiplier); + // Add to the game axis. + float axis_value = float(Axes[i].Value * Multiplier * Axes[i].Multiplier); + int code = AXIS_CODE_NULL; + + if (axis_value > 0.0f) + { + code = AxisCodes[i][0]; + } + else if (axis_value < 0.0f) + { + code = AxisCodes[i][1]; + } + + if (code != AXIS_CODE_NULL) + { + axes[code] += fabs(axis_value); + } } } @@ -667,7 +693,6 @@ void FRawPS2Controller::SetDefaultConfig() for (int i = 0; i < NUM_AXES; ++i) { Axes[i].DeadZone = JOYDEADZONE_DEFAULT; - Axes[i].GameAxis = DefaultAxes[i].GameAxis; Axes[i].Multiplier = DefaultAxes[i].Multiplier; } } @@ -734,6 +759,50 @@ bool FRawPS2Controller::IsSensitivityDefault() return Multiplier == JOYSENSITIVITY_DEFAULT; } +//=========================================================================== +// +// FRawPS2Controller :: HasHaptics +// +//=========================================================================== + +bool FRawPS2Controller::HasHaptics() +{ + return false; +} + +//=========================================================================== +// +// FRawPS2Controller :: GetHapticsStrength +// +//=========================================================================== + +float FRawPS2Controller::GetHapticsStrength() +{ + return JOYHAPSTRENGTH_DEFAULT; +} + +//=========================================================================== +// +// FRawPS2Controller :: SetHapticsStrength +// +//=========================================================================== + +void FRawPS2Controller::SetHapticsStrength(float strength) +{ + // nope +} + +//=========================================================================== +// +// FRawPS2Controller :: IsHapticsStrengthDefault +// +//=========================================================================== + +bool FRawPS2Controller::IsHapticsStrengthDefault() +{ + return true; +} + //========================================================================== // // FRawPS2Controller :: GetNumAxes @@ -760,21 +829,6 @@ float FRawPS2Controller::GetAxisDeadZone(int axis) return 0; } -//========================================================================== -// -// FRawPS2Controller :: GetAxisMap -// -//========================================================================== - -EJoyAxis FRawPS2Controller::GetAxisMap(int axis) -{ - if (unsigned(axis) < NUM_AXES) - { - return Axes[axis].GameAxis; - } - return JOYAXIS_None; -} - //========================================================================== // // FRawPS2Controller :: GetAxisName @@ -864,20 +918,6 @@ void FRawPS2Controller::SetAxisDeadZone(int axis, float deadzone) } } -//========================================================================== -// -// FRawPS2Controller :: SetAxisMap -// -//========================================================================== - -void FRawPS2Controller::SetAxisMap(int axis, EJoyAxis gameaxis) -{ - if (unsigned(axis) < NUM_AXES) - { - Axes[axis].GameAxis = (unsigned(gameaxis) < NUM_JOYAXIS) ? gameaxis : JOYAXIS_None; - } -} - //========================================================================== // // FRawPS2Controller :: SetAxisScale @@ -1020,21 +1060,6 @@ void FRawPS2Controller::SetEnabled(bool enabled) Enabled = enabled; } -//=========================================================================== -// -// FRawPS2Controller :: IsAxisMapDefault -// -//=========================================================================== - -bool FRawPS2Controller::IsAxisMapDefault(int axis) -{ - if (unsigned(axis) < NUM_AXES) - { - return Axes[axis].GameAxis == DefaultAxes[axis].GameAxis; - } - return true; -} - //========================================================================== // // FRawPS2Manager - Constructor @@ -1096,7 +1121,7 @@ bool FRawPS2Manager::GetDevice() // //=========================================================================== -void FRawPS2Manager::AddAxes(float axes[NUM_JOYAXIS]) +void FRawPS2Manager::AddAxes(float axes[NUM_AXIS_CODES]) { for (unsigned i = 0; i < Devices.Size(); ++i) { diff --git a/src/common/platform/win32/i_xinput.cpp b/src/common/platform/win32/i_xinput.cpp index 769475444..693876d55 100644 --- a/src/common/platform/win32/i_xinput.cpp +++ b/src/common/platform/win32/i_xinput.cpp @@ -1,8 +1,12 @@ /* +** i_xinput.cpp ** +** Handles direct input gamepads ** **--------------------------------------------------------------------------- +** ** Copyright 2005-2016 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -27,6 +31,7 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ @@ -34,6 +39,7 @@ // HEADER FILES ------------------------------------------------------------ #ifndef WIN32_LEAN_AND_MEAN +#include "m_joy.h" #define WIN32_LEAN_AND_MEAN #endif #include @@ -69,6 +75,8 @@ extern bool AppActive; +EXTERN_CVAR(Bool, use_joystick) + // TYPES ------------------------------------------------------------------- typedef DWORD (WINAPI *XInputGetStateType)(DWORD index, XINPUT_STATE *state); @@ -83,7 +91,7 @@ public: ~FXInputController(); void ProcessInput(); - void AddAxes(float axes[NUM_JOYAXIS]); + void AddAxes(float axes[NUM_AXIS_CODES]); bool IsConnected() { return Connected; } // IJoystickConfig interface @@ -91,9 +99,12 @@ public: float GetSensitivity(); virtual void SetSensitivity(float scale); + bool HasHaptics(); + float GetHapticsStrength(); + void SetHapticsStrength(float strength); + int GetNumAxes(); float GetAxisDeadZone(int axis); - EJoyAxis GetAxisMap(int axis); const char *GetAxisName(int axis); float GetAxisScale(int axis); float GetAxisDigitalThreshold(int axis); @@ -101,15 +112,14 @@ public: float GetAxisResponseCurvePoint(int axis, int point); void SetAxisDeadZone(int axis, float deadzone); - void SetAxisMap(int axis, EJoyAxis gameaxis); void SetAxisScale(int axis, float scale); void SetAxisDigitalThreshold(int axis, float threshold); void SetAxisResponseCurve(int axis, EJoyCurve preset); void SetAxisResponseCurvePoint(int axis, int point, float value); bool IsSensitivityDefault(); + bool IsHapticsStrengthDefault(); bool IsAxisDeadZoneDefault(int axis); - bool IsAxisMapDefault(int axis); bool IsAxisScaleDefault(int axis); bool IsAxisDigitalThresholdDefault(int axis); bool IsAxisResponseCurveDefault(int axis); @@ -117,6 +127,8 @@ public: bool GetEnabled(); void SetEnabled(bool enabled); + void Rumble(float low_freq, float high_freq); + bool AllowsEnabledInBackground() { return true; } bool GetEnabledInBackground() { return EnabledInBackground; } void SetEnabledInBackground(bool enabled) { EnabledInBackground = enabled; } @@ -130,7 +142,6 @@ protected: float Value; float DeadZone; float Multiplier; - EJoyAxis GameAxis; uint8_t ButtonValue; float DigitalThreshold; EJoyCurve ResponseCurvePreset; @@ -139,11 +150,11 @@ protected: struct DefaultAxisConfig { float DeadZone; - EJoyAxis GameAxis; float Multiplier; float DigitalThreshold; EJoyCurve ResponseCurvePreset; }; + XINPUT_VIBRATION Vibration; enum { AXIS_ThumbLX, @@ -164,6 +175,8 @@ protected: bool Connected; bool Enabled; bool EnabledInBackground; + bool Haptics; + float HapticStrength; void Attached(); void Detached(); @@ -181,10 +194,12 @@ public: bool GetDevice(); void ProcessInput(); bool WndProcHook(HWND hWnd, uint32_t message, WPARAM wParam, LPARAM lParam, LRESULT *result); - void AddAxes(float axes[NUM_JOYAXIS]); + void AddAxes(float axes[NUM_AXIS_CODES]); void GetDevices(TArray &sticks); IJoystickConfig *Rescan(); + void Rumble(float low_freq, float high_freq); + protected: HMODULE XInputDLL; FXInputController *Devices[XUSER_MAX_COUNT]; @@ -224,15 +239,25 @@ static const char *AxisNames[] = "Right Trigger" }; +static const EAxisCodes AxisCodes[][2] = +{ + { AXIS_CODE_PAD_LTHUMB_RIGHT, AXIS_CODE_PAD_LTHUMB_LEFT }, + { AXIS_CODE_PAD_LTHUMB_DOWN, AXIS_CODE_PAD_LTHUMB_UP }, + { AXIS_CODE_PAD_RTHUMB_RIGHT, AXIS_CODE_PAD_RTHUMB_LEFT }, + { AXIS_CODE_PAD_RTHUMB_DOWN, AXIS_CODE_PAD_RTHUMB_UP }, + { AXIS_CODE_PAD_LTRIGGER, AXIS_CODE_NULL }, + { AXIS_CODE_PAD_RTRIGGER, AXIS_CODE_NULL } +}; + FXInputController::DefaultAxisConfig FXInputController::DefaultAxes[NUM_AXES] = { - // Dead zone, game axis, multiplier, digitalthreshold, curveA, curveB - { XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE / 32768.f, JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbLX - { XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE / 32768.f, JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbLY - { XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE / 32768.f, JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbRX - { XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE / 32768.f, JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbRY - { XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 256.f, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT }, // LeftTrigger - { XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 256.f, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT } // RightTrigger + // Dead zone, multiplier, digitalthreshold, curveA, curveB + { XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE / 32768.f, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbLX + { XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE / 32768.f, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbLY + { XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE / 32768.f, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT }, // ThumbRX + { XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE / 32768.f, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT }, // ThumbRY + { XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 256.f, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT }, // LeftTrigger + { XINPUT_GAMEPAD_TRIGGER_THRESHOLD / 256.f, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT } // RightTrigger }; // CODE -------------------------------------------------------------------- @@ -340,18 +365,19 @@ void FXInputController::ProcessThumbstick(int value1, AxisInfo *axis1, axisval1 = (value1 - SHRT_MIN) * 2.0 / 65536 - 1.0; axisval2 = (value2 - SHRT_MIN) * 2.0 / 65536 - 1.0; - axisval1 = Joy_RemoveDeadZone(axisval1, axis1->DeadZone, NULL); - axisval2 = Joy_RemoveDeadZone(axisval2, axis2->DeadZone, NULL); - axisval1 = Joy_ApplyResponseCurveBezier(axis1->ResponseCurve, axisval1); - axisval2 = Joy_ApplyResponseCurveBezier(axis2->ResponseCurve, axisval2); + + Joy_ManageThumbstick( + &axisval1, &axisval2, + axis1->DeadZone, axis2->DeadZone, + axis1->DigitalThreshold, axis2->DigitalThreshold, + axis1->ResponseCurve, axis2->ResponseCurve, + &buttonstate + ); + axis1->Value = float(axisval1); axis2->Value = float(axisval2); // We store all four buttons in the first axis and ignore the second. - buttonstate = Joy_XYAxesToButtons( - abs(axisval1) < axis1->DigitalThreshold ? 0: axisval1, - abs(axisval2) < axis2->DigitalThreshold ? 0: axisval2 - ); Joy_GenerateButtonEvents(axis1->ButtonValue, buttonstate, 4, base); axis1->ButtonValue = buttonstate; } @@ -370,15 +396,15 @@ void FXInputController::ProcessTrigger(int value, AxisInfo *axis, int base) uint8_t buttonstate; double axisval; - axisval = Joy_RemoveDeadZone(value / 256.0, axis->DeadZone, &buttonstate); - axisval = Joy_ApplyResponseCurveBezier(axis->ResponseCurve, axisval); - - // TODO: probably just put this into Joy_RemoveDeadZone - if (abs(axisval) < axis->DigitalThreshold) buttonstate = 0; + axisval = Joy_ManageSingleAxis( + value / 256.0, + axis->DeadZone, axis->DigitalThreshold, axis->ResponseCurve, + &buttonstate + ); + axis->Value = float(axisval); Joy_GenerateButtonEvents(axis->ButtonValue, buttonstate, 1, base); axis->ButtonValue = buttonstate; - axis->Value = float(axisval); } //========================================================================== @@ -401,9 +427,27 @@ void FXInputController::Attached() Axes[i].Value = 0; Axes[i].ButtonValue = 0; } + XINPUT_CAPABILITIES capabilities; + if (XInputGetCapabilities(Index, XINPUT_FLAG_GAMEPAD, &capabilities) == ERROR_SUCCESS) + { + Haptics = capabilities.Vibration.wLeftMotorSpeed != 0 || capabilities.Vibration.wRightMotorSpeed != 0; + } UpdateJoystickMenu(this); } +//========================================================================== +// +// FXInputController :: Rumble +// +//========================================================================== + +void FXInputController::Rumble(float low_freq, float high_freq) +{ + Vibration.wLeftMotorSpeed = static_cast(USHRT_MAX*clamp(high_freq*HapticStrength, 0.f, 1.f)); + Vibration.wRightMotorSpeed = static_cast(USHRT_MAX*clamp( low_freq*HapticStrength, 0.f, 1.f)); + XInputSetState(Index, &Vibration); +} + //========================================================================== // // FXInputController :: Detached @@ -439,12 +483,27 @@ void FXInputController::Detached() // //========================================================================== -void FXInputController::AddAxes(float axes[NUM_JOYAXIS]) +void FXInputController::AddAxes(float axes[NUM_AXIS_CODES]) { - // Add to game axes. for (int i = 0; i < NUM_AXES; ++i) { - axes[Axes[i].GameAxis] -= float(Axes[i].Value * Multiplier * Axes[i].Multiplier); + // Add to the game axis. + float axis_value = float(Axes[i].Value * Multiplier * Axes[i].Multiplier); + int code = AXIS_CODE_NULL; + + if (axis_value > 0.0f) + { + code = AxisCodes[i][0]; + } + else if (axis_value < 0.0f) + { + code = AxisCodes[i][1]; + } + + if (code != AXIS_CODE_NULL) + { + axes[code] += fabs(axis_value); + } } } @@ -457,10 +516,10 @@ void FXInputController::AddAxes(float axes[NUM_JOYAXIS]) void FXInputController::SetDefaultConfig() { Multiplier = JOYSENSITIVITY_DEFAULT; + HapticStrength = JOYHAPSTRENGTH_DEFAULT; for (int i = 0; i < NUM_AXES; ++i) { Axes[i].DeadZone = DefaultAxes[i].DeadZone; - Axes[i].GameAxis = DefaultAxes[i].GameAxis; Axes[i].Multiplier = DefaultAxes[i].Multiplier; Axes[i].DigitalThreshold = DefaultAxes[i].DigitalThreshold; Axes[i].ResponseCurve = JOYCURVE[DefaultAxes[i].ResponseCurvePreset]; @@ -525,6 +584,50 @@ bool FXInputController::IsSensitivityDefault() return Multiplier == JOYSENSITIVITY_DEFAULT; } +//========================================================================== +// +// FXInputController :: HasHaptics +// +//========================================================================== + +bool FXInputController::HasHaptics() +{ + return Haptics; +} + +//========================================================================== +// +// FXInputController :: GetHapticsStrength +// +//========================================================================== + +float FXInputController::GetHapticsStrength() +{ + return HapticStrength; +} + +//========================================================================== +// +// FXInputController :: SetHapticsStrength +// +//========================================================================== + +void FXInputController::SetHapticsStrength(float strength) +{ + if (Haptics) HapticStrength = clamp(strength, 0.f, 2.f); +} + +//========================================================================== +// +// FXInputController :: IsHapticsStrengthDefault +// +//========================================================================== + +bool FXInputController::IsHapticsStrengthDefault() +{ + return true; +} + //========================================================================== // // FXInputController :: GetNumAxes @@ -551,21 +654,6 @@ float FXInputController::GetAxisDeadZone(int axis) return 0; } -//========================================================================== -// -// FXInputController :: GetAxisMap -// -//========================================================================== - -EJoyAxis FXInputController::GetAxisMap(int axis) -{ - if (unsigned(axis) < NUM_AXES) - { - return Axes[axis].GameAxis; - } - return JOYAXIS_None; -} - //========================================================================== // // FXInputController :: GetAxisName @@ -655,20 +743,6 @@ void FXInputController::SetAxisDeadZone(int axis, float deadzone) } } -//========================================================================== -// -// FXInputController :: SetAxisMap -// -//========================================================================== - -void FXInputController::SetAxisMap(int axis, EJoyAxis gameaxis) -{ - if (unsigned(axis) < NUM_AXES) - { - Axes[axis].GameAxis = (unsigned(gameaxis) < NUM_JOYAXIS) ? gameaxis : JOYAXIS_None; - } -} - //========================================================================== // // FXInputController :: SetAxisScale @@ -811,21 +885,6 @@ void FXInputController::SetEnabled(bool enabled) Enabled = enabled; } -//=========================================================================== -// -// FXInputController :: IsAxisMapDefault -// -//=========================================================================== - -bool FXInputController::IsAxisMapDefault(int axis) -{ - if (unsigned(axis) < NUM_AXES) - { - return Axes[axis].GameAxis == DefaultAxes[axis].GameAxis; - } - return true; -} - //========================================================================== // // FXInputManager - Constructor @@ -915,7 +974,7 @@ void FXInputManager::ProcessInput() // //=========================================================================== -void FXInputManager::AddAxes(float axes[NUM_JOYAXIS]) +void FXInputManager::AddAxes(float axes[NUM_AXIS_CODES]) { for (int i = 0; i < XUSER_MAX_COUNT; ++i) { @@ -970,6 +1029,22 @@ bool FXInputManager::WndProcHook(HWND hWnd, uint32_t message, WPARAM wParam, LPA return false; } +//=========================================================================== +// +// FXInputManager :: Rumble +// +//=========================================================================== + +void FXInputManager::Rumble(float low_freq, float high_freq) { + for (int i = 0; i < XUSER_MAX_COUNT; ++i) + { + if (Devices[i] && Devices[i]->IsConnected() && Devices[i]->GetEnabled()) + { + Devices[i]->Rumble(low_freq, high_freq); + } + } +} + //=========================================================================== // // FXInputManager :: Rescan @@ -1015,3 +1090,12 @@ void I_StartupXInput() } } +void I_Rumble(double high_freq, double low_freq, double _left_trig, double _right_trig) { + if (!use_joystick) return; + + FXInputManager* XInputManager = & static_cast (*JoyDevices[INPUT_XInput]); + if (XInputManager != NULL) + { + XInputManager->Rumble(high_freq, low_freq); + } +} diff --git a/src/common/rendering/v_video.cpp b/src/common/rendering/v_video.cpp index 38efbc174..8ac90ddf9 100644 --- a/src/common/rendering/v_video.cpp +++ b/src/common/rendering/v_video.cpp @@ -77,7 +77,15 @@ CVAR(Bool, r_skipmats, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL // 0 means 'no pipelining' for non GLES2 and 4 elements for GLES2 CUSTOM_CVAR(Int, gl_pipeline_depth, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) { - if (self < 0 || self >= HW_MAX_PIPELINE_BUFFERS) self = 0; + if (self < 0) + { + self = 0; + } + else if (self > HW_MAX_PIPELINE_BUFFERS) + { + self = HW_MAX_PIPELINE_BUFFERS; + } + Printf("Changing the pipeline depth requires a restart for " GAMENAME ".\n"); } diff --git a/src/common/scripting/core/maps.cpp b/src/common/scripting/core/maps.cpp index bed5110de..3ed7d4509 100644 --- a/src/common/scripting/core/maps.cpp +++ b/src/common/scripting/core/maps.cpp @@ -36,7 +36,11 @@ template void MapCopy(M * self, M * other) { MAP_GC_WRITE_BARRIER(other); } - *self = *other; // how does this handle self->info? TODO double check. + auto tmp = self->info; + + *self = *other; + + self->info = tmp; self->info->rev++; } diff --git a/src/common/scripting/core/maps.h b/src/common/scripting/core/maps.h index ed303537b..c808c535d 100644 --- a/src/common/scripting/core/maps.h +++ b/src/common/scripting/core/maps.h @@ -80,7 +80,7 @@ struct ZSMapIterator } else { - ThrowAbortException(X_FORMAT_ERROR,"MapIterator::Next called from invalid iterator"); + ThrowAbortException(X_OTHER, "MapIterator::Next called from invalid iterator"); } } @@ -92,7 +92,7 @@ struct ZSMapIterator } else { - ThrowAbortException(X_FORMAT_ERROR,p ? "MapIterator::GetValue called from invalid iterator" : "MapIterator::GetValue called from invalid position"); + ThrowAbortException(X_OTHER, p ? "MapIterator::GetValue called from invalid iterator" : "MapIterator::GetValue called from invalid position"); } } @@ -104,7 +104,7 @@ struct ZSMapIterator } else { - ThrowAbortException(X_FORMAT_ERROR,p ? "MapIterator::GetKey called from invalid iterator" : "MapIterator::GetKey called from invalid position"); + ThrowAbortException(X_OTHER, p ? "MapIterator::GetKey called from invalid iterator" : "MapIterator::GetKey called from invalid position"); } } }; \ No newline at end of file diff --git a/src/common/scripting/interface/stringformat.cpp b/src/common/scripting/interface/stringformat.cpp index b5b88a695..9e6433f53 100644 --- a/src/common/scripting/interface/stringformat.cpp +++ b/src/common/scripting/interface/stringformat.cpp @@ -526,6 +526,27 @@ DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, CharLower, StringCharLower) ACTION_RETURN_INT(StringCharLower(ch)); } +static int StringIsInt(FString *self) +{ + return self->IsInt(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, IsInt, StringIsInt) +{ + PARAM_SELF_STRUCT_PROLOGUE(FString); + ACTION_RETURN_INT(self->IsInt()); +} + +static int StringIsDouble(FString *self) +{ + return self->IsFloat(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, IsDouble, StringIsDouble) +{ + PARAM_SELF_STRUCT_PROLOGUE(FString); + ACTION_RETURN_INT(self->IsFloat()); +} static int StringToInt(FString *self, int base) { diff --git a/src/common/startscreen/startscreen.cpp b/src/common/startscreen/startscreen.cpp index f65b3664e..57b02e458 100644 --- a/src/common/startscreen/startscreen.cpp +++ b/src/common/startscreen/startscreen.cpp @@ -668,6 +668,13 @@ void FStartScreen::Render(bool force) // Do not refresh too often. This function gets called a lot more frequently than the screen can update. if (nowtime - screen->FrameTime > minwaittime || force) { + if (setmodeneeded) + { + setmodeneeded = false; + screen->ToggleFullscreen(vid_fullscreen); + V_OutputResized(screen->GetWidth(), screen->GetHeight()); + } + screen->FrameTime = nowtime; screen->FrameTimeNS = I_nsTime(); screen->BeginFrame(); diff --git a/src/common/utility/cmdlib.cpp b/src/common/utility/cmdlib.cpp index 3b8dc9981..91398519d 100644 --- a/src/common/utility/cmdlib.cpp +++ b/src/common/utility/cmdlib.cpp @@ -150,6 +150,38 @@ bool FileExists (const char *filename) return res && !isdir; } +//========================================================================== +// +// RecursiveFileExists +// +// Returns the path to the file if it was found within a subdirectory, +// otherwise returning an empty string. +// +//========================================================================== + +FString RecursiveFileExists(const FString& path, const FString& file) +{ + if (FileExists(path + "/" + file)) + return path + "/" + file; + + // If we couldn't find it in the base directory, time to start searching. + FileSys::FileList list; + if (FileSys::ScanDirectory(list, path.GetChars(), "*")) + { + for (auto& entry : list) + { + if (entry.isDirectory && !entry.isHidden && !entry.isSystem) + { + FStringf f("%s/%s", entry.FilePath.c_str(), file.GetChars()); + if (FileExists(f)) + return f; + } + } + } + + return ""; +} + //========================================================================== // // FileReadable diff --git a/src/common/utility/cmdlib.h b/src/common/utility/cmdlib.h index 88027c156..8a7ea97bd 100644 --- a/src/common/utility/cmdlib.h +++ b/src/common/utility/cmdlib.h @@ -34,6 +34,7 @@ char(&_ArraySizeHelper(T(&array)[N]))[N]; #define myoffsetof(type,identifier) ((size_t)&((type *)alignof(type))->identifier - alignof(type)) bool FileExists (const char *filename); +FString RecursiveFileExists(const FString& path, const FString& file); inline bool FileExists(const FString& filename) { return FileExists(filename.GetChars()); diff --git a/src/common/utility/findfile.cpp b/src/common/utility/findfile.cpp index d5da7b027..f2b4e4561 100644 --- a/src/common/utility/findfile.cpp +++ b/src/common/utility/findfile.cpp @@ -263,6 +263,25 @@ const char* BaseFileSearch(const char* file, const char* ext, bool lookfirstinpr } } } + else if (stricmp(key, "RecursivePath") == 0) + { + FString dir; + + dir = NicePath(value); + if (dir.IsNotEmpty()) + { + if (dir.Back() == '/') + dir.Truncate(dir.Len() - 1); + + // Folders can't be used here since those are going to be checked + // recursively, so only find actual files. + FString path = RecursiveFileExists(dir, file); + if (path.IsNotEmpty()) + { + return path.GetChars(); + } + } + } } } diff --git a/src/common/widgets/widgetresourcedata.cpp b/src/common/widgets/widgetresourcedata.cpp index 8783ff24d..eee496cfe 100644 --- a/src/common/widgets/widgetresourcedata.cpp +++ b/src/common/widgets/widgetresourcedata.cpp @@ -1,6 +1,7 @@ #include #include +#include "c_cvars.h" #include "filesystem.h" #include "printf.h" #include "zstring.h" @@ -12,6 +13,12 @@ #include #endif +CUSTOM_CVARD(Int, ui_theme, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "launcher theme. 0: auto, 1: dark, 2: light") +{ + if (self < 0) self = 0; + if (self > 2) self = 2; +} + FResourceFile* WidgetResources; bool IsZWidgetAvailable() @@ -25,7 +32,35 @@ void InitWidgetResources(const char* filename) if (!WidgetResources) I_FatalError("Unable to open %s", filename); - WidgetTheme::SetTheme(std::make_unique()); + bool use_dark = ui_theme != 2; + + if (ui_theme == 0) + { + // TODO: detect system theme + } + + if (use_dark) // light + { + // TODO: make a nice theme + WidgetTheme::SetTheme(std::make_unique()); + } + else + { + WidgetTheme::SetTheme(std::unique_ptr(new WidgetTheme{{ + Colorf::fromRgb(0xeee8d5), // background + Colorf::fromRgb(0x000000), // text + Colorf::fromRgb(0xfdf6e3), // headers / inputs + Colorf::fromRgb(0x000000), // headers / inputs text + Colorf::fromRgb(0xd7d2bf), // interactive elements + Colorf::fromRgb(0x000000), // interactive elements text + Colorf::fromRgb(0xa4c2e9), // hover / highlight + Colorf::fromRgb(0x000000), // hover / highlight text + Colorf::fromRgb(0x7ca2e9), // click + Colorf::fromRgb(0x000000), // click text + Colorf::fromRgb(0x586e75), // around elements + Colorf::fromRgb(0xbdb8a7) // between elements + }})); + } } void CloseWidgetResources() diff --git a/src/console/c_cmds.cpp b/src/console/c_cmds.cpp index dddedb081..be54e9344 100644 --- a/src/console/c_cmds.cpp +++ b/src/console/c_cmds.cpp @@ -1371,7 +1371,8 @@ CCMD (mapinfo) if (myLevel->Music.IsNotEmpty()) Printf(" Music: %s%s\n", myLevel->Music[0] == '$'? "D_" : "", testlocalised(myLevel->Music.GetChars())); - Printf(" PixelStretch: %f\n", myLevel->pixelstretch); + if (myLevel->pixelstretch != 0.0) // yes, we're making a dummy if statement to fix the misleading indentation warnings + Printf(" PixelStretch: %f\n", myLevel->pixelstretch); if (myLevel->RedirectType != NAME_None) Printf(" Redirect (Item): %s\n", myLevel->RedirectType.GetChars()); @@ -1385,7 +1386,8 @@ CCMD (mapinfo) if (myLevel->RedirectCVARMapName.IsNotEmpty()) Printf(" CVAR_Redirect (Map): %s\n", myLevel->RedirectCVARMapName.GetChars()); - Printf(" LightMode: %i\n", (int8_t)myLevel->lightmode); + if ((int8_t)myLevel->lightmode != -128) // yes, we're making a dummy if statement to fix the misleading indentation warnings + Printf(" LightMode: %i\n", (int8_t)myLevel->lightmode); if (players[consoleplayer].mo && players[consoleplayer].mo->Level) { diff --git a/src/d_anonstats.cpp b/src/d_anonstats.cpp index 8edaff008..cb12dc6b3 100644 --- a/src/d_anonstats.cpp +++ b/src/d_anonstats.cpp @@ -26,7 +26,7 @@ extern FString sys_ostype; #ifdef __APPLE__ #include #else // !__APPLE__ -#include +#include #endif // __APPLE__ #include #include diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index 44c66631f..8522639ae 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -468,6 +468,11 @@ void FIWadManager::CollectSearchPaths() FString nice = NicePath(value); if (nice.Len() > 0) mSearchPaths.Push(nice); } + else if (stricmp(key, "RecursivePath") == 0) + { + FString nice = NicePath(value); + if (nice.Len() > 0) mRecursiveSearchPaths.Push(nice); + } } } mSearchPaths.Append(I_GetGogPaths()); @@ -480,6 +485,11 @@ void FIWadManager::CollectSearchPaths() FixPathSeperator(str); if (str.Back() == '/') str.Truncate(str.Len() - 1); } + for (auto& str : mRecursiveSearchPaths) + { + FixPathSeperator(str); + if (str.Back() == '/') str.Truncate(str.Len() - 1); + } } //========================================================================== @@ -490,12 +500,12 @@ void FIWadManager::CollectSearchPaths() // //========================================================================== -void FIWadManager::AddIWADCandidates(const char *dir) +void FIWadManager::AddIWADCandidates(const char *dir, bool nosubdir) { FileSys::FileList list; FString iwadinfoName = "iwadinfo"; - if (FileSys::ScanDirectory(list, dir, "*", true)) + if (FileSys::ScanDirectory(list, dir, "*", nosubdir)) { for(auto& entry : list) { @@ -551,8 +561,16 @@ void FIWadManager::ValidateIWADs() { TArray returns; unsigned originalsize = mIWadInfos.Size(); - for (auto &p : mFoundWads) + + // Iterating normally will give CheckIWADInfo name conflicts priority to + // whatever file is found first, rather than the file that the user + // specifically requests with -iwad, because IdentifyVersion appends + // the -iwad file to the end of the list. (And it's annoying to change + // to be the other way around.) + for (int i = mFoundWads.SSize() - 1; i >= 0; i--) { + auto &p = mFoundWads[i]; + int index; auto x = strrchr(p.mFullPath.GetChars(), '.'); if (x != nullptr && (!stricmp(x, ".iwad") || !stricmp(x, ".ipk3") || !stricmp(x, ".ipk7"))) @@ -603,6 +621,11 @@ FString FIWadManager::IWADPathFileSearch(const FString &file) FString f = path + "/" + file; if(FileExists(f)) return f; } + for (const FString& path : mRecursiveSearchPaths) + { + FString f = RecursiveFileExists(path, file); + if (f.IsNotEmpty()) return f; + } return ""; } @@ -621,6 +644,10 @@ int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char { AddIWADCandidates(dir.GetChars()); } + for (auto& dir : mRecursiveSearchPaths) + { + AddIWADCandidates(dir.GetChars(), false); + } unsigned numFoundWads = mFoundWads.Size(); if (iwadparm) @@ -645,6 +672,14 @@ int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char isdiriwad = true; } } + for (const auto& dir : mRecursiveSearchPaths) + { + FString fullpath = RecursiveFileExists(dir, custwad); + if (fullpath.IsNotEmpty()) + { + mFoundWads.Push({ fullpath, "", -1 }); + } + } } else { diff --git a/src/d_main.cpp b/src/d_main.cpp index edbac17d7..d98574cbc 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1,8 +1,9 @@ //----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- +// // Copyright 1993-1996 id Software // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -28,6 +29,8 @@ // HEADER FILES ------------------------------------------------------------ +#include "c_cvars.h" +#include "i_soundinternal.h" #ifdef _WIN32 #include #define WIN32_MEAN_AND_LEAN @@ -41,41 +44,8 @@ #include #include -#include "engineerrors.h" - -#include "i_time.h" -#include "d_gui.h" -#include "m_random.h" -#include "doomdef.h" -#include "doomstat.h" -#include "gstrings.h" -#include "filesystem.h" -#include "s_sound.h" -#include "v_video.h" -#include "intermission/intermission.h" -#include "wipe.h" -#include "m_argv.h" -#include "m_misc.h" -#include "menu.h" -#include "doommenu.h" -#include "c_console.h" -#include "c_dispatch.h" -#include "i_sound.h" -#include "i_video.h" -#include "g_game.h" -#include "hu_stuff.h" -#include "wi_stuff.h" -#include "st_stuff.h" -#include "am_map.h" -#include "p_setup.h" -#include "r_utility.h" -#include "r_sky.h" -#include "d_main.h" -#include "d_dehacked.h" -#include "cmdlib.h" -#include "v_text.h" -#include "gi.h" #include "a_dynlight.h" +#include "am_map.h" #include "gameconfigfile.h" #include "sbar.h" #include "decallib.h" @@ -90,6 +60,7 @@ #include "m_cheat.h" #include "m_joy.h" #include "v_draw.h" +#include "v_palette.h" #include "po_man.h" #include "p_local.h" #include "autosegs.h" @@ -109,16 +80,67 @@ #include "d_buttons.h" #include "i_interface.h" #include "animations.h" -#include "texturemanager.h" +#include "autosegs.h" +#include "c_buttons.h" +#include "c_console.h" +#include "c_cvars.h" +#include "c_dispatch.h" +#include "cmdlib.h" +#include "common/scripting/dap/DebugServer.h" +#include "d_buttons.h" +#include "d_dehacked.h" +#include "d_event.h" +#include "d_main.h" +#include "d_net.h" +#include "d_netinf.h" +#include "decallib.h" +#include "doomdef.h" +#include "doomfont.h" +#include "doommenu.h" +#include "doomstat.h" +#include "engineerrors.h" +#include "events.h" +#include "filesystem.h" +#include "findfile.h" #include "formats/multipatchtexture.h" -#include "scriptutil.h" -#include "v_palette.h" -#include "texturemanager.h" +#include "fragglescript/t_fs.h" +#include "g_cvars.h" +#include "g_game.h" +#include "g_levellocals.h" +#include "gameconfigfile.h" +#include "gi.h" +#include "gstrings.h" +#include "hu_stuff.h" #include "hw_clock.h" #include "hwrenderer/scene/hw_drawinfo.h" -#include "doomfont.h" -#include "screenjob.h" +#include "i_interface.h" +#include "i_sound.h" +#include "i_soundinternal.h" +#include "i_system.h" +#include "i_time.h" +#include "i_video.h" +#include "wipe.h" +#include "m_argv.h" +#include "m_cheat.h" +#include "m_joy.h" +#include "m_misc.h" +#include "m_random.h" +#include "md5.h" +#include "menu.h" +#include "p_local.h" +#include "p_setup.h" +#include "po_man.h" +#include "r_data/r_vanillatrans.h" +#include "r_sky.h" +#include "r_utility.h" +#include "s_music.h" +#include "s_sound.h" +#include "sbar.h" +#include "sbarinfo.h" #include "startscreen.h" +#include "st_stuff.h" +#include "screenjob.h" +#include "scriptutil.h" #include "shiftstate.h" #include "common/scripting/dap/DebugServer.h" #include "common/widgets/errorwindow.h" @@ -206,6 +228,7 @@ EXTERN_CVAR (Bool, r_drawplayersprites) EXTERN_CVAR (Bool, show_messages) EXTERN_CVAR(Bool, ticker) EXTERN_CVAR(Bool, vid_fps) +EXTERN_CVAR(Bool, haptics_do_menus) extern bool setmodeneeded; extern bool demorecording; @@ -233,7 +256,6 @@ CUSTOM_CVAR(Float, i_timescale, 1.0f, CVAR_NOINITCALL | CVAR_VIRTUAL) } } - // PUBLIC DATA DEFINITIONS ------------------------------------------------- #ifndef NO_SWRENDERER @@ -339,6 +361,7 @@ FString lastIWAD; int restart = 0; extern bool AppActive; bool playedtitlemusic; +volatile sig_atomic_t gameloop_abort = false; FStartScreen* StartScreen; std::unique_ptr debugServer; @@ -348,7 +371,6 @@ cycle_t FrameCycles; // [SP] Store the capabilities of the renderer in a global variable, to prevent excessive per-frame processing uint32_t r_renderercaps = 0; - // PRIVATE DATA DEFINITIONS ------------------------------------------------ static int demosequence; @@ -933,7 +955,7 @@ void D_Display () if (nodrawers || screen == NULL) return; // for comparative timing / profiling - if (!AppActive && (screen->IsFullscreen() || !vid_activeinbackground)) + if (!AppActive && !setmodeneeded && (screen->IsFullscreen() || !vid_activeinbackground)) { return; } @@ -1119,7 +1141,6 @@ void D_Display () ScreenJobDraw(); break; - default: break; } @@ -1296,6 +1317,12 @@ void D_DoomLoop () D_ProcessEvents(); D_Display (); S_UpdateMusic(); + + if (gameloop_abort) + { + C_DoCommand("quickexit"); + } + if (wantToRestart) { wantToRestart = false; @@ -1606,7 +1633,6 @@ void D_DoAdvanceDemo (void) break; } - if (pagename.IsNotEmpty()) { Page = TexMan.CheckForTexture(pagename.GetChars(), ETextureType::MiscPatch); @@ -1867,7 +1893,6 @@ static void GetCmdLineFiles(std::vector& wadfiles, bool optional) } } - static FString ParseGameInfo(std::vector &pwads, const char *fn, const char *data, int size) { FScanner sc; @@ -2327,7 +2352,6 @@ static void NewFailure () I_FatalError ("Failed to allocate memory from system heap"); } - //========================================================================== // // RenameSprites @@ -2508,6 +2532,7 @@ static void RenameSprites(FileSystem &fileSystem, const TArray& deletel // MD5 checksum for Unity version of NERVE.WAD: 4214c47651b63ee2257b1c2490a518c9 (3,821,966) // //========================================================================== + void RenameNerve(FileSystem& fileSystem) { if (gameinfo.gametype != GAME_Doom) @@ -2695,7 +2720,6 @@ static void FindStrifeTeaserVoices(FileSystem& fileSystem) } } - static const char *DoomButtons[] = { "am_panleft", @@ -2856,14 +2880,13 @@ static bool System_CaptureModeInGame() static void System_PlayStartupSound(const char* sndname) { - S_Sound(CHAN_BODY, 0, sndname, 1, ATTN_NONE); + S_Sound(CHAN_BODY, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), sndname, 1, ATTN_NONE); } static bool System_IsSpecialUI() { return (generic_ui || !!log_vgafont || !!dlg_vgafont || ConsoleState != c_up || multiplayer || (menuactive == MENU_On && CurrentMenu && !CurrentMenu->IsKindOf("ConversationMenu"))); - } static bool System_DisableTextureFilter() @@ -2990,7 +3013,6 @@ void System_CrashInfo(char* buffer, size_t bufflen, const char *lfstr) void System_M_Dim(); - static void PatchTextures() { // The Hexen scripts use BLANK as a blank texture, even though it's really not. @@ -3093,7 +3115,6 @@ static void Doom_CastSpriteIDToString(FString* a, unsigned int b) *a = (b >= sprites.Size()) ? "TNT1" : sprites[b].name; } - extern DThinker* NextToThink; static void GC_MarkGameRoots() @@ -3207,6 +3228,7 @@ static int FileSystemPrintf(FSMessageLevel level, const char* fmt, ...) } return (int)text.Len(); } + //========================================================================== // // D_InitGame @@ -3305,8 +3327,6 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw lfi.gameTypeFilter.push_back(NewFilterName.GetChars()); } - - GetReserved(lfi); lfi.postprocessFunc = [&]() @@ -3405,6 +3425,7 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw FBaseCVar::EnableCallbacks (); StartScreen = nostartscreen? nullptr : GetGameStartScreen(per_shader_progress > 0 ? max_progress * 10 / 9 : max_progress + 3); + setmodeneeded = true; if (StartScreen != nullptr) StartScreen->Render(); // +compatmode cannot be used on the command line, so use this as a substitute @@ -3498,8 +3519,9 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw // [RH] Add any .deh and .bex files on the command line. // If there are none, try adding any in the config file. // Note that the command line overrides defaults from the config. - - if ((ConsiderPatches("-deh") | ConsiderPatches("-bex")) == 0 && + bool foundDeh = ConsiderPatches("-deh"); + bool foundBex = ConsiderPatches("-bex"); + if (!foundDeh && !foundBex && gameinfo.gametype == GAME_Doom && GameConfig->SetSection ("Doom.DefaultDehacked")) { const char *key; @@ -3529,7 +3551,6 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw M_Init(); M_CreateGameMenus(); - // clean up the compiler symbols which are not needed any longer. if (!dap_debugging) RemoveUnusedSymbols(); @@ -3619,7 +3640,7 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw autostart = true; } - S_Sound (CHAN_BODY, 0, "misc/startupdone", 1, ATTN_NONE); + S_Sound(CHAN_BODY, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "misc/startupdone", 1, ATTN_NONE); if (!batchrun) Printf ("Init complete.\n"); if (RunningAsTool) @@ -3725,6 +3746,7 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw staticEventManager.OnEngineInitialize(); return 0; } + //========================================================================== // // D_DoomMain @@ -3788,7 +3810,6 @@ int GameMain_Internal() RemapUserTranslation }; - std::set_new_handler(NewFailure); batchout = Args->CheckValue("-errorlog"); @@ -3996,6 +4017,20 @@ int D_DoomMain_Game() while (1); } +void SignalHandler(int signal) +{ + if (gameloop_abort) + { + Printf("Received signal %d, exiting\n", signal); + exit(0); + } + else + { + Printf("Received signal %d, shutting down\n", signal); + gameloop_abort = true; + } +} + int GameMain() { // On Windows, prefer the native win32 backend. @@ -4004,7 +4039,10 @@ int GameMain() if (!zwidget) zwidget = DisplayBackend::TryCreateSDL2(); if (!zwidget) + { + fprintf(stderr, "Unable to create init zwidget\n"); return -1; + } DisplayBackend::Set(std::move(zwidget)); int ret = 0; @@ -4254,7 +4292,7 @@ CCMD(fs_dir) auto fnid = fileSystem.GetResourceId(i); auto length = fileSystem.FileLength(i); bool hidden = fileSystem.FindFile(fn1) != i; - Printf(PRINT_HIGH | PRINT_NONOTIFY, "%s%-64s %-15s (%5d) %10d %s %s\n", hidden ? TEXTCOLOR_RED : TEXTCOLOR_UNTRANSLATED, fn1, fns, fnid, length, container, hidden ? "(h)" : ""); + Printf(PRINT_HIGH | PRINT_NONOTIFY, "%s%-64s %-15s (%5d) %10ld %s %s\n", hidden ? TEXTCOLOR_RED : TEXTCOLOR_UNTRANSLATED, fn1, fns, fnid, length, container, hidden ? "(h)" : ""); } } @@ -4265,6 +4303,6 @@ CCMD(type) if (lump >= 0) { auto data = fileSystem.ReadFile(lump); - Printf("%.*s\n", data.size(), data.string()); + Printf("%.*s\n", static_cast(data.size()), data.string()); } } diff --git a/src/d_main.h b/src/d_main.h index 661e2ab1e..7d0024da9 100644 --- a/src/d_main.h +++ b/src/d_main.h @@ -3,6 +3,7 @@ // Copyright 1993-1996 id Software // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -32,8 +33,10 @@ #include "gametype.h" #include "startupinfo.h" #include "c_cvars.h" +#include extern bool advancedemo; +extern volatile sig_atomic_t gameloop_abort; EXTERN_CVAR(Bool, hud_toggled); void D_ToggleHud(); @@ -123,6 +126,7 @@ class FIWadManager TArray mIWadInfos; TArray mIWadNames; TArray mSearchPaths; + TArray mRecursiveSearchPaths; TArray mOrderNames; TArray mFoundWads; TArray mLumpsFound; @@ -132,7 +136,7 @@ class FIWadManager int CheckIWADInfo(const char *iwad); int IdentifyVersion (std::vector& wadfiles, const char *iwad, const char *zdoom_wad, const char *optional_wad); void CollectSearchPaths(); - void AddIWADCandidates(const char *dir); + void AddIWADCandidates(const char *dir, bool nosubdir = true); void ValidateIWADs(); FString IWADPathFileSearch(const FString &file); public: diff --git a/src/d_net.cpp b/src/d_net.cpp index d2f60cab8..6c82fb182 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -26,52 +26,45 @@ //----------------------------------------------------------------------------- #include + #define __STDC_FORMAT_MACROS #include -#include "version.h" -#include "menu.h" -#include "i_video.h" -#include "i_net.h" -#include "g_game.h" -#include "c_console.h" -#include "d_netinf.h" -#include "d_net.h" -#include "cmdlib.h" -#include "m_cheat.h" -#include "p_local.h" -#include "c_dispatch.h" -#include "sbar.h" -#include "gi.h" -#include "m_misc.h" -#include "gameconfigfile.h" -#include "p_acs.h" -#include "p_trace.h" -#include "a_sharedglobal.h" -#include "common/widgets/netstartwindow.h" -#include "teaminfo.h" -#include "p_conversation.h" -#include "d_eventbase.h" -#include "p_enemy.h" -#include "m_argv.h" -#include "p_lnspec.h" -#include "p_spec.h" -#include "hardware.h" -#include "r_utility.h" #include "a_keys.h" -#include "intermission/intermission.h" -#include "g_levellocals.h" +#include "a_sharedglobal.h" #include "actorinlines.h" -#include "events.h" -#include "i_time.h" -#include "i_system.h" -#include "vm.h" -#include "gstrings.h" -#include "s_music.h" -#include "screenjob.h" +#include "c_dispatch.h" +#include "cmdlib.h" +#include "d_eventbase.h" #include "d_main.h" +#include "d_net.h" +#include "d_netinf.h" +#include "events.h" +#include "g_game.h" +#include "g_levellocals.h" +#include "gameconfigfile.h" +#include "gi.h" +#include "gstrings.h" #include "i_interface.h" +#include "i_net.h" +#include "i_system.h" +#include "i_time.h" +#include "m_argv.h" +#include "m_cheat.h" +#include "menu.h" +#include "p_conversation.h" +#include "p_enemy.h" +#include "p_lnspec.h" +#include "p_local.h" +#include "p_spec.h" +#include "p_trace.h" +#include "r_utility.h" +#include "s_music.h" #include "savegamemanager.h" +#include "sbar.h" +#include "screenjob.h" +#include "version.h" +#include "vm.h" void P_RunClientsideLogic(); @@ -673,7 +666,7 @@ static bool HGetPacket() size_t sizeCheck = GetNetBufferSize(); if (NetBufferLength != sizeCheck) { - Printf("Incorrect packet size %d (expected %d)\n", NetBufferLength, sizeCheck); + Printf("Incorrect packet size %zu (expected %zu)\n", NetBufferLength, sizeCheck); return false; } diff --git a/src/d_protocol.cpp b/src/d_protocol.cpp index fe7b397a6..36bcefe1c 100644 --- a/src/d_protocol.cpp +++ b/src/d_protocol.cpp @@ -282,7 +282,10 @@ void ReadUserCmdMessage(TArrayView& stream, int player, int tic) Net_SkipCommand(type, stream); // Subtract a byte to account for the fact the stream head is now sitting on the - // user command. + // user command. This gets cleared first because it previously got cleared after + // executing but that breaks packet-server mode and I have no idea what side effects + // might happen if it's not wiped first. + curTic.Data.SetData(nullptr, 0u); curTic.Data.SetData(start, int(stream.Data() - start - 1)); if (type == DEM_USERCMD) @@ -311,9 +314,6 @@ void RunPlayerCommands(int player, int tic) { while (stream.Size() > 0) Net_DoCommand(ReadInt8(stream), stream, player); - - if (!demorecording) - data.SetData(nullptr, 0); } } diff --git a/src/doomdef.h b/src/doomdef.h index b40d7a7bd..d3095ced8 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -3,6 +3,7 @@ // Copyright 1993-1996 id Software // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -30,6 +31,8 @@ #include #include + +#include "gi.h" #include "i_net.h" // @@ -49,7 +52,6 @@ typedef enum } GameMode_t; - // If rangecheck is undefined, most parameter validation debugging code // will not be compiled #ifndef NORANGECHECKING @@ -73,8 +75,6 @@ inline int Tics2Seconds(int tics) return tics / TICRATE; } - - typedef float skill_t; /* @@ -88,8 +88,6 @@ enum ESkillLevels }; */ - - #define TELEFOGHEIGHT (gameinfo.telefogheight) // @@ -273,7 +271,6 @@ enum #define FRICTION_LOW (0xf900/65536.) #define FRICTION_FLY (0xeb00/65536.) - #define BLINKTHRESHOLD (4*32) #endif // __DOOMDEF_H__ diff --git a/src/g_game.cpp b/src/g_game.cpp index f0bd76cd7..b4d406af2 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -211,7 +211,16 @@ CVAR (Bool, freelook, true, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) // Always mlook? CVAR (Bool, lookstrafe, false, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) // Always strafe with mouse? CVAR (Float, m_forward, 1.f, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) CVAR (Float, m_side, 2.f, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) - + +#define ANALOG_LOOK_BASE 1280 + +// You can change cl_analog_sensitivity_pitch's default to 1.6f if the old historical +// behavior is preferred, but IMO that is so fast that it's practically unplayable... +CVAR (Float, cl_analog_sensitivity_yaw, 1.f, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) +CVAR (Float, cl_analog_sensitivity_pitch, 0.6f, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) + +CVAR (Bool, cl_analog_straferun, false, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) + int turnheld; // for accelerative turning EXTERN_CVAR (Bool, invertmouse) @@ -611,6 +620,9 @@ void G_BuildTiccmd (usercmd_t *cmd) base = G_BaseTiccmd (); *cmd = *base; + // Update axis polling for the button map + buttonMap.GetAxes(); + strafe = buttonMap.ButtonDown(Button_Strafe); speed = buttonMap.ButtonDown(Button_Speed) ^ (int)cl_run; @@ -619,7 +631,7 @@ void G_BuildTiccmd (usercmd_t *cmd) // [RH] only use two stage accelerative turning on the keyboard // and not the joystick, since we treat the joystick as // the analog device it is. - if (buttonMap.ButtonDown(Button_Left) || buttonMap.ButtonDown(Button_Right)) + if (buttonMap.ButtonDownDigital(Button_Left) || buttonMap.ButtonDownDigital(Button_Right)) turnheld += TicDup; else turnheld = 0; @@ -627,9 +639,9 @@ void G_BuildTiccmd (usercmd_t *cmd) // let movement keys cancel each other out if (strafe) { - if (buttonMap.ButtonDown(Button_Right)) + if (buttonMap.ButtonDownDigital(Button_Right)) side += sidemove[speed]; - if (buttonMap.ButtonDown(Button_Left)) + if (buttonMap.ButtonDownDigital(Button_Left)) side -= sidemove[speed]; } else @@ -639,48 +651,48 @@ void G_BuildTiccmd (usercmd_t *cmd) if (turnheld < SLOWTURNTICS) tspeed += 2; // slow turn - if (buttonMap.ButtonDown(Button_Right)) + if (buttonMap.ButtonDownDigital(Button_Right)) { G_AddViewAngle (*angleturn[tspeed]); } - if (buttonMap.ButtonDown(Button_Left)) + if (buttonMap.ButtonDownDigital(Button_Left)) { G_AddViewAngle (-*angleturn[tspeed]); } } - if (buttonMap.ButtonDown(Button_LookUp)) + if (buttonMap.ButtonDownDigital(Button_LookUp)) { G_AddViewPitch (lookspeed[speed]); } - if (buttonMap.ButtonDown(Button_LookDown)) + if (buttonMap.ButtonDownDigital(Button_LookDown)) { G_AddViewPitch (-lookspeed[speed]); } - if (buttonMap.ButtonDown(Button_MoveUp)) + if (buttonMap.ButtonDownDigital(Button_MoveUp)) fly += flyspeed[speed]; - if (buttonMap.ButtonDown(Button_MoveDown)) + if (buttonMap.ButtonDownDigital(Button_MoveDown)) fly -= flyspeed[speed]; if (buttonMap.ButtonDown(Button_Klook)) { - if (buttonMap.ButtonDown(Button_Forward)) + if (buttonMap.ButtonDownDigital(Button_Forward)) G_AddViewPitch (lookspeed[speed]); - if (buttonMap.ButtonDown(Button_Back)) + if (buttonMap.ButtonDownDigital(Button_Back)) G_AddViewPitch (-lookspeed[speed]); } else { - if (buttonMap.ButtonDown(Button_Forward)) + if (buttonMap.ButtonDownDigital(Button_Forward)) forward += forwardmove[speed]; - if (buttonMap.ButtonDown(Button_Back)) + if (buttonMap.ButtonDownDigital(Button_Back)) forward -= forwardmove[speed]; } - if (buttonMap.ButtonDown(Button_MoveRight)) + if (buttonMap.ButtonDownDigital(Button_MoveRight)) side += sidemove[speed]; - if (buttonMap.ButtonDown(Button_MoveLeft)) + if (buttonMap.ButtonDownDigital(Button_MoveLeft)) side -= sidemove[speed]; // buttons @@ -712,35 +724,60 @@ void G_BuildTiccmd (usercmd_t *cmd) if (buttonMap.ButtonDown(Button_ShowScores)) cmd->buttons |= BT_SHOWSCORES; if (speed) cmd->buttons |= BT_RUN; - // Handle joysticks/game controllers. - float joyaxes[NUM_JOYAXIS]; - - I_GetAxes(joyaxes); - // Remap some axes depending on button state. + float axis_yaw = buttonMap.ButtonAnalog(Button_Left) - buttonMap.ButtonAnalog(Button_Right); + float axis_pitch = buttonMap.ButtonAnalog(Button_LookUp) - buttonMap.ButtonAnalog(Button_LookDown); + float axis_forward = buttonMap.ButtonAnalog(Button_Forward) - buttonMap.ButtonAnalog(Button_Back); + float axis_side = buttonMap.ButtonAnalog(Button_MoveLeft) - buttonMap.ButtonAnalog(Button_MoveRight); + float axis_up = buttonMap.ButtonAnalog(Button_MoveUp) - buttonMap.ButtonAnalog(Button_MoveDown); + if (buttonMap.ButtonDown(Button_Strafe) || (buttonMap.ButtonDown(Button_Mlook) && lookstrafe)) { - joyaxes[JOYAXIS_Side] = joyaxes[JOYAXIS_Yaw]; - joyaxes[JOYAXIS_Yaw] = 0; + axis_side = axis_yaw; + axis_yaw = 0.0f; } + if (buttonMap.ButtonDown(Button_Mlook)) { - joyaxes[JOYAXIS_Pitch] = joyaxes[JOYAXIS_Forward]; - joyaxes[JOYAXIS_Forward] = 0; + axis_pitch = axis_forward; + axis_forward = 0.0f; } - if (joyaxes[JOYAXIS_Pitch] != 0) + if (cl_analog_straferun) { - G_AddViewPitch(joyint(joyaxes[JOYAXIS_Pitch] * 2048)); - } - if (joyaxes[JOYAXIS_Yaw] != 0) - { - G_AddViewAngle(joyint(-1280 * joyaxes[JOYAXIS_Yaw])); + // Rescale diagonal analog input from roughly [0.77, 0.77] to [1.0, 1.0], + // which enables analog sticks to be able to strafe run like a keyboard can. + + // This is inaccurate to how Doom had originally handled analog input, but + // that's why it's an option, after all. + + const float sqrtOf2Frac = 0.41421356237309504880; // sqrt(2)'s fractional value + + float move_min = min(fabs(axis_side), fabs(axis_forward)); + float move_max = max(fabs(axis_side), fabs(axis_forward)); + + float scale = 1.0f; + if (move_max > EQUAL_EPSILON) + { + scale += (move_min / move_max) * sqrtOf2Frac; + } + + axis_forward = std::clamp(axis_forward * scale, -1.f, 1.f); + axis_side = std::clamp(axis_side * scale, -1.f, 1.f); } - side -= joyint(sidemove[speed] * joyaxes[JOYAXIS_Side]); - forward += joyint(joyaxes[JOYAXIS_Forward] * forwardmove[speed]); - fly += joyint(joyaxes[JOYAXIS_Up] * 2048); + if (axis_pitch != 0) + { + G_AddViewPitch(joyint(axis_pitch * ANALOG_LOOK_BASE * cl_analog_sensitivity_pitch)); + } + if (axis_yaw != 0) + { + G_AddViewAngle(joyint(-ANALOG_LOOK_BASE * cl_analog_sensitivity_yaw * axis_yaw)); + } + + side -= joyint(sidemove[speed] * axis_side); + forward += joyint(axis_forward * forwardmove[speed]); + fly += joyint(axis_up * 2048); // Handle mice. if (!buttonMap.ButtonDown(Button_Mlook) && !freelook) @@ -820,6 +857,17 @@ void G_BuildTiccmd (usercmd_t *cmd) cmd->sidemove <<= 8; } +ADD_STAT (analog) +{ + FString out; + + float axis_forward = buttonMap.ButtonAnalog(Button_Forward) - buttonMap.ButtonAnalog(Button_Back); + float axis_side = buttonMap.ButtonAnalog(Button_MoveLeft) - buttonMap.ButtonAnalog(Button_MoveRight); + out.AppendFormat("[%.3f, %.3f]", axis_forward, axis_side); + + return out; +} + static int LookAdjust(int look) { look <<= 16; @@ -2576,12 +2624,8 @@ void G_WriteDemoTiccmd (usercmd_t *cmd, int player, int buf) // [RH] Write any special "ticcmds" for this player to the demo if ((specdata = ClientStates[player].Tics[buf % BACKUPTICS].Data.GetData (&speclen)) && !(gametic % TicDup)) - { WriteBytes(TArrayView(specdata, speclen), demo_p); - ClientStates[player].Tics[buf % BACKUPTICS].Data.SetData(nullptr, 0); - } - // [RH] Now write out a "normal" ticcmd. WriteUserCmdMessage (*cmd, &players[player].cmd, demo_p); diff --git a/src/g_level.cpp b/src/g_level.cpp index 78cb97138..9c3c8c6d6 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1743,8 +1743,6 @@ void FLevelLocals::MoveTravellers() mo->section = nullptr; mo->floorsector = mo->ceilingsector = nullptr; } - - TravellingThinkers.Clear(); } //========================================================================== diff --git a/src/gameconfigfile.cpp b/src/gameconfigfile.cpp index 6886ff8f3..126660fb9 100644 --- a/src/gameconfigfile.cpp +++ b/src/gameconfigfile.cpp @@ -48,6 +48,7 @@ #include "gi.h" #include "d_main.h" #include "v_video.h" +#include "m_joy.h" #if !defined _MSC_VER && !defined __APPLE__ #include "i_system.h" // for SHARE_DIR #endif // !_MSC_VER && !__APPLE__ @@ -629,6 +630,13 @@ void FGameConfigFile::DoGlobalSetup () var->SetGenericRep(v, CVAR_Int); } } + if (last < 226) + { + // We can't handle key config yet, because + // the files aren't fully loaded. Just queue + // up a flag to do this later. + b226ResetGamepad = true; + } } } } @@ -739,6 +747,35 @@ void FGameConfigFile::DoKeySetup(const char *gamename) } } } + + if (b226ResetGamepad == true) + { + b226ResetGamepad = false; + + // Multiple gamepad reworks were done during + // this version. There is not any particularly + // good way to transfer older settings, so we + // are just going to reset them completely. + TArray sticks; + I_GetJoysticks(sticks); + + // Reset analog stick settings + for (int joy = 0; joy < sticks.SSize(); joy++) + { + sticks[joy]->Reset(); + } + + // Reset digital binds + TArray keys_to_reset; + keys_to_reset.Reserve(NUM_AXIS_CODES); + for (int i = 0; i < NUM_AXIS_CODES; i++) + { + keys_to_reset[i] = KeyAxisMapping[i]; + } + + C_SetDefaultBindings(&keys_to_reset); + } + OkayToWrite = true; } @@ -971,9 +1008,8 @@ void FGameConfigFile::SetRavenDefaults (bool isHexen) val.Bool = true; con_centernotify->SetGenericRepDefault (val, CVAR_Bool); snd_pitched->SetGenericRepDefault (val, CVAR_Bool); - val.Int = 9; - msg0color->SetGenericRepDefault (val, CVAR_Int); val.Int = CR_WHITE; + msg0color->SetGenericRepDefault (val, CVAR_Int); msgmidcolor->SetGenericRepDefault (val, CVAR_Int); val.Int = CR_YELLOW; msgmidcolor2->SetGenericRepDefault (val, CVAR_Int); diff --git a/src/gameconfigfile.h b/src/gameconfigfile.h index a67c8858e..b8bc1e876 100644 --- a/src/gameconfigfile.h +++ b/src/gameconfigfile.h @@ -69,6 +69,7 @@ private: void ReadCVars (unsigned flags); bool bModSetup; + bool b226ResetGamepad; char section[64]; char *subsection; diff --git a/src/gamedata/d_dehacked.cpp b/src/gamedata/d_dehacked.cpp index a42beba8c..f00de4997 100644 --- a/src/gamedata/d_dehacked.cpp +++ b/src/gamedata/d_dehacked.cpp @@ -38,36 +38,31 @@ ** executable, so it saves space on disk as well as in memory. */ +#include #include #include #include -#include -#include "doomtype.h" - -#include "doomstat.h" -#include "info.h" -#include "d_dehacked.h" -#include "g_level.h" -#include "cmdlib.h" -#include "gstrings.h" -#include "filesystem.h" -#include "d_player.h" -#include "r_state.h" -#include "c_dispatch.h" -#include "decallib.h" #include "a_sharedglobal.h" +#include "actorptrselect.h" +#include "cmdlib.h" +#include "codegen.h" +#include "d_dehacked.h" +#include "decallib.h" +#include "doomstat.h" +#include "doomtype.h" #include "engineerrors.h" +#include "filesystem.h" +#include "g_levellocals.h" +#include "gstrings.h" +#include "info.h" +#include "m_argv.h" #include "p_effect.h" +#include "r_state.h" #include "serializer.h" #include "thingdef.h" -#include "v_text.h" -#include "vmbuilder.h" #include "types.h" -#include "m_argv.h" -#include "actorptrselect.h" -#include "g_levellocals.h" -#include "codegen.h" +#include "vmbuilder.h" extern TArray TranslationColors; @@ -3055,7 +3050,7 @@ static int PatchSoundNames (int dummy, int flags) stripwhite(Line2); FString newname = skipwhite (Line2); ReplaceSoundName((int)strtoll(Line1, nullptr, 10), newname.GetChars()); - DPrintf (DMSG_SPAMMY, "Sound %d set to:\n%s\n", Line1, newname.GetChars()); + DPrintf (DMSG_SPAMMY, "Sound %p set to:\n%s\n", Line1, newname.GetChars()); // should %p be %s ? } return result; @@ -3090,7 +3085,7 @@ static int PatchSpriteNames (int dummy, int flags) int v = GetSpriteIndex(newname.GetChars()); memcpy(OrgSprNames[line1val].c, sprites[v].name, 5); - DPrintf (DMSG_SPAMMY, "Sprite %d set to:\n%s\n", Line1, newname.GetChars()); + DPrintf (DMSG_SPAMMY, "Sprite %p set to:\n%s\n", Line1, newname.GetChars()); // should %p be %s ? } return result; diff --git a/src/gamedata/doomfont.h b/src/gamedata/doomfont.h index fedeb513c..ac22180b7 100644 --- a/src/gamedata/doomfont.h +++ b/src/gamedata/doomfont.h @@ -2,14 +2,17 @@ // Globally visible constants. // #pragma once + +#include "engineerrors.h" +#include "texturemanager.h" +#include "v_font.h" + #define HU_FONTSTART uint8_t('!') // the first font characters #define HU_FONTEND uint8_t('\377') // the last font characters // Calculate # of glyphs in font. #define HU_FONTSIZE (HU_FONTEND - HU_FONTSTART + 1) - - void InitDoomFonts() { // load the heads-up font @@ -60,7 +63,6 @@ void InitDoomFonts() OriginalSmallFont = new FFont("OriginalSmallFont", "STCFN%.3d", "defsmallfont", HU_FONTSTART, HU_FONTSIZE, HU_FONTSTART, -1, -1, false, true, true); } - if (!(SmallFont2 = V_GetFont("SmallFont2"))) // Only used by Strife { if (fileSystem.CheckNumForName("STBFN033", FileSys::ns_graphics) >= 0) diff --git a/src/launcher/networkpage.cpp b/src/launcher/networkpage.cpp index 9750d3bc7..d2d6e5a23 100644 --- a/src/launcher/networkpage.cpp +++ b/src/launcher/networkpage.cpp @@ -12,6 +12,7 @@ #include #include #include +#include constexpr double EditHeight = 24.0; @@ -22,14 +23,14 @@ NetworkPage::NetworkPage(LauncherWindow* launcher, const FStartupSelectionInfo& SaveFileEdit = new LineEdit(this); SaveFileLabel = new TextLabel(this); SaveFileCheckbox = new CheckboxLabel(this); - SaveArgsCheckbox = new CheckboxLabel(this); - IWADsList = new ListView(this); + SaveParametersCheckbox = new CheckboxLabel(this); + IWADsDropdown = new Dropdown(this); SaveFileCheckbox->SetChecked(info.bSaveNetFile); if (!info.DefaultNetSaveFile.IsEmpty()) SaveFileEdit->SetText(info.DefaultNetSaveFile.GetChars()); - SaveArgsCheckbox->SetChecked(info.bSaveNetArgs); + SaveParametersCheckbox->SetChecked(info.bSaveNetArgs); if (!info.DefaultNetArgs.IsEmpty()) ParametersEdit->SetText(info.DefaultNetArgs.GetChars()); @@ -51,16 +52,11 @@ NetworkPage::NetworkPage(LauncherWindow* launcher, const FStartupSelectionInfo& else work = wad.Name.GetChars(); - IWADsList->AddItem(work.GetChars()); + IWADsDropdown->AddItem(work.GetChars()); } if (info.DefaultNetIWAD >= 0 && info.DefaultNetIWAD < info.Wads->SSize()) - { - IWADsList->SetSelectedItem(info.DefaultNetIWAD); - IWADsList->ScrollToItem(info.DefaultNetIWAD); - } - - IWADsList->OnActivated = [=]() { OnIWADsListActivated(); }; + IWADsDropdown->SetSelectedItem(info.DefaultNetIWAD); } // This has to be done after the main page is parented, otherwise it won't have the correct @@ -88,12 +84,12 @@ void NetworkPage::OnIWADsListActivated() void NetworkPage::OnSetFocus() { - IWADsList->SetFocus(); + IWADsDropdown->SetFocus(); } void NetworkPage::SetValues(FStartupSelectionInfo& info) const { - info.DefaultNetIWAD = IWADsList->GetSelectedItem(); + info.DefaultNetIWAD = IWADsDropdown->GetSelectedItem(); info.DefaultNetArgs = ParametersEdit->GetText(); info.bHosting = IsInHost(); @@ -109,7 +105,7 @@ void NetworkPage::SetValues(FStartupSelectionInfo& info) const } info.bSaveNetFile = SaveFileCheckbox->GetChecked(); - info.bSaveNetArgs = SaveArgsCheckbox->GetChecked(); + info.bSaveNetArgs = SaveParametersCheckbox->GetChecked(); const auto save = SaveFileEdit->GetText(); if (!save.empty()) info.AdditionalNetArgs.AppendFormat(" -loadgame %s", save.c_str()); @@ -131,29 +127,30 @@ void NetworkPage::OnGeometryChanged() const double w = GetWidth(); const double h = GetHeight(); - const double wSize = w * 0.45 - 2.5; + double y = 0.0; + IWADsDropdown->SetFrameGeometry(0.0, y, w, IWADsDropdown->GetPreferredHeight()); + y += IWADsDropdown->GetPreferredHeight() + 7.0; + const double pageTop = y; - double y = h - SaveArgsCheckbox->GetPreferredHeight(); - SaveArgsCheckbox->SetFrameGeometry(0.0, y, wSize, SaveArgsCheckbox->GetPreferredHeight()); + y = h - SaveFileCheckbox->GetPreferredHeight(); + SaveFileCheckbox->SetFrameGeometry(0.0, y, w, SaveFileCheckbox->GetPreferredHeight()); - y -= SaveFileCheckbox->GetPreferredHeight(); - SaveFileCheckbox->SetFrameGeometry(0.0, y, wSize, SaveFileCheckbox->GetPreferredHeight()); + y -= SaveParametersCheckbox->GetPreferredHeight(); + SaveParametersCheckbox->SetFrameGeometry(0.0, y, w, SaveParametersCheckbox->GetPreferredHeight()); y -= EditHeight + 2.0; - ParametersEdit->SetFrameGeometry(0.0, y, wSize, EditHeight); + ParametersEdit->SetFrameGeometry(0.0, y, w, EditHeight); y -= ParametersLabel->GetPreferredHeight(); - ParametersLabel->SetFrameGeometry(0.0, y, wSize, ParametersLabel->GetPreferredHeight()); + ParametersLabel->SetFrameGeometry(0.0, y, w, ParametersLabel->GetPreferredHeight()); + + const double wSize = w * 0.5; y -= EditHeight + 2.0; SaveFileEdit->SetFrameGeometry(0.0, y, wSize, EditHeight); y -= SaveFileLabel->GetPreferredHeight(); SaveFileLabel->SetFrameGeometry(0.0, y, wSize, SaveFileLabel->GetPreferredHeight()); - y -= 5.0; - IWADsList->SetFrameGeometry(0.0, 0.0, wSize, y); - - const double xOfs = w * 0.45 + 2.5; - StartPages->SetFrameGeometry(xOfs, 0.0, w - xOfs, h); + StartPages->SetFrameGeometry(0.0, pageTop, w, y - pageTop); } void NetworkPage::UpdateLanguage() @@ -161,7 +158,7 @@ void NetworkPage::UpdateLanguage() ParametersLabel->SetText(GStrings.GetString("PICKER_ADDPARM")); SaveFileLabel->SetText(GStrings.GetString("PICKER_LOADSAVE")); SaveFileCheckbox->SetText(GStrings.GetString("PICKER_REMSAVE")); - SaveArgsCheckbox->SetText(GStrings.GetString("PICKER_REMPARM")); + SaveParametersCheckbox->SetText(GStrings.GetString("PICKER_REMPARM")); StartPages->SetTabText(HostPage, GStrings.GetString("PICKER_HOST")); StartPages->SetTabText(JoinPage, GStrings.GetString("PICKER_JOIN")); @@ -172,70 +169,37 @@ void NetworkPage::UpdateLanguage() HostSubPage::HostSubPage(NetworkPage* main, const FStartupSelectionInfo& info) : Widget(nullptr), MainTab(main) { NetModesLabel = new TextLabel(this); - AutoNetmodeCheckbox = new CheckboxLabel(this); - PacketServerCheckbox = new CheckboxLabel(this); - PeerToPeerCheckbox = new CheckboxLabel(this); + NetModesDropdown = new Dropdown(this); - AutoNetmodeCheckbox->SetRadioStyle(true); - PacketServerCheckbox->SetRadioStyle(true); - PeerToPeerCheckbox->SetRadioStyle(true); - AutoNetmodeCheckbox->FuncChanged = [this](bool on) { if (on) { PacketServerCheckbox->SetChecked(false); PeerToPeerCheckbox->SetChecked(false); }}; - PacketServerCheckbox->FuncChanged = [this](bool on) { if (on) { AutoNetmodeCheckbox->SetChecked(false); PeerToPeerCheckbox->SetChecked(false); }}; - PeerToPeerCheckbox->FuncChanged = [this](bool on) { if (on) { PacketServerCheckbox->SetChecked(false); AutoNetmodeCheckbox->SetChecked(false); }}; - - switch (info.DefaultNetMode) - { - case 0: - PeerToPeerCheckbox->SetChecked(true); - break; - case 1: - PacketServerCheckbox->SetChecked(true); - break; - default: - AutoNetmodeCheckbox->SetChecked(true); - break; - } + NetModesDropdown->AddItem("Auto (recommended)"); + NetModesDropdown->AddItem("Packet-Server"); + NetModesDropdown->AddItem("Peer-to-Peer"); + NetModesDropdown->SetSelectedItem(max(info.DefaultNetMode, 0)); - TicDupList = new ListView(this); TicDupLabel = new TextLabel(this); + TicDupDropdown = new Dropdown(this); ExtraTicCheckbox = new CheckboxLabel(this); - std::vector widths = { 30.0, 30.0 }; - TicDupList->SetColumnWidths(widths); - TicDupList->AddItem("35.0"); - TicDupList->UpdateItem("Hz", 0, 1); - TicDupList->AddItem("17.5"); - TicDupList->UpdateItem("Hz", 1, 1); - TicDupList->AddItem("11.6"); - TicDupList->UpdateItem("Hz", 2, 1); - TicDupList->SetSelectedItem(info.DefaultNetTicDup); + TicDupDropdown->AddItem("35 Hz"); + TicDupDropdown->AddItem("17.5 Hz"); + TicDupDropdown->AddItem("11.6 Hz"); + TicDupDropdown->SetSelectedItem(max(info.DefaultNetTicDup, 0)); + ExtraTicCheckbox->SetChecked(info.DefaultNetExtraTic); GameModesLabel = new TextLabel(this); - CoopCheckbox = new CheckboxLabel(this); - DeathmatchCheckbox = new CheckboxLabel(this); + GameModesDropdown = new Dropdown(this); AltDeathmatchCheckbox = new CheckboxLabel(this); - TeamDeathmatchCheckbox = new CheckboxLabel(this); + + GameModesDropdown->AddItem("None"); + GameModesDropdown->AddItem("Co-op"); + GameModesDropdown->AddItem("Deathmatch"); + GameModesDropdown->AddItem("Team Deathmatch"); + GameModesDropdown->SetSelectedItem(max(info.DefaultNetGameMode, 0)); + TeamLabel = new TextLabel(this); TeamEdit = new LineEdit(this); - // These are intentionally not radio buttons, they just act similarly for clarity in the UI. - CoopCheckbox->FuncChanged = [this](bool on) { if (on) { DeathmatchCheckbox->SetChecked(false); TeamDeathmatchCheckbox->SetChecked(false); }}; - DeathmatchCheckbox->FuncChanged = [this](bool on) { if (on) { CoopCheckbox->SetChecked(false); TeamDeathmatchCheckbox->SetChecked(false); }}; - TeamDeathmatchCheckbox->FuncChanged = [this](bool on) { if (on) { CoopCheckbox->SetChecked(false); DeathmatchCheckbox->SetChecked(false); }}; - - switch (info.DefaultNetGameMode) - { - case 0: - CoopCheckbox->SetChecked(true); - break; - case 1: - DeathmatchCheckbox->SetChecked(true); - break; - case 2: - TeamDeathmatchCheckbox->SetChecked(true); - break; - } AltDeathmatchCheckbox->SetChecked(info.DefaultNetAltDM); TeamEdit->SetMaxLength(3); @@ -259,34 +223,31 @@ HostSubPage::HostSubPage(NetworkPage* main, const FStartupSelectionInfo& info) : PortHintLabel = new TextLabel(this); TeamHintLabel = new TextLabel(this); - MaxPlayerHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); - PortHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); - TeamHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); + // TODO: Themeable de-emphasis + // MaxPlayerHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); + // PortHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); + // TeamHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); } void HostSubPage::SetValues(FStartupSelectionInfo& info) const { info.AdditionalNetArgs = ""; - if (PacketServerCheckbox->GetChecked()) + info.DefaultNetMode = NetModesDropdown->GetSelectedItem(); + switch (info.DefaultNetMode) { + case 1: info.AdditionalNetArgs.AppendFormat(" -netmode 1"); - info.DefaultNetMode = 1; - } - else if (PeerToPeerCheckbox->GetChecked()) - { + break; + case 2: info.AdditionalNetArgs.AppendFormat(" -netmode 0"); - info.DefaultNetMode = 0; - } - else - { - info.DefaultNetMode = -1; + break; } info.DefaultNetExtraTic = ExtraTicCheckbox->GetChecked(); if (info.DefaultNetExtraTic) info.AdditionalNetArgs.AppendFormat(" -extratic"); - const int dup = TicDupList->GetSelectedItem(); + const int dup = TicDupDropdown->GetSelectedItem(); if (dup > 0) info.AdditionalNetArgs.AppendFormat(" -dup %d", dup + 1); info.DefaultNetTicDup = dup; @@ -305,60 +266,50 @@ void HostSubPage::SetValues(FStartupSelectionInfo& info) const } info.DefaultNetAltDM = AltDeathmatchCheckbox->GetChecked(); - if (CoopCheckbox->GetChecked()) + info.DefaultNetGameMode = GameModesDropdown->GetSelectedItem(); + switch (info.DefaultNetGameMode) { + case 1: info.AdditionalNetArgs.AppendFormat(" -coop"); - info.DefaultNetGameMode = 0; - } - else if (DeathmatchCheckbox->GetChecked()) - { - if (AltDeathmatchCheckbox->GetChecked()) - info.AdditionalNetArgs.AppendFormat(" -altdeath"); - else - info.AdditionalNetArgs.AppendFormat(" -deathmatch"); - - info.DefaultNetGameMode = 1; - } - else if (TeamDeathmatchCheckbox->GetChecked()) - { - if (AltDeathmatchCheckbox->GetChecked()) - info.AdditionalNetArgs.AppendFormat(" -altdeath"); - else - info.AdditionalNetArgs.AppendFormat(" -deathmatch"); - info.AdditionalNetArgs.AppendFormat(" +teamplay 1"); - info.DefaultNetGameMode = 2; - - int team = 255; - if (!TeamEdit->GetText().empty()) + break; + case 3: { - team = TeamEdit->GetTextInt(); - if (team < 0 || team > 255) - team = 255; + info.AdditionalNetArgs.AppendFormat(" +teamplay 1"); + int team = 255; + if (!TeamEdit->GetText().empty()) + { + team = TeamEdit->GetTextInt(); + if (team < 0 || team > 255) + team = 255; + } + info.AdditionalNetArgs.AppendFormat(" +team %d", team); + info.DefaultNetHostTeam = team; } - info.AdditionalNetArgs.AppendFormat(" +team %d", team); - info.DefaultNetHostTeam = team; - } - else - { - info.DefaultNetGameMode = -1; + case 2: + if (AltDeathmatchCheckbox->GetChecked()) + info.AdditionalNetArgs.AppendFormat(" -altdeath"); + else + info.AdditionalNetArgs.AppendFormat(" -deathmatch"); + break; } } void HostSubPage::UpdateLanguage() { NetModesLabel->SetText(GStrings.GetString("PICKER_NETMODE")); - AutoNetmodeCheckbox->SetText(GStrings.GetString("PICKER_NETAUTO")); - PacketServerCheckbox->SetText(GStrings.GetString("PICKER_NETSERVER")); - PeerToPeerCheckbox->SetText(GStrings.GetString("PICKER_NETPEER")); + NetModesDropdown->UpdateItem(GStrings.GetString("PICKER_NETAUTO"), 0); + NetModesDropdown->UpdateItem(GStrings.GetString("PICKER_NETSERVER"), 1); + NetModesDropdown->UpdateItem(GStrings.GetString("PICKER_NETPEER"), 2); TicDupLabel->SetText(GStrings.GetString("PICKER_NETRATE")); ExtraTicCheckbox->SetText(GStrings.GetString("PICKER_NETBACKUP")); GameModesLabel->SetText(GStrings.GetString("PICKER_GAMEMODE")); - CoopCheckbox->SetText(GStrings.GetString("PICKER_COOP")); - DeathmatchCheckbox->SetText(GStrings.GetString("PICKER_DM")); + GameModesDropdown->UpdateItem(GStrings.GetString("OPTVAL_NONE"), 0); + GameModesDropdown->UpdateItem(GStrings.GetString("PICKER_COOP"), 1); + GameModesDropdown->UpdateItem(GStrings.GetString("PICKER_DM"), 2); + GameModesDropdown->UpdateItem(GStrings.GetString("PICKER_TDM"), 3); AltDeathmatchCheckbox->SetText(GStrings.GetString("PICKER_ALTDM")); - TeamDeathmatchCheckbox->SetText(GStrings.GetString("PICKER_TDM")); TeamLabel->SetText(GStrings.GetString("PICKER_TEAM")); MaxPlayersLabel->SetText(GStrings.GetString("PICKER_PLAYERS")); @@ -374,67 +325,51 @@ void HostSubPage::OnGeometryChanged() const double w = GetWidth(); const double h = GetHeight(); - constexpr double LabelOfsSize = 90.0; - constexpr double hintOfs = 170.0; + constexpr double LabelOfsSize = 100.0; + constexpr double hintOfs = 160.0; + constexpr double DropdownSize = 220.0; + constexpr double EditWidth = 55.0; double y = 0.0; + const double wSize = w * 0.5; MaxPlayersLabel->SetFrameGeometry(0.0, y, LabelOfsSize, MaxPlayersLabel->GetPreferredHeight()); - MaxPlayersEdit->SetFrameGeometry(MaxPlayersLabel->GetWidth(), y, 30.0, EditHeight); + MaxPlayersEdit->SetFrameGeometry(LabelOfsSize, y, EditWidth, EditHeight); y += EditHeight + 2.0; PortLabel->SetFrameGeometry(0.0, y, LabelOfsSize, PortLabel->GetPreferredHeight()); - PortEdit->SetFrameGeometry(PortLabel->GetWidth(), y, 60.0, EditHeight); + PortEdit->SetFrameGeometry(LabelOfsSize, y, EditWidth, EditHeight); - MaxPlayerHintLabel->SetFrameGeometry(hintOfs, 0.0, w - hintOfs, MaxPlayerHintLabel->GetPreferredHeight()); - PortHintLabel->SetFrameGeometry(hintOfs, y, w - hintOfs, PortHintLabel->GetPreferredHeight()); + MaxPlayerHintLabel->SetFrameGeometry(hintOfs, 0.0, wSize - hintOfs, MaxPlayerHintLabel->GetPreferredHeight()); + PortHintLabel->SetFrameGeometry(hintOfs, y, wSize - hintOfs, PortHintLabel->GetPreferredHeight()); y += EditHeight + 7.0; - const double optionsTop = y; - TicDupLabel->SetFrameGeometry(0.0, y, 100.0, TicDupLabel->GetPreferredHeight()); - y += TicDupLabel->GetPreferredHeight(); - TicDupList->SetFrameGeometry(0.0, y, 100.0, (TicDupList->GetItemAmount() + 1) * 20.0); - y += TicDupList->GetHeight() + ExtraTicCheckbox->GetPreferredHeight() + 2.0; - - ExtraTicCheckbox->SetFrameGeometry(0.0, y, w, ExtraTicCheckbox->GetPreferredHeight()); - y += ExtraTicCheckbox->GetPreferredHeight(); - - const double optionsBottom = y; - y = optionsTop; - - constexpr double NetModeXOfs = 115.0; - NetModesLabel->SetFrameGeometry(NetModeXOfs, y, w - NetModeXOfs, NetModesLabel->GetPreferredHeight()); - y += NetModesLabel->GetPreferredHeight(); - - AutoNetmodeCheckbox->SetFrameGeometry(NetModeXOfs, y, w - NetModeXOfs, AutoNetmodeCheckbox->GetPreferredHeight()); - y += AutoNetmodeCheckbox->GetPreferredHeight(); - - PacketServerCheckbox->SetFrameGeometry(NetModeXOfs, y, w - NetModeXOfs, PacketServerCheckbox->GetPreferredHeight()); - y += PacketServerCheckbox->GetPreferredHeight(); - - PeerToPeerCheckbox->SetFrameGeometry(NetModeXOfs, y, w - NetModeXOfs, PeerToPeerCheckbox->GetPreferredHeight()); - y += PeerToPeerCheckbox->GetPreferredHeight(); - - y = max(optionsBottom, y) + 10.0; - GameModesLabel->SetFrameGeometry(0.0, y, w, GameModesLabel->GetPreferredHeight()); + GameModesLabel->SetFrameGeometry(0.0, y, wSize, GameModesLabel->GetPreferredHeight()); y += GameModesLabel->GetPreferredHeight(); + GameModesDropdown->SetFrameGeometry(0.0, y, DropdownSize, GameModesDropdown->GetPreferredHeight()); + y += GameModesDropdown->GetPreferredHeight() + 2.0; - CoopCheckbox->SetFrameGeometry(0.0, y, w, CoopCheckbox->GetPreferredHeight()); - y += CoopCheckbox->GetPreferredHeight(); - - DeathmatchCheckbox->SetFrameGeometry(0.0, y, w, DeathmatchCheckbox->GetPreferredHeight()); - y += DeathmatchCheckbox->GetPreferredHeight(); - - TeamDeathmatchCheckbox->SetFrameGeometry(0.0, y, w, TeamDeathmatchCheckbox->GetPreferredHeight()); - y += TeamDeathmatchCheckbox->GetPreferredHeight() + 2.0; - - TeamLabel->SetFrameGeometry(14.0, y, LabelOfsSize - 14.0, TeamLabel->GetPreferredHeight()); - TeamEdit->SetFrameGeometry(TeamLabel->GetWidth() + 14.0, y, 45.0, EditHeight); - TeamHintLabel->SetFrameGeometry(hintOfs, y, w - hintOfs, TeamHintLabel->GetPreferredHeight()); + TeamLabel->SetFrameGeometry(0.0, y, LabelOfsSize, TeamLabel->GetPreferredHeight()); + TeamEdit->SetFrameGeometry(LabelOfsSize, y, EditWidth, EditHeight); + TeamHintLabel->SetFrameGeometry(hintOfs, y, wSize - hintOfs, TeamHintLabel->GetPreferredHeight()); y += EditHeight + 2.0; - AltDeathmatchCheckbox->SetFrameGeometry(0.0, y, w, AltDeathmatchCheckbox->GetPreferredHeight()); + AltDeathmatchCheckbox->SetFrameGeometry(0.0, y, wSize, AltDeathmatchCheckbox->GetPreferredHeight()); + + y = 0.0; + + TicDupLabel->SetFrameGeometry(w - DropdownSize, y, DropdownSize, TicDupLabel->GetPreferredHeight()); + y += TicDupLabel->GetPreferredHeight(); + TicDupDropdown->SetFrameGeometry(w - DropdownSize, y, DropdownSize, TicDupDropdown->GetPreferredHeight()); + y += TicDupDropdown->GetPreferredHeight() + 2.0; + + NetModesLabel->SetFrameGeometry(w - DropdownSize, y, DropdownSize, NetModesLabel->GetPreferredHeight()); + y += NetModesLabel->GetPreferredHeight(); + NetModesDropdown->SetFrameGeometry(w - DropdownSize, y, DropdownSize, NetModesDropdown->GetPreferredHeight()); + y += NetModesDropdown->GetPreferredHeight() + 2.0; + + ExtraTicCheckbox->SetFrameGeometry(w - DropdownSize, y, DropdownSize, ExtraTicCheckbox->GetPreferredHeight()); MainTab->UpdatePlayButton(); } @@ -463,8 +398,9 @@ JoinSubPage::JoinSubPage(NetworkPage* main, const FStartupSelectionInfo& info) : AddressPortHintLabel = new TextLabel(this); TeamHintLabel = new TextLabel(this); - AddressPortHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); - TeamHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); + // TODO: Themeable de-emphasis + // AddressPortHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); + // TeamHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); } void JoinSubPage::SetValues(FStartupSelectionInfo& info) const @@ -513,26 +449,26 @@ void JoinSubPage::OnGeometryChanged() const double w = GetWidth(); const double h = GetHeight(); - constexpr double LabelOfsSize = 90.0; - constexpr double hintOfs = 170.0; + constexpr double LabelOfsSize = 100.0; + constexpr double hintOfs = 160.0; double y = 0.0; AddressLabel->SetFrameGeometry(0.0, y, LabelOfsSize, AddressLabel->GetPreferredHeight()); - AddressEdit->SetFrameGeometry(AddressLabel->GetWidth(), y, 120.0, EditHeight); + AddressEdit->SetFrameGeometry(LabelOfsSize, y, w * 0.5 - LabelOfsSize, EditHeight); y += EditHeight + 2.0; AddressPortLabel->SetFrameGeometry(0.0, y, LabelOfsSize, AddressPortLabel->GetPreferredHeight()); - AddressPortEdit->SetFrameGeometry(AddressPortLabel->GetWidth(), y, 60.0, EditHeight); + AddressPortEdit->SetFrameGeometry(LabelOfsSize, y, 55.0, EditHeight); AddressPortHintLabel->SetFrameGeometry(hintOfs, y, w - hintOfs, AddressPortHintLabel->GetPreferredHeight()); - y += EditHeight + 12.0; + y += EditHeight + 7.0; TeamDeathmatchLabel->SetFrameGeometry(0.0, y, w, TeamDeathmatchLabel->GetPreferredHeight()); y += TeamDeathmatchLabel->GetPreferredHeight(); TeamLabel->SetFrameGeometry(0.0, y, LabelOfsSize, TeamLabel->GetPreferredHeight()); - TeamEdit->SetFrameGeometry(TeamLabel->GetWidth(), y, 45.0, EditHeight); + TeamEdit->SetFrameGeometry(LabelOfsSize, y, 55.0, EditHeight); TeamHintLabel->SetFrameGeometry(hintOfs, y, w - hintOfs, TeamHintLabel->GetPreferredHeight()); MainTab->UpdatePlayButton(); diff --git a/src/launcher/networkpage.h b/src/launcher/networkpage.h index 23cce71cd..780d52747 100644 --- a/src/launcher/networkpage.h +++ b/src/launcher/networkpage.h @@ -6,7 +6,7 @@ class LauncherWindow; class CheckboxLabel; class LineEdit; -class ListView; +class Dropdown; class TextLabel; class PushButton; class TabWidget; @@ -33,19 +33,18 @@ private: LauncherWindow* Launcher = nullptr; - // Direct hook into the play page for these so their editing is synchronized. - LineEdit* ParametersEdit = nullptr; - ListView* IWADsList = nullptr; - TextLabel* ParametersLabel = nullptr; - + Dropdown* IWADsDropdown = nullptr; + TextLabel* SaveFileLabel = nullptr; + LineEdit* SaveFileEdit = nullptr; + CheckboxLabel* SaveFileCheckbox = nullptr; + HostSubPage* HostPage = nullptr; JoinSubPage* JoinPage = nullptr; TabWidget* StartPages = nullptr; - LineEdit* SaveFileEdit = nullptr; - TextLabel* SaveFileLabel = nullptr; - CheckboxLabel* SaveFileCheckbox = nullptr; - CheckboxLabel* SaveArgsCheckbox = nullptr; + TextLabel* ParametersLabel = nullptr; + LineEdit* ParametersEdit = nullptr; + CheckboxLabel* SaveParametersCheckbox = nullptr; }; class HostSubPage : public Widget @@ -61,29 +60,24 @@ private: NetworkPage* MainTab = nullptr; TextLabel* NetModesLabel = nullptr; - CheckboxLabel* AutoNetmodeCheckbox = nullptr; - CheckboxLabel* PeerToPeerCheckbox = nullptr; - CheckboxLabel* PacketServerCheckbox = nullptr; - ListView* TicDupList = nullptr; + Dropdown* NetModesDropdown = nullptr; TextLabel* TicDupLabel = nullptr; + Dropdown* TicDupDropdown = nullptr; CheckboxLabel* ExtraTicCheckbox = nullptr; TextLabel* GameModesLabel = nullptr; - CheckboxLabel* CoopCheckbox = nullptr; - CheckboxLabel* DeathmatchCheckbox = nullptr; + Dropdown* GameModesDropdown = nullptr; CheckboxLabel* AltDeathmatchCheckbox = nullptr; - CheckboxLabel* TeamDeathmatchCheckbox = nullptr; TextLabel* TeamLabel = nullptr; LineEdit* TeamEdit = nullptr; - - LineEdit* MaxPlayersEdit = nullptr; - LineEdit* PortEdit = nullptr; - TextLabel* MaxPlayersLabel = nullptr; - TextLabel* PortLabel = nullptr; - - TextLabel* MaxPlayerHintLabel = nullptr; - TextLabel* PortHintLabel = nullptr; TextLabel* TeamHintLabel = nullptr; + + TextLabel* MaxPlayersLabel = nullptr; + LineEdit* MaxPlayersEdit = nullptr; + TextLabel* MaxPlayerHintLabel = nullptr; + TextLabel* PortLabel = nullptr; + LineEdit* PortEdit = nullptr; + TextLabel* PortHintLabel = nullptr; }; class JoinSubPage : public Widget @@ -98,15 +92,14 @@ private: NetworkPage* MainTab = nullptr; - LineEdit* AddressEdit = nullptr; - LineEdit* AddressPortEdit = nullptr; TextLabel* AddressLabel = nullptr; + LineEdit* AddressEdit = nullptr; TextLabel* AddressPortLabel = nullptr; - + LineEdit* AddressPortEdit = nullptr; + TextLabel* AddressPortHintLabel = nullptr; + TextLabel* TeamDeathmatchLabel = nullptr; TextLabel* TeamLabel = nullptr; LineEdit* TeamEdit = nullptr; - - TextLabel* AddressPortHintLabel = nullptr; TextLabel* TeamHintLabel = nullptr; }; diff --git a/src/launcher/playgamepage.cpp b/src/launcher/playgamepage.cpp index f257fd648..5891334ba 100644 --- a/src/launcher/playgamepage.cpp +++ b/src/launcher/playgamepage.cpp @@ -84,33 +84,30 @@ void PlayGamePage::OnGeometryChanged() { double y = 10.0; - WelcomeLabel->SetFrameGeometry(0.0, y, GetWidth(), WelcomeLabel->GetPreferredHeight()); - y += WelcomeLabel->GetPreferredHeight(); + const double halfW = GetWidth() * 0.5; + WelcomeLabel->SetFrameGeometry(0.0, y, halfW, WelcomeLabel->GetPreferredHeight()); + VersionLabel->SetFrameGeometry(halfW, y, halfW, VersionLabel->GetPreferredHeight()); - VersionLabel->SetFrameGeometry(20.0, y - VersionLabel->GetPreferredHeight(), GetWidth() - 19.0, VersionLabel->GetPreferredHeight()); - - y += 10.0; + y += VersionLabel->GetPreferredHeight() + 10.0; SelectLabel->SetFrameGeometry(0.0, y, GetWidth(), SelectLabel->GetPreferredHeight()); y += SelectLabel->GetPreferredHeight(); - double listViewTop = y; + const double listViewTop = y; - y = GetHeight() - 10.0 - SaveArgsCheckbox->GetPreferredHeight(); + y = GetHeight() - SaveArgsCheckbox->GetPreferredHeight(); SaveArgsCheckbox->SetFrameGeometry(0.0, y, GetWidth(), SaveArgsCheckbox->GetPreferredHeight()); - y -= 2.0; - double editHeight = 24.0; - y -= editHeight; + const double editHeight = 24.0; + y -= editHeight + 2.0; ParametersEdit->SetFrameGeometry(0.0, y, GetWidth(), editHeight); - double labelHeight = ParametersLabel->GetPreferredHeight(); + const double labelHeight = ParametersLabel->GetPreferredHeight(); y -= labelHeight; ParametersLabel->SetFrameGeometry(0.0, y, GetWidth(), labelHeight); - y -= 10.0; - double listViewBottom = y - 10.0; - GamesList->SetFrameGeometry(0.0, listViewTop, GetWidth(), std::max(listViewBottom - listViewTop, 0.0)); + y -= 20.0; + GamesList->SetFrameGeometry(0.0, listViewTop, GetWidth(), std::max(y - listViewTop, 0.0)); Launcher->UpdatePlayButton(); } diff --git a/src/launcher/settingspage.cpp b/src/launcher/settingspage.cpp index f1f257a48..93bb47aca 100644 --- a/src/launcher/settingspage.cpp +++ b/src/launcher/settingspage.cpp @@ -177,7 +177,7 @@ void SettingsPage::OnGeometryChanged() y += DontAskAgainCheckbox->GetPreferredHeight(); SupportWadsCheckbox->SetFrameGeometry(0.0, y, 190.0, SupportWadsCheckbox->GetPreferredHeight()); - y += SupportWadsCheckbox->GetPreferredHeight(); + y += SupportWadsCheckbox->GetPreferredHeight() + 10.0; const double optionsBottom = y; #ifdef RENDER_BACKENDS diff --git a/src/maploader/glnodes.cpp b/src/maploader/glnodes.cpp index 33051b6c1..20637ae92 100644 --- a/src/maploader/glnodes.cpp +++ b/src/maploader/glnodes.cpp @@ -3,6 +3,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 2005-2010 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -442,20 +443,25 @@ bool MapLoader::LoadGLSubsectors(FileReader &lump) lump.Seek(0, FileReader::SeekSet); auto datab = lump.Read(); const unsigned numsegs = Level->segs.Size(); - + if (numsubsectors == 0) { return false; } - + if (!format5 && memcmp(datab.data(), "gNd3", 4)) { auto data = (const mapsubsector_t*) datab.data(); numsubsectors /= sizeof(mapsubsector_t); Level->subsectors.Alloc(numsubsectors); auto &subsectors = Level->subsectors; - memset(&subsectors[0],0,numsubsectors * sizeof(subsector_t)); - + + for (i = 0; i < numsubsectors; i++) + { + subsectors[i].sprites.Clear(); + memset((void*)&subsectors[i], 0, sizeof(subsector_t)); + } + for (i=0; isubsectors.Alloc(numsubsectors); auto &subsectors = Level->subsectors; - memset(&subsectors[0],0,numsubsectors * sizeof(subsector_t)); - + + for (i = 0; i < numsubsectors; i++) + { + subsectors[i].sprites.Clear(); + memset((void*)&subsectors[i], 0, sizeof(subsector_t)); + } + for (i=0; isubsectors.Alloc(numSubs); - memset (&Level->subsectors[0], 0, Level->subsectors.Size()*sizeof(subsector_t)); + + for (i = 0; i < Level->subsectors.Size(); i++) + { + Level->subsectors[i].sprites.Clear(); + memset((void*)&Level->subsectors[i], 0, sizeof(subsector_t)); + } for (i = currSeg = 0; i < numSubs; ++i) { @@ -1017,9 +1023,13 @@ bool MapLoader::LoadSubsectors (MapData * map) auto &subsectors = Level->subsectors; subsectors.Alloc(numsubsectors); auto &fr = map->Reader(ML_SSECTORS); - - memset (&subsectors[0], 0, numsubsectors*sizeof(subsector_t)); - + + for (unsigned i = 0; i < numsubsectors; i++) + { + subsectors[i].sprites.Clear(); + memset((void*)&subsectors[i], 0, sizeof(subsector_t)); + } + for (unsigned i = 0; i < numsubsectors; i++) { subsectortype subd; diff --git a/src/maploader/renderinfo.cpp b/src/maploader/renderinfo.cpp index 533526237..b2bc55865 100644 --- a/src/maploader/renderinfo.cpp +++ b/src/maploader/renderinfo.cpp @@ -1,7 +1,8 @@ // //--------------------------------------------------------------------------- // -// Copyright(C) 2005-2016 Christoph Oelckers +// Copyright 2005-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // All rights reserved. // // This program is free software: you can redistribute it and/or modify @@ -937,7 +938,10 @@ void MapLoader::FixHoles() DPrintf(DMSG_NOTIFY, "Adding dummy subsector for sector %d\n", Index(segloop[0]->Subsector->render_sector)); subsector_t &sub = Level->subsectors[newssstart++]; - memset(&sub, 0, sizeof(sub)); + + sub.sprites.Clear(); + memset((void*)&sub, 0, sizeof(sub)); + sub.sector = segloop[0]->frontsector; sub.render_sector = segloop[0]->Subsector->render_sector; sub.numlines = segloop.Size(); diff --git a/src/maploader/udmf.cpp b/src/maploader/udmf.cpp index 2cf9ccfbf..6b9996cf0 100644 --- a/src/maploader/udmf.cpp +++ b/src/maploader/udmf.cpp @@ -5,6 +5,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 2008 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -2732,7 +2733,7 @@ public: // Create the real vertices Level->vertexes.Alloc(ParsedVertices.Size()); - memcpy(&Level->vertexes[0], &ParsedVertices[0], Level->vertexes.Size() * sizeof(vertex_t)); + std::copy(&ParsedVertices[0], &ParsedVertices[0] + ParsedVertices.Size(), &Level->vertexes[0]); // Create the real sectors Level->sectors.Alloc(ParsedSectors.Size()); diff --git a/src/menu/doommenu.cpp b/src/menu/doommenu.cpp index dd24ed916..1c2323f98 100644 --- a/src/menu/doommenu.cpp +++ b/src/menu/doommenu.cpp @@ -1,9 +1,11 @@ /* -** menu.cpp +** doommenu.cpp ** Menu base class and global interface ** **--------------------------------------------------------------------------- +** ** Copyright 2010 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -28,6 +30,7 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ @@ -46,6 +49,8 @@ #include "gstrings.h" #include "hwrenderer/scene/hw_drawinfo.h" #include "i_interface.h" +#include "i_net.h" +#include "i_soundinternal.h" #include "i_time.h" #include "i_system.h" #include "menu.h" @@ -65,13 +70,13 @@ EXTERN_CVAR(Bool, m_quickexit) EXTERN_CVAR(Bool, saveloadconfirmation) // [mxd] EXTERN_CVAR(Bool, quicksaverotation) EXTERN_CVAR(Bool, show_messages) +EXTERN_CVAR(Bool, haptics_do_menus) EXTERN_CVAR(Float, hud_scalefactor) typedef void(*hfunc)(); DMenu* CreateMessageBoxMenu(DMenu* parent, const char* message, int messagemode, bool playsound, FName action = NAME_None, hfunc handler = nullptr); bool OkForLocalization(FTextureID texnum, const char* substitute); - FNewGameStartup NewGameStartupInfo; int LastSkill = -1; @@ -280,7 +285,6 @@ bool M_SetSpecialMenu(FName& menu, int param) } } - // End of special checks return true; } @@ -304,11 +308,10 @@ void OnMenuOpen(bool makeSound) if (makeSound) { - S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/activate", snd_menuvolume, ATTN_NONE); } } - //========================================================================== // // M_Dim @@ -350,7 +353,6 @@ void System_M_Dim() Dim(twod, dimmer, amount, 0, 0, twod->GetWidth(), twod->GetHeight()); } - static void M_Quit() { DeleteScreenJob(); @@ -366,6 +368,17 @@ static void M_Quit() // //============================================================================= +CCMD (quickexit) +{ + M_Quit(); +} + +//============================================================================= +// +// +// +//============================================================================= + CCMD (menu_quit) { // F10 if (m_quickexit) @@ -397,19 +410,16 @@ CCMD (menu_quit) { if (gameinfo.quitSound.IsNotEmpty()) { - S_Sound(CHAN_VOICE, CHANF_UI, gameinfo.quitSound, snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), gameinfo.quitSound, snd_menuvolume, ATTN_NONE); I_WaitVBL(105); } } M_Quit(); }); - M_ActivateMenu(newmenu); } - - //============================================================================= // // @@ -437,12 +447,12 @@ CCMD (menu_endgame) { // F7 if (!usergame) { - S_Sound (CHAN_VOICE, CHANF_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + S_Sound (CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/invalid", snd_menuvolume, ATTN_NONE); return; } //M_StartControlPanel (true); - S_Sound (CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + S_Sound (CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/activate", snd_menuvolume, ATTN_NONE); ActivateEndGameMenu(); } @@ -457,7 +467,7 @@ CCMD (quicksave) { // F6 if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) { - S_Sound (CHAN_VOICE, CHANF_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + S_Sound (CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/invalid", snd_menuvolume, ATTN_NONE); return; } @@ -476,7 +486,7 @@ CCMD (quicksave) if (savegameManager.quickSaveSlot == NULL || savegameManager.quickSaveSlot == (FSaveGameNode*)1) { - S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/activate", snd_menuvolume, ATTN_NONE); M_StartControlPanel(false); M_SetMenu(NAME_SavegameMenu); return; @@ -489,7 +499,7 @@ CCMD (quicksave) return; } - S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/activate", snd_menuvolume, ATTN_NONE); FString tempstring = GStrings.GetString("QSPROMPT"); tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars()); @@ -497,7 +507,8 @@ CCMD (quicksave) DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring.GetChars(), 0, false, NAME_None, []() { G_SaveGame(savegameManager.quickSaveSlot->Filename.GetChars(), savegameManager.quickSaveSlot->SaveTitle.GetChars()); - S_Sound(CHAN_VOICE, CHANF_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/dismiss", snd_menuvolume, ATTN_NONE); M_ClearMenus(); }); @@ -542,15 +553,12 @@ CCMD (quickload) DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring.GetChars(), 0, false, NAME_None, []() { G_LoadGame(savegameManager.quickSaveSlot->Filename.GetChars()); - S_Sound(CHAN_VOICE, CHANF_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/dismiss", snd_menuvolume, ATTN_NONE); M_ClearMenus(); }); M_ActivateMenu(newmenu); } - - - // // Toggle messages on/off // @@ -580,7 +588,8 @@ CCMD (sizedown) { screenblocks = screenblocks - 1; } - S_Sound (CHAN_VOICE, CHANF_UI, "menu/change", snd_menuvolume, ATTN_NONE); + + S_Sound (CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/change", snd_menuvolume, ATTN_NONE); } CCMD (sizeup) @@ -593,7 +602,8 @@ CCMD (sizeup) { screenblocks = screenblocks + 1; } - S_Sound(CHAN_VOICE, CHANF_UI, "menu/change", snd_menuvolume, ATTN_NONE); + + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/change", snd_menuvolume, ATTN_NONE); } CCMD(reset2defaults) @@ -616,7 +626,6 @@ CCMD(resetb2defaults) C_SetDefaultBindings (); } - //============================================================================= // // Creates the episode menu @@ -1010,7 +1019,6 @@ static void InitKeySections() } } - //============================================================================= // // Special menus will be created once all engine data is loaded @@ -1118,6 +1126,7 @@ DEFINE_ACTION_FUNCTION(DNewPlayerMenu, UpdateSkinOptions) // The skill menu must be refeshed each time it starts up // //============================================================================= + extern int restart; void M_StartupSkillMenu(FNewGameStartup *gs) diff --git a/src/p_setup.cpp b/src/p_setup.cpp index f2255311b..6b34019e8 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -297,6 +297,7 @@ void FLevelLocals::ClearLevelData(bool fullgc) probe->ClearNativePointerFields({ fieldTypes, std::size(fieldTypes) }); } + TravellingThinkers.Clear(); interpolator.ClearInterpolations(); // [RH] Nothing to interpolate on a fresh level. Thinkers.DestroyAllThinkers(fullgc); ClientsideThinkers.DestroyAllThinkers(fullgc); diff --git a/src/p_states.cpp b/src/p_states.cpp index 841bb4925..d7a997079 100644 --- a/src/p_states.cpp +++ b/src/p_states.cpp @@ -33,15 +33,14 @@ ** ** */ + #include "actor.h" -#include "cmdlib.h" #include "c_dispatch.h" -#include "v_text.h" -#include "thingdef.h" +#include "cmdlib.h" +#include "codegen.h" #include "r_state.h" #include "templates.h" -#include "codegen.h" - +#include "thingdef.h" // stores indices for symbolic state labels for some old-style DECORATE functions. FStateLabelStorage StateLabels; @@ -1097,16 +1096,18 @@ void DumpStateHelper(FStateLabels *StateList, const FString &prefix) if (state != NULL) { const PClassActor *owner = FState::StaticFindStateOwner(state); + auto pfx = prefix.GetChars(); + auto label = StateList->Labels[i].Label.GetChars(); if (owner == NULL) { if (state->DehIndex >= 0) - Printf(PRINT_LOG, "%s%s: DehExtra %d\n", prefix.GetChars(), state->DehIndex); + Printf(PRINT_LOG, "%s%s: DehExtra %d\n", pfx, label, state->DehIndex); else - Printf(PRINT_LOG, "%s%s: invalid\n", prefix.GetChars(), StateList->Labels[i].Label.GetChars()); + Printf(PRINT_LOG, "%s%s: invalid\n", pfx, label); } else { - Printf(PRINT_LOG, "%s%s: %s\n", prefix.GetChars(), StateList->Labels[i].Label.GetChars(), FState::StaticGetStateName(state).GetChars()); + Printf(PRINT_LOG, "%s%s: %s\n", pfx, label, FState::StaticGetStateName(state).GetChars()); } } if (StateList->Labels[i].Children != NULL) diff --git a/src/playsim/a_dynlight.cpp b/src/playsim/a_dynlight.cpp index 2728c321d..833d52e3f 100644 --- a/src/playsim/a_dynlight.cpp +++ b/src/playsim/a_dynlight.cpp @@ -1,7 +1,8 @@ // //--------------------------------------------------------------------------- // -// Copyright(C) 2004-2016 Christoph Oelckers +// Copyright 2004-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // All rights reserved. // // This program is free software: you can redistribute it and/or modify @@ -108,7 +109,7 @@ static FDynamicLight *GetLight(FLevelLocals *Level) FreeList.Pop(ret); } else ret = (FDynamicLight*)DynLightArena.Alloc(sizeof(FDynamicLight)); - memset(ret, 0, sizeof(*ret)); + memset((void*)ret, 0, sizeof(*ret)); ret = new(ret)FDynamicLight(); ret->m_cycler.m_increment = true; ret->next = Level->lights; diff --git a/src/playsim/a_morph.h b/src/playsim/a_morph.h index 762cd01bb..a19e4935d 100644 --- a/src/playsim/a_morph.h +++ b/src/playsim/a_morph.h @@ -1,6 +1,8 @@ #ifndef __A_MORPH__ #define __A_MORPH__ +#include "info.h" + #define MORPHTICS (40*TICRATE) #define MAXMORPHHEALTH 30 diff --git a/src/playsim/mapthinkers/a_ceiling.h b/src/playsim/mapthinkers/a_ceiling.h index 92b0c22bb..8494678bb 100644 --- a/src/playsim/mapthinkers/a_ceiling.h +++ b/src/playsim/mapthinkers/a_ceiling.h @@ -1,5 +1,7 @@ #pragma once +#include "dsectoreffect.h" + // // P_CEILING // diff --git a/src/playsim/p_acs.cpp b/src/playsim/p_acs.cpp index dda80c9ba..3ce65b755 100644 --- a/src/playsim/p_acs.cpp +++ b/src/playsim/p_acs.cpp @@ -37,47 +37,47 @@ #include -#include "templates.h" -#include "doomdef.h" -#include "p_local.h" -#include "d_player.h" -#include "p_spec.h" -#include "p_acs.h" -#include "p_saveg.h" -#include "p_lnspec.h" -#include "p_enemy.h" +#include "a_morph.h" +#include "a_sharedglobal.h" +#include "actorinlines.h" +#include "actorptrselect.h" +#include "c_bind.h" #include "c_console.h" #include "c_dispatch.h" +#include "cmdlib.h" +#include "d_player.h" +#include "decallib.h" +#include "doomdef.h" +#include "filesystem.h" +#include "g_game.h" +#include "g_levellocals.h" +#include "gi.h" +#include "gstrings.h" +#include "m_png.h" +#include "p_acs.h" +#include "p_effect.h" +#include "p_enemy.h" +#include "p_lnspec.h" +#include "p_local.h" +#include "p_saveg.h" +#include "p_setup.h" +#include "p_spec.h" +#include "p_terrain.h" +#include "po_man.h" +#include "r_sky.h" +#include "r_utility.h" +#include "s_music.h" #include "s_sndseq.h" #include "sbar.h" -#include "a_sharedglobal.h" -#include "filesystem.h" -#include "r_sky.h" -#include "gstrings.h" -#include "gi.h" -#include "g_game.h" -#include "c_bind.h" -#include "cmdlib.h" -#include "m_png.h" -#include "p_setup.h" -#include "po_man.h" -#include "actorptrselect.h" -#include "serializer_doom.h" -#include "serialize_obj.h" -#include "decallib.h" -#include "p_terrain.h" -#include "version.h" -#include "p_effect.h" -#include "r_utility.h" -#include "a_morph.h" -#include "thingdef.h" -#include "g_levellocals.h" -#include "actorinlines.h" -#include "types.h" #include "scriptutil.h" -#include "s_music.h" -#include "v_video.h" +#include "serialize_obj.h" +#include "serializer_doom.h" +#include "templates.h" #include "texturemanager.h" +#include "thingdef.h" +#include "types.h" +#include "v_video.h" +#include "version.h" // P-codes for ACS scripts enum @@ -470,7 +470,7 @@ PCD_TRANSLATIONRANGE4, PCD_TRANSLATIONRANGE5, -/*381*/ PCODE_COMMAND_COUNT +/*385*/ PCODE_COMMAND_COUNT }; // Some constants used by ACS scripts @@ -10880,7 +10880,7 @@ static void ShowProfileData(TArray &profiles, int ilimit, if (ilimit > 0) { - Printf(TEXTCOLOR_ORANGE "Top %lld %ss:\n", ilimit, typelabels[functions]); + Printf(TEXTCOLOR_ORANGE "Top %d %ss:\n", ilimit, typelabels[functions]); limit = (unsigned int)ilimit; } else diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index 575e16d2b..2d6e9d878 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -1689,6 +1689,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_SpawnParticleEx) PARAM_FLOAT (startroll) PARAM_FLOAT (rollvel) PARAM_FLOAT (rollacc) + PARAM_FLOAT (fadeoutstep) startalpha = clamp(startalpha, 0., 1.); fadestep = clamp(fadestep, -1.0, 1.0); @@ -1729,7 +1730,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_SpawnParticleEx) style = STYLE_None; } - P_SpawnParticle(self->Level, self->Vec3Offset(pos), vel, acc, color, startalpha, lifetime, size, fadestep, sizestep, flags, texid, ERenderStyle(style), startroll, rollvel, rollacc); + P_SpawnParticle(self->Level, self->Vec3Offset(pos), vel, acc, color, startalpha, lifetime, size, fadestep, sizestep, flags, texid, ERenderStyle(style), startroll, rollvel, rollacc, fadeoutstep); } return 0; } diff --git a/src/playsim/p_effect.cpp b/src/playsim/p_effect.cpp index 7f6ae19bd..db0383d1b 100644 --- a/src/playsim/p_effect.cpp +++ b/src/playsim/p_effect.cpp @@ -301,6 +301,18 @@ void P_ThinkParticles (FLevelLocals *Level) } particle->alpha -= particle->fadestep; + if ( (!!(particle->flags & SPF_FADE_IN_OUT) && particle->alpha >= 1.0) + || (!!(particle->flags & SPF_FADE_IN_HOLD_OUT) && (particle->ttl * fabs(particle->fadeoutstep)) <= std::min(1.0f, fabs(particle->alpha))) + ) { // [Jay] if SPF_FADE_IN_HOLD_OUT, hold until the fade out would line up with ttl + particle->alpha = std::min(1.0f, fabs(particle->alpha)); + particle->fadestep = particle->fadeoutstep; + particle->flags &= ~(SPF_FADE_IN_OUT|SPF_FADE_IN_HOLD_OUT); + } + + // It looks really glitchy if the alpha is allowed to overshoot + if (particle->alpha > 1.0) + particle->alpha = 1.0; + particle->size += particle->sizestep; if (particle->alpha <= 0 || --particle->ttl <= 0 || (particle->size <= 0)) { // The particle has expired, so free it @@ -345,7 +357,7 @@ void P_ThinkParticles (FLevelLocals *Level) } void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &vel, const DVector3 &accel, PalEntry color, double startalpha, int lifetime, double size, - double fadestep, double sizestep, int flags, FTextureID texture, ERenderStyle style, double startroll, double rollvel, double rollacc) + double fadestep, double sizestep, int flags, FTextureID texture, ERenderStyle style, double startroll, double rollvel, double rollacc, double fadeoutstep) { particle_t *particle = NewParticle(Level, !!(flags & SPF_REPLACE)); @@ -356,8 +368,21 @@ void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &v particle->Acc = FVector3(accel); particle->color = ParticleColor(color); particle->alpha = float(startalpha); - if ((fadestep < 0 && !(flags & SPF_NEGATIVE_FADESTEP)) || fadestep <= -1.0) particle->fadestep = FADEFROMTTL(lifetime); - else particle->fadestep = float(fadestep); + if ((flags & SPF_FADE_IN_OUT) || (flags & SPF_FADE_IN_HOLD_OUT)) + particle->fadeoutstep = fadeoutstep; + if ((fadestep < 0 && !(flags & SPF_NEGATIVE_FADESTEP)) || fadestep <= -1.0) + { + particle->fadestep = FADEFROMTTL(lifetime); + if (flags & SPF_FADE_IN_OUT) + particle->fadestep *= (float)-2; + else if (flags & SPF_FADE_IN_HOLD_OUT) + particle->fadestep *= (float)-3; + particle->fadeoutstep = -particle->fadestep; + } + else + { + particle->fadestep = float(fadestep); + } particle->ttl = lifetime; particle->size = size; particle->sizestep = sizestep; diff --git a/src/playsim/p_effect.h b/src/playsim/p_effect.h index b23efb01b..c722ab01e 100644 --- a/src/playsim/p_effect.h +++ b/src/playsim/p_effect.h @@ -79,6 +79,8 @@ enum EParticleFlags SPF_ROLLCENTER = 1 << 13, SPF_STRETCHPIXELS = 1 << 14, SPF_ALLOWSHADERS = 1 << 15, + SPF_FADE_IN_OUT = 1 << 16, + SPF_FADE_IN_HOLD_OUT = 1 << 17, }; class DVisualThinker; @@ -96,12 +98,14 @@ struct particle_t ERenderStyle style; //+4 = 88 float Roll, RollVel, RollAcc; //+12 = 100 uint16_t tnext, snext, tprev; //+6 = 106 - uint16_t flags; //+2 = 108 - // uint32_t padding; //+4 = 112 + // uint16_t padding; //+2 = 108 + uint32_t flags; //+4 = 112 FStandaloneAnimation animData; //+16 = 128 + float fadeoutstep; //+4 = 132 + // float padding2; //+4 = 136 }; -static_assert(sizeof(particle_t) == 128, "Only LP64/LLP64 is supported"); +static_assert(sizeof(particle_t) == 136, "Only LP64/LLP64 is supported"); const uint16_t NO_PARTICLE = 0xffff; @@ -134,13 +138,14 @@ struct FSpawnParticleParams double startalpha; double fadestep; + double fadeoutstep; double startroll; double rollvel; double rollacc; }; -void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &vel, const DVector3 &accel, PalEntry color, double startalpha, int lifetime, double size, double fadestep, double sizestep, int flags = 0, FTextureID texture = FNullTextureID(), ERenderStyle style = STYLE_None, double startroll = 0, double rollvel = 0, double rollacc = 0); +void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &vel, const DVector3 &accel, PalEntry color, double startalpha, int lifetime, double size, double fadestep, double sizestep, int flags = 0, FTextureID texture = FNullTextureID(), ERenderStyle style = STYLE_None, double startroll = 0, double rollvel = 0, double rollacc = 0, double fadeoutstep = 0); void P_InitEffects (void); diff --git a/src/playsim/p_enemy.cpp b/src/playsim/p_enemy.cpp index 4c654f3e4..1e9296bd9 100644 --- a/src/playsim/p_enemy.cpp +++ b/src/playsim/p_enemy.cpp @@ -5,6 +5,7 @@ // Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -28,31 +29,28 @@ // //----------------------------------------------------------------------------- - #include - -#include "m_random.h" - +#include "a_ceiling.h" +#include "a_sharedglobal.h" +#include "actorinlines.h" +#include "d_player.h" #include "doomdef.h" +#include "g_game.h" +#include "g_levellocals.h" +#include "gi.h" +#include "i_soundinternal.h" +#include "m_random.h" +#include "p_blockmap.h" +#include "p_checkposition.h" +#include "p_enemy.h" +#include "p_lnspec.h" #include "p_local.h" #include "p_maputl.h" -#include "d_player.h" -#include "p_lnspec.h" -#include "g_game.h" -#include "p_enemy.h" -#include "a_sharedglobal.h" -#include "p_blockmap.h" -#include "teaminfo.h" #include "p_spec.h" -#include "p_checkposition.h" -#include "g_levellocals.h" -#include "vm.h" -#include "actorinlines.h" -#include "a_ceiling.h" #include "shadowinlines.h" - -#include "gi.h" +#include "teaminfo.h" +#include "vm.h" static FRandom pr_checkmissilerange ("CheckMissileRange"); static FRandom pr_opendoor ("OpenDoor"); @@ -111,7 +109,6 @@ double yspeed[8] = {0,SQRTHALF,1,SQRTHALF,0,-SQRTHALF,-1,-SQRTHALF}; // but some can be made preaware // - //---------------------------------------------------------------------------- // // PROC P_RecursiveSound @@ -153,7 +150,6 @@ static void NoiseMarkSector(sector_t *sec, AActor *soundtarget, bool splash, AAc NoiseList.Push({ sec, soundblocks }); } - static void P_RecursiveSound(sector_t *sec, AActor *soundtarget, bool splash, AActor *emitter, int soundblocks, double maxdist) { bool checkabove = !sec->PortalBlocksSound(sector_t::ceiling); @@ -184,7 +180,6 @@ static void P_RecursiveSound(sector_t *sec, AActor *soundtarget, bool splash, AA } } - if (check->sidedef[1] == NULL || !(check->flags & ML_TWOSIDED)) { @@ -229,9 +224,6 @@ static void P_RecursiveSound(sector_t *sec, AActor *soundtarget, bool splash, AA } } - - - //---------------------------------------------------------------------------- // // PROC P_NoiseAlert @@ -366,7 +358,6 @@ static int P_CheckMissileRange (AActor *actor) if (actor->MeleeState == NULL) dist -= 128; // no melee attack, so fire more - if (actor->maxtargetrange > 0 && dist > actor->maxtargetrange) return false; // The Arch Vile's special behavior turned into a property @@ -673,7 +664,6 @@ static int P_Move (AActor *actor) else actor->AddZ(-actor->FloatSpeed); - // [RH] Check to make sure there's nothing in the way of the float if (P_TestMobjZ(actor)) { @@ -927,7 +917,6 @@ void P_DoNewChaseDir (AActor *actor, double deltax, double deltay) actor->movedir = DI_NODIR; // cannot move } - //============================================================================= // // killough 11/98: @@ -1456,8 +1445,8 @@ DEFINE_ACTION_FUNCTION(AActor, LookForEnemiesEx) while (it.Next(&cres)) { - if (cres.thing->player == nullptr && ValidEnemyInBlock(cres.thing, self, params) || - !noPlayers && cres.thing->player && isTargetablePlayer(self, cres.thing->player, allaround, params)) + if ((cres.thing->player == nullptr && ValidEnemyInBlock(cres.thing, self, params)) || + (!noPlayers && cres.thing->player && isTargetablePlayer(self, cres.thing->player, allaround, params))) targets->Push(cres.thing); } @@ -1763,7 +1752,6 @@ int P_LookForEnemies (AActor *actor, INTBOOL allaround, FLookExParams *params) return false; } - /* ================ = @@ -1832,7 +1820,6 @@ int P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params) if (actor->FriendPlayer == 0 && (!teamplay || actor->GetTeam() == TEAM_NONE)) return result; if (result || !deathmatch) return true; - } // [SP] if false, and in deathmatch, intentional fall-through else if (actor->flags8 & MF8_SEEFRIENDLYMONSTERS) { @@ -2042,7 +2029,6 @@ DEFINE_ACTION_FUNCTION(AActor, A_Look) return 0; } - //========================================================================== // // A_LookEx (int flags, fixed minseedist, fixed maxseedist, fixed maxheardist, fixed fov, state wakestate) @@ -2254,6 +2240,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_ClearLastHeard) // A_Wander // //========================================================================== + enum ChaseFlags { CHF_FASTCHASE = 1, @@ -2315,6 +2302,7 @@ void A_Wander(AActor *self, int flags) // A_Look2 // //========================================================================== + DEFINE_ACTION_FUNCTION(AActor, A_Look2) { PARAM_SELF_PROLOGUE(AActor); @@ -2379,6 +2367,7 @@ nosee: // enhancements. // //============================================================================= + #define CLASS_BOSS_STRAFE_RANGE 64*10 void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missilestate, bool playactive, bool nightmarefast, bool dontmove, int flags) @@ -2743,6 +2732,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi // functions to check. // //========================================================================== + // [MC] Code is almost a duplicate of CanCollideWith but with changes to // accommodate checking of just one actor. bool P_CanResurrect(AActor *raiser, AActor *thing) @@ -2942,7 +2932,6 @@ bool P_CheckForResurrection(AActor* self, bool usevilestates, FState* state = nu return false; } - // for internal use void A_Chase(AActor *self) { @@ -3007,6 +2996,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_DoChase, A_DoChaseNative) // A_FaceTracer // //============================================================================= + enum FAF_Flags { FAF_BOTTOM = 1, @@ -3122,6 +3112,7 @@ void A_FaceTarget(AActor *self) // New function to let monsters shoot a railgun // //=========================================================================== + DEFINE_ACTION_FUNCTION(AActor, A_MonsterRail) { PARAM_SELF_PROLOGUE(AActor); @@ -3223,7 +3214,13 @@ DEFINE_ACTION_FUNCTION(AActor, A_Pain) sfx_id = S_FindSound(pain_amount); } - S_Sound (self, CHAN_VOICE, 0, sfx_id, 1, ATTN_NORM); + IFVIRTUALPTR(self, AActor, PlayerHurtMakeRumble) + { + VMValue params[2] = { self, self->player->attacker.Get() }; + VMCall(func, params, 2, nullptr, 0); + } + + S_Sound (self, CHAN_VOICE, CHANF_NORUMBLE, sfx_id, 1, ATTN_NORM); } else if (self->PainSound.isvalid()) { diff --git a/src/playsim/p_lnspec.cpp b/src/playsim/p_lnspec.cpp index be85ba44f..62bb31456 100644 --- a/src/playsim/p_lnspec.cpp +++ b/src/playsim/p_lnspec.cpp @@ -3,7 +3,9 @@ ** Handles line specials ** **--------------------------------------------------------------------------- +** ** Copyright 1998-2007 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -28,36 +30,36 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** ** Each function returns true if it caused something to happen ** or false if it could not perform the desired action. */ -#include "doomstat.h" -#include "p_local.h" -#include "p_lnspec.h" -#include "p_enemy.h" -#include "g_level.h" -#include "v_palette.h" -#include "a_sharedglobal.h" -#include "a_lightning.h" #include "a_keys.h" -#include "gi.h" -#include "p_conversation.h" -#include "p_3dmidtex.h" -#include "d_net.h" +#include "a_sharedglobal.h" #include "d_event.h" -#include "gstrings.h" -#include "po_man.h" +#include "d_net.h" #include "d_player.h" -#include "r_utility.h" +#include "doomstat.h" #include "fragglescript/t_fs.h" -#include "p_spec.h" +#include "g_level.h" #include "g_levellocals.h" -#include "vm.h" +#include "gi.h" +#include "gstrings.h" +#include "p_3dmidtex.h" +#include "p_conversation.h" #include "p_destructible.h" +#include "p_enemy.h" +#include "p_lnspec.h" +#include "p_local.h" +#include "p_spec.h" +#include "po_man.h" +#include "r_utility.h" #include "s_sndseq.h" +#include "v_palette.h" +#include "vm.h" // Remaps EE sector change types to Generic_Floor values. According to the Eternity Wiki: /* @@ -71,7 +73,6 @@ */ static const uint8_t ChangeMap[8] = { 0, 1, 5, 3, 7, 2, 6, 0 }; - #define FUNC(a) static int a (FLevelLocals *Level, line_t *ln, AActor *it, bool backSide, \ int arg0, int arg1, int arg2, int arg3, int arg4) @@ -134,7 +135,7 @@ FName MODtoDamageType (int mod) } } -int NativeStartConversation(AActor* self, AActor* player, bool faceTalker, bool saveAngle); +int NativeStartConversation(AActor* self, AActor* player, bool faceTalker, bool saveAngle, bool rumble); FUNC(LS_NOP) { @@ -585,7 +586,6 @@ FUNC(LS_Floor_Stop) return Level->EV_StopFloor(arg0, ln); } - FUNC(LS_Stairs_BuildDown) // Stair_BuildDown (tag, speed, height, delay, reset) { @@ -649,7 +649,6 @@ FUNC(LS_Stairs_BuildUpDoomSync) arg2, SPEED(arg1), 0, arg3, 0, DFloor::stairSync); } - FUNC(LS_Generic_Stairs) // Generic_Stairs (tag, speed, step, dir/igntxt, reset) { @@ -896,7 +895,6 @@ FUNC(LS_Ceiling_Stop) return Level->EV_StopCeiling(arg0, ln); } - FUNC(LS_Generic_Ceiling) // Generic_Ceiling (tag, speed, height, target, change/model/direct/crush) { @@ -1038,7 +1036,6 @@ FUNC(LS_Plat_RaiseAndStayTx0) break; } - return Level->EV_DoPlat (arg0, ln, type, 0, SPEED(arg1), 0, 0, 1); } @@ -1824,7 +1821,6 @@ FUNC(LS_Thing_Stop) return ok; } - FUNC(LS_Thing_SetGoal) // Thing_SetGoal (tid, goal, delay, chasegoal) { @@ -2036,8 +2032,6 @@ FUNC(LS_FS_Execute) return T_RunScript(Level, arg0, it); } - - FUNC(LS_FloorAndCeiling_LowerByValue) // FloorAndCeiling_LowerByValue (tag, speed, height) { @@ -2242,9 +2236,6 @@ FUNC(LS_Sector_ChangeFlags) return rtn; } - - - FUNC(LS_Sector_SetWind) // Sector_SetWind (tag, amount, angle) { @@ -2306,7 +2297,6 @@ FUNC(LS_Sector_SetLink) void SetWallScroller(FLevelLocals *Level, int id, int sidechoice, double dx, double dy, EScrollPos Where); void SetScroller(FLevelLocals *Level, int tag, EScroll type, double dx, double dy); - FUNC(LS_Scroll_Texture_Both) // Scroll_Texture_Both (id, left, right, up, down) { @@ -2838,7 +2828,6 @@ FUNC(LS_Line_SetAutomapStyle) return false; } - FUNC(LS_ChangeCamera) // ChangeCamera (tid, who, revert?) { @@ -3403,7 +3392,7 @@ FUNC(LS_StartConversation) return false; } - return NativeStartConversation(target, it, !!arg1, true); + return NativeStartConversation(target, it, !!arg1, true, false); // TODO: expose rumble? } FUNC(LS_Thing_SetConversation) @@ -3458,7 +3447,6 @@ FUNC(LS_Sector_SetPlaneReflection) return true; } - FUNC(LS_SetGlobalFogParameter) // SetGlobalFogParameter (type, value) { @@ -3962,7 +3950,6 @@ int P_FindLineSpecial (const char *string, int *min_args, int *max_args) return 0; } - //========================================================================== // // P_ExecuteSpecial @@ -3991,6 +3978,7 @@ int P_ExecuteSpecial(FLevelLocals *Level, int num, // Execute a line special / script // //========================================================================== + DEFINE_ACTION_FUNCTION(FLevelLocals, ExecuteSpecial) { PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals); diff --git a/src/playsim/p_map.cpp b/src/playsim/p_map.cpp index 2350ad058..aa6b54dd3 100644 --- a/src/playsim/p_map.cpp +++ b/src/playsim/p_map.cpp @@ -5,6 +5,7 @@ // Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman // Copyright 1999-2016 Randy Heit // Copyright 2002-2017 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -57,45 +58,42 @@ ** */ -#include -#include #include - - - -#include "m_bbox.h" -#include "m_random.h" -#include "c_dispatch.h" - -#include "doomdef.h" -#include "p_local.h" -#include "p_spec.h" -#include "d_player.h" -#include "p_maputl.h" -#include "p_lnspec.h" -#include "p_effect.h" -#include "p_terrain.h" -#include "p_trace.h" -#include "p_checkposition.h" -#include "p_linetracedata.h" -#include "r_utility.h" -#include "p_blockmap.h" -#include "p_3dmidtex.h" -#include "events.h" -#include "vm.h" -#include "d_main.h" -#include "gi.h" - -#include "decallib.h" - -// State. +#include +#include #include "a_sharedglobal.h" -#include "p_conversation.h" -#include "r_sky.h" -#include "g_levellocals.h" #include "actorinlines.h" -#include +#include "c_cvars.h" +#include "c_dispatch.h" +#include "d_main.h" +#include "d_player.h" +#include "decallib.h" +#include "doomdef.h" +#include "events.h" +#include "g_levellocals.h" +#include "gi.h" +#include "m_bbox.h" +#include "m_haptics.h" +#include "m_random.h" +#include "p_3dmidtex.h" +#include "p_blockmap.h" +#include "p_checkposition.h" +#include "p_conversation.h" +#include "p_effect.h" +#include "p_linetracedata.h" +#include "p_lnspec.h" +#include "p_local.h" +#include "p_maputl.h" +#include "p_spec.h" +#include "p_terrain.h" +#include "p_trace.h" +#include "r_sky.h" +#include "r_utility.h" +#include "shadowinlines.h" +#include "vm.h" + +// State. CVAR(Bool, cl_bloodsplats, true, CVAR_ARCHIVE) CVAR(Int, sv_smartaim, 0, CVAR_ARCHIVE | CVAR_SERVERINFO) @@ -116,6 +114,7 @@ TArray spechit; TArray portalhit; EXTERN_CVAR(Bool, net_limitconversations) +EXTERN_CVAR(Bool, haptics_do_menus) //========================================================================== // @@ -276,6 +275,7 @@ static DVector2 FindRefPoint(line_t *ld, const DVector2 &pos) // only3d set means to only check against 3D floors and midtexes. // //========================================================================== + bool ffcf_verbose; static bool PIT_FindFloorCeiling(FMultiBlockLinesIterator &mit, FMultiBlockLinesIterator::CheckResult &cres, const FBoundingBox &box, FCheckPosition &tmf, int flags) @@ -343,7 +343,6 @@ static bool PIT_FindFloorCeiling(FMultiBlockLinesIterator &mit, FMultiBlockLines return true; } - //========================================================================== // // calculates the actual floor and ceiling position at a given @@ -449,7 +448,6 @@ void P_FindFloorCeiling(AActor *actor, int flags) } } - // Debug CCMD for checking errors in the MultiBlockLinesIterator (needs to be removed when this code is complete) CCMD(ffcf) { @@ -481,7 +479,6 @@ bool P_TeleportMove(AActor* thing, const DVector3 &pos, bool telefrag, bool modi // kill anything occupying the position - // The base floor/ceiling is from the subsector that contains the point. // Any contacted lines the step closer together will adjust them. tmf.thing = thing; @@ -1225,7 +1222,6 @@ static bool PIT_CheckPortal(FMultiBlockLinesIterator &mit, FMultiBlockLinesItera return ret; } - //========================================================================== // // Isolated to keep the code readable and fix the logic @@ -1239,7 +1235,6 @@ static bool CheckRipLevel(AActor *victim, AActor *projectile) return true; } - //========================================================================== // // Isolated to keep the code readable and allow reuse in other attacks @@ -1386,6 +1381,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, DoMissileDamage, P_DoMissileDamage) P_DoMissileDamage(self, victim); return 0; } + //========================================================================== // // PIT_CheckThing @@ -1511,7 +1507,6 @@ bool PIT_CheckThing(FMultiBlockThingsIterator &it, FMultiBlockThingsIterator::Ch if (!P_CanCollideWith(tm.thing, thing)) return true; } - if (tm.thing->player == NULL || !(tm.thing->player->cheats & CF_PREDICTING)) { // touchy object is alive, toucher is solid @@ -1719,11 +1714,30 @@ bool PIT_CheckThing(FMultiBlockThingsIterator &it, FMultiBlockThingsIterator::Ch } if (thing->flags2 & MF2_PUSHABLE && !(tm.thing->flags2 & MF2_CANNOTPUSH)) { // Push thing + if (thing->lastpush != tm.PushTime) { thing->PlayPushSound(); thing->Vel += tm.thing->Vel.XY() * thing->pushfactor; thing->lastpush = tm.PushTime; + + if (tm.thing == players[consoleplayer].mo || tm.thing == players[consoleplayer].camera) + { + IFVIRTUALPTR(tm.thing, AActor, PlayerPushedSomethingMakeRumble) + { + VMValue params[1] = { thing }; + VMCall(func, params, 1, nullptr, 0); + } + } + else if (thing == players[consoleplayer].mo || thing == players[consoleplayer].camera) + { + // I assume the player can be pushed, correct? + IFVIRTUALPTR(thing, AActor, PlayerWasPushedMakeRumble) + { + VMValue params[1] = { tm.thing }; + VMCall(func, params, 1, nullptr, 0); + } + } } } solid = (thing->flags & MF_SOLID) && @@ -1750,8 +1764,6 @@ bool PIT_CheckThing(FMultiBlockThingsIterator &it, FMultiBlockThingsIterator::Ch // return !(thing->flags & MF_SOLID); // old code -- killough } - - /* =============================================================================== @@ -1958,7 +1970,6 @@ bool P_CheckPosition(AActor *thing, const DVector2 &pos, FCheckPosition &tm, boo if (actorsonly || (thing->flags & MF_NOCLIP)) return (thing->BlockingMobj = thingblocker) == NULL; - FMultiBlockLinesIterator it(pcheck, thing->Level, pos.X, pos.Y, thing->Z(), thing->Height, thing->radius, newsec); FMultiBlockLinesIterator::CheckResult lcres; @@ -2013,7 +2024,6 @@ bool P_CheckPosition(AActor *thing, const DVector2 &pos, bool actorsonly) return P_CheckPosition(thing, pos, tm, actorsonly); } - //---------------------------------------------------------------------------- // // FUNC P_TestMobjLocation @@ -2217,7 +2227,6 @@ int P_TestMobjZ(AActor *actor, bool quick, AActor **pOnmobj) return onmobj == NULL; } - //============================================================================= // // P_FakeZMovement @@ -2496,7 +2505,6 @@ bool P_TryMove(AActor *thing, const DVector2 &pos, dropoff = false; } - // killough 3/15/98: Allow certain objects to drop off if ((!dropoff && !(thing->flags & (MF_DROPOFF | MF_FLOAT | MF_MISSILE))) || (thing->flags5&MF5_NODROPOFF)) { @@ -2582,7 +2590,6 @@ bool P_TryMove(AActor *thing, const DVector2 &pos, return false; } - // Check for crossed portals bool portalcrossed; portalcrossed = false; @@ -2698,8 +2705,6 @@ bool P_TryMove(AActor *thing, const DVector2 &pos, break; } - - if (!portalcrossed) { // the move is ok, so link the thing into its new position @@ -2865,7 +2870,6 @@ bool P_TryMove(AActor *thing, const DVector2 &pos, return P_TryMove(thing, pos, dropoff, onfloor, tm, missilecheck); } - //========================================================================== // // P_CheckMove @@ -2973,7 +2977,6 @@ bool P_CheckMove(AActor *thing, const DVector2 &pos, int flags) return P_CheckMove(thing, pos, tm, flags); } - //========================================================================== // // SLIDE MOVE @@ -3137,7 +3140,6 @@ void FSlide::HitSlideLine(line_t* ld) } // phares } - //========================================================================== // // PTR_SlideTraverse @@ -3218,8 +3220,6 @@ void FSlide::SlideTraverse(const DVector2 &start, const DVector2 &end) } } - - //========================================================================== // // P_SlideMove @@ -3518,7 +3518,6 @@ bool FSlide::BounceTraverse(const DVector2 &start, const DVector2 &end) goto bounceblocking; } - P_LineOpening(open, slidemo, li, it.InterceptPoint(in)); // set openrange, opentop, openbottom if (open.range < slidemo->Height) goto bounceblocking; // doesn't fit @@ -3988,7 +3987,6 @@ struct aim_t return result; } - //============================================================================ // // AimTraverse3DFloors @@ -4031,7 +4029,6 @@ struct aim_t double ff_bottom = rover->bottom.plane->ZatPoint(trX, trY); double ff_top = rover->top.plane->ZatPoint(trX, trY); - highpitch = -VecToAngle(dist, ff_top - shootz); lowpitch = -VecToAngle(dist, ff_bottom - shootz); @@ -4106,7 +4103,6 @@ struct aim_t else if (position == sector_t::floor && portalz > limitz) return; aim_t newtrace = Clone(); - newtrace.toppitch = newtoppitch; newtrace.bottompitch = newbottompitch; newtrace.aimdir = position == sector_t::ceiling? aim_t::aim_up : aim_t::aim_down; @@ -4302,7 +4298,6 @@ struct aim_t return; } - if (!(li->flags & ML_TWOSIDED) || (li->flags & ML_BLOCKEVERYTHING)) return; // stop @@ -4690,7 +4685,6 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance, return nullptr; } - bool nointeract = !!(flags & LAF_NOINTERACT); DVector3 direction; double shootz; @@ -4705,7 +4699,6 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance, if (flags & LAF_NORANDOMPUFFZ) puffFlags |= PF_NORANDOMZ; - if (victim != NULL) { memset(victim, 0, sizeof(*victim)); @@ -5037,6 +5030,7 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance, // P_LineTrace // //========================================================================== + struct CheckLineData { AActor *Caller; @@ -5216,7 +5210,6 @@ void P_TraceBleed(int damage, const DVector3 &pos, AActor *actor, DAngle angle, int count; double noise; - if ((actor->flags & MF_NOBLOOD) || (actor->flags5 & MF5_NOBLOODDECALS) || (actor->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)) || @@ -5297,7 +5290,6 @@ void P_TraceBleed(int damage, AActor *target, DAngle angle, DAngle pitch) P_TraceBleed(damage, target->PosPlusZ(target->Height/2), target, angle, pitch); } - //========================================================================== // // @@ -5344,7 +5336,6 @@ void P_TraceBleed(int damage, FTranslatedLineTarget *t, AActor *puff) P_TraceBleed(damage, t->linetarget->PosPlusZ(t->linetarget->Height/2), t->linetarget, t->angleFromSource, pitch); } - //========================================================================== // // @@ -5459,6 +5450,7 @@ static ETraceStatus ProcessRailHit(FTraceResults &res, void *userdata) // // //========================================================================== + void P_RailAttack(FRailParams *p) { AActor *source = p->source; @@ -5741,7 +5733,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, HasConversation, HasConversation) ACTION_RETURN_BOOL(HasConversation(self)); } -int NativeStartConversation(AActor *self, AActor *player, bool faceTalker, bool saveAngle) +int NativeStartConversation(AActor *self, AActor *player, bool faceTalker, bool saveAngle, bool rumble) { if (!CanTalk(self)) return false; @@ -5753,6 +5745,9 @@ int NativeStartConversation(AActor *self, AActor *player, bool faceTalker, bool return false; } + if (rumble && haptics_do_menus) + Joy_Rumble("menu/activate"); + self->ConversationAnimation(0); P_StartConversation(self, player, faceTalker, saveAngle); return true; @@ -5764,7 +5759,8 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, StartConversation, NativeStartConversation PARAM_OBJECT(player, AActor); PARAM_BOOL(faceTalker); PARAM_BOOL(saveAngle); - ACTION_RETURN_BOOL(NativeStartConversation(self, player, faceTalker, saveAngle)); + PARAM_BOOL(rumble); + ACTION_RETURN_BOOL(NativeStartConversation(self, player, faceTalker, saveAngle, rumble)); } bool P_TalkFacing(AActor *player) @@ -5776,7 +5772,9 @@ bool P_TalkFacing(AActor *player) { P_AimLineAttack(player, player->Angles.Yaw + DAngle::fromDeg(angle), TALKRANGE, &t, DAngle::fromDeg(35.), ALF_FORCENOSMART | ALF_CHECKCONVERSATION | ALF_PORTALRESTRICT); if (t.linetarget != nullptr) - return NativeStartConversation(t.linetarget, player, true, true); + { + return NativeStartConversation(t.linetarget, player, true, true, true); + } } return false; } @@ -6066,7 +6064,6 @@ int P_UsePuzzleItem(AActor *PuzzleItemUser, int PuzzleItemType) // //========================================================================== - // [RH] Damage scale to apply to thing that shot the missile. static float selfthrustscale; @@ -6358,7 +6355,8 @@ int P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, double if (!(flags & RADF_NODAMAGE) && !(bombspot->flags3 & MF3_BLOODLESSIMPACT)) P_TraceBleed(newdam > 0 ? newdam : damage, thing, bombspot); - if ((flags & RADF_NODAMAGE && !(flags & RADF_THRUSTLESS)) || !(bombspot->flags2 & MF2_NODMGTHRUST) && !(flags & RADF_THRUSTLESS)) + if ((flags & RADF_NODAMAGE && !(flags & RADF_THRUSTLESS)) || + (!(bombspot->flags2 & MF2_NODMGTHRUST) && !(flags & RADF_THRUSTLESS))) { if (bombsource == NULL || !(bombsource->flags2 & MF2_NODMGTHRUST)) { @@ -6451,7 +6449,6 @@ int P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, double // //========================================================================== - struct FChangePosition { sector_t *sector; @@ -7073,7 +7070,6 @@ bool P_ChangeSector(sector_t *sector, int crunch, double amt, int floorOrCeil, b unsigned i; sector_t* sec; - // Use different functions for the four different types of sector movement. // for 3D-floors the meaning of floor and ceiling is inverted!!! if (floorOrCeil == 1) diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 63a0f2f06..e8dcaa3c5 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -5,6 +5,7 @@ // Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman // Copyright 1999-2016 Randy Heit // Copyright 2002-2017 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -57,52 +58,50 @@ */ // HEADER FILES ------------------------------------------------------------ + #include - -#include "m_random.h" -#include "doomdef.h" -#include "p_local.h" -#include "p_maputl.h" -#include "p_lnspec.h" -#include "p_effect.h" -#include "p_terrain.h" -#include "hu_stuff.h" -#include "v_video.h" -#include "c_dispatch.h" -#include "b_bot.h" //Added by MC: -#include "a_sharedglobal.h" -#include "gi.h" -#include "sbar.h" -#include "p_acs.h" -#include "cmdlib.h" -#include "decallib.h" -#include "a_keys.h" -#include "p_conversation.h" -#include "g_game.h" -#include "teaminfo.h" -#include "r_sky.h" -#include "d_event.h" -#include "p_enemy.h" -#include "gstrings.h" -#include "po_man.h" -#include "p_spec.h" -#include "p_checkposition.h" -#include "serializer_doom.h" -#include "serialize_obj.h" -#include "r_utility.h" -#include "thingdef.h" -#include "d_player.h" -#include "g_levellocals.h" -#include "a_morph.h" -#include "events.h" -#include "actorinlines.h" #include "a_dynlight.h" +#include "a_keys.h" +#include "a_morph.h" +#include "a_sharedglobal.h" +#include "actorinlines.h" +#include "b_bot.h" //Added by MC: +#include "c_dispatch.h" +#include "cmdlib.h" +#include "d_event.h" +#include "d_net.h" +#include "d_player.h" +#include "decallib.h" +#include "doomdef.h" +#include "events.h" #include "fragglescript/t_fs.h" -#include "shadowinlines.h" +#include "g_game.h" +#include "g_levellocals.h" +#include "gi.h" +#include "gstrings.h" +#include "hu_stuff.h" +#include "m_random.h" #include "model.h" #include "models.h" -#include "d_net.h" +#include "p_acs.h" +#include "p_checkposition.h" +#include "p_conversation.h" +#include "p_effect.h" +#include "p_enemy.h" +#include "p_lnspec.h" +#include "p_local.h" +#include "p_maputl.h" +#include "p_spec.h" +#include "p_terrain.h" +#include "r_sky.h" +#include "r_utility.h" +#include "sbar.h" +#include "serialize_obj.h" +#include "serializer_doom.h" +#include "shadowinlines.h" +#include "teaminfo.h" +#include "thingdef.h" // MACROS ------------------------------------------------------------------ @@ -120,6 +119,7 @@ static void PlayerLandedOnThing (AActor *mo, AActor *onmobj); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- EXTERN_CVAR (Int, cl_rockettrails) +EXTERN_CVAR (Bool, haptics_do_action) // PRIVATE DATA DEFINITIONS ------------------------------------------------ @@ -437,7 +437,6 @@ void AActor::Serialize(FSerializer &arc) ("behaviors", Behaviors) A("decalgenerator", DecalGenerator); - SerializeTerrain(arc, "floorterrain", floorterrain, &def->floorterrain); SerializeArgs(arc, "args", args, def->args, special); @@ -838,7 +837,6 @@ DEFINE_ACTION_FUNCTION_NATIVE(FState, InStateSequence, InStateSequence) ACTION_RETURN_BOOL(InStateSequence(self, basestate)); } - bool AActor::IsMapActor() { // [SP] Don't remove owned inventory objects. @@ -992,7 +990,6 @@ DEFINE_ACTION_FUNCTION(AActor, SetState) ACTION_RETURN_BOOL(self->SetState(state, nofunction)); }; - //============================================================================ // // DestroyAllInventory @@ -1035,6 +1032,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, DestroyAllInventory, DestroyAllInventory) DestroyAllInventory(self); return 0; } + //============================================================================ // // AActor :: UseInventory @@ -2103,7 +2101,6 @@ DEFINE_ACTION_FUNCTION(AActor, ExplodeMissile) return 0; } - void AActor::PlayBounceSound(bool onfloor, double volume) { if (!onfloor && (BounceFlags & BOUNCE_NoWallSound)) @@ -2410,7 +2407,6 @@ bool P_SeekerMissile (AActor *actor, DAngle thresh, DAngle turnMax, bool precise return true; } - // // P_XYMovement // @@ -2926,7 +2922,6 @@ static double P_XYMovement (AActor *mo, DVector2 scroll) return Oldfloorz; } - static void P_MonsterFallingDamage (AActor *mo) { int damage; @@ -2983,7 +2978,6 @@ static void P_ZMovement (AActor *mo, double oldfloorz) mo->SetZ(mo->floorz + mo->specialf1); } - // // adjust height // @@ -3278,6 +3272,31 @@ DEFINE_ACTION_FUNCTION(AActor, CheckFakeFloorTriggers) P_CheckFakeFloorTriggers(self, oldz, oldz_has_viewh); return 0; } + +//========================================================================== +// +// Rumble functions +// +//========================================================================== + +static void PlayerLandedMakeRumble(AActor* self, AActor *onmobj) +{ + IFVIRTUALPTR(self, AActor, PlayerLandedMakeRumble) + { + VMValue params[2] = { self, onmobj }; + VMCall(func, params, 2, nullptr, 0); + } +} + +static void PlayerDiedMakeRumble(AActor* self, AActor *source) +{ + IFVIRTUALPTR(self, AActor, PlayerDiedMakeRumble) + { + VMValue params[2] = { self, source }; + VMCall(func, params, 2, nullptr, 0); + } +} + //=========================================================================== // // PlayerLandedOnThing @@ -3311,6 +3330,7 @@ static void PlayerLandedOnThing (AActor *mo, AActor *onmobj) P_FallingDamage (mo); + PlayerLandedMakeRumble(mo, onmobj); PlayerLandedMakeGruntSound(mo, onmobj); // mo->player->centering = true; @@ -3520,7 +3540,6 @@ void P_NightmareRespawn (AActor *mobj) mobj->Destroy (); } - // // P_AddMobjToHash // @@ -3588,7 +3607,6 @@ void AActor::SetTID (int newTID) AddToHash(); } - //========================================================================== // // P_IsTIDUsed @@ -3671,7 +3689,6 @@ int FLevelLocals::FindUniqueTID(int start_tid, int limit, bool clientside) return 0; } - CCMD(utid) { for (auto Level : AllLevels()) @@ -3799,8 +3816,6 @@ bool AActor::CallSlam(AActor *thing) else return Slam(thing); } - - // This virtual method only exists on the script side. int AActor::SpecialMissileHit (AActor *victim) { @@ -4188,7 +4203,6 @@ PClassActor *AActor::GetBloodType(int type) const return nullptr; } - DVector3 AActor::GetPortalTransition(double byoffset, sector_t **pSec) { bool moved = false; @@ -4222,8 +4236,6 @@ DVector3 AActor::GetPortalTransition(double byoffset, sector_t **pSec) return pos; } - - void AActor::CheckPortalTransition(bool islinked) { bool moved = false; @@ -4475,7 +4487,6 @@ void AActor::GetObjectToWorldMatrix(double *outMat) } } - // // P_MobjThinker // @@ -5559,7 +5570,6 @@ void ConstructActor(AActor *actor, const DVector3 &pos, bool SpawningMapThing) actor->flags8 |= MF8_INSCROLLSEC; } - AActor *AActor::StaticSpawn(FLevelLocals *Level, PClassActor *type, const DVector3 &pos, replace_t allowreplacement, bool SpawningMapThing) { if (type == NULL) @@ -5755,7 +5765,6 @@ void AActor::CallBeginPlay() else BeginPlay(); } - void AActor::PostBeginPlay () { PrevAngles = Angles; @@ -5830,7 +5839,6 @@ void AActor::CallActivate(AActor *activator) else Activate(activator); } - //=========================================================================== // // Deactivate @@ -6948,6 +6956,7 @@ AActor *FLevelLocals::SpawnMapThing (FMapThing *mthing, int position) // SpawnMapThing // //=========================================================================== + CVAR(Bool, dumpspawnedthings, false, 0) AActor *FLevelLocals::SpawnMapThing(int index, FMapThing *mt, int position) @@ -6963,13 +6972,10 @@ AActor *FLevelLocals::SpawnMapThing(int index, FMapThing *mt, int position) return spawned; } - - // // GAME SPAWN FUNCTIONS // - // // P_SpawnPuff // @@ -7043,13 +7049,17 @@ AActor *P_SpawnPuff (AActor *source, PClassActor *pufftype, const DVector3 &pos1 if (cl_pufftype == 1) puff->renderflags |= RF_INVISIBLE; } + bool meleeFromPlayer = ( + (flags & PF_MELEERANGE) && source->player + && (source->player->mo == players[consoleplayer].mo || source->player->mo == players[consoleplayer].camera)); + if ((flags & PF_HITTHING) && puff->SeeSound.isvalid()) { // Hit thing sound - S_Sound (puff, CHAN_BODY, 0, puff->SeeSound, 1, ATTN_NORM); + S_Sound (puff, CHAN_BODY, (meleeFromPlayer&&haptics_do_action)?CHANF_RUMBLE:CHANF_NONE, puff->SeeSound, 1, ATTN_NORM); } else if (puff->AttackSound.isvalid()) { - S_Sound (puff, CHAN_BODY, 0, puff->AttackSound, 1, ATTN_NORM); + S_Sound (puff, CHAN_BODY, (meleeFromPlayer&&haptics_do_action)?CHANF_RUMBLE:CHANF_NONE, puff->AttackSound, 1, ATTN_NORM); } } @@ -7185,7 +7195,6 @@ DEFINE_ACTION_FUNCTION(AActor, SpawnBlood) ACTION_RETURN_OBJECT(P_SpawnBlood(DVector3(x, y, z), dir, damage, self)); } - //--------------------------------------------------------------------------- // // PROC P_BloodSplatter @@ -7248,7 +7257,6 @@ void P_BloodSplatter2 (const DVector3 &pos, AActor *originator, DAngle hitangle) { AActor *mo; - mo = Spawn (originator->Level, bloodcls, pos + add, NO_REPLACE); // GetBloodType already performed the replacement mo->target = originator; @@ -7279,7 +7287,6 @@ DEFINE_ACTION_FUNCTION(AActor, BloodSplatter) return 0; } - //--------------------------------------------------------------------------- // // PROC P_RipperBlood @@ -7344,7 +7351,6 @@ int P_GetThingFloorType (AActor *thing) } } - //--------------------------------------------------------------------------- // // FUNC P_HitWater @@ -7521,7 +7527,6 @@ DEFINE_ACTION_FUNCTION(AActor, HitWater) ACTION_RETURN_BOOL(P_HitWater(self, sec, DVector3(x, y, z), checkabove, alert, force, flags)); } - //--------------------------------------------------------------------------- // // FUNC P_HitFloor @@ -7680,7 +7685,6 @@ DEFINE_ACTION_FUNCTION(AActor, CheckMissileSpawn) ACTION_RETURN_BOOL(P_CheckMissileSpawn(self, add)); } - //--------------------------------------------------------------------------- // // FUNC P_PlaySpawnSound @@ -7720,8 +7724,6 @@ DEFINE_ACTION_FUNCTION(AActor, PlaySpawnSound) return 0; } - - double GetDefaultSpeed(PClassActor *type) { if (type == NULL) @@ -7765,13 +7767,13 @@ AActor *P_SpawnMissileXYZ (DVector3 pos, AActor *source, AActor *dest, PClassAct } AActor *th = Spawn (source->Level, type, pos, ALLOW_REPLACE); - - P_PlaySpawnSound(th, source); // record missile's originator if (owner == NULL) owner = source; th->target = owner; + P_PlaySpawnSound(th, source); + double speed = th->Speed; // [RH] @@ -7854,8 +7856,6 @@ DEFINE_ACTION_FUNCTION(AActor, SpawnMissileZ) ACTION_RETURN_OBJECT(P_SpawnMissileZ(self, z, dest, type)); } - - AActor *P_OldSpawnMissile(AActor *source, AActor *owner, AActor *dest, PClassActor *type) { if (source == nullptr || type == nullptr) @@ -7864,13 +7864,13 @@ AActor *P_OldSpawnMissile(AActor *source, AActor *owner, AActor *dest, PClassAct } AActor *th = Spawn (source->Level, type, source->PosPlusZ(32.), ALLOW_REPLACE); - P_PlaySpawnSound(th, source); th->target = owner; // record missile's originator + P_PlaySpawnSound(th, source); + th->Angles.Yaw = source->AngleTo(dest); th->VelFromAngle(); - double dist = source->DistanceBySpeed(dest, max(1., th->Speed)); th->Vel.Z = (dest->Z() - source->Z()) / dist; @@ -7892,7 +7892,6 @@ DEFINE_ACTION_FUNCTION(AActor, OldSpawnMissile) ACTION_RETURN_OBJECT(P_OldSpawnMissile(self, owner, dest, type)); } - //--------------------------------------------------------------------------- // // FUNC P_SpawnMissileAngle @@ -7957,9 +7956,11 @@ AActor *P_SpawnMissileAngleZSpeed (AActor *source, double z, mo = Spawn (source->Level, type, source->PosAtZ(z), ALLOW_REPLACE); - P_PlaySpawnSound(mo, source); if (owner == NULL) owner = source; mo->target = owner; + + P_PlaySpawnSound(mo, source); + mo->Angles.Yaw = angle; mo->VelFromAngle(speed); mo->Vel.Z = vz; @@ -7985,7 +7986,6 @@ DEFINE_ACTION_FUNCTION(AActor, SpawnMissileAngleZSpeed) ACTION_RETURN_OBJECT(P_SpawnMissileAngleZSpeed(self, z, type, angle, vz, speed, owner, checkspawn)); } - AActor *P_SpawnSubMissile(AActor *source, PClassActor *type, AActor *target) { AActor *other = Spawn(source->Level, type, source->Pos(), ALLOW_REPLACE); @@ -8106,8 +8106,8 @@ AActor *P_SpawnPlayerMissile (AActor *source, double x, double y, double z, DVector3 pos = source->Vec2OffsetZ(x, y, z); AActor *MissileActor = Spawn (source->Level, type, pos, ALLOW_REPLACE); if (pMissileActor) *pMissileActor = MissileActor; - P_PlaySpawnSound(MissileActor, source); MissileActor->target = source; + P_PlaySpawnSound(MissileActor, source); MissileActor->Angles.Yaw = an; if (MissileActor->flags3 & (MF3_FLOORHUGGER | MF3_CEILINGHUGGER)) { @@ -8149,7 +8149,6 @@ DEFINE_ACTION_FUNCTION(AActor, SpawnPlayerMissile) return numret; } - int AActor::GetTeam() { if (player) @@ -8386,7 +8385,6 @@ DEFINE_ACTION_FUNCTION(AActor, TakeSpecialDamage) ACTION_RETURN_INT(self->TakeSpecialDamage(inflictor, source, damage, damagetype)); } - int AActor::CallTakeSpecialDamage(AActor *inflictor, AActor *source, int damage, FName damagetype) { IFVIRTUAL(AActor, TakeSpecialDamage) @@ -8452,7 +8450,6 @@ void AActor::SetIdle(bool nofunction) SetState(idle, nofunction); } - int AActor::SpawnHealth() const { int defhealth = StartHealth ? StartHealth : GetDefault()->health; @@ -8568,7 +8565,6 @@ int AActor::GetGibHealth() const return -SpawnHealth(); } - // killough 11/98: // Whether an object is "sentient" or not. Used for environmental influences. // (left precisely the same as MBF even though it doesn't make much sense.) @@ -8577,7 +8573,6 @@ bool AActor::IsSentient() const return health > 0 && SeeState != NULL; } - FSharedStringArena AActor::mStringPropertyData; const char *AActor::GetTag(const char *def) const @@ -8661,7 +8656,6 @@ int AActor::GetModifiedDamage(FName damagetype, int damage, bool passive, AActor return damage; } - int AActor::ApplyDamageFactor(FName damagetype, int damage) const { damage = int(damage * DamageFactor); diff --git a/src/playsim/p_spec.cpp b/src/playsim/p_spec.cpp index 44730bf87..04beabdc8 100644 --- a/src/playsim/p_spec.cpp +++ b/src/playsim/p_spec.cpp @@ -5,6 +5,7 @@ // Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -67,43 +68,37 @@ #include - +#include "a_keys.h" +#include "a_sharedglobal.h" +#include "actorinlines.h" +#include "d_event.h" +#include "d_player.h" +#include "doomdata.h" #include "doomdef.h" #include "doomstat.h" -#include "d_event.h" -#include "g_level.h" -#include "gstrings.h" #include "events.h" - +#include "g_game.h" +#include "g_levellocals.h" +#include "gstrings.h" +#include "i_soundinternal.h" +#include "m_joy.h" #include "m_random.h" - -#include "p_local.h" -#include "p_spec.h" +#include "p_3dmidtex.h" +#include "p_acs.h" #include "p_blockmap.h" #include "p_lnspec.h" -#include "p_terrain.h" -#include "p_acs.h" -#include "p_3dmidtex.h" - -#include "g_game.h" - -#include "a_sharedglobal.h" -#include "a_keys.h" -#include "c_dispatch.h" -#include "r_sky.h" -#include "d_player.h" -#include "g_levellocals.h" -#include "actorinlines.h" -#include "vm.h" +#include "p_local.h" #include "p_setup.h" - -#include "c_console.h" -#include "p_spec_thinkers.h" +#include "p_spec.h" +#include "p_terrain.h" +#include "r_sky.h" +#include "vm.h" static FRandom pr_actorinspecialsector ("ActorInSpecialSector"); EXTERN_CVAR(Bool, cl_predict_specials) EXTERN_CVAR(Bool, forcewater) +EXTERN_CVAR (Bool, haptics_do_world) // [RH] Check dmflags for noexit and respond accordingly bool FLevelLocals::CheckIfExitIsGood (AActor *self, level_info_t *info) @@ -141,7 +136,6 @@ bool FLevelLocals::CheckIfExitIsGood (AActor *self, level_info_t *info) return true; } - // // UTILITIES // @@ -194,6 +188,16 @@ bool P_ActivateLine (line_t *line, AActor *mo, int side, int activationType, DVe { P_ChangeSwitchTexture (line->sidedef[0], repeat, special); } + + if ((mo == players[consoleplayer].mo || mo == players[consoleplayer].camera) && + (activationType == SPAC_Use || activationType == SPAC_Push || activationType == SPAC_UseThrough || activationType == SPAC_UseBack)) + { + IFVIRTUALPTR(mo, AActor, PlayerUsedSomethingMakeRumble) + { + VMValue params[5] = { mo, activationType, Level->levelnum, line->linenum, line->special}; + VMCall(func, params, 5, nullptr, 0); + } + } } // some old WADs use this method to create walls that change the texture when shot. else if (activationType == SPAC_Impact && // only for shootable triggers @@ -208,6 +212,7 @@ bool P_ActivateLine (line_t *line, AActor *mo, int side, int activationType, DVe P_ChangeSwitchTexture (line->sidedef[0], repeat, special); line->special = 0; } + // end of changed code if (developer >= DMSG_SPAMMY && buttonSuccess) { @@ -637,7 +642,8 @@ void P_GiveSecret(FLevelLocals *Level, AActor *actor, bool printmessage, bool pl Printf(PRINT_HIGH | PRINT_NONOTIFY, "Secret found in sector %d\n", sectornum); } } - if (playsound) S_Sound (CHAN_AUTO, CHANF_UI, "misc/secret", 1, ATTN_NORM); + if (playsound) + S_Sound (CHAN_AUTO, CHANF_UI|(haptics_do_world?CHANF_RUMBLE:CHANF_NORUMBLE), "misc/secret", 1, ATTN_NORM); } } Level->found_secrets++; diff --git a/src/playsim/p_teleport.cpp b/src/playsim/p_teleport.cpp index f26bb6fcb..0c3b79786 100644 --- a/src/playsim/p_teleport.cpp +++ b/src/playsim/p_teleport.cpp @@ -5,6 +5,7 @@ // Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -28,11 +29,10 @@ #include "d_player.h" #include "doomdef.h" -#include "vm.h" #include "g_levellocals.h" #include "p_maputl.h" -#include "gi.h" #include "r_utility.h" +#include "vm.h" #define FUDGEFACTOR 10 @@ -190,6 +190,13 @@ bool P_Teleport (AActor *thing, DVector3 pos, DAngle angle, int flags) { player->mo->Angles.Pitch = nullAngle; } + if (player->mo == players[consoleplayer].mo || player->mo == players[consoleplayer].camera) + { + IFVIRTUALPTR(player->mo, AActor, PlayerTeleportedMakeRumble) + { + CallVM(func, player->mo); + } + } } if (!(flags & TELF_KEEPORIENTATION)) { diff --git a/src/playsim/p_user.cpp b/src/playsim/p_user.cpp index 8533abf38..0bcf52fd7 100644 --- a/src/playsim/p_user.cpp +++ b/src/playsim/p_user.cpp @@ -5,6 +5,7 @@ // Copyright 1998-1998 Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -58,42 +59,38 @@ ** */ - -#include "doomdef.h" -#include "d_event.h" -#include "p_local.h" -#include "doomstat.h" -#include "s_sound.h" -#include "gi.h" -#include "m_random.h" -#include "p_pspr.h" -#include "p_enemy.h" -#include "a_sharedglobal.h" #include "a_keys.h" -#include "filesystem.h" -#include "cmdlib.h" -#include "sbar.h" -#include "intermission/intermission.h" -#include "c_console.h" -#include "c_dispatch.h" -#include "d_net.h" -#include "serializer_doom.h" -#include "serialize_obj.h" -#include "d_player.h" -#include "r_utility.h" -#include "p_blockmap.h" #include "a_morph.h" -#include "p_spec.h" -#include "vm.h" -#include "g_levellocals.h" #include "actorinlines.h" -#include "p_acs.h" -#include "events.h" -#include "g_game.h" -#include "v_video.h" -#include "gstrings.h" -#include "s_music.h" +#include "c_dispatch.h" +#include "cmdlib.h" +#include "d_event.h" #include "d_main.h" +#include "d_net.h" +#include "d_player.h" +#include "doomdef.h" +#include "doomstat.h" +#include "events.h" +#include "filesystem.h" +#include "g_game.h" +#include "g_levellocals.h" +#include "gi.h" +#include "gstrings.h" +#include "intermission/intermission.h" +#include "m_random.h" +#include "p_acs.h" +#include "p_blockmap.h" +#include "p_enemy.h" +#include "p_local.h" +#include "p_pspr.h" +#include "p_spec.h" +#include "r_utility.h" +#include "s_music.h" +#include "s_sound.h" +#include "sbar.h" +#include "serialize_obj.h" +#include "serializer_doom.h" +#include "vm.h" extern int paused; @@ -478,8 +475,6 @@ DEFINE_ACTION_FUNCTION(_PlayerInfo, SetSubtitleNumber) return 0; } - - int player_t::GetSpawnClass() { const PClass * type = PlayerClasses[CurrentPlayerClass].Type; @@ -648,7 +643,6 @@ DEFINE_ACTION_FUNCTION_NATIVE(APlayerPawn, GetPainFlashForType, GetPainFlash) EXTERN_CVAR(Float, maxviewpitch) EXTERN_CVAR(Bool, cl_oldfreelooklimit); - static int GetSoftPitch(bool down) { int MAX_DN_ANGLE = min(56, (int)maxviewpitch); // Max looking down angle @@ -685,7 +679,6 @@ DEFINE_ACTION_FUNCTION(_PlayerInfo, SendPitchLimits) return 0; } - bool player_t::HasWeaponsInSlot(int slot) const { for (int i = 0; i < weapons.SlotSize(slot); i++) @@ -703,7 +696,6 @@ DEFINE_ACTION_FUNCTION(_PlayerInfo, HasWeaponsInSlot) ACTION_RETURN_BOOL(self->HasWeaponsInSlot(slot)); } - bool player_t::Resurrect() { if (mo == nullptr || mo->IsKindOf(NAME_PlayerChunk)) return false; @@ -885,7 +877,6 @@ DEFINE_ACTION_FUNCTION(_PlayerInfo, GetStillBob) ACTION_RETURN_FLOAT(self->userinfo.GetStillBob()); } - //=========================================================================== // // @@ -977,11 +968,11 @@ DEFINE_ACTION_FUNCTION(AActor, A_PlayerScream) { if (self->DeathSound != NO_SOUND) { - S_Sound (self, CHAN_VOICE, 0, self->DeathSound, 1, ATTN_NORM); + S_Sound (self, CHAN_VOICE, CHANF_NORUMBLE, self->DeathSound, 1, ATTN_NORM); } else { - S_Sound (self, CHAN_VOICE, 0, "*death", 1, ATTN_NORM); + S_Sound (self, CHAN_VOICE, CHANF_NORUMBLE, "*death", 1, ATTN_NORM); } return 0; } @@ -1031,11 +1022,10 @@ DEFINE_ACTION_FUNCTION(AActor, A_PlayerScream) } } } - S_Sound (self, chan, 0, sound, 1, ATTN_NORM); + S_Sound (self, chan, CHANF_NORUMBLE, sound, 1, ATTN_NORM); return 0; } - //=========================================================================== // // P_CheckPlayerSprites @@ -1284,7 +1274,6 @@ void P_CheckUse(player_t *player) } } - DEFINE_ACTION_FUNCTION(APlayerPawn, CheckUse) { PARAM_SELF_PROLOGUE(AActor); @@ -1841,7 +1830,6 @@ bool P_IsPlayerTotallyFrozen(const player_t *player) player->mo->isFrozen(); } - //========================================================================== // // native members diff --git a/src/posix/i_steam.cpp b/src/posix/i_steam.cpp index f65478f3e..ed47d10ed 100644 --- a/src/posix/i_steam.cpp +++ b/src/posix/i_steam.cpp @@ -159,12 +159,19 @@ static struct SteamAppInfo {"Hexen/base", 2360}, {"Hexen Deathkings of the Dark Citadel/base", 2370}, {"Ultimate Doom/base", 2280}, + {"Ultimate Doom/base/doom2", 2280}, + {"Ultimate Doom/base/tnt", 2280}, + {"Ultimate Doom/base/plutonia", 2280}, {"DOOM 3 BFG Edition/base/wads", 208200}, {"Strife", 317040}, {"Ultimate Doom/rerelease/DOOM_Data/StreamingAssets", 2280}, + {"Ultimate Doom/rerelease", 2280}, {"Doom 2/rerelease/DOOM II_Data/StreamingAssets", 2300}, {"Doom 2/finaldoombase", 2300}, - {"Master Levels of Doom/doom2", 9160} + {"Master Levels of Doom/doom2", 9160}, + {"Heretic + Hexen/base/heretic", 3286930}, + {"Heretic + Hexen/base/hexen", 3286930}, + {"Heretic + Hexen/base/hexendk", 3286930} }; TArray I_GetSteamPath() diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index 8b1045f27..818e30389 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -1,7 +1,8 @@ // //--------------------------------------------------------------------------- // -// Copyright(C) 2005-2016 Christoph Oelckers +// Copyright 2005-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // All rights reserved. // // This program is free software: you can redistribute it and/or modify @@ -19,6 +20,7 @@ // //-------------------------------------------------------------------------- // + /* ** gl_models.cpp ** @@ -710,7 +712,7 @@ void InitModels() { FVoxelModel *md = (FVoxelModel*)Models[VoxelDefs[i]->Voxel->VoxelIndex]; FSpriteModelFrame smf; - memset(&smf, 0, sizeof(smf)); + memset((void*)&smf, 0, sizeof(smf)); smf.isVoxel = true; smf.modelsAmount = 1; smf.modelframes.Alloc(1); @@ -788,7 +790,7 @@ void ParseModelDefLump(int Lump) sc.MustGetString(); FSpriteModelFrame smf; - memset(&smf, 0, sizeof(smf)); + memset((void*)&smf, 0, sizeof(smf)); smf.xscale=smf.yscale=smf.zscale=1.f; auto type = PClass::FindClass(sc.String); @@ -1203,7 +1205,7 @@ FSpriteModelFrame * FindModelFrameRaw(const AActor * actorDefaults, const PClass { FSpriteModelFrame smf; - memset(&smf, 0, sizeof(smf)); + memset((void*)&smf, 0, sizeof(smf)); smf.type = ti; smf.sprite = sprite; smf.frame = frame; diff --git a/src/rendering/r_utility.cpp b/src/rendering/r_utility.cpp index 5eaa80dc1..42b84e488 100644 --- a/src/rendering/r_utility.cpp +++ b/src/rendering/r_utility.cpp @@ -4,6 +4,7 @@ // Copyright 1994-1996 Raven Software // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers +// Copyright 2017-2025 GZDoom Maintainers and Contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -32,46 +33,39 @@ #include #include - -#include "doomdef.h" -#include "d_net.h" -#include "doomstat.h" -#include "m_random.h" -#include "m_bbox.h" -#include "r_sky.h" -#include "st_stuff.h" -#include "c_dispatch.h" -#include "v_video.h" -#include "stats.h" -#include "i_video.h" #include "a_sharedglobal.h" -#include "p_3dmidtex.h" -#include "r_data/r_interpolate.h" -#include "po_man.h" -#include "p_effect.h" -#include "v_font.h" -#include "swrenderer/r_renderer.h" -#include "serializer.h" -#include "r_utility.h" -#include "d_player.h" -#include "p_local.h" -#include "g_levellocals.h" -#include "p_maputl.h" -#include "sbar.h" -#include "vm.h" -#include "i_time.h" #include "actorinlines.h" -#include "g_game.h" -#include "i_system.h" -#include "v_draw.h" -#include "i_interface.h" #include "d_main.h" +#include "d_net.h" +#include "d_player.h" +#include "doomstat.h" +#include "g_game.h" +#include "g_levellocals.h" +#include "i_interface.h" +#include "i_time.h" +#include "i_video.h" +#include "m_haptics.h" +#include "m_random.h" +#include "p_3dmidtex.h" +#include "p_effect.h" +#include "p_local.h" +#include "p_maputl.h" +#include "r_data/r_interpolate.h" +#include "r_sky.h" +#include "r_utility.h" +#include "sbar.h" +#include "serializer.h" +#include "swrenderer/r_renderer.h" +#include "v_draw.h" +#include "v_video.h" +#include "vm.h" const float MY_SQRT2 = float(1.41421356237309504880); // sqrt(2) // EXTERNAL DATA DECLARATIONS ---------------------------------------------- extern bool DrawFSHUD; // [RH] Defined in d_main.cpp EXTERN_CVAR (Bool, cl_capfps) +EXTERN_CVAR (Bool, haptics_do_world) // TYPES ------------------------------------------------------------------- @@ -282,7 +276,6 @@ void R_SetWindow (FRenderViewpoint &viewpoint, FViewWindow &viewwindow, int wind viewwindow.centerxwide = viewwindow.centerx * AspectMultiplier(viewwindow.WidescreenRatio) / 48; } - DAngle fov = viewpoint.FieldOfView; // For widescreen displays, increase the FOV so that the middle part of the @@ -854,7 +847,6 @@ bool R_GetViewInterpolationStatus() return NoInterpolateView; } - //========================================================================== // // R_ClearInterpolationPath @@ -1050,8 +1042,15 @@ void R_SetupFrame(FRenderViewpoint& viewPoint, const FViewWindow& viewWindow, AA if (!WorldPaused()) { FQuakeJiggers jiggers; - if (DEarthquake::StaticGetQuakeIntensities(viewPoint.TicFrac, viewPoint.camera, jiggers) > 0) + int intensity = DEarthquake::StaticGetQuakeIntensities(viewPoint.TicFrac, viewPoint.camera, jiggers); + if (intensity > 0) { + if (haptics_do_world) + { + // f(0)->1 f(9)->0 + Joy_Rumble("world/quake", (9.0-intensity)/9); + } + const double quakeFactor = r_quakeintensity; if (jiggers.RollIntensity || jiggers.RollWave) iView->AngleOffsets.Roll = DAngle::fromDeg(QuakePower(quakeFactor, jiggers.RollIntensity, jiggers.RollWave)); @@ -1248,7 +1247,6 @@ void R_SetupFrame(FRenderViewpoint& viewPoint, const FViewWindow& viewWindow, AA viewPoint.ViewActor = viewPoint.showviewer ? nullptr : actor; } - CUSTOM_CVAR(Float, maxviewpitch, 90.f, CVAR_ARCHIVE | CVAR_SERVERINFO) { if (self>90.f) self = 90.f; @@ -1300,5 +1298,4 @@ bool R_ShouldDrawSpriteShadow(AActor *thing) } return doit; - } diff --git a/src/scripting/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index d5b72c5ad..a66d281a9 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -2205,6 +2205,7 @@ DEFINE_FIELD_X(FSpawnParticleParams, FSpawnParticleParams, vel); DEFINE_FIELD_X(FSpawnParticleParams, FSpawnParticleParams, accel); DEFINE_FIELD_X(FSpawnParticleParams, FSpawnParticleParams, startalpha); DEFINE_FIELD_X(FSpawnParticleParams, FSpawnParticleParams, fadestep); +DEFINE_FIELD_X(FSpawnParticleParams, FSpawnParticleParams, fadeoutstep); DEFINE_FIELD_X(FSpawnParticleParams, FSpawnParticleParams, startroll); DEFINE_FIELD_X(FSpawnParticleParams, FSpawnParticleParams, rollvel); DEFINE_FIELD_X(FSpawnParticleParams, FSpawnParticleParams, rollacc); @@ -2215,7 +2216,7 @@ static void SpawnParticle(FLevelLocals *Level, FSpawnParticleParams *params) params->color, params->startalpha, params->lifetime, params->size, params->fadestep, params->sizestep, params->flags, params->texture, ERenderStyle(params->style), - params->startroll, params->rollvel, params->rollacc); + params->startroll, params->rollvel, params->rollacc, params->fadeoutstep); } DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, SpawnParticle, SpawnParticle) diff --git a/src/sound/s_advsound.cpp b/src/sound/s_advsound.cpp index 8717c4b9e..985dfd3a8 100644 --- a/src/sound/s_advsound.cpp +++ b/src/sound/s_advsound.cpp @@ -1,9 +1,12 @@ /* ** s_advsound.cpp +** ** Routines for managing SNDINFO lumps and ambient sounds ** **--------------------------------------------------------------------------- +** ** Copyright 1998-2008 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -28,28 +31,26 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ // HEADER FILES ------------------------------------------------------------ - #include "actor.h" #include "c_dispatch.h" -#include "filesystem.h" -#include "gi.h" -#include "i_sound.h" #include "d_netinf.h" #include "d_player.h" -#include "serializer.h" -#include "v_text.h" -#include "g_levellocals.h" -#include "r_data/sprites.h" -#include "vm.h" -#include "i_system.h" -#include "s_music.h" +#include "filesystem.h" +#include "gi.h" #include "i_music.h" +#include "i_sound.h" +#include "m_haptics.h" +#include "r_data/sprites.h" +#include "s_music.h" +#include "serializer.h" +#include "vm.h" using namespace FileSys; @@ -148,6 +149,8 @@ enum SICommands SI_Attenuation, SI_PitchSet, SI_ModPlayer, + SI_RumbleDef, + SI_Rumble, }; // Blood was a cool game. If Monolith ever releases the source for it, @@ -238,6 +241,8 @@ static const char *SICommandStrings[] = "$attenuation", "$pitchset", "$modplayer", + "$rumbledef", + "$rumble", nullptr }; @@ -249,7 +254,6 @@ static bool PlayerClassesIsSorted; static TArray PlayerClassLookups; static TArray PlayerSounds; - static FString DefPlayerClassName; static int DefPlayerClass; @@ -348,6 +352,8 @@ void S_CheckIntegrity() sfx.link = NO_SOUND; // link to the empty sound. } } + + Joy_ReadyRumbleMapping(); } //========================================================================== @@ -574,6 +580,8 @@ void S_ClearSoundData() MidiDevices.Clear(); HexenMusic.Clear(); ModPlayers.Clear(); + + Joy_ResetRumbleMapping(); } //========================================================================== @@ -1136,6 +1144,63 @@ static void S_AddSNDINFO (int lump) case SI_IfHexen: skipToEndIf = !CheckGame(sc.String+3, true); break; + + case SI_RumbleDef: { + // $rumbledef + // $rumbledef + + sc.MustGetString(); + FString identifier (sc.String); + + sc.GetToken(); + bool isAlias = sc.TokenType == TK_Identifier; + sc.UnGet(); + + if (isAlias) + { + sc.MustGetString(); + Joy_AddRumbleAlias(identifier, FName(sc.String)); + } + else + { + sc.MustGetNumber(); + int duration = sc.Number; + sc.MustGetFloat(); + double low_freq = sc.Float; + sc.MustGetFloat(); + double high_freq = sc.Float; + sc.MustGetFloat(); + double left_trig = sc.Float; + sc.MustGetFloat(); + double right_trig = sc.Float; + + Joy_AddRumbleType( + identifier, + { duration, low_freq, high_freq, left_trig, right_trig, } + ); + } + + // if (sc.CheckToken(TK_IntConst)) + // { + // } + // else + // { + // Printf("Alias: %s\n", identifier.GetChars()); + // } + } + break; + + case SI_Rumble: { + // $rumble + + sc.MustGetString(); + FString sound (sc.String); + sc.MustGetString(); + FString mapping (sc.String); + + Joy_MapRumbleType(sound, mapping); + } + break; } } else @@ -1404,7 +1469,6 @@ static FSoundID S_LookupPlayerSound (int classidx, int gender, FSoundID refid) return sndnum; } - //========================================================================== // // S_SavePlayerSound / S_RestorePlayerSounds @@ -1847,7 +1911,6 @@ DEFINE_ACTION_FUNCTION(AAmbientSound, Deactivate) return 0; } - //========================================================================== // // S_ParseMusInfo @@ -1889,7 +1952,6 @@ void S_ParseMusInfo() } } - DEFINE_ACTION_FUNCTION(DObject, MarkSound) { PARAM_PROLOGUE; diff --git a/src/sound/s_doomsound.cpp b/src/sound/s_doomsound.cpp index 525bb9b54..6ff228fb6 100644 --- a/src/sound/s_doomsound.cpp +++ b/src/sound/s_doomsound.cpp @@ -1,5 +1,5 @@ /* -** doomspund.cpp +** s_doomsound.cpp ** ** Game dependent part of the sound engine. ** @@ -7,6 +7,7 @@ ** ** Copyright 1999-2016 Randy Heit ** Copyright 2002-2019 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions @@ -30,47 +31,51 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ - #include #include + #ifdef _WIN32 #include #endif -#include "i_system.h" -#include "i_sound.h" -#include "i_music.h" -#include "s_sound.h" -#include "s_sndseq.h" -#include "s_playlist.h" -#include "c_dispatch.h" -#include "m_random.h" -#include "filesystem.h" -#include "p_local.h" -#include "doomstat.h" -#include "cmdlib.h" -#include "v_video.h" -#include "v_text.h" #include "a_sharedglobal.h" -#include "gstrings.h" -#include "gi.h" -#include "po_man.h" -#include "serializer_doom.h" +#include "c_cvars.h" +#include "c_dispatch.h" +#include "cmdlib.h" #include "d_player.h" -#include "g_levellocals.h" -#include "vm.h" +#include "doomstat.h" +#include "filesystem.h" #include "g_game.h" -#include "s_music.h" -#include "v_draw.h" +#include "g_levellocals.h" +#include "gi.h" +#include "gstrings.h" +#include "i_music.h" +#include "i_sound.h" +#include "i_soundinternal.h" #include "m_argv.h" +#include "m_random.h" +#include "p_local.h" +#include "po_man.h" +#include "printf.h" +#include "s_music.h" +#include "s_sndseq.h" +#include "s_sound.h" +#include "serializer_doom.h" +#include "v_draw.h" +#include "v_font.h" +#include "vm.h" + +// EXTERNAL DATA DEFINITIONS ------------------------------------------------- + +EXTERN_CVAR(Bool, haptics_do_action); // PUBLIC DATA DEFINITIONS ------------------------------------------------- - static FString LastLocalSndInfo; static FString LastLocalSndSeq; void S_AddLocalSndInfo(int lump); @@ -99,14 +104,12 @@ class DoomSoundEngine : public SoundEngine return SoundEngine::CheckSoundLimit(sfx, pos, near_limit, limit_range, sourcetype, actor, channel, attenuation); } - public: DoomSoundEngine() = default; void NoiseDebug(void); void PrintSoundList(); }; - //========================================================================== // // LookupMusic @@ -254,7 +257,6 @@ void S_Shutdown() } } - //========================================================================== // // S_Start @@ -356,7 +358,6 @@ void S_PrecacheLevel(FLevelLocals* Level) } } - //========================================================================== // // S_InitData @@ -370,7 +371,6 @@ void S_InitData() S_ParseSndSeq(-1); } - //========================================================================== // // S_Sound - Unpositioned version @@ -437,7 +437,6 @@ DEFINE_ACTION_FUNCTION_NATIVE(DObject, S_StartSoundAt, S_StartSoundAt) return 0; } - //========================================================================== // // @@ -510,7 +509,6 @@ void DoomSoundEngine::StopChannel(FSoundChan* chan) SoundEngine::StopChannel(chan); } - //========================================================================== // // S_Sound - An actor is source @@ -519,6 +517,32 @@ void DoomSoundEngine::StopChannel(FSoundChan* chan) void S_SoundPitchActor(AActor *ent, int channel, EChanFlags flags, FSoundID sound_id, float volume, float attenuation, float pitch, float startTime) { +#if 0 + // sound source debug printout + Printf("sound '%s' from '%s'\n", soundEngine->GetSoundName(sound_id), ent->GetClass()->TypeName.GetChars()); +#endif + + if (flags & CHANF_NORUMBLE) + { + // remove conflicting flag + if (flags & CHANF_RUMBLE) + { + flags = (EChanFlag)(flags - CHANF_RUMBLE); + } + } + else if (!(flags & CHANF_RUMBLE)) + { + // add flag if needed + if (haptics_do_action && ( + // sound from self + (ent->player && (ent->player->mo == players[consoleplayer].mo || ent->player->mo == players[consoleplayer].camera)) || + // sound from self missile + ((ent->flags & MF_MISSILE) + && ent->target && ent->target->player + && (ent->target->player->mo == players[consoleplayer].mo || ent->target->player->mo == players[consoleplayer].camera)) + )) flags |= CHANF_RUMBLE; + } + if (VerifyActorSound(ent, sound_id, channel, flags)) soundEngine->StartSound (SOURCE_Actor, ent, nullptr, channel, flags, sound_id, volume, attenuation, 0, pitch, startTime); } @@ -636,7 +660,6 @@ void A_PlaySound(AActor* self, int soundid, int channel, double volume, int loop A_StartSound(self, soundid, channel & 7, channel & ~7, volume, attenuation, pitch, 0.0f); } - //========================================================================== // // S_StopSound @@ -1186,6 +1209,7 @@ TArray DoomSoundEngine::ReadSound(int lumpnum) // This is overridden to use a synchronized RNG. // //========================================================================== + static FCRandom pr_randsound("RandSound"); FSoundID DoomSoundEngine::PickReplacement(FSoundID refid) @@ -1234,7 +1258,6 @@ void DoomSoundEngine::NoiseDebug() return; } - listener = players[consoleplayer].camera->SoundPos(); // Display the oldest channel first. @@ -1318,7 +1341,6 @@ void DoomSoundEngine::NoiseDebug() mysnprintf(temp, countof(temp), "%u", GSnd->GetPosition(chan)); DrawText(twod, NewConsoleFont, color, 520, y, temp, TAG_DONE); - y += NewConsoleFont->GetHeight(); if (chan->PrevChan == &Channels) { @@ -1334,7 +1356,6 @@ ADD_STAT(sounddebug) return ""; } - //========================================================================== // // CCMD soundlist diff --git a/src/utility/nodebuilder/nodebuild_extract.cpp b/src/utility/nodebuilder/nodebuild_extract.cpp index 9714a52c6..300f07792 100644 --- a/src/utility/nodebuilder/nodebuild_extract.cpp +++ b/src/utility/nodebuilder/nodebuild_extract.cpp @@ -6,6 +6,7 @@ ** **--------------------------------------------------------------------------- ** Copyright 2002-2006 Randy Heit +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -54,13 +55,11 @@ void FNodeBuilder::Extract (FLevelLocals &theLevel) { - int i; - auto &outVerts = theLevel.vertexes; int vertCount = Vertices.Size (); outVerts.Alloc(vertCount); - for (i = 0; i < vertCount; ++i) + for (int i = 0; i < vertCount; ++i) { outVerts[i].set(Vertices[i].x, Vertices[i].y); } @@ -68,13 +67,17 @@ void FNodeBuilder::Extract (FLevelLocals &theLevel) auto &outSubs = theLevel.subsectors; auto subCount = Subsectors.Size(); outSubs.Alloc(subCount); - memset(&outSubs[0], 0, subCount * sizeof(subsector_t)); + memset((void*)&outSubs[0], 0, subCount * sizeof(subsector_t)); auto &outNodes = theLevel.nodes; auto nodeCount = Nodes.Size (); outNodes.Alloc(nodeCount); - memcpy (&outNodes[0], &Nodes[0], nodeCount*sizeof(node_t)); + for (unsigned i = 0; i < nodeCount; ++i) + { + outNodes[i] = Nodes[i]; + } + for (unsigned i = 0; i < nodeCount; ++i) { D(Printf(PRINT_LOG, "Node %d: Splitter[%08x,%08x] [%08x,%08x]\n", i, @@ -135,7 +138,11 @@ void FNodeBuilder::Extract (FLevelLocals &theLevel) } else { - memcpy (&outSubs[0], &Subsectors[0], subCount*sizeof(subsector_t)); + for (unsigned i = 0; i < subCount; ++i) + { + outSubs[i] = Subsectors[i]; + } + auto segCount = Segs.Size (); outSegs.Alloc(segCount); for (unsigned i = 0; i < segCount; ++i) @@ -161,7 +168,7 @@ void FNodeBuilder::Extract (FLevelLocals &theLevel) D(Printf("%i segs, %i nodes, %i subsectors\n", segCount, nodeCount, subCount)); - for (i = 0; i < Level.NumLines; ++i) + for (int i = 0; i < Level.NumLines; ++i) { Level.Lines[i].v1 = &outVerts[(size_t)Level.Lines[i].v1]; Level.Lines[i].v2 = &outVerts[(size_t)Level.Lines[i].v2]; @@ -180,10 +187,18 @@ void FNodeBuilder::ExtractMini (FMiniBSP *bsp) } bsp->Subsectors.Resize(Subsectors.Size()); - memset(&bsp->Subsectors[0], 0, Subsectors.Size() * sizeof(subsector_t)); + for (i = 0; i < bsp->Subsectors.Size(); ++i) + { + bsp->Subsectors[i].sprites.Clear(); + memset((void*)&bsp->Subsectors[i], 0, sizeof(subsector_t)); + } bsp->Nodes.Resize(Nodes.Size()); - memcpy(&bsp->Nodes[0], &Nodes[0], Nodes.Size()*sizeof(node_t)); + for (i = 0; i < Nodes.Size(); ++i) + { + bsp->Nodes[i] = Nodes[i]; + } + for (i = 0; i < Nodes.Size(); ++i) { D(Printf(PRINT_LOG, "Node %d:\n", i)); @@ -228,7 +243,11 @@ void FNodeBuilder::ExtractMini (FMiniBSP *bsp) } else { - memcpy(&bsp->Subsectors[0], &Subsectors[0], Subsectors.Size()*sizeof(subsector_t)); + for (i = 0; i < Subsectors.Size(); ++i) + { + bsp->Subsectors[i] = Subsectors[i]; + } + bsp->Segs.Resize(Segs.Size()); for (i = 0; i < Segs.Size(); ++i) { diff --git a/src/version.h b/src/version.h index 76fe960e8..af2240a16 100644 --- a/src/version.h +++ b/src/version.h @@ -66,7 +66,7 @@ const char *GetVersionString(); // Version stored in the ini's [LastRun] section. // Bump it if you made some configuration change that you want to // be able to migrate in FGameConfigFile::DoGlobalSetup(). -#define LASTRUNVERSION "225" +#define LASTRUNVERSION "226" // Protocol version used in demos. // Bump it if you change existing DEM_ commands or add new ones. diff --git a/src/win32/i_steam.cpp b/src/win32/i_steam.cpp index 919dc8f82..0b6075898 100644 --- a/src/win32/i_steam.cpp +++ b/src/win32/i_steam.cpp @@ -310,7 +310,10 @@ TArray I_GetSteamPath() "Ultimate Doom/rerelease", // 2024 KEX Port "Doom 2/rerelease/DOOM II_Data/StreamingAssets", "Doom 2/finaldoombase", - "Master Levels of Doom/doom2" + "Master Levels of Doom/doom2", + "Heretic + Hexen/base/heretic", // vanilla WAD included with the 2025 Kex port + "Heretic + Hexen/base/hexen", // vanilla WAD included with the 2025 Kex port + "Heretic + Hexen/base/hexendk" // vanilla WAD included with the 2025 Kex port }; FString steamPath; diff --git a/AppImageBuilder.yml b/tools/AppImageBuilder.yml similarity index 76% rename from AppImageBuilder.yml rename to tools/AppImageBuilder.yml index fcf249f2e..22ac97e42 100644 --- a/AppImageBuilder.yml +++ b/tools/AppImageBuilder.yml @@ -1,7 +1,8 @@ # appimage-builder recipe see https://appimage-builder.readthedocs.io for details + version: 1 + AppDir: - path: ./AppDir app_info: id: com.VKDoom.app name: VKDoom @@ -9,21 +10,23 @@ AppDir: version: latest exec: ./vkdoom exec_args: $@ - apt: - arch: amd64 - allow_unauthenticated: true - sources: - - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ jammy main restricted - - sourceline: deb http://us.archive.ubuntu.com/ubuntu/ jammy-updates main restricted - include: [] + + runtime: + path_mappings: + - /usr/share/games/doom:$APPDIR/usr/share/games/doom + files: - include: [] + include: + - /lib64/ld-linux-x86-64.so.2 + - /lib64/libbz2.so.1 exclude: - usr/share/man - usr/share/doc/*/README.* - usr/share/doc/*/changelog.* - usr/share/doc/*/NEWS.* - usr/share/doc/*/TODO.* + + # ... why are these so old ? test: fedora-30: image: appimagecrafters/tests-env:fedora-30 @@ -40,6 +43,7 @@ AppDir: ubuntu-xenial: image: appimagecrafters/tests-env:ubuntu-xenial command: ./AppRun + AppImage: arch: x86_64 update-information: guess diff --git a/tools/re2c/src/ir/adfa/prepare.cc b/tools/re2c/src/ir/adfa/prepare.cc index 39cf65c1b..a65ed37e3 100644 --- a/tools/re2c/src/ir/adfa/prepare.cc +++ b/tools/re2c/src/ir/adfa/prepare.cc @@ -125,7 +125,7 @@ void DFA::findBaseState() operator delete (s->go.span); s->go.nSpans = nSpans; s->go.span = allocate (nSpans); - memcpy(s->go.span, span, nSpans*sizeof(Span)); + memcpy((void*)(s->go.span), span, nSpans*sizeof(Span)); } break; diff --git a/tools/update-subtrees.cmd b/tools/update-subtrees.cmd new file mode 100644 index 000000000..b74eee670 --- /dev/null +++ b/tools/update-subtrees.cmd @@ -0,0 +1,7 @@ +@echo off +setlocal +cd "%~dp0" +for /f "delims=" %%I in ('where git.exe') do set GIT_PATH="%%~dpI" +if errorlevel 1 echo "Unable to find git.exe!" +if errorlevel 1 goto :eof +%GIT_PATH%\..\bin\bash.exe update-subtrees.sh %* diff --git a/tools/update-subtrees.sh b/tools/update-subtrees.sh new file mode 100755 index 000000000..14f1f145a --- /dev/null +++ b/tools/update-subtrees.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +export GITROOT="$(git rev-parse --show-toplevel)" + +pull() { + if [[ "${1}" == "${2}" || "${1}" == "all" ]] + then + echo "fetching ${2}" + git -C $GITROOT subtree pull --prefix="${3}" "${4}" "${5}" --squash || exit + else + echo "${2} not specified, skipping" + fi +} + +pull "$1" 'zwidget' 'libraries/ZWidget' 'https://github.com/dpjudas/ZWidget' 'master' +pull "$1" 'zmusic' 'libraries/ZMusic' 'https://github.com/ZDoom/ZMusic' 'master' diff --git a/docs/rh-log.txt b/unused/docs/rh-log.txt similarity index 100% rename from docs/rh-log.txt rename to unused/docs/rh-log.txt diff --git a/docs/skins.txt b/unused/docs/skins.txt similarity index 100% rename from docs/skins.txt rename to unused/docs/skins.txt diff --git a/update-subtrees.cmd b/update-subtrees.cmd deleted file mode 100644 index 6facb94b6..000000000 --- a/update-subtrees.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -git subtree pull --prefix=libraries/ZWidget https://github.com/dpjudas/ZWidget master --squash -git subtree pull --prefix=libraries/ZMusic https://github.com/ZDoom/ZMusic master --squash diff --git a/update-subtrees.sh b/update-subtrees.sh deleted file mode 100755 index e579e013f..000000000 --- a/update-subtrees.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -git subtree pull --prefix=libraries/ZWidget https://github.com/dpjudas/ZWidget master --squash -git subtree pull --prefix=libraries/ZMusic https://github.com/ZDoom/ZMusic master --squash diff --git a/wadsrc/static/engine/commonbinds.txt b/wadsrc/static/engine/commonbinds.txt index 8493529f2..346c0bdc8 100644 --- a/wadsrc/static/engine/commonbinds.txt +++ b/wadsrc/static/engine/commonbinds.txt @@ -64,21 +64,14 @@ dpadup togglemap pad_start menu_main pad_back pause lthumb crouch - -// Generic gamepad bindings -joy1 +use -joy4 +jump -axis6plus +attack -axis3plus +altattack -joy5 weapprev -joy6 weapnext -pov1left invprev -pov1right invnext -pov1down invuse -pov1up togglemap -joy8 menu_main -joy7 pause -joy10 crouch +lstickleft +moveleft +lstickright +moveright +lstickup +forward +lstickdown +back +rstickleft +left +rstickright +right +rstickup +lookup +rstickdown +lookdown /* Default automap bindings */ mapbind f am_togglefollow @@ -108,13 +101,3 @@ mapbind dpadup +am_panup mapbind dpaddown +am_pandown mapbind lshoulder +am_zoomout mapbind rshoulder +am_zoomin - -mapbind joy3 am_togglefollow -mapbind joy1 am_setmark -mapbind joy2 am_clearmarks -mapbind pov1right +am_panright -mapbind pov1left +am_panleft -mapbind pov1up +am_panup -mapbind pov1down +am_pandown -mapbind joy5 +am_zoomout -mapbind joy6 +am_zoomin diff --git a/wadsrc/static/engine/origbinds.txt b/wadsrc/static/engine/origbinds.txt index 02326c7c0..90fd7ea4d 100644 --- a/wadsrc/static/engine/origbinds.txt +++ b/wadsrc/static/engine/origbinds.txt @@ -12,9 +12,3 @@ space +use mouse2 +strafe mouse3 +forward mouse4 +speed - -// Generic joystick buttons -joy1 +attack -joy2 +strafe -joy3 +speed -joy4 +use diff --git a/wadsrc/static/filter/game-doomchex/sndinfo.txt b/wadsrc/static/filter/game-doomchex/sndinfo.txt index 4ecddbe6b..de76f3ee4 100644 --- a/wadsrc/static/filter/game-doomchex/sndinfo.txt +++ b/wadsrc/static/filter/game-doomchex/sndinfo.txt @@ -121,6 +121,24 @@ $playercompat player other *jump player/cyborg/jump1 $playercompat player other *gibbed player/cyborg/gibbed $playercompat player other *fist player/cyborg/fist +$rumble *death P_DEATH +$rumble *xdeath P_DEATH +$rumble *pain100 P_HURT +$rumble *pain75 P_HURT +$rumble *pain50 P_HURT +$rumble *pain25 P_HURT +$rumble *grunt P_GRUNT +$rumble *land P_GRUNT +$rumble *jump NONE +$rumble *gibbed P_DEATH +$rumble *fist P_FIST +$rumble *usefail P_GRUNT + +$rumbledef P_DEATH INTENSE +$rumbledef P_HURT HEAVY +$rumbledef P_GRUNT 2 .3 .6 .3 .3 +$rumbledef P_FIST 3 .6 .6 .6 .6 + // // Weapons // @@ -151,6 +169,34 @@ weapons/grbnce dsbounce weapons/grenlx dsgrnexp weapons/grenlf dsglaunc +$rumble weapons/sawup LIGHT +$rumble weapons/sawidle W_SAW_IDLE +$rumble weapons/sawfull W_SAW_FULL +$rumble weapons/sawhit W_SAW_FULL +$rumble weapons/pistol W_BULLET +$rumble weapons/shotgf W_SHELL +$rumble weapons/shotgr W_RELOAD // is this ever used? +$rumble weapons/sshotf W_SHELL +$rumble weapons/sshoto W_RELOAD +$rumble weapons/sshotc W_RELOAD +$rumble weapons/sshotl W_RELOAD +$rumble weapons/chngun W_BULLET +$rumble weapons/rocklx NONE +$rumble weapons/rocklf W_ROCKET +$rumble weapons/plasmaf W_ENERGY +$rumble weapons/plasmax NONE +$rumble weapons/bfgf W_BFG +$rumble weapons/bfgx NONE + +$rumbledef W_SAW_IDLE 1 0 .6 0 0 +$rumbledef W_SAW_FULL 4 .6 .6 .6 .6 +$rumbledef W_BULLET 3 .6 .6 .6 .6 +$rumbledef W_SHELL 6 .6 .6 .6 .6 +$rumbledef W_RELOAD 1 .3 .6 0 0 +$rumbledef W_ROCKET 6 .6 .6 .6 .6 +$rumbledef W_ENERGY 2 .6 .6 .6 .6 +$rumbledef W_BFG 35 .6 .6 .6 .6 + // Problem: weapons/rocklx needs to be unlimited but // is also used for the MAP30 brain explosion. // This alias remaps to the original but has its own limit @@ -436,6 +482,16 @@ switches/exitbutn dsswtchx misc/teleport dstelept +$rumble misc/secret SECRET +$rumble misc/w_pkup LIGHT +$rumble misc/p_pkup LIGHT +$rumble misc/i_pkup LIGHT +$rumble misc/k_pkup LIGHT +$rumble misc/teleport TELEPORT + +$rumbledef TELEPORT 35 .3 .6 0 0 +$rumbledef SECRET 35 .3 .6 0 0 + menu/activate dsswtchn // Activate a new menu menu/backup dsswtchn // Backup to previous menu menu/prompt dsswtchn // Activate a prompt "menu" @@ -450,6 +506,19 @@ $alias menu/advance menu/choose // Open a submenu $random menu/quit1 { player/male/death1 demon/pain grunt/pain misc/gibbed misc/teleport grunt/sight1 grunt/sight3 demon/melee } $random menu/quit2 { vile/active misc/p_pkup brain/cube misc/gibbed skeleton/swing knight/death baby/active demon/melee } +$rumble menu/activate HEAVY +$rumble menu/backup MEDIUM +$rumble menu/prompt MEDIUM +$rumble menu/cursor LIGHT +$rumble menu/change LIGHT +$rumble menu/invalid MEDIUM +$rumble menu/dismiss MEDIUM +$rumble menu/choose HEAVY +$rumble menu/clear MEDIUM +$rumble menu/advance HEAVY +$rumble menu/quit1 MEDIUM +$rumble menu/quit2 MEDIUM + $alias intermission/tick weapons/pistol $alias intermission/cooptotal *death $alias intermission/nextstage weapons/rocklx @@ -457,6 +526,12 @@ $alias intermission/paststats weapons/shotgr $alias intermission/pastcoopstats weapons/shotgr $alias intermission/pastdmstats *gibbed +$rumble intermission/tick LIGHT +$rumble intermission/cooptotal LIGHT +$rumble intermission/nextstage MEDIUM +$rumble intermission/paststats MEDIUM +$rumble intermission/pastcoopstats MEDIUM +$rumble intermission/pastdmstats MEDIUM // id24 sounds @@ -517,3 +592,17 @@ weapons/incinerator/hot3 dsincht3 weapons/calamityblade/charge dshetchg weapons/calamityblade/shoot dshetsht weapons/calamityblade/explode dshetxpl + +$rumble weapons/incinerator/fire1 W_FIRE_FIRE +$rumble weapons/incinerator/fire2 W_FIRE_FIRE +$rumble weapons/incinerator/burn NONE +$rumble weapons/incinerator/hot1 NONE +$rumble weapons/incinerator/hot2 NONE +$rumble weapons/incinerator/hot3 NONE +$rumble weapons/calamityblade/charge W_FIRE_CHARGE +$rumble weapons/calamityblade/shoot W_FIRE_FIRE +$rumble weapons/calamityblade/explode NONE +$rumble weapons/unmaker W_ENERGY // from Doom64CE + +$rumbledef W_FIRE_CHARGE 2 .4 .4 .4 .4 +$rumbledef W_FIRE_FIRE 4 .6 .6 .6 .6 diff --git a/wadsrc/static/filter/game-heretic/sndinfo.txt b/wadsrc/static/filter/game-heretic/sndinfo.txt index 60a0cda6f..fea8cad22 100644 --- a/wadsrc/static/filter/game-heretic/sndinfo.txt +++ b/wadsrc/static/filter/game-heretic/sndinfo.txt @@ -38,6 +38,26 @@ chicken/attack chicatk misc/burn hedat1 +$rumble *wimpydeath P_DEATH +$rumble *death P_DEATH +$rumble *crazydeath P_DEATH +$rumble *gibbed P_DEATH +$rumble *pain100 P_HURT +$rumble *pain75 P_HURT +$rumble *pain50 P_HURT +$rumble *pain25 P_HURT +$rumble *weaponlaugh LIGHT +$rumble *evillaugh LIGHT +$rumble *grunt P_GRUNT +$rumble *usefail P_GRUNT +$rumble *land P_GRUNT +$rumble *jump NONE +$rumble *burndeath P_DEATH + +$rumbledef P_DEATH INTENSE +$rumbledef P_HURT HEAVY +$rumbledef P_GRUNT 2 .3 .6 .3 .3 + weapons/staffhit stfhit weapons/staffpowerhit stfpow weapons/staffcrackle stfcrk @@ -80,6 +100,43 @@ $limit weapons/hornrodshoot 0 $limit weapons/hornrodhit 0 $limit weapons/maceshoot 0 +$rumble weapons/staffhit W_STAFF +$rumble weapons/staffpowerhit W_STAFF +$rumble weapons/staffcrackle NONE +$rumble weapons/wandhit W_CRYSTAL +$rumble weapons/bowshoot W_ARROW +$rumble weapons/bowhit NONE +$rumble weapons/gauntletsactivate NONE +$rumble weapons/gauntletsuse NONE +$rumble weapons/gauntletson W_GAUNTLET_IDLE +$rumble weapons/gauntletshit W_GAUNTLET_FULL +$rumble weapons/gauntletspowhit W_GAUNTLET_FULL +$rumble weapons/maceshoot W_SPHERE +$rumble weapons/macebounce NONE +$rumble weapons/macehit NONE +$rumble weapons/macestop NONE +$rumble weapons/maceexplode NONE +$rumble weapons/blasterhit NONE +$rumble weapons/blasterpowhit NONE +$rumble weapons/blastershoot W_ENERGY_ORB +$rumble weapons/hornrodshoot W_RUNE +$rumble weapons/hornrodhit NONE +$rumble weapons/hornrodpowshoot W_RUNE +$rumble weapons/hornrodpowhit NONE +$rumble weapons/phoenixhit NONE +$rumble weapons/phoenixshoot W_INFERNO_ORB +$rumble weapons/phoenixpowshoot W_INFERNO_ORB + +$rumbledef W_STAFF 2 .6 .6 .6 .6 +$rumbledef W_GAUNTLET_IDLE 1 0 .6 0 0 +$rumbledef W_GAUNTLET_FULL 4 .6 .6 .6 .6 +$rumbledef W_CRYSTAL 2 .6 .6 .6 .6 +$rumbledef W_ARROW 2 .6 .6 .6 .6 +$rumbledef W_SPHERE 3 .6 .6 .6 .6 +$rumbledef W_ENERGY_ORB 3 .6 .6 .6 .6 +$rumbledef W_RUNE 3 .6 .6 .6 .6 +$rumbledef W_INFERNO_ORB 3 .6 .6 .6 .6 + himp/sight impsit himp/attack impat1 himp/pain imppai @@ -91,6 +148,8 @@ misc/invuse artiuse $limit misc/invuse 1 +$rumble misc/invuse LIGHT + world/podexplode podexp world/podgrow newpod world/wind wind @@ -110,6 +169,11 @@ misc/spawn respawn $limit misc/spawn 1 +$rumble misc/i_pkup LIGHT +$rumble misc/k_pkup LIGHT +$rumble misc/p_pkup LIGHT +$rumble misc/w_pkup LIGHT + // // Minotaur sounds // @@ -197,6 +261,10 @@ misc/ripslop ripslop $limit misc/chat 1 +$rumble misc/teleport TELEPORT + +$rumbledef TELEPORT 35 .3 .6 0 0 + world/drip gloop world/watersplash gloop world/lavasizzle burn @@ -285,10 +353,31 @@ menu/dismiss dorcls menu/clear dorcls $alias menu/advance menu/choose +$rumble menu/activate HEAVY +$rumble menu/backup MEDIUM +$rumble menu/prompt MEDIUM +$rumble menu/choose HEAVY +$rumble menu/cursor LIGHT +$rumble menu/change LIGHT +$rumble menu/invalid MEDIUM +$rumble menu/dismiss MEDIUM +$rumble menu/clear MEDIUM +$rumble menu/advance HEAVY + misc/secret dssecret +$rumble misc/secret SECRET + +$rumbledef SECRET 35 .3 .6 0 0 + $alias intermission/cooptotal *death $alias intermission/nextstage doors/dr1_clos $alias intermission/paststats plats/pt1_stop $alias intermission/pastcoopstats plats/pt1_stop $alias intermission/pastdmstats *gibbed + +$rumble intermission/cooptotal LIGHT +$rumble intermission/nextstage MEDIUM +$rumble intermission/paststats MEDIUM +$rumble intermission/pastcoopstats MEDIUM +$rumble intermission/pastdmstats MEDIUM diff --git a/wadsrc/static/filter/game-hexen/sndinfo.txt b/wadsrc/static/filter/game-hexen/sndinfo.txt index 06598288c..b6d92439d 100644 --- a/wadsrc/static/filter/game-hexen/sndinfo.txt +++ b/wadsrc/static/filter/game-hexen/sndinfo.txt @@ -82,6 +82,46 @@ $playeralias pig male *poison PigActive2 $playeralias pig male *falling PigPain $playeralias pig male *splat PigDeath +$playeralias fighter male *death PlayerFighterNormalDeath +$playeralias fighter male *crazydeath PlayerFighterCrazyDeath +$playeralias fighter male *burndeath PlayerFighterBurnDeath +$playeralias fighter male *xdeath PlayerFighterExtremeDeathPicker +$playeralias fighter male *pain100 PlayerFighterPain +$playersounddup fighter male *pain75 *pain100 +$playersounddup fighter male *pain50 *pain100 +$playersounddup fighter male *pain25 *pain100 +$playeralias fighter male *grunt PlayerFighterGrunt +$playeralias fighter male *land PlayerLand +$playeralias fighter male *poison PlayerPoisonCough +$playeralias fighter male *falling PlayerFighterFallingScream +$playeralias fighter male *splat PlayerFallingSplat +$playeralias fighter male *usefail PlayerFighterFailedUse +$playeralias fighter male *puzzfail PuzzleFailFighter +$playersound fighter male *jump fgtjump +$playeralias fighter male *fistgrunt FighterGrunt + +$rumble *death P_DEATH +$rumble *crazydeath P_DEATH +$rumble *burndeath P_DEATH +$rumble *xdeath P_DEATH +$rumble *pain100 P_HURT +$rumble *pain75 P_HURT +$rumble *pain50 P_HURT +$rumble *pain25 P_HURT +$rumble *grunt P_GRUNT +$rumble *land P_GRUNT +$rumble *poison P_GRUNT +$rumble *falling P_GRUNT +$rumble *splat P_GRUNT +$rumble *usefail P_GRUNT +$rumble *puzzfail P_GRUNT +$rumble *jump NONE +$rumble *fistgrunt P_GRUNT + +$rumbledef P_DEATH INTENSE +$rumbledef P_HURT HEAVY +$rumbledef P_GRUNT 2 .3 .6 .3 .3 + $alias world/drip Ambient10 $alias world/watersplash WaterSplash $alias world/lavasizzle LavaSizzle @@ -90,6 +130,10 @@ $alias world/wind Wind $alias world/quake Earthquake $alias world/thunder ThunderCrash +$rumble world/quake QUAKE + +$rumbledef QUAKE 2 1 1 0 0 + $alias misc/w_pkup PickupWeapon $alias misc/p_pkup PickupArtifact $alias misc/k_pkup PickupKey @@ -101,6 +145,16 @@ $alias misc/invuse UseArtifact $alias misc/freeze FreezeDeath $alias misc/icebreak FreezeShatter +$rumble misc/w_pkup LIGHT +$rumble misc/p_pkup LIGHT +$rumble misc/k_pkup LIGHT +$rumble misc/i_pkup LIGHT +$rumble misc/teleport TELEPORT +$rumble misc/keytry LIGHT +$rumble misc/invuse LIGHT + +$rumbledef TELEPORT 35 .3 .6 0 0 + $alias misc/chat Chat $alias misc/chat2 Chat @@ -156,6 +210,10 @@ $pitchshift PickupPiece 0 $pitchshift WeaponBuild 0 $pitchshift BellRing 0 +$rumble MysticIncant HEAVY +$rumble WeaponBuild LIGHT +$rumble BlastRadius NONE + $alias menu/activate DoorCloseLight $alias menu/backup PickupKey $alias menu/prompt Chat @@ -167,17 +225,39 @@ $alias menu/choose DoorCloseLight $alias menu/clear PlatformStop $alias menu/advance menu/choose +$rumble menu/activate HEAVY +$rumble menu/backup MEDIUM +$rumble menu/prompt MEDIUM +$rumble menu/cursor LIGHT +$rumble menu/change LIGHT +$rumble menu/invalid MEDIUM +$rumble menu/dismiss MEDIUM +$rumble menu/choose HEAVY +$rumble menu/clear MEDIUM +$rumble menu/advance HEAVY + // Hexen does not have ripslop sound like Heretic misc/ripslop dsempty misc/netnotch blddrp1 misc/secret dssecret +$rumble misc/ripslop NONE +$rumble misc/secret SECRET + +$rumbledef SECRET 35 .3 .6 0 0 + $alias intermission/cooptotal *death $alias intermission/nextstage DoorCloseLight $alias intermission/paststats PlatformStop $alias intermission/pastcoopstats PlatformStop $alias intermission/pastdmstats *gibbed +$rumble intermission/cooptotal LIGHT +$rumble intermission/nextstage MEDIUM +$rumble intermission/paststats MEDIUM +$rumble intermission/pastcoopstats MEDIUM +$rumble intermission/pastdmstats MEDIUM + $limit DoorCloseLight 4 $limit PuppyBeat 0 @@ -191,3 +271,53 @@ $limit EttinPain 0 $limit FireDemonPain 0 $limit SorcererPain 0 $limit DragonPain 0 + +$rumble FighterPunchMiss NONE +$rumble FighterPunchHitWall W_F_PUNCH +$rumble FighterPunchHitThing W_F_PUNCH +$rumble FighterAxeHitThing W_F_AXE +$rumble FighterHammerMiss W_F_HAMMER +$rumble FighterHammerHitWall W_F_HAMMER +$rumble FighterHammerHitThing W_F_HAMMER +$rumble FighterHammerContinuous NONE +$rumble FighterHammerExplode NONE +$rumble FighterSwordFire W_F_SWORD +$rumble FighterSwordExplode NONE + +$rumble ClericCStaffFire W_C_STAFF +$rumble ClericCStaffExplode W_C_STAFF +$rumble ClericCStaffHitThing W_C_MELEE +$rumble ClericFlameFire W_C_FLAME +$rumble ClericFlameExplode W_C_FLAME +$rumble ClericFlameCircle NONE +$rumble HolySymbolFire W_C_SYMBOL +$rumble SpiritActive NONE +$rumble SpiritAttack NONE +$rumble SpiritDie NONE + +$rumble MageWandFire W_M_WAND +$rumble MageLightningFire W_M_LIGHTNING +$rumble MageLightningContinuous NONE +$rumble MageLightningReady NONE +$rumble MageLightningZap NONE +$rumble MageShardsFire W_M_SHARD +$rumble MageShardsExplode NONE +$rumble MageStaffFire W_M_STAFF +$rumble MageStaffExplode NONE + +$rumble PigActive W_P_BITE +$rumble PigAttack W_P_BITE + +$rumbledef W_F_PUNCH 2 .6 .6 .6 .6 +$rumbledef W_F_AXE 3 .6 .6 .6 .6 +$rumbledef W_F_HAMMER 4 .6 .6 .6 .6 +$rumbledef W_F_SWORD 2 .6 .6 .6 .6 +$rumbledef W_C_MELEE 2 .6 .6 .6 .6 +$rumbledef W_C_STAFF 3 .6 .6 .6 .6 +$rumbledef W_C_FLAME 3 .6 .6 .6 .6 +$rumbledef W_C_SYMBOL 4 .6 .6 .6 .6 +$rumbledef W_M_WAND 2 .6 .6 .6 .6 +$rumbledef W_M_LIGHTNING 4 .6 .6 .6 .6 +$rumbledef W_M_SHARD 2 .6 .6 .6 .6 +$rumbledef W_M_STAFF 4 .6 .6 .6 .6 +$rumbledef W_P_BITE 2 .6 .6 .6 .6 diff --git a/wadsrc/static/filter/game-strife/sndinfo.txt b/wadsrc/static/filter/game-strife/sndinfo.txt index f029373dd..e59a7db67 100644 --- a/wadsrc/static/filter/game-strife/sndinfo.txt +++ b/wadsrc/static/filter/game-strife/sndinfo.txt @@ -46,6 +46,24 @@ $playersound player other *jump dscjump $playersound player other *fist dspunch $playersound player other *usefail dscnoway +$rumble *death P_DEATH +$rumble *xdeath P_DEATH +$rumble *gibbed P_DEATH +$rumble *pain100 P_HURT +$rumble *pain75 P_HURT +$rumble *pain50 P_HURT +$rumble *pain25 P_HURT +$rumble *grunt P_GRUNT +$rumble *land P_GRUNT +$rumble *jump NONE +$rumble *fist P_FIST +$rumble *usefail P_GRUNT + +$rumbledef P_DEATH INTENSE +$rumbledef P_HURT HEAVY +$rumbledef P_GRUNT 2 .3 .6 .3 .3 +$rumbledef P_FIST 3 .6 .6 .6 .6 + weapons/xbowshoot dsxbow weapons/xbowhit dsfirxpl weapons/assaultgun dsrifle @@ -65,6 +83,37 @@ weapons/sigil dssigil weapons/sigilhit dssglhit weapons/sigilcharge dssiglup +$rumble weapons/xbowshoot W_BOLT +$rumble weapons/xbowhit NONE +$rumble weapons/assaultgun W_BULLET +$rumble weapons/minimissile W_ROCKET +$rumble weapons/minimissilehit NONE +$rumble weapons/flamethrower W_FLAME +$rumble weapons/flameidle W_FLAME_IDLE +$rumble weapons/mauler1 W_MAULER +$rumble weapons/mauler2charge W_MAULER_CHARGE +$rumble weapons/mauler2fire W_MAULER_ALT +$rumble weapons/mauler2hit NONE +$rumble weapons/hegrenadeshoot W_GRENADE +$rumble weapons/hegrenadebang NONE +$rumble weapons/phgrenadeshoot W_GRENADE +$rumble weapons/phgrenadebang NONE +$rumble weapons/sigil W_SIGIL +$rumble weapons/sigilhit NONE +$rumble weapons/sigilcharge W_SIGIL_CHARGE + +$rumbledef W_BULLET 3 .6 .6 .6 .6 +$rumbledef W_BOLT 6 .2 .4 .4 .4 +$rumbledef W_GRENADE 6 .2 .4 0 .4 +$rumbledef W_FLAME_IDLE 1 0 .3 0 0 +$rumbledef W_FLAME 6 .4 .6 .6 .6 +$rumbledef W_MAULER_CHARGE 3 .3 .3 .3 .3 +$rumbledef W_MAULER_ALT 6 .6 .6 .6 .6 +$rumbledef W_MAULER 6 .6 .6 .6 .6 +$rumbledef W_SIGIL 18 .6 .6 .6 .6 +$rumbledef W_SIGIL_CHARGE 4 .6 .6 .6 .6 +$rumbledef W_ROCKET 6 .6 .6 .6 .6 + monsters/rifle dsrifle switches/normbutn dsswtchn @@ -109,6 +158,17 @@ menu/choose dsrifl // Choose a menu item menu/clear dsmtalht // Close top menu $alias menu/advance menu/choose +$rumble menu/activate HEAVY +$rumble menu/backup MEDIUM +$rumble menu/prompt MEDIUM +$rumble menu/cursor LIGHT +$rumble menu/change LIGHT +$rumble menu/invalid MEDIUM +$rumble menu/dismiss MEDIUM +$rumble menu/choose HEAVY +$rumble menu/clear MEDIUM +$rumble menu/advance HEAVY + misc/startupdone dspsdtha misc/teleport dstelept misc/swish dsswish @@ -135,6 +195,25 @@ misc/chat dsradio misc/invuse dsitemup misc/mask dsmask +$rumble misc/startupdone MEDIUM +$rumble misc/teleport TELEPORT +$rumble misc/swish LIGHT +$rumble misc/meathit W_MELEE +$rumble misc/metalhit W_MELEE +$rumble misc/missileinflight NONE +$rumble misc/disruptordeath NONE +$rumble misc/secret SECRET +$rumble misc/w_pkup LIGHT +$rumble misc/p_pkup LIGHT +$rumble misc/i_pkup LIGHT +$rumble misc/k_pkup LIGHT +$rumble misc/invuse LIGHT +$rumble misc/mask NONE + +$rumbledef W_MELEE 3 .6 .6 .6 .6 +$rumbledef TELEPORT 35 .3 .6 0 0 +$rumbledef SECRET 35 .3 .6 0 0 + plats/pt1_strt dspstart plats/pt1_stop dspstop plats/pt1_mid dsstnmov @@ -314,3 +393,10 @@ $alias intermission/nextstage misc/explosion $alias intermission/paststats world/barrelx $alias intermission/pastcoopstats world/barrelx $alias intermission/pastdmstats *gibbed + +$rumble intermission/tick LIGHT +$rumble intermission/cooptotal LIGHT +$rumble intermission/nextstage MEDIUM +$rumble intermission/paststats MEDIUM +$rumble intermission/pastcoopstats MEDIUM +$rumble intermission/pastdmstats MEDIUM diff --git a/wadsrc/static/language.csv b/wadsrc/static/language.csv index f826d3551..181598512 100644 --- a/wadsrc/static/language.csv +++ b/wadsrc/static/language.csv @@ -2782,7 +2782,7 @@ Apprentice,MNU_APPRENTICE,,,,,,Učeň,Lærling,Lehrling,,Lernanto,Aprendiz,,Kis Enchanter,MNU_ENCHANTER,,,,,,Kouzelník,Fortryller,Zauberer,,Ravisto,Encantador,,Lumooja,Enchanteur,,Bűvölő,Incantatore,妖術士,주술사,Betoveraar,Trollmann,Zaklinacz,Encantador,,Fermecător,Чародей,Чаробњак,Magiker,Sihirbaz,Чарувальник Sorcerer,MNU_SORCERER,,,,,,Čaroděj,Troldmand,Hexer,,Sorĉisto,Hechicero,,Taikuri,Sorcier,,Varázsló,Mago,魔術士,마법사,Tovenaar,Magiker,Mag,Feiticeiro,,Mag,Колдун,Врачар,Trollkarl,Büyücü,Чаклун Warlock,MNU_WARLOCK,,,,,,Zaklínač,,Hexenmeister,,Magiisto,Brujo,,Noita,Mage de Guerre,,Boszorkánymester,Stregone,黒魔術士,전투술사,Tovenaar,Veiviser,Wiedźmak,Mago,,Vrăjitor,Чернокнижник,Вештац,Häxkarl,Efsuncu,Відьмак -Archmage,MNU_ARCHMAGE,,,,,,Arcimág,Ærkemagiker,Erzmagier,,Magiantestro,Archimago,,Arkkivelho,Archimage,,Főmágus,Arcimago,大魔導師,대마법사,Aartsbisschop,Erkemagiker,Arcymag,Arquimago,,Prim-Vrăjitor,Архимаг,Врховни вештац,Ärkemagiker,Başbüyücü,Архімаг +Archimage,MNU_ARCHMAGE,,,,,,Arcimág,Ærkemagiker,Erzmagier,,Magiantestro,Archimago,,Arkkivelho,Archimage,,Főmágus,Arcimago,大魔導師,대마법사,Aartsbisschop,Erkemagiker,Arcymag,Arquimago,,Prim-Vrăjitor,Архимаг,Врховни вештац,Ärkemagiker,Başbüyücü,Архімаг Choose Class:,MNU_CHOOSECLASS,,,,,,Vyber třídu:,Vælg klasse:,Figurengruppe wählen:,,Elektu klason:,Elige tu clase:,,Valitse luokka:,Choissisez une Classe:,,Válassz kasztot:,Scegli la classe:,クラス選抜:,영웅 선택:,Kies klasse:,Velg klasse:,Wybierz klasę:,Escolha a classe:,,Alege Clasa:,Класс:,Класа:,Välj klass:,Sınıf seç:,Клас: Fighter,MNU_FIGHTER,,,,,,Bojovník,Kriger,Krieger,,Batalanto,Luchador,,Taistelija,Guerrier,,Harcos,Combattente,戦士,전사,Vechter,Kriger,Wojownik,Lutador,,Luptător,Воин,Борац,Kämpe,Dövüşçü,Боєць Cleric,MNU_CLERIC,,,,,,Klerik,Kleriker,Geistlicher,,Kleriko,Clérigo,,Pappi,Moine,,Lelkész,Chierico,僧侶,성직자,Geestelijk,Kleriker,Kleryk,Clérigo,,,Клерик,Свештеник,Kleriker,Ruhban,Клірик @@ -3543,4 +3543,4 @@ Slime Proof Suit,TAG_SLIMESUIT,,,,,,Slizuodolný oblek,,,,Muk-imuna kompleto,,,, Zorch Pack,TAG_ZORCHPACK,,,,,,Bzukový balík,,,,Zorĉpako,,,,,,,,,,,,,Pacote Zorch,,,,,,, Blue Key,TAG_CBLUEKEY,,,,,,Modrý klíč,,,,Blua ŝlosilo,,,,,,,,,,,,,Chave Azul,,,,,,, Yellow Key,TAG_CYELLOWKEY,,,,,,Žlutý klíč,,,,Flava ŝlosilo,,,,,,,,,,,,,Chave Amarela,,,,,,, -Red Key,TAG_CREDKEY,,,,,,Červený klíč,,,,Ruĝa ŝlosilo,,,,,,,,,,,,,Chave Vermelha,,,,,,, \ No newline at end of file +Red Key,TAG_CREDKEY,,,,,,Červený klíč,,,,Ruĝa ŝlosilo,,,,,,,,,,,,,Chave Vermelha,,,,,,, diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 7c01a2d74..3eb59b5f2 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -379,6 +379,7 @@ OptionMenu "InputMenu" protected Submenu "$OPTMNU_CONTROLS", "CustomizeControls" Submenu "$OPTMNU_MOUSE", "MouseOptions" Submenu "$OPTMNU_JOYSTICK", "JoystickOptions" + Submenu "$OPTMNU_HAPTICS", "HapticsOptions" IfOption(Windows) { @@ -417,21 +418,6 @@ OptionMenu "GameplayMenu" protected Submenu "$OPTMNU_COMPATIBILITY", "CompatibilityOptions" Submenu "$OPTMNU_AUTOMAP", "AutomapOptions" Submenu "$OPTMNU_HUD", "HUDOptions" -} - -//------------------------------------------------------------------------------------------- -// -// Multiplayer menu -// -//------------------------------------------------------------------------------------------- - -OptionMenu "MultiplayerMenu" protected -{ - Title "$OPTMNU_MULTIPLAYER" - - Submenu "$OPTMNU_PLAYER", "NewPlayerMenu" - Submenu "$GMPLYMNU_DEATHMATCH", "DeathmatchOptions" - Submenu "$GMPLYMNU_COOPERATIVE", "CoopOptions" Submenu "$OPTMNU_NETWORK", "NetworkOptions" } @@ -873,7 +859,6 @@ OptionMenu "MouseOptions" protected Option "$MOUSEMNU_LOOKSTRAFE", "lookstrafe", "OnOff" } - //------------------------------------------------------------------------------------------- // // Joystick Menu @@ -885,6 +870,7 @@ OptionMenu "JoystickOptionsDefaults" protected Title "$JOYMNU_OPTIONS" Option "$JOYMNU_ENABLE", "use_joystick", "YesNo" Option "$JOYMNU_NOMENU", "m_blockcontrollers", "YesNo" + ScaleSlider "$JOYMNU_HAPTICS", "haptics_strength", 0, 10, 1, "$OPTVAL_OFF", 0 IfOption(Windows) { Option "$JOYMNU_DINPUT", "joy_dinput", "YesNo" @@ -892,6 +878,10 @@ OptionMenu "JoystickOptionsDefaults" protected Option "$JOYMNU_PS2", "joy_ps2raw", "YesNo" } StaticText "" + Slider "$JOYMNU_TURNSPEED", "cl_analog_sensitivity_yaw", 0, 2.5, 0.1 + Slider "$JOYMNU_LOOKSPEED", "cl_analog_sensitivity_pitch", 0, 2.5, 0.1 + Option "$JOYMNU_ANALOGSTRAFERUN", "cl_analog_straferun", "OnOff" + StaticText "" StaticTextSwitchable "$JOYMNU_NOCON", "$JOYMNU_CONFIG", "ConfigureMessage" StaticTextSwitchable " ", "$JOYMNU_DISABLED1", "ConnectMessage1" StaticTextSwitchable " ", "$JOYMNU_DISABLED2", "ConnectMessage2" @@ -936,7 +926,6 @@ OptionMenu "JoystickConfigMenu" protected // Will be filled in by joystick code. } - //------------------------------------------------------------------------------------------- // // Video Menu @@ -1103,7 +1092,6 @@ OptionMenu "AppearOptions" protected } - OptionMenu "SWROptions" protected { Title "$OPTMNU_SWRENDERER" @@ -1382,6 +1370,34 @@ OptionMenu "AltHUDOptions" protected option "$ALTHUDMNU_STATSCOLOR", "hudcolor_stats", "TextColors" } +//------------------------------------------------------------------------------------------- +// +// Haptics menu +// +//------------------------------------------------------------------------------------------- + +OptionValue HapticsCompatTypes +{ + 0, "$OPTVAL_WARN" + 1, "$OPTVAL_NONE" + 2, "$OPTVAL_MATCH" + 3, "$OPTVAL_ALL" +} + +OptionMenu "HapticsOptions" protected +{ + Title "$HAPMNU_TITLE" + + ScaleSlider "$JOYMNU_HAPTICS", "haptics_strength", 0, 10, 1, "$OPTVAL_OFF" + Option "$HAPMNU_COMPAT", "haptics_compat", "HapticsCompatTypes" + StaticText "" + StaticText "$HAPMNU_OPTIONS" + Option "$HAPMNU_OPT_MENU", "haptics_do_menus", "OnOff" + Option "$HAPMNU_OPT_ACTION", "haptics_do_action", "OnOff" + Option "$HAPMNU_OPT_DAMAGE", "haptics_do_damage", "OnOff" + Option "$HAPMNU_OPT_WORLD", "haptics_do_world", "OnOff" +} + //------------------------------------------------------------------------------------------- // // Misc menu @@ -1702,7 +1718,6 @@ OptionMenu ColorPickerMenu protected // //------------------------------------------------------------------------------------------- - OptionValue MessageLevels { 0.0, "$OPTVAL_ITEMPICKUP" @@ -1817,7 +1832,6 @@ OptionValue JumpCrouchFreeLook 2, "$OPTVAL_ON" } - OptionMenu GameplayOptions protected { Position -35 @@ -1918,7 +1932,6 @@ OptionMenu CoopOptions protected * *=======================================*/ - OptionValue CompatModes { 0, "$OPTVAL_DEFAULT" @@ -2013,7 +2026,6 @@ OptionMenu "CompatPhysicsMenu" protected Class "CompatibilityMenu" } - OptionMenu "CompatRenderMenu" protected { Position -35 @@ -2053,7 +2065,6 @@ OptionValue SampleRates 48000, "$OPTVAL_48000HZ" } - OptionValue BufferSizes { 0, "$OPTVAL_DEFAULT" @@ -2066,7 +2077,6 @@ OptionValue BufferSizes 4096, "$OPTVAL_4096SAMPLES" } - OptionValue BufferCounts { 0, "$OPTVAL_DEFAULT" @@ -2083,7 +2093,6 @@ OptionValue BufferCounts 12, "12" } - OptionString ALDevices { // filled in by the sound code @@ -2106,7 +2115,6 @@ OptionString SpeakerModes "7.1", "$OPTSTR_7POINT1" } - OptionString Resamplers { "NoInterp", "$OPTSTR_NOINTERPOLATION" @@ -2115,7 +2123,6 @@ OptionString Resamplers "Spline", "$OPTSTR_SPLINE" } - OptionMenu OpenALSoundItems protected { Title "$OPENALMNU_TITLE" @@ -2124,7 +2131,6 @@ OptionMenu OpenALSoundItems protected Option "$OPENALMNU_RESAMPLER", "snd_alresampler", "ALResamplers" } - OptionValue MidiDevices { // filled in by the sound code @@ -2174,7 +2180,6 @@ OptionValue OplCores 3, "$OPTVAL_NUKEDOPL3" } - OptionMenu AdvSoundOptions protected { Title "$ADVSNDMNU_TITLE" @@ -2232,14 +2237,12 @@ OptionMenu OPNMIDICustomBanksMenu protected Title "$ADVSNDMNU_OPNBANKFILE" } - /*======================================= * * Module Replayer Options Menu * *=======================================*/ - OptionValue ModQuality { 0.0, "$OPTVAL_ALIASING" @@ -2252,7 +2255,6 @@ OptionValue ModQuality 7.0, "$OPTVAL_SINC" } - OptionValue ModVolumeRamps { 0.0, "$OPTVAL_NONE" @@ -2266,7 +2268,6 @@ OptionValue ModReplayers 1.0, "$OPTVAL_DUMB" } - OptionMenu ModReplayerOptions protected { Title "$MODMNU_TITLE" @@ -2501,7 +2502,6 @@ OptionValue ScaleModes 4, "1280x800" 5, "$OPTVAL_CUSTOM" 1, "$OPTVAL_LOWEST" - 7, "$OPTVAL_LOWEST12" } OptionValue CropAspect { @@ -2766,7 +2766,6 @@ OptionValue "BillboardModes" 1, "$OPTVAL_XYAXIS" } - OptionValue "Particles" { 0, "$OPTVAL_SQUARE" @@ -2867,7 +2866,6 @@ OptionValue ShadowFilter 3, "$OPTVAL_PCF_HIGH" } - OptionMenu "GLTextureGLOptions" protected { Title "$GLTEXMNU_TITLE" diff --git a/wadsrc/static/sndinfo.txt b/wadsrc/static/sndinfo.txt index c680201d6..cc2a9ff7e 100644 --- a/wadsrc/static/sndinfo.txt +++ b/wadsrc/static/sndinfo.txt @@ -258,3 +258,36 @@ dehextra/sound196 dsfre196 dehextra/sound197 dsfre197 dehextra/sound198 dsfre198 dehextra/sound199 dsfre199 + +// define set of rumble routines to be mapped later +$rumbledef INTENSE 35 .6 .6 .6 .6 +$rumbledef HEAVY 4 .6 .6 .6 .6 +$rumbledef MEDIUM 3 .4 .5 .4 .4 +$rumbledef LIGHT 2 .2 .4 .3 .3 +$rumbledef SUBTLE 2 .1 .3 0 0 +$rumbledef NONE 0 0 0 0 0 +$rumbledef QUAKE 2 1 1 0 0 +$rumbledef SECRET 35 .3 .6 0 0 + +// these exist hard-coded +// if the corresponding sounds also exist in game specific +// sndinfo files, they are overwritten there +$rumble world/quake QUAKE +$rumble misc/teleport SECRET +$rumble misc/startupdone MEDIUM +$rumble misc/push SUBTLE +$rumble misc/pushed SUBTLE +$rumble misc/i_pkup LIGHT +$rumble menu/activate HEAVY +$rumble menu/invalid MEDIUM +$rumble menu/dismiss MEDIUM +$rumble menu/choose HEAVY +$rumble menu/change LIGHT +$rumble menu/cursor LIGHT +$rumble misc/secret SECRET +$rumble menu/cursor LIGHT +$rumble *death INTENSE +$rumble *usesuccess MEDIUM +$rumble *grunt MEDIUM +$rumble *land MEDIUM +$rumble *pain HEAVY diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 04f6dfeda..0c68e0427 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -98,6 +98,7 @@ version "4.15.1" #include "zscript/actors/shared/corona.zs" #include "zscript/actors/shared/fogball.zs" #include "zscript/actors/shared/lightprobe.zs" +#include "zscript/actors/shared/haptics.zs" #include "zscript/actors/doom/doomplayer.zs" #include "zscript/actors/doom/possessed.zs" diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index 832e6071c..4e62651d9 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -1,3 +1,39 @@ +/* +** actor.zs +** +**--------------------------------------------------------------------------- +** +** Copyright 2010-2017 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +**--------------------------------------------------------------------------- +** +*/ + struct FCheckPosition { // in @@ -25,11 +61,11 @@ struct FCheckPosition native int portalgroup; native int PushTime; - + // These are internal helpers to properly initialize an object of this type. private native void _Constructor(); private native void _Destructor(); - + native void ClearLastRipped(); } @@ -110,7 +146,7 @@ class Actor : Thinker native const MELEEDELTA = 20; // flags are not defined here, the native fields for those get synthesized from the internal tables. - + // for some comments on these fields, see their native representations in actor.h. native readonly Actor snext; // next in sector list. native PlayerInfo Player; @@ -419,11 +455,10 @@ class Actor : Thinker native native deprecated("2.3", "Use Scale.Y instead") double ScaleY; //FStrifeDialogueNode *Conversation; // [RH] The dialogue to show when this actor is used.; - - + Default { - LightLevel -1; + LightLevel -1; Scale 1; Health DEFAULT_HEALTH; Reactiontime 8; @@ -491,8 +526,8 @@ class Actor : Thinker native RenderRequired 0; FriendlySeeBlocks 10; // 10 (blocks) * 128 (one map unit block) LandingSpeed -8; // landing speed from a jump with normal gravity (squats the player's view) -} - + } + // Functions // 'parked' global functions. @@ -501,7 +536,7 @@ class Actor : Thinker native native clearscope static Vector2 AngleToVector(double angle, double length = 1); native clearscope static Vector2 RotateVector(Vector2 vec, double angle); native clearscope static double Normalize180(double ang); - + virtual void MarkPrecacheSounds() { MarkSound(SeeSound); @@ -522,7 +557,7 @@ class Actor : Thinker native { return GetPointer(ptr_select1) == GetPointer(ptr_select2); } - + clearscope static double BobSin(double fb) { return sin(fb * (180./32)) * 8; @@ -564,8 +599,8 @@ class Actor : Thinker native return true; } - // [AA] Called by inventory items in CallTryPickup to see if this actor needs - // to process them in some way before they're received. Gets called before + // [AA] Called by inventory items in CallTryPickup to see if this actor needs + // to process them in some way before they're received. Gets called before // the item's TryPickup, allowing fully customized handling of all items. virtual bool CanReceive(Inventory item) { @@ -631,7 +666,7 @@ class Actor : Thinker native { return false; } - + virtual class GetBloodType(int type) { Class bloodcls; @@ -658,7 +693,7 @@ class Actor : Thinker native } return bloodcls; } - + virtual int GetGibHealth() { if (GibHealth != int.min) @@ -670,7 +705,7 @@ class Actor : Thinker native return -int(GetSpawnHealth() * gameinfo.gibfactor); } } - + virtual double GetDeathHeight() { // [RH] Allow the death height to be overridden using metadata. @@ -692,7 +727,7 @@ class Actor : Thinker native return MAX(metaheight, 0); } } - + virtual String GetObituary(Actor victim, Actor inflictor, Name mod, bool playerattack) { if (mod == 'Telefrag') @@ -710,7 +745,7 @@ class Actor : Thinker native { return SelfObituary; } - + virtual int OnDrain(Actor victim, int damage, Name dmgtype) { return damage; @@ -723,7 +758,7 @@ class Actor : Thinker native // return false in PreTeleport() to cancel the action early virtual bool PreTeleport( Vector3 destpos, double destangle, int flags ) { return true; } virtual void PostTeleport( Vector3 destpos, double destangle, int flags ) {} - + native virtual bool OkayToSwitchTarget(Actor other); native clearscope static class GetReplacement(class cls); native clearscope static class GetReplacee(class cls); @@ -744,7 +779,7 @@ class Actor : Thinker native protected native void CheckPortalTransition(bool linked = true); native clearscope bool HasConversation() const; native clearscope bool CanTalk() const; - native bool StartConversation(Actor player, bool faceTalker = true, bool saveAngle = true); + native bool StartConversation(Actor player, bool faceTalker = true, bool saveAngle = true, bool rumble = true); native clearscope string GetTag(string defstr = "") const; native clearscope string GetCharacterName() const; @@ -769,7 +804,7 @@ class Actor : Thinker native native clearscope Vector3 PosRelative(sector sec) const; native void RailAttack(FRailParams p); native clearscope Name GetDecalName() const; - + native void HandleSpawnFlags(); native void ExplodeMissile(line lin = null, Actor target = null, bool onsky = false); native void RestoreDamage(); @@ -791,7 +826,7 @@ class Actor : Thinker native native bool CheckFor3DFloorHit(double z, bool trigger); native bool CheckFor3DCeilingHit(double z, bool trigger); native int CheckMonsterUseSpecials(Line blocking = null); - + native bool CheckMissileSpawn(double maxdist); native bool CheckPosition(Vector2 pos, bool actorsonly = false, FCheckPosition tm = null); native bool TestMobjLocation(); @@ -816,7 +851,7 @@ class Actor : Thinker native void A_Light1() { if (player) player.extralight = 1; } void A_Light2() { if (player) player.extralight = 2; } void A_LightInverse() { if (player) player.extralight = 0x80000000; } - + native Actor OldSpawnMissile(Actor dest, class type, Actor owner = null); native Actor SpawnPuff(class pufftype, vector3 pos, double hitdir, double particledir, int updown, int flags = 0, Actor victim = null); native Actor SpawnBlood (Vector3 pos1, double dir, int damage); @@ -824,7 +859,7 @@ class Actor : Thinker native native bool HitWater (sector sec, Vector3 pos, bool checkabove = false, bool alert = true, bool force = false, int flags = 0); native void PlaySpawnSound(Actor missile); native clearscope bool CountsAsKill() const; - + native bool Teleport(Vector3 pos, double angle, int flags); native void TraceBleed(int damage, Actor missile); native void TraceBleedAngle(int damage, double angle, double pitch); @@ -843,7 +878,7 @@ class Actor : Thinker native native bool, Actor, double PerformShadowChecks (Actor other, Vector3 pos); native bool HitFriend(); native bool MonsterMove(); - + native SeqNode StartSoundSequenceID (int sequence, int type, int modenum, bool nostop = false); native SeqNode StartSoundSequence (Name seqname, int modenum); native void StopSoundSequence(); @@ -852,7 +887,7 @@ class Actor : Thinker native native double, double GetFriction(); native bool, Actor TestMobjZ(bool quick = false); native clearscope static bool InStateSequence(State newstate, State basestate); - + bool TryWalk () { if (!MonsterMove ()) @@ -862,7 +897,7 @@ class Actor : Thinker native movecount = random[TryWalk](0, 15); return true; } - + native bool TryMove(vector2 newpos, int dropoff, bool missilecheck = false, FCheckPosition tm = null); native bool CheckMove(vector2 newpos, int flags = 0, FCheckPosition tm = null); native void NewChaseDir(); @@ -937,10 +972,11 @@ class Actor : Thinker native // // AActor :: GetLevelSpawnTime // - // Returns the time when this actor was spawned, + // Returns the time when this actor was spawned, // relative to the current level. // //========================================================================== + clearscope int GetLevelSpawnTime() const { return SpawnTime - level.totaltime + level.time; @@ -953,7 +989,7 @@ class Actor : Thinker native // Returns the number of ticks passed since this actor was spawned. // //========================================================================== - + clearscope int GetAge() const { return level.totaltime - SpawnTime; @@ -964,8 +1000,6 @@ class Actor : Thinker native return 1. / (1 << (accuracy * 5 / 100)); } - - protected native void DestroyAllInventory(); // This is not supposed to be called by user code! native clearscope Inventory FindInventory(class itemtype, bool subclass = false) const; native Inventory GiveInventoryType(class itemtype); @@ -1019,7 +1053,7 @@ class Actor : Thinker native void A_SetRipMax(int maximum) { RipLevelMax = maximum; } void A_ScreamAndUnblock() { A_Scream(); A_NoBlocking(); } void A_ActiveAndUnblock() { A_ActiveSound(); A_NoBlocking(); } - + //--------------------------------------------------------------------------- // // FUNC P_SpawnMissileAngle @@ -1038,7 +1072,6 @@ class Actor : Thinker native { return SpawnMissileAngleZSpeed (z, type, angle, vz, GetDefaultSpeed (type), owner); } - void A_SetScale(double scalex, double scaley = 0, int ptr = AAPTR_DEFAULT, bool usezero = false) { @@ -1099,7 +1132,7 @@ class Actor : Thinker native } void A_ChangeLinkFlags(int blockmap = FLAG_NO_CHANGE, int sector = FLAG_NO_CHANGE) - { + { UnlinkFromWorld(); if (blockmap != FLAG_NO_CHANGE) bNoBlockmap = blockmap; if (sector != FLAG_NO_CHANGE) bNoSector = sector; @@ -1124,7 +1157,7 @@ class Actor : Thinker native mo.Vel.Z = random[Dirt]() / 64.; } } - + // // A_SinkMobj // Sink a mobj incrementally into the floor @@ -1142,8 +1175,8 @@ class Actor : Thinker native // // A_RaiseMobj - // Raise a mobj incrementally from the floor to - // + // Raise a mobj incrementally from the floor to + // bool RaiseMobj (double speed) { @@ -1163,14 +1196,14 @@ class Actor : Thinker native } return true; } - + Actor AimTarget() { FTranslatedLineTarget t; BulletSlope(t, ALF_PORTALRESTRICT); return t.linetarget; } - + void RestoreRenderStyle() { bShadow = default.bShadow; @@ -1178,7 +1211,7 @@ class Actor : Thinker native RenderStyle = default.RenderStyle; Alpha = default.Alpha; } - + virtual bool ShouldSpawn() { return true; @@ -1203,7 +1236,7 @@ class Actor : Thinker native // Meh, MBF redundant functions. Only for DeHackEd support. native bool A_LineEffect(int boomspecial = 0, int tag = 0); // End of MBF redundant functions. - + native void A_MonsterRail(); native void A_Pain(); native void A_NoBlocking(bool drop = true); @@ -1256,7 +1289,7 @@ class Actor : Thinker native native void A_FadeTo(double target, double amount = 0.1, int flags = 0); native void A_SpawnDebris(class spawntype, bool transfer_translation = false, double mult_h = 1, double mult_v = 1); native void A_SpawnParticle(color color1, int flags = 0, int lifetime = TICRATE, double size = 1, double angle = 0, double xoff = 0, double yoff = 0, double zoff = 0, double velx = 0, double vely = 0, double velz = 0, double accelx = 0, double accely = 0, double accelz = 0, double startalphaf = 1, double fadestepf = -1, double sizestep = 0); - native void A_SpawnParticleEx(color color1, TextureID texture, int style = STYLE_None, int flags = 0, int lifetime = TICRATE, double size = 1, double angle = 0, double xoff = 0, double yoff = 0, double zoff = 0, double velx = 0, double vely = 0, double velz = 0, double accelx = 0, double accely = 0, double accelz = 0, double startalphaf = 1, double fadestepf = -1, double sizestep = 0, double startroll = 0, double rollvel = 0, double rollacc = 0); + native void A_SpawnParticleEx(color color1, TextureID texture, int style = STYLE_None, int flags = 0, int lifetime = TICRATE, double size = 1, double angle = 0, double xoff = 0, double yoff = 0, double zoff = 0, double velx = 0, double vely = 0, double velz = 0, double accelx = 0, double accely = 0, double accelz = 0, double startalphaf = 1, double fadestepf = -1, double sizestep = 0, double startroll = 0, double rollvel = 0, double rollacc = 0, double fadeoutstepf = 0); native void A_ExtChase(bool usemelee, bool usemissile, bool playactive = true, bool nightmarefast = false); native void A_DropInventory(class itemtype, int amount = -1); native void A_SetBlend(color color1, double alpha, int tics, color color2 = 0, double alpha2 = 0.); @@ -1285,7 +1318,7 @@ class Actor : Thinker native native void A_CountdownArg(int argnum, statelabel targstate = null); native state A_MonsterRefire(int chance, statelabel label); native void A_LookEx(int flags = 0, double minseedist = 0, double maxseedist = 0, double maxheardist = 0, double fov = 0, statelabel label = null); - + native void A_Recoil(double xyvel); native int A_RadiusGive(class itemtype, double distance, int flags, int amount = 0, class filter = null, name species = "None", double mindist = 0, int limit = 0); native void A_CustomMeleeAttack(int damage = 0, sound meleesound = "", sound misssound = "", name damagetype = "none", bool bleed = true); @@ -1294,7 +1327,7 @@ class Actor : Thinker native native void A_RadiusDamageSelf(int damage = 128, double distance = 128.0, int flags = 0, class flashtype = null); native int GetRadiusDamage(Actor thing, int damage, double distance, double fulldmgdistance = 0.0, bool oldradiusdmg = false, bool circular = false); native int RadiusAttack(Actor bombsource, int bombdamage, double bombdistance, Name bombmod = 'none', int flags = RADF_HURTSOURCE, double fulldamagedistance = 0.0, name species = "None"); - + native void A_Respawn(int flags = 1); native void A_RestoreSpecialPosition(); native void A_QueueCorpse(); @@ -1365,15 +1398,15 @@ class Actor : Thinker native action native void A_OverlayAlpha(int layer, double alph); action native void A_OverlayRenderStyle(int layer, int style); action native void A_OverlayTranslation(int layer, name trname); - + native bool A_AttachLightDef(Name lightid, Name lightdef); native bool A_AttachLight(Name lightid, int type, Color lightcolor, int radius1, int radius2, int flags = 0, Vector3 ofs = (0,0,0), double param = 0, double spoti = 10, double spoto = 25, double spotp = 0, double intensity = 1.0, double softshadowradius = 5.0); native bool A_RemoveLight(Name lightid); //================================================ - // + // // Bone Offset Setters - // + // //================================================ native version("4.15.1") void SetBoneRotation(int boneIndex, Quat rotation, int mode = SB_ADD, double interpolation_duration = 1.0); @@ -1428,9 +1461,9 @@ class Actor : Thinker native native version("4.15.1") void ClearBoneOffsets(); //================================================ - // + // // Bone Offset Getters - // + // //================================================ /* rotation, translation, scaling */ @@ -1438,58 +1471,58 @@ class Actor : Thinker native native version("4.15.1") Quat, Vector3, Vector3 GetNamedBoneOffset(Name boneName); //================================================ - // + // // Bone Info Getters - // + // //================================================ - + native version("4.15.1") void GetRootBones(out Array rootBones); - + native version("4.15.1") Name GetBoneName(int boneIndex); native version("4.15.1") int GetBoneIndex(Name boneName); - + native version("4.15.1") int GetBoneParent(int boneIndex); native version("4.15.1") int GetNamedBoneParent(Name boneName); // return value lower than 0 means it's a root bone, and as such has no parent - + native version("4.15.1") void GetBoneChildren(int boneIndex, out Array children); native version("4.15.1") void GetNamedBoneChildren(Name boneName, out Array children); - + /* rotation, translation, scaling */ native version("4.15.1") Quat, Vector3, Vector3 GetBoneBaseTRS(int boneIndex); native version("4.15.1") Quat, Vector3, Vector3 GetNamedBoneBaseTRS(Name boneName); - + native version("4.15.1") Vector3 GetBoneBasePosition(int boneIndex); native version("4.15.1") Vector3 GetNamedBoneBasePosition(Name boneName); - + native version("4.15.1") Quat GetBoneBaseRotation(int boneIndex); native version("4.15.1") Quat GetNamedBoneBaseRotation(Name boneName); native version("4.15.1") int GetBoneCount(); //================================================ - // + // // Bone Pose Getters - // + // //================================================ native version("4.15.1") int GetAnimStartFrame(Name animName); native version("4.15.1") int GetAnimEndFrame(Name animName); native version("4.15.1") double GetAnimFramerate(Name animName); - + /* rotation, translation, scaling */ native version("4.15.1") Quat, Vector3, Vector3 GetBoneFramePose(int boneIndex, int frame); native version("4.15.1") Quat, Vector3, Vector3 GetNamedBoneFramePose(Name boneName, int frame); //================================================ - // + // // Bone TRS Getters - // + // //================================================ - + /* rotation, translation, scaling, doesn't include parent bones */ native version("4.15.1") Quat, Vector3, Vector3 GetBoneTRS(int boneIndex, bool include_offsets = true); native version("4.15.1") Quat, Vector3, Vector3 GetNamedBoneTRS(Name boneName, bool include_offsets = true); - + /* angle, pitch, roll, includes parent bones */ native version("4.15.1") Vector3 GetBoneEulerAngles(int boneIndex, bool include_offsets = true); native version("4.15.1") Vector3 GetNamedBoneEulerAngles(Name boneName, bool include_offsets = true); @@ -1497,13 +1530,13 @@ class Actor : Thinker native //input position/direction vectors are in xzy, model space native version("4.15.1") Vector3, Vector3, Vector3 TransformByBone(int boneIndex, Vector3 position, Vector3 forward = (1,0,0), Vector3 up = (0,0,1), bool include_offsets = true); native version("4.15.1") Vector3, Vector3, Vector3 TransformByNamedBone(Name boneName, Vector3 position, Vector3 forward = (1,0,0), Vector3 up = (0,0,1), bool include_offsets = true); - + version("4.15.1") Vector3, Vector3, Vector3 GetBonePosition(int boneIndex, bool include_offsets = true) { let [a, b, c] = TransformByBone(boneIndex, GetBoneBasePosition(boneIndex), include_offsets:include_offsets); return a, b, c; } - + version("4.15.1") Vector3, Vector3, Vector3 GetNamedBonePosition(name boneName, bool include_offsets = true) { let [a, b, c] = GetBonePosition(GetBoneIndex(boneName), include_offsets); @@ -1511,11 +1544,11 @@ class Actor : Thinker native } //================================================ - // + // // Bone Matrix Getters - // + // //================================================ - + //outMatrix will be a 16-length array containing the raw matrix data native version("4.15.1") void GetBoneMatrixRaw(int boneIndex, out Array outMatrix, bool include_offsets = true); native version("4.15.1") void GetNamedBoneMatrixRaw(Name boneName, out Array outMatrix, bool include_offsets = true); @@ -1523,9 +1556,9 @@ class Actor : Thinker native native version("4.15.1") void GetObjectToWorldMatrixRaw(out Array outMatrix); //================================================ - // - // - // + // + // + // //================================================ native version("4.12") void SetAnimation(Name animName, double framerate = -1, int startFrame = -1, int loopFrame = -1, int endFrame = -1, int interpolateTics = -1, int flags = 0); @@ -1537,8 +1570,7 @@ class Actor : Thinker native native version("4.12") void SetModelFlag(int flag, int iqmFlags = 0); native version("4.12") void ClearModelFlag(int flag, int iqmFlags = 0); native version("4.12") void ResetModelFlags(bool resetModel = true, bool resetIqm = false); - - + action version("4.12") void A_SetAnimation(Name animName, double framerate = -1, int startFrame = -1, int loopFrame = -1, int endFrame = -1, int interpolateTics = -1, int flags = 0) { invoker.SetAnimation(animName, framerate, startFrame, loopFrame, endFrame, interpolateTics, flags); @@ -1553,20 +1585,16 @@ class Actor : Thinker native { invoker.SetModelFlag(flag); } - + action version("4.12") void A_ClearModelFlag(int flag) { invoker.ClearModelFlag(flag); } - + action version("4.12") void A_ResetModelFlags() { invoker.ResetModelFlags(); } - - - - int ACS_NamedExecute(name script, int mapnum=0, int arg1=0, int arg2=0, int arg3=0) { @@ -1600,7 +1628,7 @@ class Actor : Thinker native { return ACS_ExecuteWithResult(-int(script), arg1, arg2, arg3, arg4); } - + //=========================================================================== // // Sounds @@ -1611,23 +1639,23 @@ class Actor : Thinker native { if (DeathSound) { - A_StartSound(DeathSound, CHAN_VOICE, CHANF_DEFAULT, 1, bBoss || bFullvolDeath? ATTN_NONE : ATTN_NORM); + A_StartSound(DeathSound, CHAN_VOICE, CHANF_DEFAULT|CHANF_NORUMBLE, 1, bBoss || bFullvolDeath? ATTN_NONE : ATTN_NORM); } } void A_XScream() { - A_StartSound(player? Sound("*gibbed") : Sound("misc/gibbed"), CHAN_VOICE); + A_StartSound(player? Sound("*gibbed") : Sound("misc/gibbed"), CHAN_VOICE, CHANF_NORUMBLE); } void A_ActiveSound() { if (ActiveSound) { - A_StartSound(ActiveSound, CHAN_VOICE); + A_StartSound(ActiveSound, CHAN_VOICE, CHANF_NORUMBLE); } } - + virtual void PlayerLandedMakeGruntSound(actor onmobj) { bool grunted; @@ -1639,7 +1667,7 @@ class Actor : Thinker native // Why should this number vary by gravity? if (self.Vel.Z < -self.player.mo.GruntSpeed) { - A_StartSound("*grunt", CHAN_VOICE); + A_StartSound("*grunt", CHAN_VOICE, CHANF_NORUMBLE); grunted = true; } bool isliquid = (pos.Z <= floorz) && HitFloor (); @@ -1647,11 +1675,11 @@ class Actor : Thinker native { if (!grunted) { - A_StartSound("*land", CHAN_AUTO); + A_StartSound("*land", CHAN_AUTO, CHANF_NORUMBLE); } else { - A_StartSoundIfNotSame("*land", "*grunt", CHAN_AUTO); + A_StartSoundIfNotSame("*land", "*grunt", CHAN_AUTO, CHANF_NORUMBLE); } } } @@ -1673,7 +1701,7 @@ class Actor : Thinker native if (oldlevel < 3 && WaterLevel == 3) { // Our head just went under. - A_StartSound("*dive", CHAN_VOICE, attenuation: ATTN_NORM); + A_StartSound("*dive", CHAN_VOICE, CHANF_NORUMBLE, attenuation: ATTN_NORM); } else if (oldlevel == 3 && WaterLevel < 3) { @@ -1681,12 +1709,75 @@ class Actor : Thinker native if (player.air_finished > Level.maptime) { // We hadn't run out of air yet. - A_StartSound("*surface", CHAN_VOICE, attenuation: ATTN_NORM); + A_StartSound("*surface", CHAN_VOICE, CHANF_NORUMBLE, attenuation: ATTN_NORM); } // If we were running out of air, then ResetAirSupply() will play *gasp. } } + //---------------------------------------------------------------------------- + // + // player rumble events + // + //---------------------------------------------------------------------------- + + virtual void PlayerLandedMakeRumble(actor onmobj) + { + if (!CVar.GetCVar("haptics_do_world").GetBool()) return; + + bool isliquid = (pos.Z <= floorz) && HitFloor (); + if (onmobj != NULL || !isliquid) + { + Haptics.Rumble("*land"); + } + else if (self.Vel.Z < -self.player.mo.GruntSpeed) + { + Haptics.Rumble("*grunt"); + } + } + + virtual void PlayerHurtMakeRumble(actor source) + { + if (!CVar.GetCVar("haptics_do_damage").GetBool()) return; + + Haptics.Rumble("*pain"); + } + + virtual void PlayerDiedMakeRumble(actor source) + { + if (!CVar.GetCVar("haptics_do_damage").GetBool()) return; + + Haptics.Rumble("*death"); + } + + virtual void PlayerUsedSomethingMakeRumble(int activationType, int levelNum, int lineNum, int lineSpecial) + { + if (!CVar.GetCVar("haptics_do_action").GetBool()) return; + + Haptics.Rumble("*usesuccess"); + } + + virtual void PlayerTeleportedMakeRumble() + { + if (!CVar.GetCVar("haptics_do_world").GetBool()) return; + + Haptics.Rumble("misc/teleport"); + } + + virtual void PlayerPushedSomethingMakeRumble(actor thing) + { + if (!CVar.GetCVar("haptics_do_world").GetBool()) return; + + Haptics.Rumble("misc/push"); + } + + virtual void PlayerWasPushedMakeRumble(actor source) + { + if (!CVar.GetCVar("haptics_do_world").GetBool()) return; + + Haptics.Rumble("misc/pushed"); + } + //---------------------------------------------------------------------------- // // PROC A_CheckSkullDone @@ -1698,8 +1789,6 @@ class Actor : Thinker native if (player == NULL) Destroy(); } - - States(Actor, Overlay, Weapon, Item) { Spawn: diff --git a/wadsrc/static/zscript/actors/inventory/powerups.zs b/wadsrc/static/zscript/actors/inventory/powerups.zs index 5d0f0d7da..f12540cfc 100644 --- a/wadsrc/static/zscript/actors/inventory/powerups.zs +++ b/wadsrc/static/zscript/actors/inventory/powerups.zs @@ -1104,6 +1104,7 @@ class PowerWeaponLevel2 : Powerup if (weap.GetReadyState() != ready) { player.ReadyWeapon = sister; + player.ReadyWeapon.OnSelect(fromPowerup: true); player.SetPsprite(PSP_WEAPON, ready); } else @@ -1114,11 +1115,13 @@ class PowerWeaponLevel2 : Powerup // If the weapon changes but the state does not, we have to manually change the PSprite's caller here. psp.Caller = sister; player.ReadyWeapon = sister; + player.ReadyWeapon.OnSelect(fromPowerup: true); } else { // Something went wrong. Initiate a regular weapon change. player.PendingWeapon = sister; + player.PendingWeapon.OnSelect(fromPowerup: true); } } } diff --git a/wadsrc/static/zscript/actors/inventory/weapons.zs b/wadsrc/static/zscript/actors/inventory/weapons.zs index 26c61269a..19a7d093c 100644 --- a/wadsrc/static/zscript/actors/inventory/weapons.zs +++ b/wadsrc/static/zscript/actors/inventory/weapons.zs @@ -143,6 +143,15 @@ class Weapon : StateProvider } return -1, 0; } + + + // [AA] Called when the weapon is selected, including + // PowerWeaponLevel2 activation: + virtual void OnSelect(bool fromPowerup = false) {} + + // [AA] Called when the weapon is deselected, including + // PowerWeaponLevel2 running out or being tossed: + virtual void OnDeselect(bool fromPowerup = false, bool onToss = false) {} virtual State GetReadyState () { @@ -690,6 +699,13 @@ class Weapon : StateProvider { return SisterWeapon.CreateTossable (amt); } + + // [AA] This weapon was selected and its amount is about to become 0: + if (Amount == 1 && Owner != NULL && Owner.Player != NULL && Owner.Player.ReadyWeapon == self) + { + OnDeselect(onToss: true); + } + let copy = Weapon(Super.CreateTossable (-1)); if (copy != NULL) @@ -868,6 +884,7 @@ class Weapon : StateProvider if (player.PendingWeapon == NULL || player.PendingWeapon == WP_NOCHANGE) { player.refire = 0; + OnDeselect(fromPowerup: true); player.ReadyWeapon = SisterWeapon; player.SetPsprite(PSP_WEAPON, SisterWeapon.GetReadyState()); } @@ -878,6 +895,7 @@ class Weapon : StateProvider if (psp != null && psp.Caller == player.ReadyWeapon && psp.CurState.InStateSequence(ready)) { // If the weapon changes but the state does not, we have to manually change the PSprite's caller here. + OnDeselect(fromPowerup: true); psp.Caller = SisterWeapon; player.ReadyWeapon = SisterWeapon; } @@ -886,6 +904,7 @@ class Weapon : StateProvider if (player.PendingWeapon == NULL || player.PendingWeapon == WP_NOCHANGE) { // Something went wrong. Initiate a regular weapon change. + OnDeselect(fromPowerup: true); player.refire = 0; player.ReadyWeapon = SisterWeapon; player.SetPsprite(PSP_WEAPON, SisterWeapon.GetReadyState()); diff --git a/wadsrc/static/zscript/actors/player/player.zs b/wadsrc/static/zscript/actors/player/player.zs index a16183204..152be9b72 100644 --- a/wadsrc/static/zscript/actors/player/player.zs +++ b/wadsrc/static/zscript/actors/player/player.zs @@ -1,3 +1,36 @@ +/* +** player.zs +** +**--------------------------------------------------------------------------- +** Copyright 2010-2017 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ struct UserCmd native { @@ -17,7 +50,7 @@ class PlayerPawn : Actor const TURN180_TICKS = ((TICRATE / 4) + 1); // 16 pixels of bob const MAXBOB = 16.; - + int crouchsprite; int MaxHealth; int BonusHealth; @@ -61,7 +94,7 @@ class PlayerPawn : Actor meta int TeleportFreezeTime; meta int ColorRangeStart; // Skin color range meta int ColorRangeEnd; - + property prefix: Player; property HealRadiusType: HealingradiusType; property InvulnerabilityMode: InvulMode; @@ -83,7 +116,7 @@ class PlayerPawn : Actor property ViewBob: ViewBob; property ViewBobSpeed: ViewBobSpeed; property WaterClimbSpeed : WaterClimbSpeed; - + flagdef NoThrustWhenInvul: PlayerFlags, 0; flagdef CanSuperMorph: PlayerFlags, 1; flagdef CrouchableMorph: PlayerFlags, 2; @@ -95,7 +128,7 @@ class PlayerPawn : Actor { PF_VOODOO_ZOMBIE = 1<<4, } - + Default { Health 100; @@ -138,8 +171,7 @@ class PlayerPawn : Actor Player.TeleportFreezeTime 18; Obituary "$OB_MPDEFAULT"; } - - + //=========================================================================== // // PlayerPawn :: Tick @@ -176,7 +208,7 @@ class PlayerPawn : Actor //=========================================================================== // - // + // // //=========================================================================== @@ -190,7 +222,7 @@ class PlayerPawn : Actor FullHeight = Height; if (!SetupCrouchSprite(crouchsprite)) crouchsprite = 0; } - + //=========================================================================== // // PlayerPawn :: PostBeginPlay @@ -215,7 +247,6 @@ class PlayerPawn : Actor } } - //=========================================================================== // // PlayerPawn :: MarkPrecacheSounds @@ -227,10 +258,10 @@ class PlayerPawn : Actor Super.MarkPrecacheSounds(); MarkPlayerSounds(); } - + //---------------------------------------------------------------------------- // - // + // // //---------------------------------------------------------------------------- @@ -245,7 +276,7 @@ class PlayerPawn : Actor if (InStateSequence(CurState, SpawnState) && SeeState != NULL) SetState (SeeState); } - + virtual void PlayAttacking () { if (MissileState != null) SetState (MissileState); @@ -255,14 +286,14 @@ class PlayerPawn : Actor { if (MeleeState != null) SetState (MeleeState); } - + virtual void MorphPlayerThink() { } - + //---------------------------------------------------------------------------- // - // + // // //---------------------------------------------------------------------------- @@ -282,10 +313,10 @@ class PlayerPawn : Actor bRespawnInvul = true; // [RH] special effect } } - + //---------------------------------------------------------------------------- // - // + // // //---------------------------------------------------------------------------- @@ -334,21 +365,21 @@ class PlayerPawn : Actor return message; } - + //---------------------------------------------------------------------------- // // This is for SBARINFO. // //---------------------------------------------------------------------------- - clearscope int, int GetEffectTicsForItem(class item) const + clearscope int, int GetEffectTicsForItem(class item) const { let pg = (class)(item); if (pg != null) { let powerupType = (class)(GetDefaultByType(pg).PowerupType); let powerup = Powerup(FindInventory(powerupType)); - if(powerup != null) + if(powerup != null) { let maxtics = GetDefaultByType(pg).EffectTics; if (maxtics == 0) maxtics = powerup.default.EffectTics; @@ -357,7 +388,6 @@ class PlayerPawn : Actor } return -1, -1; } - //=========================================================================== // @@ -370,11 +400,11 @@ class PlayerPawn : Actor void CheckWeaponSwitch(Class ammotype) { let player = self.player; - if (!player.GetNeverSwitch() && player.PendingWeapon == WP_NOCHANGE && + if (!player.GetNeverSwitch() && player.PendingWeapon == WP_NOCHANGE && (player.ReadyWeapon == NULL || player.ReadyWeapon.bWimpy_Weapon)) { let best = BestWeapon (ammotype); - if (best != NULL && !best.bNoAutoSwitchTo && + if (best != NULL && !best.bNoAutoSwitchTo && (player.ReadyWeapon == NULL || best.SelectionOrder < player.ReadyWeapon.SelectionOrder)) { player.PendingWeapon = best; @@ -391,7 +421,7 @@ class PlayerPawn : Actor virtual void FireWeapon (State stat) { let player = self.player; - + let weapn = player.ReadyWeapon; if (weapn == null || !weapn.CheckAmmo (Weapon.PrimaryFire, true)) { @@ -497,7 +527,7 @@ class PlayerPawn : Actor virtual void CheckWeaponChange () { let player = self.player; - if (!player) return; + if (!player) return; if ((player.WeaponState & WF_DISABLESWITCH) || // Weapon changing has been disabled. Alternative) // Morphed classes cannot change weapons. { // ...so throw away any pending weapon requests. @@ -512,7 +542,7 @@ class PlayerPawn : Actor DropWeapon(); } } - + //------------------------------------------------------------------------ // // PROC P_MovePsprites @@ -528,7 +558,7 @@ class PlayerPawn : Actor while (pspr) { // Destroy the psprite if it's from a weapon that isn't currently selected by the player - // or if it's from an inventory item that the player no longer owns. + // or if it's from an inventory item that the player no longer owns. if ((pspr.Caller == null || (pspr.Caller is "Inventory" && Inventory(pspr.Caller).Owner != pspr.Owner.mo) || (pspr.Caller is "Weapon" && pspr.Caller != pspr.Owner.ReadyWeapon))) @@ -562,7 +592,7 @@ class PlayerPawn : Actor } } } - + /* ================== = @@ -664,8 +694,8 @@ class PlayerPawn : Actor if (player.deltaviewheight <= 0) player.deltaviewheight = 1 / 65536.; } - - if (player.deltaviewheight) + + if (player.deltaviewheight) { player.deltaviewheight += 0.25; if (!player.deltaviewheight) @@ -694,7 +724,6 @@ class PlayerPawn : Actor } } - //========================================================================== // // P_DeathThink @@ -749,12 +778,12 @@ class PlayerPawn : Actor } } player.mo.CalcHeight (); - + if (player.attacker && player.attacker != self) { // Watch killer double diff = deltaangle(angle, AngleTo(player.attacker)); double delta = abs(diff); - + if (delta < 10) { // Looking at killer, so fade damage and poison counters if (player.damagecount) @@ -779,7 +808,7 @@ class PlayerPawn : Actor { player.poisoncount--; } - } + } if ((player.cmd.buttons & BT_USE || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn)) && !sv_norespawn) @@ -806,8 +835,10 @@ class PlayerPawn : Actor { Super.Die (source, inflictor, dmgflags, MeansOfDeath); + if (player.mo == self) Super.PlayerDiedMakeRumble(inflictor); + if (player != NULL && player.mo == self) player.bonuscount = 0; - + // [RL0] To allow voodoo zombies, don't kill the player together with voodoo dolls if the compat flag is enabled if (player != NULL && player.mo != self && !(Level.compatflags2 & COMPATF2_VOODOO_ZOMBIES)) { // Make the real player die, too @@ -835,7 +866,7 @@ class PlayerPawn : Actor } di = di.Next; } - } + } else if (weap.SpawnState != NULL && weap.SpawnState != GetDefaultByType('Actor').SpawnState) { @@ -876,7 +907,7 @@ class PlayerPawn : Actor } } } - + //=========================================================================== // // PlayerPawn :: FilterCoopRespawnInventory @@ -994,7 +1025,6 @@ class PlayerPawn : Actor } } - //---------------------------------------------------------------------------- // // PROC P_CheckFOV @@ -1118,7 +1148,7 @@ class PlayerPawn : Actor virtual void CrouchMove(int direction) { let player = self.player; - + double defaultheight = FullHeight; double savedheight = Height; double crouchspeed = direction * CROUCHSPEED; @@ -1237,7 +1267,7 @@ class PlayerPawn : Actor } player.Vel += AngleToVector(angle, move); } - + //=========================================================================== // // PlayerPawn :: TweakSpeeds @@ -1378,7 +1408,7 @@ class PlayerPawn : Actor player.camera = player.mo; } } - } + } //---------------------------------------------------------------------------- // @@ -1531,7 +1561,6 @@ class PlayerPawn : Actor } } - //---------------------------------------------------------------------------- // // PROC P_HandleMovement @@ -1656,7 +1685,7 @@ class PlayerPawn : Actor // PROC P_PlayerThink // //---------------------------------------------------------------------------- - + virtual void PlayerThink() { let player = self.player; @@ -1667,7 +1696,7 @@ class PlayerPawn : Actor { PlayerFlags |= PF_VOODOO_ZOMBIE; } - + CheckFOV(); CheckCheats(); @@ -1818,7 +1847,7 @@ class PlayerPawn : Actor else if(Delay > 0) { // delay-based terrain footstepLength = 0; - + if(footstepCounter % Delay == 0) { DoFootstep(Ground); @@ -1856,7 +1885,7 @@ class PlayerPawn : Actor if (player.ReadyWeapon != null) { let psp = player.GetPSprite(PSP_WEAPON); - if (psp) + if (psp) { psp.y = WEAPONTOP; player.ReadyWeapon.ResetPSprite(psp); @@ -1884,6 +1913,7 @@ class PlayerPawn : Actor if (weapon != null) { + weapon.OnSelect(); weapon.PlayUpSound(self); player.refire = 0; @@ -1895,7 +1925,7 @@ class PlayerPawn : Actor player.SetPsprite(PSP_WEAPON, weapon.GetUpState()); } } - + //=========================================================================== // // PlayerPawn :: BestWeapon @@ -1955,7 +1985,6 @@ class PlayerPawn : Actor return bestMatch; } - //--------------------------------------------------------------------------- // // PROC P_DropWeapon @@ -1976,10 +2005,11 @@ class PlayerPawn : Actor Weapon weap = player.ReadyWeapon; if ((weap != null) && (player.health > 0 || !weap.bNoDeathDeselect)) { + weap.OnDeselect(); player.SetPsprite(PSP_WEAPON, weap.GetDownState()); } } - + //=========================================================================== // // PlayerPawn :: PickNewWeapon @@ -2125,7 +2155,7 @@ class PlayerPawn : Actor //=========================================================================== // - // + // // //=========================================================================== @@ -2135,10 +2165,10 @@ class PlayerPawn : Actor if (withupgrades) ret += stamina + BonusHealth; return ret; } - + //=========================================================================== // - // + // // //=========================================================================== @@ -2153,8 +2183,7 @@ class PlayerPawn : Actor } return TeleportFreezeTime; } - - + //=========================================================================== // // G_PlayerFinishLevel @@ -2272,7 +2301,7 @@ class PlayerPawn : Actor me.GiveDefaultInventory(); } } - + //=========================================================================== // // FWeaponSlot :: PickWeapon @@ -2357,7 +2386,7 @@ class PlayerPawn : Actor let ReadyWeapon = player.ReadyWeapon; if (player.PendingWeapon != WP_NOCHANGE) { - // Workaround for the current inability + // Workaround for the current inability bool found; int slot; int index; @@ -2617,10 +2646,10 @@ class PlayerPawn : Actor Vector2 oldBob = BobWeapon(ticfrac); return (0, 0, 0) , ( oldBob.x / 4, oldBob.y / -4, 0); } - + //---------------------------------------------------------------------------- // - // + // // //---------------------------------------------------------------------------- @@ -2630,7 +2659,7 @@ class PlayerPawn : Actor if (painFlash == 0) painFlash = DamageFade; return painFlash; } - + //=========================================================================== // // PlayerPawn :: ResetAirSupply @@ -2657,7 +2686,7 @@ class PlayerPawn : Actor //---------------------------------------------------------------------------- // - // + // // //---------------------------------------------------------------------------- @@ -2748,7 +2777,7 @@ class PSprite : Object native play TARGETRIGHT, }; - native readonly State CurState; + native readonly State CurState; native Actor Caller; native readonly PSprite Next; native readonly PlayerInfo Owner; @@ -2779,7 +2808,7 @@ class PSprite : Object native play native bool bAddBob; native bool bPowDouble; native bool bCVarFast; - native bool bFlip; + native bool bFlip; native bool bMirror; native bool bPlayerTranslated; native bool bPivotPercent; @@ -2817,13 +2846,13 @@ class PSprite : Object native play } } } - - void ResetInterpolation() - { - oldx = x; - oldy = y; + + void ResetInterpolation() + { + oldx = x; + oldy = y; } - + } enum EPlayerState @@ -2848,7 +2877,7 @@ struct PlayerInfo native play // self is what internally is known as player_t // technically engine constants but the only part of the playsim using them is the player. const NOFIXEDCOLORMAP = -1; const NUMCOLORMAPS = 32; - + native PlayerPawn mo; native uint8 playerstate; native readonly uint buttons; @@ -2969,7 +2998,7 @@ struct PlayerInfo native play // self is what internally is known as player_t native clearscope bool GetClassicFlight() const; native void SendPitchLimits(); native clearscope bool HasWeaponsInSlot(int slot) const; - + native clearscope int GetAverageLatency() const; native clearscope static PlayerInfo GetNextPlayer(PlayerInfo p, bool noBots = false); @@ -2980,7 +3009,7 @@ struct PlayerInfo native play // self is what internally is known as player_t { return mo ? mo.MorphPlayer(activator, spawnType, duration, style, enterFlash, exitFlash) : false; } - + // This somehow got its arguments mixed up. 'self' should have been the player to be unmorphed, not the activator deprecated("3.7", "UndoPlayerMorph() should be used on a PlayerPawn object") bool UndoPlayerMorph(PlayerInfo player, EMorphFlags unmorphFlags = 0, bool force = false) { @@ -2999,7 +3028,7 @@ struct PlayerInfo native play // self is what internally is known as player_t { if (mo) mo.BringUpWeapon(); } - + clearscope bool IsTotallyFrozen() const { return @@ -3020,14 +3049,13 @@ struct PlayerInfo native play // self is what internally is known as player_t viewheight = mo.ViewHeight; } } - clearscope int fragSum () const { int i; int allfrags = 0; int playernum = mo.PlayerNumber(); - + for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] @@ -3036,17 +3064,17 @@ struct PlayerInfo native play // self is what internally is known as player_t allfrags += frags[i]; } } - + // JDC hack - negative frags. allfrags -= frags[playernum]; return allfrags; } - + double GetDeltaViewHeight() { return (mo.ViewHeight + crouchviewdelta - viewheight) / 8; } - + } struct PlayerClass native @@ -3054,7 +3082,7 @@ struct PlayerClass native native class Type; native uint Flags; native Array Skins; - + native bool CheckSkin(int skin); native void EnumColorsets(out Array data); native Name GetColorsetName(int setnum); diff --git a/wadsrc/static/zscript/actors/shared/haptics.zs b/wadsrc/static/zscript/actors/shared/haptics.zs new file mode 100644 index 000000000..a1c56ebea --- /dev/null +++ b/wadsrc/static/zscript/actors/shared/haptics.zs @@ -0,0 +1,5 @@ +class Haptics +{ + native static void RumbleDirect(int tic_count, float high_frequency, float low_frequency, float left_trigger, float right_trigger); + native static void Rumble(Name identifier); +} diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index ab0e0fcaa..cfdb08c09 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -726,6 +726,8 @@ enum EParticleFlags SPF_ROLLCENTER = 1 << 13, SPF_STRETCHPIXELS = 1 << 14, SPF_ALLOWSHADERS = 1 << 15, + SPF_FADE_IN_OUT = 1 << 16, + SPF_FADE_IN_HOLD_OUT = 1 << 17, SPF_RELATIVE = SPF_RELPOS|SPF_RELVEL|SPF_RELACCEL|SPF_RELANG }; diff --git a/wadsrc/static/zscript/doombase.zs b/wadsrc/static/zscript/doombase.zs index 81ad5c658..575b048cd 100644 --- a/wadsrc/static/zscript/doombase.zs +++ b/wadsrc/static/zscript/doombase.zs @@ -479,6 +479,7 @@ struct FSpawnParticleParams native double startalpha; native double fadestep; + native double fadeoutstep; // unlike fadestep, this is always expected to be a positive value. native double startroll; native double rollvel; diff --git a/wadsrc/static/zscript/engine/base.zs b/wadsrc/static/zscript/engine/base.zs index be0a40bdc..b7e95b1ba 100644 --- a/wadsrc/static/zscript/engine/base.zs +++ b/wadsrc/static/zscript/engine/base.zs @@ -1,4 +1,38 @@ - +/* +** base.zs +** +**--------------------------------------------------------------------------- +** +** Copyright 2016-2017 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +**--------------------------------------------------------------------------- +** +*/ // constants for A_PlaySound enum ESoundFlags @@ -11,7 +45,7 @@ enum ESoundFlags CHAN_5 = 5, CHAN_6 = 6, CHAN_7 = 7, - + // modifier flags CHAN_LISTENERZ = 8, CHAN_MAYBE_LOCAL = 16, @@ -35,7 +69,8 @@ enum ESoundFlags CHANF_TRANSIENT = 32768, // Do not record in savegames - used for sounds that get restarted outside the sound system (e.g. ambients in SW and Blood) CHANF_FORCE = 65536, // Start, even if sound is paused. CHANF_SINGULAR = 0x20000, // Only start if no sound of this name is already playing. - + CHANF_RUMBLE = 0x40000, // Hint to rumble trigger rumble from sound + CHANF_NORUMBLE = 0x80000, // Disable rumble even if it would normally happen CHANF_LOOPING = CHANF_LOOP | CHANF_NOSTOP, // convenience value for replicating the old 'looping' boolean. }; @@ -46,7 +81,6 @@ const ATTN_NORM = 1; const ATTN_IDLE = 1.001; const ATTN_STATIC = 3; - enum ERenderStyle { STYLE_None, // Do not draw @@ -71,7 +105,6 @@ enum ERenderStyle }; - enum EGameState { GS_LEVEL, @@ -122,7 +155,6 @@ const TEXTCOLOR_BOLD = "\034+"; const TEXTCOLOR_CHAT = "\034*"; const TEXTCOLOR_TEAMCHAT = "\034!"; - enum EMonospacing { Mono_Off = 0, @@ -264,7 +296,6 @@ struct MusPlayingInfo native native int baseorder; native bool loop; native readonly voidptr handle; - }; struct TexMan @@ -300,7 +331,7 @@ struct TexMan ForceLookup = 128, NoAlias = 256 }; - + enum ETexReplaceFlags { NOT_BOTTOM = 1, @@ -375,7 +406,6 @@ enum EScaleMode FSMode_ScaleToFit43Bottom = 6, FSMode_ScaleToHeight = 7, - FSMode_Max, // These all use ScaleToFit43, their purpose is to cut down on verbosity because they imply the virtual screen size. @@ -621,7 +651,7 @@ struct Font native CR_TEAL, NUM_TEXT_COLORS }; - + const TEXTCOLOR_BRICK = "\034A"; const TEXTCOLOR_TAN = "\034B"; const TEXTCOLOR_GRAY = "\034C"; @@ -775,7 +805,7 @@ class Object native private native static Function BuiltinFunctionPtrCast(Function inptr, voidptr newtype); private native static void HandleDeprecatedFlags(Object obj, bool set, int index); private native static bool CheckDeprecatedFlags(Object obj, int index); - + native static Name ValidateNameIndex(int index); static class FindClass(Name cls, class baseType = null) { return BuiltinNameToClass(cls, baseType); } @@ -945,8 +975,12 @@ struct StringStruct native unsafe(internal) native String MakeLower() const; native static int CharUpper(int ch); native static int CharLower(int ch); + + native bool IsInt() const; + native bool IsDouble() const; native int ToInt(int base = 0) const; native double ToDouble() const; + native void Split(out Array tokens, String delimiter, EmptyTokenType keepEmpty = TOK_KEEPEMPTY) const; native void AppendCharacter(int c); native void DeleteLastCharacter(); @@ -967,7 +1001,7 @@ struct StringStruct native unsafe(internal) struct Translation version("2.4") { Color colors[256]; - + native TranslationID AddTranslation(); native static TranslationID MakeID(int group, int num); native static TranslationID GetID(Name transname); @@ -1037,7 +1071,7 @@ class ScriptScanner native native void MustGetString(); native void MustGetStringName(String name); native void MustGetBoolToken(); - + // This DOES NOT advance the parser! This returns the string the parser got. native String GetStringContents(); diff --git a/wadsrc/static/zscript/engine/inputevents.zs b/wadsrc/static/zscript/engine/inputevents.zs index ba63b59e9..37a017c1a 100644 --- a/wadsrc/static/zscript/engine/inputevents.zs +++ b/wadsrc/static/zscript/engine/inputevents.zs @@ -234,8 +234,15 @@ struct InputEvent native play version("2.4") Key_Pad_B = 0x1C1, Key_Pad_X = 0x1C2, Key_Pad_Y = 0x1C3, + Key_Pad_Paddle1 = 0x1C4, + Key_Pad_Paddle2 = 0x1C5, + Key_Pad_Paddle3 = 0x1C6, + Key_Pad_Paddle4 = 0x1C7, + Key_Pad_Guide = 0x1C8, + Key_Pad_Misc1 = 0x1C9, + Key_Pad_Touchpad = 0x1CA, - Num_Keys = 0x1C4 + Num_Keys = 0x1CB } // diff --git a/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs b/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs index 425b7dd67..384ad30ab 100644 --- a/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/joystickmenu.zs @@ -1,9 +1,11 @@ /* -** joystickmenu.cpp +** joystickmenu.zs ** The joystick configuration menus ** **--------------------------------------------------------------------------- +** ** Copyright 2010-2017 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -28,6 +30,7 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ @@ -66,6 +69,34 @@ class OptionMenuSliderJoySensitivity : OptionMenuSliderBase // //============================================================================= +class OptionMenuSliderJoyHapticsStrength : OptionMenuSliderBase +{ + JoystickConfig mJoy; + + OptionMenuSliderJoyHapticsStrength Init(String label, double min, double max, double step, int showval, JoystickConfig joy) + { + Super.Init(label, min, max, step, showval); + mJoy = joy; + return self; + } + + override double GetSliderValue() + { + return mJoy.GetHapticsStrength(); + } + + override void SetSliderValue(double val) + { + mJoy.SetHapticsStrength(val); + } +} + +//============================================================================= +// +// +// +//============================================================================= + class OptionMenuSliderJoyScale : OptionMenuSliderBase { int mAxis; @@ -267,6 +298,9 @@ class OptionMenuItemJoyCurve : OptionMenuItemOptionBase class OptionMenuItemJoyMap : OptionMenuItemOptionBase { + // For backwards compatibility with menu mods, we need to leave this class alone. + // It simply is always set to "None" and does nothing now. + int mAxis; JoystickConfig mJoy; @@ -314,7 +348,7 @@ class OptionMenuItemJoyMap : OptionMenuItemOptionBase //============================================================================= // -// +// // //============================================================================= @@ -351,7 +385,6 @@ class OptionMenuItemInverter : OptionMenuItemOptionBase // //============================================================================= - class OptionMenuJoyEnable : OptionMenuItemOptionBase { JoystickConfig mJoy; @@ -374,7 +407,6 @@ class OptionMenuJoyEnable : OptionMenuItemOptionBase } } - class OptionMenuJoyEnableInBackground : OptionMenuItemOptionBase { JoystickConfig mJoy; @@ -397,6 +429,44 @@ class OptionMenuJoyEnableInBackground : OptionMenuItemOptionBase } } + +class OptionMenuJoyReset : OptionMenuItemSubmenu +{ + JoystickConfig mJoy; + + OptionMenuJoyReset Init(String label, JoystickConfig joy) + { + Super.Init(label, ""); + mJoy = joy; + return self; + } + + override bool MenuEvent(int mkey, bool fromcontroller) + { + if (mkey == Menu.MKEY_MBYes) + { + Menu.MenuSound("menu/choose"); + mJoy.Reset(); + return true; + } + + return Super.MenuEvent(mkey, fromcontroller); + } + + override bool Activate() + { + String msg = "$SAFEMESSAGE"; + msg = StringTable.Localize(msg); + String actionLabel = StringTable.localize(mLabel); + + String FullString; + FullString = String.Format("%s%s%s\n\n%s", TEXTCOLOR_WHITE, actionLabel, TEXTCOLOR_NORMAL, msg); + Menu.StartMessage(FullString, 0); + return true; + } +} + + class OptionMenuItemJoyConfigMenu : OptionMenuItemSubmenu { JoystickConfig mJoy; @@ -452,6 +522,16 @@ class OptionMenuItemJoyConfigMenu : OptionMenuItemSubmenu it = new("OptionMenuSliderJoySensitivity").Init("$JOYMNU_OVRSENS", 0, 2, 0.1, 3, joy); opt.mItems.Push(it); + + if (joy.HasHaptics()) + { + it = new("OptionMenuSliderJoyHapticsStrength").Init("$JOYMNU_HAPTICS", 0, 2, 0.1, 3, joy); + opt.mItems.Push(it); + } + + it = new("OptionMenuJoyReset").Init("$JOYMNU_RESETALL", joy); + opt.mItems.Push(it); + it = new("OptionMenuItemStaticText").Init(" ", false); opt.mItems.Push(it); @@ -465,7 +545,7 @@ class OptionMenuItemJoyConfigMenu : OptionMenuItemSubmenu it = new("OptionMenuItemStaticText").Init(" ", false); opt.mItems.Push(it); - it = new("OptionMenuItemJoyMap").Init(joy.GetAxisName(i), i, "JoyAxisMapNames", false, joy); + it = new("OptionMenuItemStaticText").Init(joy.GetAxisName(i), false); opt.mItems.Push(it); it = new("OptionMenuSliderJoyScale").Init("$JOYMNU_OVRSENS", i, 0, 4, 0.1, 3, joy); opt.mItems.Push(it); diff --git a/wadsrc/static/zscript/engine/ui/menu/menu.zs b/wadsrc/static/zscript/engine/ui/menu/menu.zs index cb8756f3b..c08000e20 100644 --- a/wadsrc/static/zscript/engine/ui/menu/menu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/menu.zs @@ -3,7 +3,9 @@ ** The menu engine core ** **--------------------------------------------------------------------------- +** ** Copyright 2010-2020 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -28,11 +30,11 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ - struct KeyBindings native version("2.4") { native static String NameKeys(int k1, int k2); @@ -81,6 +83,10 @@ struct JoystickConfig native version("2.4") native float GetSensitivity(); native void SetSensitivity(float scale); + native bool HasHaptics(); + native float GetHapticsStrength(); + native void SetHapticsStrength(float strength); + native float GetAxisScale(int axis); native void SetAxisScale(int axis, float scale); @@ -96,8 +102,15 @@ struct JoystickConfig native version("2.4") native float GetAxisResponseCurvePoint(int axis, int point); native void SetAxisResponseCurvePoint(int axis, int point, float value); - native int GetAxisMap(int axis); - native void SetAxisMap(int axis, int gameaxis); + deprecated("4.15.1", "Axis mapping was replaced with binds; remove this menu item") int GetAxisMap(int axis) + { + return JOYAXIS_None; + } + + deprecated("4.15.1", "Axis mapping was replaced with binds; remove this menu item") void SetAxisMap(int axis, int gameaxis) + { + // NOP + } native String GetName(); native int GetNumAxes(); @@ -110,6 +123,7 @@ struct JoystickConfig native version("2.4") native bool GetEnabledInBackground(); native void SetEnabledInBackground(bool enabled); + native void Reset(); } class Menu : Object native ui version("2.4") @@ -129,7 +143,7 @@ class Menu : Object native ui version("2.4") MKEY_Clear, NUM_MKEYS, - // These are not buttons but events sent from other menus + // These are not buttons but events sent from other menus MKEY_Input, MKEY_Abort, @@ -207,7 +221,6 @@ class Menu : Object native ui version("2.4") return false; } - //============================================================================= // // @@ -245,14 +258,14 @@ class Menu : Object native ui version("2.4") //============================================================================= virtual bool OnUIEvent(UIEvent ev) - { + { bool res = false; int y = ev.MouseY; if (ev.type == UIEvent.Type_LButtonDown) { res = MouseEventBack(MOUSE_Click, ev.MouseX, y); // make the menu's mouse handler believe that the current coordinate is outside the valid range - if (res) y = -1; + if (res) y = -1; res |= MouseEvent(MOUSE_Click, ev.MouseX, y); if (res) { @@ -266,7 +279,7 @@ class Menu : Object native ui version("2.4") if (mMouseCapture || m_use_mouse == 1) { res = MouseEventBack(MOUSE_Move, ev.MouseX, y); - if (res) y = -1; + if (res) y = -1; res |= MouseEvent(MOUSE_Move, ev.MouseX, y); } } @@ -276,15 +289,15 @@ class Menu : Object native ui version("2.4") { SetCapture(false); res = MouseEventBack(MOUSE_Release, ev.MouseX, y); - if (res) y = -1; + if (res) y = -1; res |= MouseEvent(MOUSE_Release, ev.MouseX, y); } } - return false; + return false; } virtual bool OnInputEvent(InputEvent ev) - { + { return false; } @@ -294,7 +307,7 @@ class Menu : Object native ui version("2.4") // //============================================================================= - virtual void Drawer () + virtual void Drawer () { if (self == GetCurrentMenu() && BackbuttonAlpha > 0 && m_show_backbutton >= 0 && m_use_mouse) { @@ -354,8 +367,12 @@ class Menu : Object native ui version("2.4") // //============================================================================= - static void MenuSound(Name snd) + static void MenuSound(Name snd, bool rumble = true) { + if (rumble && CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble(snd); + } menuDelegate.PlaySound(snd); } @@ -369,7 +386,7 @@ class Menu : Object native ui version("2.4") return NewSmallFont; } - static int OptionHeight() + static int OptionHeight() { return OptionFont().GetHeight(); } @@ -386,7 +403,6 @@ class Menu : Object native ui version("2.4") screen.DrawText (OptionFont(), color, x, y, text, DTA_CleanNoMove_1, true, DTA_ColorOverlay, overlay, DTA_Localize, localize); } - } class MenuDescriptor : Object native ui version("2.4") diff --git a/wadsrc/static/zscript/engine/ui/menu/messagebox.zs b/wadsrc/static/zscript/engine/ui/menu/messagebox.zs index 32dce5038..36f99cc8f 100644 --- a/wadsrc/static/zscript/engine/ui/menu/messagebox.zs +++ b/wadsrc/static/zscript/engine/ui/menu/messagebox.zs @@ -54,7 +54,7 @@ class MessageBoxMenu : Menu // //============================================================================= - virtual void Init(Menu parent, String message, int messagemode, bool playsound = false, Name cmd = 'None') + virtual void Init(Menu parent, String message, int messagemode, bool playsound = false, Name cmd = 'None', voidptr native_handler = null) { Super.Init(parent); mAction = cmd; diff --git a/wadsrc/static/zscript/engine/ui/menu/optionmenu.zs b/wadsrc/static/zscript/engine/ui/menu/optionmenu.zs index e77b952c0..5d2da7fa2 100644 --- a/wadsrc/static/zscript/engine/ui/menu/optionmenu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/optionmenu.zs @@ -1,9 +1,10 @@ /* -** optionmenu.cpp +** optionmenu.zs ** Handler class for the option menus and associated items ** **--------------------------------------------------------------------------- ** Copyright 2010-2017 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -101,6 +102,11 @@ class OptionMenu : Menu int MaxItems; OptionMenuItem mFocusControl; + bool ScrollSound; + bool HoverSound; + int OverScroll; + int OverScrollThreshold; // >= 0 : how much smaller a menu can be than the page to overscroll + //============================================================================= // // @@ -115,6 +121,12 @@ class OptionMenu : Menu DontBlur = desc.mDontBlur; AnimatedTransition = desc.mAnimatedTransition; Animated = desc.mAnimated; + MaxItems = 1; + + ScrollSound = ! Cvar.FindCVar("silence_menu_scroll").getInt(); + HoverSound = ! Cvar.FindCVar("silence_menu_hover").getInt(); + OverScroll = MAX(0, Cvar.FindCVar("menu_overscroll").getInt()); + OverScrollThreshold = OverScroll / 3; // this _could_ be exposed, but meh let itemCount = mDesc.mItems.size(); if (itemCount > 0) @@ -159,7 +171,6 @@ class OptionMenu : Menu return NULL; } - //============================================================================= // // @@ -202,6 +213,23 @@ class OptionMenu : Menu // //============================================================================= + int LastSelectableItem() + { + int i = mDesc.mItems.Size(); + do + { + i--; + } + while (i >= 0 && !(mDesc.mItems[i].Selectable() && mDesc.mItems[i].Visible())); + return i; + } + + //============================================================================= + // + // + // + //============================================================================= + int RemainingVisibleItems(int start) { int count = 0; @@ -223,12 +251,11 @@ class OptionMenu : Menu override bool OnUIEvent(UIEvent ev) { - bool silentScroll = Cvar.FindCVar("silence_menu_scroll").getInt(); if (ev.type == UIEvent.Type_WheelUp) { if (MenuScrollViewport(-2, true) ) { - if (!silentScroll) MenuSound ("menu/cursor"); + if (ScrollSound) MenuSound ("menu/cursor"); } return true; } @@ -236,7 +263,7 @@ class OptionMenu : Menu { if (MenuScrollViewport(2, true)) { - if (!silentScroll) MenuSound ("menu/cursor"); + if (ScrollSound) MenuSound ("menu/cursor"); } return true; } @@ -333,29 +360,28 @@ class OptionMenu : Menu if (mDesc.mSelectedItem != startedAt) { int viewTop = mDesc.mScrollTop + mDesc.mScrollPos; - int lastItem = LastVisibleItem(); - if (startedAt == FirstSelectable() && mDesc.mSelectedItem == lastItem) + if (startedAt == FirstSelectable() && mDesc.mSelectedItem == LastSelectableItem()) { int y = mDesc.mPosition; if (y <= 0) y = DrawCaption(mDesc.mTitle, -y, false); int maxItemsInternal = MaxItems; if (maxItemsInternal < RemainingVisibleItems(0)) { - maxItemsInternal -= 2; + maxItemsInternal -= OverScroll; } if (maxItemsInternal <= 0) maxItemsInternal = 1; int newTopIndex = 0; int visibleItemsOnPage = 0; - for (int i = lastItem; i >= 0; i--) + for (int i = LastVisibleItem(); i >= 0; i--) { if (mDesc.mItems[i].Visible()) { visibleItemsOnPage++; - if (visibleItemsOnPage >= maxItemsInternal) + if (visibleItemsOnPage > maxItemsInternal) { - newTopIndex = i; + newTopIndex = i - 1; break; } } @@ -408,7 +434,7 @@ class OptionMenu : Menu if (mDesc.mSelectedItem != startedAt) { - if (startedAt == LastVisibleItem()) + if (startedAt == LastSelectableItem()) { mDesc.mScrollPos = 0; } @@ -440,6 +466,36 @@ class OptionMenu : Menu return mDesc.mSelectedItem - startedAt; } + void ClampCursor() + { + int first = FirstSelectable(); + int last = LastSelectableItem(); + + mDesc.mSelectedItem = min(max(first, mDesc.mSelectedItem), last); + if (mDesc.mSelectedItem < 0) mDesc.mSelectedItem = 0; + + int firstSelectable = -1; + int lastSelectable = -1; + int visible = 0; + for (int i = max(0, mDesc.mScrollPos); visible < MaxItems && i <= last; i++) + { + if (!mDesc.mItems[i].Visible()) continue; + visible++; + if (!mDesc.mItems[i].Selectable()) continue; + lastSelectable = i; + if (firstSelectable == -1) firstSelectable = i; + } + + if (firstSelectable != -1 && mDesc.mSelectedItem < firstSelectable) + { + mDesc.mSelectedItem = firstSelectable; + } + else if (lastSelectable != -1 && mDesc.mSelectedItem > lastSelectable) + { + mDesc.mSelectedItem = lastSelectable; + } + } + //============================================================================= // // Moves the viewport by the specified number of lines @@ -469,50 +525,23 @@ class OptionMenu : Menu return 0; } - if (lines < 0) // base case up + mDesc.mScrollPos = min(max(0, mDesc.mScrollPos + lines), LastVisibleItem()); + + if (lines < 0) // up { - mDesc.mScrollPos += lines; - - // backtrack if we overshot - if (mDesc.mScrollPos < 0) - { - mDesc.mScrollPos = 0; - } - // ensure cursor is visible (if possible) - int lastItem = LastVisibleItem(); - int lastSelectable = -1; - int visible = 0; - for (int i = mDesc.mScrollPos; visible <= MaxItems && i < lastItem; i++) - { - if (!mDesc.mItems[i].Visible()) continue; - visible++; - if (!mDesc.mItems[i].Selectable()) continue; - lastSelectable = i; - } - if (cursor && lastSelectable != -1 && mDesc.mSelectedItem > lastSelectable) - { - mDesc.mSelectedItem = lastSelectable; - } + if (cursor) ClampCursor(); } - else if (lines > 0) // base case down + else if (lines > 0) // down { - mDesc.mScrollPos += lines; - // backtrack if we overshot - int visible = RemainingVisibleItems(mDesc.mScrollPos); - if (visible < MaxItems) + int visible; + int MinItems = MaxItems - OverScroll; + while (mDesc.mScrollPos > 0) { - mDesc.mScrollPos = MAX(0, LastVisibleItem() - MaxItems); visible = RemainingVisibleItems(mDesc.mScrollPos); - while (visible < MaxItems && mDesc.mScrollPos > 0) - { - if (mDesc.mItems[mDesc.mScrollPos].Visible()) - { - visible++; - } - mDesc.mScrollPos--; - } + if (visible > MinItems) break; + mDesc.mScrollPos--; } // ensure cursor is visible (if possible) @@ -527,6 +556,7 @@ class OptionMenu : Menu mDesc.mSelectedItem = temp + i; break; } + ClampCursor(); } } @@ -641,8 +671,7 @@ class OptionMenu : Menu { mDesc.mSelectedItem = i; - bool silentHover = Cvar.FindCVar("silence_menu_hover").getInt(); - if (!silentHover) MenuSound ("menu/cursor"); + if (HoverSound) MenuSound ("menu/cursor"); } mDesc.mItems[i].MouseEvent(type, x, y); return true; @@ -713,6 +742,9 @@ class OptionMenu : Menu //============================================================================= override void Drawer () { + + int lastVisible; + bool drawCanScrollDown; int y = mDesc.mPosition; if (y <= 0) @@ -727,7 +759,11 @@ class OptionMenu : Menu int ytop = y + mDesc.mScrollTop * 8 * CleanYfac_1; LastRow = screen.GetHeight() - OptionHeight() * CleanYfac_1; int rowheight = OptionMenuSettings.mLinespacing * CleanYfac_1 + 1; - MaxItems = (LastRow - y) / rowheight + 1; + + int _MaxItems = (LastRow - y) / rowheight + 1; + bool resized = _MaxItems != MaxItems; + MaxItems = _MaxItems; + if (resized) ClampCursor(); int i; int lastDrawnItemIndex = -1; @@ -757,21 +793,26 @@ class OptionMenu : Menu DrawOptionText(cur_indent + 3 * CleanXfac_1, y, OptionMenuSettings.mFontColorSelection, "◄"); } } - y += fontheight; + + y += rowheight; } - CanScrollUp = (mDesc.mScrollPos > 0); - CanScrollDown = LastVisibleItem() >= i; + lastVisible = LastVisibleItem(); + drawCanScrollDown = lastVisible >= i; + CanScrollUp = mDesc.mScrollPos > 0; + CanScrollDown = lastVisible + OverScroll >= i + && RemainingVisibleItems(0) > MaxItems-OverScrollThreshold; VisBottom = lastDrawnItemIndex; if (CanScrollUp) { DrawOptionText(screen.GetWidth() - 11 * CleanXfac_1, ytop, OptionMenuSettings.mFontColorSelection, "▲"); } - if (CanScrollDown) + if (drawCanScrollDown) { DrawOptionText(screen.GetWidth() - 11 * CleanXfac_1 , y - 8*CleanYfac_1, OptionMenuSettings.mFontColorSelection, "▼"); } + Super.Drawer(); } diff --git a/wadsrc/static/zscript/ui/menu/conversationmenu.zs b/wadsrc/static/zscript/ui/menu/conversationmenu.zs index 96d24da00..d4e9dd0d7 100644 --- a/wadsrc/static/zscript/ui/menu/conversationmenu.zs +++ b/wadsrc/static/zscript/ui/menu/conversationmenu.zs @@ -1,9 +1,11 @@ /* -** conversationmenu.txt +** conversationmenu.zs ** The Strife dialogue display ** **--------------------------------------------------------------------------- +** ** Copyright 2010-2017 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without @@ -28,6 +30,7 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ @@ -66,11 +69,10 @@ struct StrifeDialogueReply native version("2.4") native int LogNumber; native bool NeedsGold; native bool CloseDialog; - + native bool ShouldSkipReply(PlayerInfo player); } - class ConversationMenu : Menu { String mSpeaker; @@ -95,12 +97,12 @@ class ConversationMenu : Menu int refwidth; int refheight; Array ypositions; - + int SpeechWidth; int ReplyWidth; - + native static void SendConversationReply(int node, int reply); - + const NUM_RANDOM_LINES = 10; const NUM_RANDOM_GOODBYES = 3; @@ -117,11 +119,11 @@ class ConversationMenu : Menu mShowGold = false; ConversationPauseTic = gametic + 20; DontDim = true; - + let tex = TexMan.CheckForTexture (CurNode.Backdrop, TexMan.Type_MiscPatch); mHasBackdrop = tex.isValid(); DontBlur = !mHasBackdrop; - + if (!generic_ui && !dlg_vgafont) { displayFont = SmallFont; @@ -158,22 +160,21 @@ class ConversationMenu : Menu SpeechWidth = speechDisplayWidth - (24*3 * CleanXfac / fontScale); mConfineTextToBackdrop = true; } - + LineHeight = displayFont.GetHeight() + 2; ReplyLineHeight = LineHeight * fontScale / CleanYfac; } - FormatSpeakerMessage(); return FormatReplies(activereply); } - + //============================================================================= // // // //============================================================================= - + virtual int FormatReplies(int activereply) { mSelection = -1; @@ -206,7 +207,7 @@ class ConversationMenu : Menu { mResponseLines.Push(ReplyLines.StringAt(j)); } - + ++i; ReplyLines.Destroy(); } @@ -242,7 +243,7 @@ class ConversationMenu : Menu } return mYpos; } - + //============================================================================= // // @@ -270,7 +271,7 @@ class ConversationMenu : Menu } mDialogueLines = displayFont.BreakLines(toSay, SpeechWidth); } - + //============================================================================= // // @@ -313,24 +314,46 @@ class ConversationMenu : Menu if (mkey == MKEY_Back) { Close(); + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/dismiss"); + } return true; } return false; } if (mkey == MKEY_Up) { - if (--mSelection < 0) mSelection = mResponses.Size() - 1; + if (--mSelection < 0) + { + mSelection = mResponses.Size() - 1; + } + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/cursor"); + } return true; } else if (mkey == MKEY_Down) { - if (++mSelection >= mResponses.Size()) mSelection = 0; + if (++mSelection >= mResponses.Size()) + { + mSelection = 0; + } + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/cursor"); + } return true; } else if (mkey == MKEY_Back) { SendConversationReply(-1, GetReplyNum()); Close(); + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/dismiss"); + } return true; } else if (mkey == MKEY_Enter) @@ -346,6 +369,10 @@ class ConversationMenu : Menu SendConversationReply(mCurNode.ThisNodeNum, replynum); } Close(); + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/choose"); + } return true; } return false; @@ -364,7 +391,7 @@ class ConversationMenu : Menu // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture x = ((x - (screen.GetWidth() / 2)) / fontScale) + refWidth/2; - + if (x >= 24 && x <= refWidth-24) { for (int i = 0; i < ypositions.Size()-1; i++) @@ -384,7 +411,6 @@ class ConversationMenu : Menu return true; } - //============================================================================= // // @@ -404,7 +430,7 @@ class ConversationMenu : Menu } return Super.OnUIEvent(ev); } - + //============================================================================ // // Draw the backdrop, returns true if the text background should be dimmed @@ -444,7 +470,6 @@ class ConversationMenu : Menu speakerName = players[consoleplayer].ConversationNPC.GetTag("$TXT_PERSON"); } - // Dim the screen behind the dialogue (but only if there is no backdrop). if (dimbg) { @@ -472,7 +497,6 @@ class ConversationMenu : Menu } } - //============================================================================ // // Draw the replies @@ -485,7 +509,6 @@ class ConversationMenu : Menu screen.Dim(0, 0.45, (24 - 160) * CleanXfac + screen.GetWidth() / 2, (mYpos - 2 - 100) * CleanYfac + screen.GetHeight() / 2, 272 * CleanXfac, MIN(mResponseLines.Size() * ReplyLineHeight + 4, 200 - mYpos) * CleanYfac); - int y = mYpos; int response = 0; @@ -576,8 +599,7 @@ class ConversationMenu : Menu DrawReplies(); DrawGold(); } - - + //============================================================================ // // @@ -592,5 +614,5 @@ class ConversationMenu : Menu menuactive = Menu.On; } } - + } diff --git a/wadsrc/static/zscript/ui/statscreen/statscreen.zs b/wadsrc/static/zscript/ui/statscreen/statscreen.zs index 6603c2e06..0b9fe66aa 100644 --- a/wadsrc/static/zscript/ui/statscreen/statscreen.zs +++ b/wadsrc/static/zscript/ui/statscreen/statscreen.zs @@ -1,3 +1,39 @@ +/* +** statscreen.zs +** +**--------------------------------------------------------------------------- +** +** Copyright 2010-2017 Christoph Oelckers +** Copyright 2017-2025 GZDoom Maintainers and Contributors +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +**--------------------------------------------------------------------------- +** +*/ + // Note that the status screen needs to run in 'play' scope! class InterBackground native ui version("2.5") @@ -39,7 +75,6 @@ struct PatchInfo ui version("2.5") } }; - class StatusScreen : ScreenJob abstract version("2.5") { enum EValues @@ -129,13 +164,12 @@ class StatusScreen : ScreenJob abstract version("2.5") int player_deaths[MAXPLAYERS]; int sp_state; - + int cWidth, cHeight; // size of the canvas int scalemode; int wrapwidth; // size used to word wrap level names int scaleFactorX, scaleFactorY; - //==================================================================== // // Set fixed size mode. @@ -165,7 +199,7 @@ class StatusScreen : ScreenJob abstract version("2.5") else screen.DrawChar(fnt, translation, x, y, charcode, DTA_FullscreenScale, scalemode, DTA_VirtualWidth, cwidth, DTA_VirtualHeight, cheight); return x - width; } - + //==================================================================== // // @@ -201,7 +235,7 @@ class StatusScreen : ScreenJob abstract version("2.5") int DrawName(int y, TextureID tex, String levelname) { - // draw + // draw if (tex.isValid()) { let size = TexMan.GetScaledSize(tex); @@ -236,16 +270,16 @@ class StatusScreen : ScreenJob abstract version("2.5") // Draws a level author's name with the given font // //==================================================================== - + int DrawAuthor(int y, String levelname) { if (levelname.Length() > 0) { int h = 0; int lumph = author.mFont.GetHeight() * scaleFactorY; - + BrokenLines lines = author.mFont.BreakLines(levelname, wrapwidth / scaleFactorX); - + int count = lines.Count(); for (int i = 0; i < count; i++) { @@ -256,7 +290,7 @@ class StatusScreen : ScreenJob abstract version("2.5") } return y; } - + //==================================================================== // // Only kept so that mods that were accessing it continue to compile @@ -277,12 +311,12 @@ class StatusScreen : ScreenJob abstract version("2.5") // Draws a text, either as patch or as string from the string table // //==================================================================== - + int DrawPatchOrText(int y, PatchInfo pinfo, TextureID patch, String stringname) { String string = Stringtable.Localize(stringname); int midx = cwidth / 2; - + if (TexMan.OkForLocalization(patch, stringname)) { let size = TexMan.GetScaledSize(patch); @@ -295,7 +329,7 @@ class StatusScreen : ScreenJob abstract version("2.5") return y + pinfo.mFont.GetHeight() * scaleFactorY; } } - + //==================================================================== // // Draws " Finished!" @@ -310,7 +344,7 @@ class StatusScreen : ScreenJob abstract version("2.5") bool ispatch = wbs.LName0.isValid(); int oldy = TITLEY * scaleFactorY; int h; - + if (!ispatch) { let asc = mapname.mFont.GetMaxAscender(lnametexts[1]); @@ -319,11 +353,11 @@ class StatusScreen : ScreenJob abstract version("2.5") oldy = (asc+2) * scaleFactorY; } } - + int y = DrawName(oldy, wbs.LName0, lnametexts[0]); // If the displayed info is made of patches we need some additional offsetting here. - if (ispatch) + if (ispatch) { int disp = 0; // The offset getting applied here must at least be as tall as the largest ascender in the following text to avoid overlaps. @@ -332,7 +366,7 @@ class StatusScreen : ScreenJob abstract version("2.5") int h1 = BigFont.GetHeight() - BigFont.GetDisplacement(); int h2 = (y - oldy) / scaleFactorY / 4; disp = min(h1, h2); - + if (!TexMan.OkForLocalization(finishedPatch, "$WI_FINISHED")) { disp += finishedp.mFont.GetMaxAscender("$WI_FINISHED"); @@ -344,9 +378,9 @@ class StatusScreen : ScreenJob abstract version("2.5") } y += disp * scaleFactorY; } - + y = DrawAuthor(y, authortexts[0]); - + // draw "Finished!" int statsy = multiplayer? NG_STATSY : SP_STATSY * scaleFactorY; @@ -382,9 +416,9 @@ class StatusScreen : ScreenJob abstract version("2.5") } int y = DrawPatchOrText(oldy, entering, enteringPatch, "$WI_ENTERING"); - + // If the displayed info is made of patches we need some additional offsetting here. - + if (ispatch) { int h1 = BigFont.GetHeight() - BigFont.GetDisplacement(); @@ -401,12 +435,12 @@ class StatusScreen : ScreenJob abstract version("2.5") y = DrawName(y, wbs.LName1, lnametexts[1]); - if (wbs.LName1.isValid() && authortexts[1].length() > 0) + if (wbs.LName1.isValid() && authortexts[1].length() > 0) { // Consdider the ascender height of the following text. y += author.mFont.GetMaxAscender(authortexts[1]) * scaleFactorY; } - + DrawAuthor(y, authortexts[1]); } @@ -441,7 +475,7 @@ class StatusScreen : ScreenJob abstract version("2.5") } len = text.Length(); } - + for(int text_p = len-1; text_p >= 0; text_p--) { // Digits are centered in a box the width of the '3' character. @@ -505,7 +539,6 @@ class StatusScreen : ScreenJob abstract version("2.5") } } - //==================================================================== // // Display level completion time and par, or "sucks" message if overflow. @@ -693,7 +726,7 @@ class StatusScreen : ScreenJob abstract version("2.5") protected virtual void initShowNextLoc () { - if (wbs.next == "") + if (wbs.next == "") { // Last map in episode - there is no next location! jobstate = finished; @@ -731,7 +764,7 @@ class StatusScreen : ScreenJob abstract version("2.5") bg.drawBackground(CurState, true, snl_pointeron); // draws which level you are entering.. - drawEL (); + drawEL (); } @@ -746,7 +779,7 @@ class StatusScreen : ScreenJob abstract version("2.5") snl_pointeron = true; drawShowNextLoc(); } - + //==================================================================== // // @@ -757,7 +790,7 @@ class StatusScreen : ScreenJob abstract version("2.5") { int i; int frags = 0; - + for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] @@ -766,7 +799,7 @@ class StatusScreen : ScreenJob abstract version("2.5") frags += Plrs[playernum].frags[i]; } } - + // JDC hack - negative frags. frags -= Plrs[playernum].frags[playernum]; @@ -781,10 +814,15 @@ class StatusScreen : ScreenJob abstract version("2.5") static void PlaySound(Sound snd) { - S_StartSound(snd, CHAN_VOICE, CHANF_MAYBE_LOCAL|CHANF_UI, 1, ATTN_NONE); + S_StartSound( + snd, + CHAN_VOICE, + CHANF_MAYBE_LOCAL|CHANF_UI|(CVar.GetCVar('haptics_do_menus').GetBool()? CHANF_RUMBLE: CHANF_NORUMBLE), + 1, + ATTN_NONE + ); } - - + // ==================================================================== // // Purpose: See if the player has hit either the attack or use key @@ -814,7 +852,7 @@ class StatusScreen : ScreenJob abstract version("2.5") deprecated("4.8") void checkForAccelerate() { } - + // ==================================================================== // Ticker // Purpose: Do various updates every gametic, for stats, animation, @@ -823,7 +861,7 @@ class StatusScreen : ScreenJob abstract version("2.5") // Returns: void // // ==================================================================== - + virtual void StartMusic() { if (!bg.IsUsingMusic()) @@ -839,25 +877,25 @@ class StatusScreen : ScreenJob abstract version("2.5") protected virtual void Ticker() { // counter for general background animation - bcnt++; - + bcnt++; + if (bcnt == 1) { StartMusic(); } - + bg.updateAnimatedBack(); - + switch (CurState) { case StatCount: updateStats(); break; - + case ShowNextLoc: updateShowNextLoc(); break; - + case NoState: updateNoState(); break; @@ -866,7 +904,7 @@ class StatusScreen : ScreenJob abstract version("2.5") break; } } - + override void OnTick() { Ticker(); @@ -888,12 +926,12 @@ class StatusScreen : ScreenJob abstract version("2.5") bg.drawBackground(CurState, false, false); drawStats(); break; - + case ShowNextLoc: case LeavingIntermission: // this must still draw the screen once more for the wipe code to pick up. drawShowNextLoc(); break; - + default: drawNoState(); break; @@ -930,7 +968,7 @@ class StatusScreen : ScreenJob abstract version("2.5") wbs.partime = 0; wbs.sucktime = 0; } - + entering.Init(gameinfo.mStatscreenEnteringFont); finishedp.Init(gameinfo.mStatscreenFinishedFont); mapname.Init(gameinfo.mStatscreenMapNameFont); @@ -947,7 +985,7 @@ class StatusScreen : ScreenJob abstract version("2.5") enteringPatch = TexMan.CheckForTexture("WIENTER", TexMan.Type_MiscPatch); // "entering" finishedPatch = TexMan.CheckForTexture("WIF", TexMan.Type_MiscPatch); // "finished" - lnametexts[0] = StringTable.Localize(wbstartstruct.thisname); + lnametexts[0] = StringTable.Localize(wbstartstruct.thisname); lnametexts[1] = StringTable.Localize(wbstartstruct.nextname); authortexts[0] = StringTable.Localize(wbstartstruct.thisauthor); authortexts[1] = StringTable.Localize(wbstartstruct.nextauthor); @@ -955,14 +993,14 @@ class StatusScreen : ScreenJob abstract version("2.5") bg = InterBackground.Create(wbs); noautostartmap = bg.LoadBackground(false); initStats(); - + wrapwidth = cwidth = screen.GetWidth(); cheight = screen.GetHeight(); scalemode = -1; scaleFactorX = CleanXfac; scaleFactorY = CleanYfac; } - + protected virtual void initStats() {} protected virtual void updateStats() {} protected virtual void drawStats() {} diff --git a/wadsrc/static/zscript/visualthinker.zs b/wadsrc/static/zscript/visualthinker.zs index a926243b6..4f57d3672 100644 --- a/wadsrc/static/zscript/visualthinker.zs +++ b/wadsrc/static/zscript/visualthinker.zs @@ -12,7 +12,7 @@ Class VisualThinker : Thinker native native TranslationID Translation; native int16 LightLevel; - native uint16 Flags; + native uint Flags; native int VisualThinkerFlags; FlagDef FlipOffsetX : VisualThinkerFlags, 0; diff --git a/wadsrc_extra/static/filter/doom.id/fonts/bigfont/012E.lmp b/wadsrc_extra/static/filter/doom.id/fonts/bigfont/012E.lmp index 264532468..c003534f9 100644 Binary files a/wadsrc_extra/static/filter/doom.id/fonts/bigfont/012E.lmp and b/wadsrc_extra/static/filter/doom.id/fonts/bigfont/012E.lmp differ diff --git a/wadsrc_extra/static/filter/doom.id/fonts/bigupper/012E.lmp b/wadsrc_extra/static/filter/doom.id/fonts/bigupper/012E.lmp index 191fa0835..93a6e59f7 100644 Binary files a/wadsrc_extra/static/filter/doom.id/fonts/bigupper/012E.lmp and b/wadsrc_extra/static/filter/doom.id/fonts/bigupper/012E.lmp differ diff --git a/wadsrc_extra/static/filter/doom.id/fonts/bigupper/012F.lmp b/wadsrc_extra/static/filter/doom.id/fonts/bigupper/012F.lmp index c4dbf13d5..a0539ecdd 100644 Binary files a/wadsrc_extra/static/filter/doom.id/fonts/bigupper/012F.lmp and b/wadsrc_extra/static/filter/doom.id/fonts/bigupper/012F.lmp differ