Merge remote-tracking branch 'gzdoom/master' into gzdoom_merge
8
.github/workflows/continuous_integration.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
3
.gitignore
vendored
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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 <SDL2/SDL.h>. 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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
44
libraries/ZWidget/.github/workflows/build.yaml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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$<$<CONFIG:Debug>: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$<$<CONFIG:Debug>:Debug>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -13,136 +13,29 @@
|
|||
#include <zwidget/widgets/pushbutton/pushbutton.h>
|
||||
#include <zwidget/widgets/checkboxlabel/checkboxlabel.h>
|
||||
#include "picopng.h"
|
||||
#include <zwidget/widgets/tabwidget/tabwidget.h>
|
||||
|
||||
// ************************************************************
|
||||
// Prototypes
|
||||
// ************************************************************
|
||||
|
||||
static std::vector<uint8_t> 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<unsigned char> 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<uint8_t> 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<unsigned char> 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<double>(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<SingleFontData> LoadWidgetFontData(const std::string& name)
|
||||
{
|
||||
|
|
@ -174,13 +315,24 @@ std::vector<uint8_t> 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<DarkWidgetTheme>());
|
||||
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<DarkWidgetTheme>()); break;
|
||||
case Theme::Dark: WidgetTheme::SetTheme(std::make_unique<DarkWidgetTheme>()); break;
|
||||
case Theme::Light: WidgetTheme::SetTheme(std::make_unique<LightWidgetTheme>()); 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 <Windows.h>
|
||||
#include <stdexcept>
|
||||
|
||||
|
|
@ -277,6 +412,7 @@ static std::vector<uint8_t> 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<uint8_t> 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
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ int decodePNG(std::vector<unsigned char>& 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<unsigned char> 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<unsigned char>& 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)
|
||||
}
|
||||
|
|
|
|||
BIN
libraries/ZWidget/example/surreal-engine-icon-128.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
libraries/ZWidget/example/surreal-engine-icon-16.png
Normal file
|
After Width: | Height: | Size: 896 B |
BIN
libraries/ZWidget/example/surreal-engine-icon-24.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
libraries/ZWidget/example/surreal-engine-icon-256.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
libraries/ZWidget/example/surreal-engine-icon-32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
libraries/ZWidget/example/surreal-engine-icon-48.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
libraries/ZWidget/example/surreal-engine-icon-64.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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<SpanObject> objects;
|
||||
|
|
|
|||
|
|
@ -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> widgetStyle, const std::string& widgetClass);
|
||||
|
|
|
|||
|
|
@ -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<std::shared_ptr<Image>> GetWindowIcon() const;
|
||||
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images);
|
||||
|
||||
// Widget content box
|
||||
Size GetSize() const;
|
||||
|
|
@ -233,6 +233,7 @@ private:
|
|||
Colorf WindowBackground = Colorf::fromRgba8(240, 240, 240);
|
||||
|
||||
std::string WindowTitle;
|
||||
std::vector<std::shared_ptr<Image>> WindowIcon;
|
||||
std::unique_ptr<DisplayWindow> DispWindow;
|
||||
std::unique_ptr<Canvas> DispCanvas;
|
||||
Widget* FocusWidget = nullptr;
|
||||
|
|
|
|||
|
|
@ -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<Line> lines = { Line() };
|
||||
std::vector<Line> lines = { Line{this} };
|
||||
ivec2 cursor_pos = { 0, 0 };
|
||||
int max_length = -1;
|
||||
bool mouse_selecting = false;
|
||||
|
|
|
|||
|
|
@ -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<std::shared_ptr<Image>>& images) = 0;
|
||||
virtual void SetWindowFrame(const Rect& box) = 0;
|
||||
virtual void SetClientFrame(const Rect& box) = 0;
|
||||
virtual void Show() = 0;
|
||||
|
|
|
|||
|
|
@ -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<BitmapTexture*>(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<BitmapTexture*>(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;
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -262,7 +262,7 @@ int decodePNG(std::vector<unsigned char>& 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<unsigned char> 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<unsigned char>& 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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<BasicWidgetStyle>(), "widget");
|
||||
auto textlabel = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
|
||||
/*auto textlabel =*/ RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
|
||||
auto pushbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "pushbutton");
|
||||
auto lineedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "lineedit");
|
||||
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
|
||||
|
|
@ -167,272 +181,177 @@ DarkWidgetTheme::DarkWidgetTheme()
|
|||
auto statusbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(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<BasicWidgetStyle>(), "widget");
|
||||
auto textlabel = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
|
||||
auto pushbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "pushbutton");
|
||||
auto lineedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "lineedit");
|
||||
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
|
||||
auto listview = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "listview");
|
||||
auto dropdown = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "dropdown");
|
||||
auto scrollbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "scrollbar");
|
||||
auto tabbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar");
|
||||
auto tabbar_tab = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-tab");
|
||||
auto tabbar_spacer = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-spacer");
|
||||
auto tabwidget_stack = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabwidget-stack");
|
||||
auto checkbox_label = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "checkbox-label");
|
||||
auto menubar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubar");
|
||||
auto menubaritem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubaritem");
|
||||
auto menu = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menu");
|
||||
auto menuitem = RegisterStyle(std::make_unique<BasicWidgetStyle>(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
|
||||
}) {};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -173,6 +173,18 @@ void Widget::SetWindowTitle(const std::string& text)
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Image>> Widget::GetWindowIcon() const
|
||||
{
|
||||
return WindowIcon;
|
||||
}
|
||||
|
||||
void Widget::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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<double>& 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<std::string> 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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@
|
|||
#include "core/colorf.h"
|
||||
#include <stdexcept>
|
||||
|
||||
#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"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Image>& 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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
|
||||
#include "dbus_open_folder_dialog.h"
|
||||
#include <dbus/dbus.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
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<std::string> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
#include "sdl2_display_window.h"
|
||||
#include <stdexcept>
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
|
|
@ -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<std::string> 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<std::shared_ptr<Image>>& 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public:
|
|||
~SDL2DisplayWindow();
|
||||
|
||||
void SetWindowTitle(const std::string& text) override;
|
||||
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
|
||||
void SetWindowFrame(const Rect& box) override;
|
||||
void SetClientFrame(const Rect& box) override;
|
||||
void Show() override;
|
||||
|
|
|
|||
|
|
@ -9,49 +9,69 @@
|
|||
#include <wayland-client-protocol-unstable.hpp>
|
||||
#include <wayland-cursor.hpp>
|
||||
#include "wl_fractional_scaling_protocol.hpp"
|
||||
#include "wl_xdg_toplevel_icon.hpp"
|
||||
#include <linux/input.h>
|
||||
#include <poll.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
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<size_t>(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<void()> onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {}
|
||||
|
||||
int timeoutMilliseconds = 0;
|
||||
std::function<void()> onTimer;
|
||||
int64_t nextTime = 0;
|
||||
};
|
||||
|
||||
class WaylandDisplayWindow;
|
||||
|
|
@ -60,7 +80,7 @@ class WaylandDisplayBackend : public DisplayBackend
|
|||
{
|
||||
public:
|
||||
WaylandDisplayBackend();
|
||||
~WaylandDisplayBackend();
|
||||
~WaylandDisplayBackend();
|
||||
|
||||
std::unique_ptr<DisplayWindow> 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<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner) override;
|
||||
std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner) override;
|
||||
|
|
@ -93,76 +117,92 @@ public:
|
|||
wayland::registry_t s_waylandRegistry;
|
||||
std::vector<WaylandDisplayWindow*> 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<InputKey, bool> 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<InputKey, bool> 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<wayland::data_offer_t, std::set<std::string>, hash_proxy_t> m_DataOfferMimeTypes;
|
||||
|
||||
std::string m_ClipboardContents;
|
||||
|
||||
std::vector<std::shared_ptr<WaylandTimer>> m_timers;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,288 +3,313 @@
|
|||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#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<std::shared_ptr<Image>>& 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<SharedMemHelper>(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<SharedMemHelper>(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<std::shared_ptr<Image>>& 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<SharedMemHelper>(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);
|
||||
|
|
|
|||
|
|
@ -25,70 +25,71 @@ template <typename R, typename T, typename... Args>
|
|||
std::function<R(Args...)> 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<unsigned int> distribution(0, std::numeric_limits<unsigned int>::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<unsigned int> distribution(0, std::numeric_limits<unsigned int>::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<std::shared_ptr<Image>>& 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<SharedMemHelper> shared_mem;
|
||||
std::string m_windowID;
|
||||
|
||||
bool isFullscreen = false;
|
||||
std::shared_ptr<SharedMemHelper> shared_mem;
|
||||
|
||||
// Helper functions
|
||||
void CreateBuffers(int32_t width, int32_t height);
|
||||
std::string GetWaylandWindowID();
|
||||
std::vector<std::shared_ptr<SharedMemHelper>> appIconSharedMems;
|
||||
std::vector<wayland::buffer_t> appIconBuffers;
|
||||
|
||||
friend WaylandDisplayBackend;
|
||||
bool isFullscreen = false;
|
||||
|
||||
// Helper functions
|
||||
void CreateBuffers(int32_t width, int32_t height);
|
||||
|
||||
void CreateAppIconBuffers(const std::vector<std::shared_ptr<Image>>& images);
|
||||
std::string GetWaylandWindowID();
|
||||
|
||||
friend WaylandDisplayBackend;
|
||||
};
|
||||
|
|
|
|||
218
libraries/ZWidget/src/window/wayland/wl_cursor_shape.cpp
Normal file
|
|
@ -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<detail::events_base_t>(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<wl_proxy*> (p), t){
|
||||
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
|
||||
{
|
||||
set_events(std::shared_ptr<detail::events_base_t>(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<wp_cursor_shape_manager_v1*> (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<wl_object*>(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<wl_object*>(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<any>& args, const std::shared_ptr<detail::events_base_t>& 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<detail::events_base_t>(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<wl_proxy*> (p), t){
|
||||
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
|
||||
{
|
||||
set_events(std::shared_ptr<detail::events_base_t>(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<wp_cursor_shape_device_v1*> (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<uint32_t>(shape));
|
||||
}
|
||||
|
||||
|
||||
int cursor_shape_device_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
258
libraries/ZWidget/src/window/wayland/wl_cursor_shape.hpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <wayland-client.hpp>
|
||||
#include <wayland-client-protocol-unstable.hpp>
|
||||
|
||||
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<detail::any>& args, const std::shared_ptr<detail::events_base_t>& 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<detail::any>& args, const std::shared_ptr<detail::events_base_t>& 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
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
267
libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.cpp
Normal file
|
|
@ -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<detail::events_base_t>(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<wl_proxy*> (p), t){
|
||||
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
|
||||
{
|
||||
set_events(std::shared_ptr<detail::events_base_t>(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<xdg_toplevel_icon_manager_v1*> (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<wl_object*>(toplevel.c_ptr()) : nullptr, icon.proxy_has_object() ? reinterpret_cast<wl_object*>(icon.c_ptr()) : nullptr);
|
||||
}
|
||||
|
||||
|
||||
std::function<void(int32_t)> &xdg_toplevel_icon_manager_v1_t::on_icon_size()
|
||||
{
|
||||
return std::static_pointer_cast<events_t>(get_events())->icon_size;
|
||||
}
|
||||
|
||||
std::function<void()> &xdg_toplevel_icon_manager_v1_t::on_done()
|
||||
{
|
||||
return std::static_pointer_cast<events_t>(get_events())->done;
|
||||
}
|
||||
|
||||
int xdg_toplevel_icon_manager_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
|
||||
{
|
||||
std::shared_ptr<events_t> events = std::static_pointer_cast<events_t>(e);
|
||||
switch(opcode)
|
||||
{
|
||||
case 0:
|
||||
if(events->icon_size) events->icon_size(args[0].get<int32_t>());
|
||||
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<detail::events_base_t>(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<wl_proxy*> (p), t){
|
||||
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
|
||||
{
|
||||
set_events(std::shared_ptr<detail::events_base_t>(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<xdg_toplevel_icon_v1*> (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<wl_object*>(buffer.c_ptr()) : nullptr, scale);
|
||||
}
|
||||
|
||||
|
||||
int xdg_toplevel_icon_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
244
libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.hpp
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <wayland-client.hpp>
|
||||
#include <wayland-client-protocol-extra.hpp>
|
||||
|
||||
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<void(int32_t)> icon_size;
|
||||
std::function<void()> done;
|
||||
};
|
||||
|
||||
static int dispatcher(uint32_t opcode, const std::vector<detail::any>& args, const std::shared_ptr<detail::events_base_t>& 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<void(int32_t)> &on_icon_size();
|
||||
|
||||
/** \brief all information has been sent
|
||||
|
||||
This event is sent after all 'icon_size' events have been sent.
|
||||
|
||||
*/
|
||||
std::function<void()> &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<detail::any>& args, const std::shared_ptr<detail::events_base_t>& 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
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
#include "win32_display_window.h"
|
||||
#include <zwidget/core/image.h>
|
||||
#include <windowsx.h>
|
||||
#include <stdexcept>
|
||||
#include <cmath>
|
||||
|
|
@ -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<std::shared_ptr<Image>>& 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<uint32_t> 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<std::shared_ptr<Image>>& 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;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public:
|
|||
~Win32DisplayWindow();
|
||||
|
||||
void SetWindowTitle(const std::string& text) override;
|
||||
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& 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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "win32_display_window.h"
|
||||
#include "core/widget.h"
|
||||
#include <stdexcept>
|
||||
#include <exception>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
261
libraries/ZWidget/src/window/x11/x11_connection.cpp
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
|
||||
#include "x11_connection.h"
|
||||
#include "x11_display_window.h"
|
||||
#include <stdexcept>
|
||||
#include <chrono>
|
||||
|
||||
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<double>(disp_width_px) / static_cast<double>(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<milliseconds>(steady_clock::now().time_since_epoch()).count());
|
||||
}
|
||||
|
||||
void* X11Connection::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
|
||||
{
|
||||
timeoutMilliseconds = std::max(timeoutMilliseconds, 1);
|
||||
timers.push_back(std::make_shared<X11Timer>(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<X11Timer> 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);
|
||||
}
|
||||
65
libraries/ZWidget/src/window/x11/x11_connection.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#pragma once
|
||||
|
||||
#include "window/window.h"
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <X11/keysymdef.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
|
||||
class X11DisplayWindow;
|
||||
|
||||
class X11Timer
|
||||
{
|
||||
public:
|
||||
X11Timer(int timeoutMilliseconds, std::function<void()> onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {}
|
||||
|
||||
int timeoutMilliseconds = 0;
|
||||
std::function<void()> 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<void()> onTimer);
|
||||
void StopTimer(void* timerID);
|
||||
|
||||
Size GetScreenSize();
|
||||
|
||||
Atom GetAtom(const std::string& name);
|
||||
|
||||
Display* display = nullptr;
|
||||
std::map<std::string, Atom> atoms;
|
||||
std::map<Window, X11DisplayWindow*> windows;
|
||||
std::map<StandardCursor, Cursor> 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<std::shared_ptr<X11Timer>> timers;
|
||||
};
|
||||
|
||||
X11Connection* GetX11Connection();
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
|
||||
#include "x11_display_backend.h"
|
||||
#include "x11_display_window.h"
|
||||
#include "x11_connection.h"
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef USE_DBUS
|
||||
#include "window/dbus/dbus_open_file_dialog.h"
|
||||
|
|
@ -15,32 +17,32 @@ std::unique_ptr<DisplayWindow> 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<void()> 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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
|
||||
#include "x11_display_window.h"
|
||||
#include "x11_connection.h"
|
||||
#include <zwidget/core/image.h>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
|
@ -10,83 +12,24 @@
|
|||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
|
||||
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<std::string, Atom> atoms;
|
||||
std::map<Window, X11DisplayWindow*> windows;
|
||||
std::map<StandardCursor, Cursor> 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<double>(disp_width_px) / static_cast<double>(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<char> 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<std::shared_ptr<Image>>& 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<uint8_t> 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<uint8_t> 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<void*>(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<double> 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<Request> 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<double>(disp_width_px) / static_cast<double>(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<void()> 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
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <X11/cursorfont.h>
|
||||
#include <X11/keysymdef.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
#include <map>
|
||||
|
||||
class X11DisplayWindow : public DisplayWindow
|
||||
|
|
@ -16,6 +17,7 @@ public:
|
|||
~X11DisplayWindow();
|
||||
|
||||
void SetWindowTitle(const std::string& text) override;
|
||||
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
|
||||
void SetWindowFrame(const Rect& box) override;
|
||||
void SetClientFrame(const Rect& box) override;
|
||||
void Show() override;
|
||||
|
|
@ -59,13 +61,6 @@ public:
|
|||
std::vector<std::string> 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<void()> 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<uint8_t> 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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -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 $<TARGET_FILE_DIR:zdoom>/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 $<TARGET_FILE:zmusic> $<TARGET_FILE_DIR:zdoom>
|
||||
DEPENDS zmusic )
|
||||
if ( NOT WIN32 )
|
||||
add_custom_command(TARGET zdoom POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE_NAME:zmusic> $<TARGET_SONAME_FILE_NAME:zmusic>
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_SONAME_FILE_NAME:zmusic> $<TARGET_LINKER_FILE_NAME:zmusic>
|
||||
WORKING_DIRECTORY $<TARGET_FILE_DIR:zdoom>
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
#ifndef __SNDINT_H
|
||||
#define __SNDINT_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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<EChanFlag> EChanFlags;
|
||||
|
|
@ -146,5 +148,4 @@ struct FISoundChannel
|
|||
|
||||
void S_SetSoundPaused(int state);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
#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<FSoundID> 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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -334,11 +334,23 @@ void FKeyBindings::DoBind (const char *key, const char *bind)
|
|||
//
|
||||
//=============================================================================
|
||||
|
||||
void FKeyBindings::UnbindAll ()
|
||||
void FKeyBindings::UnbindAll (const TArray<int> *filter_ptr)
|
||||
{
|
||||
for (int i = 0; i < NUM_KEYS; ++i)
|
||||
if (filter_ptr != nullptr)
|
||||
{
|
||||
Binds[i] = "";
|
||||
const TArray<int> &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<int> *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<int> *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<int> *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<int> *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<int> *filter)
|
||||
{
|
||||
C_UnbindAll();
|
||||
C_BindDefaults();
|
||||
C_UnbindAll(filter);
|
||||
C_BindDefaults(filter);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public:
|
|||
int GetKeysForCommand (const char *cmd, int *first, int *second);
|
||||
TArray<int> GetKeysForCommand (const char *cmd);
|
||||
void UnbindACommand (const char *str);
|
||||
void UnbindAll ();
|
||||
void UnbindAll (const TArray<int> *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<int> *filter = nullptr);
|
||||
void C_UnbindAll (const TArray<int> *filter = nullptr);
|
||||
|
||||
extern const char *KeyNames[];
|
||||
|
||||
|
|
|
|||
|
|
@ -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<int> positive_keys = Bindings.GetKeysForCommand(cmd_name);
|
||||
|
||||
cmd_name[0] = '-';
|
||||
TArray<int> 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<float>(Axis, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
void ButtonMap::AddButtonTabCommands()
|
||||
{
|
||||
// Add all the action commands for tab completion
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <stdint.h>
|
||||
#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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#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()
|
||||
|
|
|
|||
|
|
@ -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 <string>
|
||||
|
||||
|
||||
#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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = {};
|
||||
|
|
|
|||
832
src/common/engine/m_haptics.cpp
Normal file
|
|
@ -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 <math.h>
|
||||
|
||||
#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<FName, struct Haptics> channels; // active rumbles (that will be mixed)
|
||||
} Haptics;
|
||||
|
||||
TMap<FName, struct Haptics> RumbleDefinition = {};
|
||||
TMap<FName, FName> RumbleMapping = {};
|
||||
TMap<FName, FName> RumbleAlias = {};
|
||||
TArray<FName> 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<FString> 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<FName> found;
|
||||
TMapIterator<FName, FName> it(RumbleAlias);
|
||||
TMap<FName, FName>::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<FName, struct Haptics> it(Haptics.channels);
|
||||
TMap<FName, struct Haptics>::Pair* pair;
|
||||
TArray<FName> 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 <double> (std::stof(argv[2], nullptr));
|
||||
low_freq = static_cast <double> (std::stof(argv[3], nullptr));
|
||||
left_trig = static_cast <double> (std::stof(argv[4], nullptr));
|
||||
right_trig = static_cast <double> (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<FString> unused, used;
|
||||
|
||||
{
|
||||
TMapIterator<FName, struct Haptics> it(RumbleDefinition);
|
||||
TMap<FName, struct Haptics>::Pair* pair;
|
||||
while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars());
|
||||
}
|
||||
{
|
||||
TMapIterator<FName, FName> it(RumbleAlias);
|
||||
TMap<FName, FName>::Pair* pair;
|
||||
while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars());
|
||||
}
|
||||
{
|
||||
TMapIterator<FName, FName> it(RumbleAlias);
|
||||
TMap<FName, FName>::Pair* pair;
|
||||
while (it.NextPair(pair))
|
||||
{
|
||||
if (unused.Contains(pair->Value.GetChars()))
|
||||
unused.Delete(unused.Find(pair->Value.GetChars()));
|
||||
}
|
||||
}
|
||||
{
|
||||
TMapIterator<FName, FName> it(RumbleMapping);
|
||||
TMap<FName, FName>::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());
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/common/engine/m_haptics.h
Normal file
|
|
@ -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
|
||||
|
|
@ -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 <math.h>
|
||||
|
||||
#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
|
||||
|
|
|
|||
|
|
@ -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<IJoystickConfig *> &sticks);
|
||||
IJoystickConfig *I_UpdateDeviceList();
|
||||
extern void UpdateJoystickMenu(IJoystickConfig *);
|
||||
|
|
|
|||
1833
src/common/filesystem/source/wildcards.hpp
Normal file
|
|
@ -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<IJoystickConfig *> 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)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ DMenu *CreateMessageBoxMenu(DMenu *parent, const char *message, int messagemode,
|
|||
IFVIRTUALPTRNAME(p, NAME_MessageBoxMenu, Init)
|
||||
{
|
||||
p->PointerVar<void>(NAME_Handler) = reinterpret_cast<void*>(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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 <IOKit/IOCFPlugIn.h>
|
||||
#include <IOKit/IOMessage.h>
|
||||
|
|
@ -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 <NUM_JOYAXIS)
|
||||
? gameAxis
|
||||
: JOYAXIS_None;
|
||||
}
|
||||
}
|
||||
|
||||
void IOKitJoystick::SetAxisScale(int axis, float scale)
|
||||
{
|
||||
if (IS_AXIS_VALID)
|
||||
|
|
@ -465,6 +461,11 @@ bool IOKitJoystick::IsSensitivityDefault()
|
|||
return JOYSENSITIVITY_DEFAULT == m_sensitivity;
|
||||
}
|
||||
|
||||
bool IOKitJoystick::IsHapticsStrengthDefault()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IOKitJoystick::IsAxisDeadZoneDefault(int axis)
|
||||
{
|
||||
return IS_AXIS_VALID
|
||||
|
|
@ -472,13 +473,6 @@ bool IOKitJoystick::IsAxisDeadZoneDefault(int axis)
|
|||
: true;
|
||||
}
|
||||
|
||||
bool IOKitJoystick::IsAxisMapDefault(int axis)
|
||||
{
|
||||
return IS_AXIS_VALID
|
||||
? (m_axes[axis].gameAxis == m_axes[axis].defaultGameAxis)
|
||||
: true;
|
||||
}
|
||||
|
||||
bool IOKitJoystick::IsAxisScaleDefault(int axis)
|
||||
{
|
||||
return IS_AXIS_VALID
|
||||
|
|
@ -521,7 +515,6 @@ void IOKitJoystick::SetDefaultConfig()
|
|||
{
|
||||
m_axes[i].deadZone = JOYDEADZONE_DEFAULT;
|
||||
m_axes[i].sensitivity = JOYSENSITIVITY_DEFAULT;
|
||||
m_axes[i].gameAxis = JOYAXIS_None;
|
||||
m_axes[i].digitalThreshold = JOYTHRESH_DEFAULT;
|
||||
m_axes[i].responseCurvePreset = JOYCURVE_DEFAULT;
|
||||
m_axes[i].responseCurve = JOYCURVE[JOYCURVE_DEFAULT];
|
||||
|
|
@ -531,10 +524,7 @@ void IOKitJoystick::SetDefaultConfig()
|
|||
|
||||
if (2 == axisCount)
|
||||
{
|
||||
m_axes[0].gameAxis = JOYAXIS_Yaw;
|
||||
m_axes[0].digitalThreshold = JOYTHRESH_STICK_X;
|
||||
|
||||
m_axes[1].gameAxis = JOYAXIS_Forward;
|
||||
m_axes[1].digitalThreshold = JOYTHRESH_STICK_Y;
|
||||
}
|
||||
|
||||
|
|
@ -542,20 +532,14 @@ void IOKitJoystick::SetDefaultConfig()
|
|||
|
||||
else if (axisCount >= 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<float>(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<float>(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<float>(axisval_x * m_sensitivity * axis_x.sensitivity);
|
||||
axis_y.value = static_cast<float>(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<float>(smoothedValue * m_sensitivity * axis.sensitivity);
|
||||
const double filteredValue = Joy_ManageSingleAxis(
|
||||
scaledValue,
|
||||
axis.deadZone,
|
||||
axis.digitalThreshold,
|
||||
axis.responseCurve,
|
||||
&buttonstate
|
||||
);
|
||||
|
||||
axis.value = static_cast<float>(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<IJoystickConfig*>& 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<IJoystickConfig*>& joysticks) const
|
||||
{
|
||||
const size_t joystickCount = m_joysticks.Size();
|
||||
|
|
@ -1133,7 +1177,7 @@ void IOKitJoystickManager::GetJoysticks(TArray<IJoystickConfig*>& 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<IJoystickConfig*>& 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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <sys/sysctl.h>
|
||||
#include <csignal>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#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<FString> 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__);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <zvulkan/vulkansurface.h>
|
||||
#include <zvulkan/vulkanbuilders.h>
|
||||
#include <zvulkan/vulkansurface.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <SDL_vulkan.h>
|
||||
#include <SDL2/SDL_vulkan.h>
|
||||
#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)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef __POSIX_SDL_GL_SYSFB_H__
|
||||
#define __POSIX_SDL_GL_SYSFB_H__
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "v_video.h"
|
||||
|
||||
|
|
@ -26,4 +26,3 @@ protected:
|
|||
};
|
||||
|
||||
#endif // __POSIX_SDL_GL_SYSFB_H__
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <SDL.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <signal.h>
|
||||
|
||||
#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)
|
||||
|
|
|
|||
|
|
@ -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 <string.h>
|
||||
|
||||
#include <SDL.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "bitmap.h"
|
||||
#include "textures.h"
|
||||
|
|
|
|||
|
|
@ -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 <SDL.h>
|
||||
#include <SDL_events.h>
|
||||
#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 <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
#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 ();
|
||||
|
|
|
|||
|
|
@ -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 <SDL.h>
|
||||
#include <SDL_gamecontroller.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_gamecontroller.h>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
#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<uint16_t> (0xffff * clamp(high_freq*HapticsStrength, 0.f, 1.f)),
|
||||
static_cast<uint16_t> (0xffff * clamp(low_freq*HapticsStrength, 0.f, 1.f)),
|
||||
duration_ms);
|
||||
}
|
||||
|
||||
if (Haptics & HAPTICS_TRIGGERS)
|
||||
{
|
||||
SDL_GameControllerRumbleTriggers(
|
||||
Mapping,
|
||||
static_cast<uint16_t> (0xffff * clamp(left_trig*HapticsStrength, 0.f, 1.f)),
|
||||
static_cast<uint16_t> (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<SDL_GameControllerAxis>(index1)) / 32767.0;
|
||||
axisval2 = SDL_GameControllerGetAxis(Mapping, static_cast<SDL_GameControllerAxis>(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<SDL_GameControllerAxis>(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<SDL_GameControllerAxis>(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<AxisInfo> 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<IJoystickConfig *> &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)
|
||||
|
|
|
|||
|
|
@ -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 <SDL.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <new>
|
||||
#include <sys/param.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <csignal>
|
||||
#include <locale.h>
|
||||
#include <new>
|
||||
#include <signal.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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__);
|
||||
|
|
|
|||