| @@ -11,13 +11,15 @@ DPF_DIR=/tmp/dpf-carla | |||
| rm -rf ${DPF_DIR} | |||
| git clone git@github.com:DISTRHO/DPF.git ${DPF_DIR} --depth=1 --recursive -b develop | |||
| cp -v ${DPF_DIR}/dgl/*.hpp ${CARLA_DIR}/source/modules/dgl/ | |||
| cp -v ${DPF_DIR}/dgl/src/*.cpp ${CARLA_DIR}/source/modules/dgl/src/ | |||
| cp -v ${DPF_DIR}/dgl/src/*.hpp ${CARLA_DIR}/source/modules/dgl/src/ | |||
| cp -v ${DPF_DIR}/distrho/*.cpp ${CARLA_DIR}/source/modules/distrho/ | |||
| cp -v ${DPF_DIR}/distrho/*.hpp ${CARLA_DIR}/source/modules/distrho/ | |||
| cp -v ${DPF_DIR}/distrho/src/*.cpp ${CARLA_DIR}/source/modules/distrho/src/ | |||
| cp -v ${DPF_DIR}/distrho/src/*.hpp ${CARLA_DIR}/source/modules/distrho/src/ | |||
| cp -v ${DPF_DIR}/dgl/*.hpp ${CARLA_DIR}/source/modules/dgl/ | |||
| cp -v ${DPF_DIR}/dgl/src/*.cpp ${CARLA_DIR}/source/modules/dgl/src/ | |||
| cp -v ${DPF_DIR}/dgl/src/*.hpp ${CARLA_DIR}/source/modules/dgl/src/ | |||
| cp -v ${DPF_DIR}/dgl/src/nanovg/*.* ${CARLA_DIR}/source/modules/dgl/src/nanovg/ | |||
| cp -v ${DPF_DIR}/distrho/*.cpp ${CARLA_DIR}/source/modules/distrho/ | |||
| cp -v ${DPF_DIR}/distrho/*.hpp ${CARLA_DIR}/source/modules/distrho/ | |||
| cp -v ${DPF_DIR}/distrho/src/*.cpp ${CARLA_DIR}/source/modules/distrho/src/ | |||
| cp -v ${DPF_DIR}/distrho/src/*.h ${CARLA_DIR}/source/modules/distrho/src/ | |||
| cp -v ${DPF_DIR}/distrho/src/*.hpp ${CARLA_DIR}/source/modules/distrho/src/ | |||
| cp -v ${DPF_DIR}/distrho/extra/LeakDetector.hpp ${CARLA_DIR}/source/modules/distrho/extra/ | |||
| cp -v ${DPF_DIR}/distrho/extra/ScopedPointer.hpp ${CARLA_DIR}/source/modules/distrho/extra/ | |||
| @@ -31,20 +33,25 @@ cp -r -v ${DPF_DIR}/dgl/src/pugl-upstream/COPYING ${CARLA_DIR}/source/modules/dg | |||
| rm ${CARLA_DIR}/source/modules/dgl/Cairo.hpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/FileBrowserDialog.hpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/Layout.hpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/Vulkan.hpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/WebView.hpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/Cairo.cpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/Vulkan.cpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/Layout.cpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/Resources.{cpp,hpp} | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/Stub.cpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/Vulkan.cpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/WebViewWin32.cpp | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/src/.clang-tidy | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/include/.clang-tidy | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/include/meson.build | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/include/pugl/cairo.h | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/include/pugl/vulkan.h | |||
| rm ${CARLA_DIR}/source/modules/dgl/src/pugl-upstream/src/.clang-tidy | |||
| rm ${CARLA_DIR}/source/modules/distrho/DistrhoInfo.hpp | |||
| rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoPlugin{JACK,LADSPA+DSSI,LV2,LV2export,VST2,VST3}.cpp | |||
| rm ${CARLA_DIR}/source/modules/distrho/DistrhoUI_win32.cpp | |||
| rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoPlugin{AU,CLAP,Export,JACK,LADSPA+DSSI,LV2,LV2export,Stub,VST2,VST3}.cpp | |||
| rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoPluginVST.hpp | |||
| rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoUI{DSSI,LV2,VST3}.cpp | |||
| rm ${CARLA_DIR}/source/modules/distrho/src/DistrhoUI{DSSI,LV2,Stub,VST3}.cpp | |||
| rm -rf ${DPF_DIR} | |||
| @@ -1,33 +0,0 @@ | |||
| #!/bin/bash | |||
| set -e | |||
| cd $(dirname ${0}) | |||
| cd .. | |||
| CARLA_DIR=$(pwd) | |||
| DISTRHO_PORTS_DIR=/tmp/distrho-ports-carla | |||
| rm -rf ${DISTRHO_PORTS_DIR} | |||
| git clone git@github.com:DISTRHO/DISTRHO-Ports.git ${DISTRHO_PORTS_DIR} --depth=1 | |||
| CARLA_MODULES_DIR=${CARLA_DIR}/source/modules | |||
| JUCE_MODULES_DIR=${DISTRHO_PORTS_DIR}/libs/juce7/source/modules | |||
| MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_dsp juce_events juce_graphics juce_gui_basics juce_gui_extra") | |||
| for M in ${MODULES}; do | |||
| echo ${CARLA_MODULES_DIR}/${M}; | |||
| rm -f ${CARLA_MODULES_DIR}/${M}/juce_* | |||
| rm -rf ${CARLA_MODULES_DIR}/${M}/{a..z}* | |||
| cp -r -v ${JUCE_MODULES_DIR}/${M}/* ${CARLA_MODULES_DIR}/${M}/ | |||
| rm ${CARLA_MODULES_DIR}/${M}/juce_*.mm | |||
| done | |||
| find ${CARLA_MODULES_DIR} -name juce_module_info -delete | |||
| rm -rf ${CARLA_MODULES_DIR}/../includes/vst3sdk | |||
| mv ${CARLA_MODULES_DIR}/juce_audio_processors/format_types/VST3_SDK ${CARLA_MODULES_DIR}/../includes/vst3sdk | |||
| rm -rf ${CARLA_MODULES_DIR}/../includes/vst3sdk/*.pdf | |||
| # rm -rf ${DISTRHO_PORTS_DIR} | |||
| @@ -186,7 +186,6 @@ endif | |||
| endif | |||
| ifneq ($(USING_CUSTOM_DPF),true) | |||
| BASE_FLAGS += -DDGL_FILE_BROWSER_DISABLED | |||
| BASE_FLAGS += -DDGL_NO_SHARED_RESOURCES | |||
| endif | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -27,6 +27,47 @@ END_NAMESPACE_DISTRHO | |||
| START_NAMESPACE_DGL | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // build config sentinels | |||
| /** | |||
| This set of static variables act as a build sentinel that detects a configuration error. | |||
| Usually this means the way DGL was built and how it is being used and linked into your program is different, | |||
| we want to avoid such combinations as memory layout would then also be different | |||
| leading to all sort of subtle but very nasty memory corruption issues. | |||
| Make sure the flags used to build DGL match the ones used by your program and the link errors should go away. | |||
| */ | |||
| #define BUILD_CONFIG_SENTINEL(NAME) \ | |||
| static struct DISTRHO_JOIN_MACRO(_, NAME) { bool ok; DISTRHO_JOIN_MACRO(_, NAME)() noexcept; } NAME; | |||
| #ifdef DPF_DEBUG | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dpf_debug_on) | |||
| #else | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dpf_debug_off) | |||
| #endif | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_on) | |||
| #else | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_off) | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_web_view_on) | |||
| #else | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_web_view_off) | |||
| #endif | |||
| #ifdef DGL_NO_SHARED_RESOURCES | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_no_shared_resources_on) | |||
| #else | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_no_shared_resources_off) | |||
| #endif | |||
| #undef BUILD_CONFIG_SENTINEL | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| /** | |||
| @@ -43,11 +84,16 @@ class DISTRHO_API Application | |||
| { | |||
| public: | |||
| /** | |||
| Constructor. | |||
| Constructor for standalone or plugin application. | |||
| */ | |||
| // NOTE: the default value is not yet passed, so we catch where we use this | |||
| Application(bool isStandalone = true); | |||
| /** | |||
| Constructor for a standalone application. | |||
| This specific constructor is required if using web views in standalone applications. | |||
| */ | |||
| Application(int argc, char* argv[]); | |||
| /** | |||
| Destructor. | |||
| */ | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -20,6 +20,35 @@ | |||
| #include "../distrho/extra/LeakDetector.hpp" | |||
| #include "../distrho/extra/ScopedPointer.hpp" | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Compatibility checks | |||
| #if defined(DGL_CAIRO) && defined(DGL_EXTERNAL) | |||
| # error invalid build config: trying to build for both cairo and external at the same time | |||
| #elif defined(DGL_CAIRO) && defined(DGL_OPENGL) | |||
| # error invalid build config: trying to build for both cairo and opengl at the same time | |||
| #elif defined(DGL_CAIRO) && defined(DGL_VULKAN) | |||
| # error invalid build config: trying to build for both cairo and vulkan at the same time | |||
| #elif defined(DGL_EXTERNAL) && defined(DGL_OPENGL) | |||
| # error invalid build config: trying to build for both external and opengl at the same time | |||
| #elif defined(DGL_EXTERNAL) && defined(DGL_VULKAN) | |||
| # error invalid build config: trying to build for both external and vulkan at the same time | |||
| #elif defined(DGL_OPENGL) && defined(DGL_VULKAN) | |||
| # error invalid build config: trying to build for both opengl and vulkan at the same time | |||
| #endif | |||
| #ifdef DGL_USE_FILEBROWSER | |||
| # error typo detected use DGL_USE_FILE_BROWSER instead of DGL_USE_FILEBROWSER | |||
| #endif | |||
| #ifdef DGL_USE_WEBVIEW | |||
| # error typo detected use DGL_USE_WEB_VIEW instead of DGL_USE_WEBVIEW | |||
| #endif | |||
| #if defined(DGL_FILE_BROWSER_DISABLED) | |||
| # error DGL_FILE_BROWSER_DISABLED has been replaced by DGL_USE_FILE_BROWSER (opt-in vs opt-out) | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Define namespace | |||
| @@ -40,10 +69,13 @@ START_NAMESPACE_DGL | |||
| Keyboard modifier flags. | |||
| */ | |||
| enum Modifier { | |||
| kModifierShift = 1u << 0u, ///< Shift key | |||
| kModifierControl = 1u << 1u, ///< Control key | |||
| kModifierAlt = 1u << 2u, ///< Alt/Option key | |||
| kModifierSuper = 1u << 3u ///< Mod4/Command/Windows key | |||
| kModifierShift = 1U << 0U, ///< Shift key | |||
| kModifierControl = 1U << 1U, ///< Control key | |||
| kModifierAlt = 1U << 2U, ///< Alt/Option key | |||
| kModifierSuper = 1U << 3U, ///< Mod4/Command/Windows key | |||
| kModifierNumLock = 1U << 4U, ///< Num lock enabled | |||
| kModifierScrollLock = 1U << 5U, ///< Scroll lock enabled | |||
| kModifierCapsLock = 1U << 6U, ///< Caps lock enabled | |||
| }; | |||
| /** | |||
| @@ -62,55 +94,88 @@ enum Modifier { | |||
| */ | |||
| enum Key { | |||
| // Convenience symbols for ASCII control characters | |||
| kKeyBackspace = 0x08, | |||
| kKeyEscape = 0x1B, | |||
| kKeyDelete = 0x7F, | |||
| kKeyBackspace = 0x00000008U, ///< Backspace | |||
| kKeyTab = 0x00000009U, ///< Tab | |||
| kKeyEnter = 0x0000000DU, ///< Enter | |||
| kKeyEscape = 0x0000001BU, ///< Escape | |||
| kKeyDelete = 0x0000007FU, ///< Delete | |||
| kKeySpace = 0x00000020U, ///< Space | |||
| // Unicode Private Use Area | |||
| kKeyF1 = 0xE000U, ///< F1 | |||
| kKeyF2, ///< F2 | |||
| kKeyF3, ///< F3 | |||
| kKeyF4, ///< F4 | |||
| kKeyF5, ///< F5 | |||
| kKeyF6, ///< F6 | |||
| kKeyF7, ///< F7 | |||
| kKeyF8, ///< F8 | |||
| kKeyF9, ///< F9 | |||
| kKeyF10, ///< F10 | |||
| kKeyF11, ///< F11 | |||
| kKeyF12, ///< F12 | |||
| kKeyPageUp = 0xE031U, ///< Page Up | |||
| kKeyPageDown, ///< Page Down | |||
| kKeyEnd, ///< End | |||
| kKeyHome, ///< Home | |||
| kKeyLeft, ///< Left | |||
| kKeyUp, ///< Up | |||
| kKeyRight, ///< Right | |||
| kKeyDown, ///< Down | |||
| kKeyPrintScreen = 0xE041U, ///< Print Screen | |||
| kKeyInsert, ///< Insert | |||
| kKeyPause, ///< Pause/Break | |||
| kKeyMenu, ///< Menu | |||
| kKeyNumLock, ///< Num Lock | |||
| kKeyScrollLock, ///< Scroll Lock | |||
| kKeyCapsLock, ///< Caps Lock | |||
| kKeyShiftL = 0xE051U, ///< Left Shift | |||
| kKeyShiftR, ///< Right Shift | |||
| kKeyControlL, ///< Left Control | |||
| kKeyControlR, ///< Right Control | |||
| kKeyAltL, ///< Left Alt | |||
| kKeyAltR, ///< Right Alt / AltGr | |||
| kKeySuperL, ///< Left Super | |||
| kKeySuperR, ///< Right Super | |||
| kKeyPad0 = 0xE060U, ///< Keypad 0 | |||
| kKeyPad1, ///< Keypad 1 | |||
| kKeyPad2, ///< Keypad 2 | |||
| kKeyPad3, ///< Keypad 3 | |||
| kKeyPad4, ///< Keypad 4 | |||
| kKeyPad5, ///< Keypad 5 | |||
| kKeyPad6, ///< Keypad 6 | |||
| kKeyPad7, ///< Keypad 7 | |||
| kKeyPad8, ///< Keypad 8 | |||
| kKeyPad9, ///< Keypad 9 | |||
| kKeyPadEnter, ///< Keypad Enter | |||
| kKeyPadPageUp = 0xE071U, ///< Keypad Page Up | |||
| kKeyPadPageDown, ///< Keypad Page Down | |||
| kKeyPadEnd, ///< Keypad End | |||
| kKeyPadHome, ///< Keypad Home | |||
| kKeyPadLeft, ///< Keypad Left | |||
| kKeyPadUp, ///< Keypad Up | |||
| kKeyPadRight, ///< Keypad Right | |||
| kKeyPadDown, ///< Keypad Down | |||
| kKeyPadClear = 0xE09DU, ///< Keypad Clear/Begin | |||
| kKeyPadInsert, ///< Keypad Insert | |||
| kKeyPadDelete, ///< Keypad Delete | |||
| kKeyPadEqual, ///< Keypad Equal | |||
| kKeyPadMultiply = 0xE0AAU, ///< Keypad Multiply | |||
| kKeyPadAdd, ///< Keypad Add | |||
| kKeyPadSeparator, ///< Keypad Separator | |||
| kKeyPadSubtract, ///< Keypad Subtract | |||
| kKeyPadDecimal, ///< Keypad Decimal | |||
| kKeyPadDivide, ///< Keypad Divide | |||
| // Backwards compatibility with old DPF | |||
| kCharBackspace DISTRHO_DEPRECATED_BY("kKeyBackspace") = kKeyBackspace, | |||
| kCharEscape DISTRHO_DEPRECATED_BY("kKeyEscape") = kKeyEscape, | |||
| kCharDelete DISTRHO_DEPRECATED_BY("kKeyDelete") = kKeyDelete, | |||
| // Unicode Private Use Area | |||
| kKeyF1 = 0xE000, | |||
| kKeyF2, | |||
| kKeyF3, | |||
| kKeyF4, | |||
| kKeyF5, | |||
| kKeyF6, | |||
| kKeyF7, | |||
| kKeyF8, | |||
| kKeyF9, | |||
| kKeyF10, | |||
| kKeyF11, | |||
| kKeyF12, | |||
| kKeyLeft, | |||
| kKeyUp, | |||
| kKeyRight, | |||
| kKeyDown, | |||
| kKeyPageUp, | |||
| kKeyPageDown, | |||
| kKeyHome, | |||
| kKeyEnd, | |||
| kKeyInsert, | |||
| kKeyShift, | |||
| kKeyShiftL = kKeyShift, | |||
| kKeyShiftR, | |||
| kKeyControl, | |||
| kKeyControlL = kKeyControl, | |||
| kKeyControlR, | |||
| kKeyAlt, | |||
| kKeyAltL = kKeyAlt, | |||
| kKeyAltR, | |||
| kKeySuper, | |||
| kKeySuperL = kKeySuper, | |||
| kKeySuperR, | |||
| kKeyMenu, | |||
| kKeyCapsLock, | |||
| kKeyScrollLock, | |||
| kKeyNumLock, | |||
| kKeyPrintScreen, | |||
| kKeyPause | |||
| kKeyShift DISTRHO_DEPRECATED_BY("kKeyShiftL") = kKeyShiftL, | |||
| kKeyControl DISTRHO_DEPRECATED_BY("kKeyControlL") = kKeyControlL, | |||
| kKeyAlt DISTRHO_DEPRECATED_BY("kKeyAltL") = kKeyAltL, | |||
| kKeySuper DISTRHO_DEPRECATED_BY("kKeySuperL") = kKeySuperL, | |||
| }; | |||
| /** | |||
| @@ -118,7 +183,7 @@ enum Key { | |||
| */ | |||
| enum EventFlag { | |||
| kFlagSendEvent = 1, ///< Event is synthetic | |||
| kFlagIsHint = 2 ///< Event is a hint (not direct user input) | |||
| kFlagIsHint = 2, ///< Event is a hint (not direct user input) | |||
| }; | |||
| /** | |||
| @@ -127,7 +192,7 @@ enum EventFlag { | |||
| enum CrossingMode { | |||
| kCrossingNormal, ///< Crossing due to pointer motion | |||
| kCrossingGrab, ///< Crossing due to a grab | |||
| kCrossingUngrab ///< Crossing due to a grab release | |||
| kCrossingUngrab, ///< Crossing due to a grab release | |||
| }; | |||
| /** | |||
| @@ -159,15 +224,20 @@ enum MouseButton { | |||
| This is a portable subset of mouse cursors that exist on X11, MacOS, and Windows. | |||
| */ | |||
| enum MouseCursor { | |||
| kMouseCursorArrow, ///< Default pointing arrow | |||
| kMouseCursorCaret, ///< Caret (I-Beam) for text entry | |||
| kMouseCursorCrosshair, ///< Cross-hair | |||
| kMouseCursorHand, ///< Hand with a pointing finger | |||
| kMouseCursorNotAllowed, ///< Operation not allowed | |||
| kMouseCursorLeftRight, ///< Left/right arrow for horizontal resize | |||
| kMouseCursorUpDown, ///< Up/down arrow for vertical resize | |||
| kMouseCursorDiagonal, ///< Top-left to bottom-right arrow for diagonal resize | |||
| kMouseCursorAntiDiagonal ///< Bottom-left to top-right arrow for diagonal resize | |||
| kMouseCursorArrow, ///< Default pointing arrow | |||
| kMouseCursorCaret, ///< Caret (I-Beam) for text entry | |||
| kMouseCursorCrosshair, ///< Cross-hair | |||
| kMouseCursorHand, ///< Hand with a pointing finger | |||
| kMouseCursorNotAllowed, ///< Operation not allowed | |||
| kMouseCursorLeftRight, ///< Left/right arrow for horizontal resize | |||
| kMouseCursorUpDown, ///< Up/down arrow for vertical resize | |||
| kMouseCursorUpLeftDownRight, ///< Diagonal arrow for down/right resize | |||
| kMouseCursorUpRightDownLeft, ///< Diagonal arrow for down/left resize | |||
| kMouseCursorAllScroll, ///< Omnidirectional "arrow" for scrolling | |||
| // Backwards compatibility with old DPF | |||
| kMouseCursorDiagonal DISTRHO_DEPRECATED_BY("kMouseCursorUpLeftDownRight") = kMouseCursorUpLeftDownRight, | |||
| kMouseCursorAntiDiagonal DISTRHO_DEPRECATED_BY("kMouseCursorUpRightDownLeft") = kMouseCursorUpRightDownLeft, | |||
| }; | |||
| /** | |||
| @@ -178,11 +248,11 @@ enum MouseCursor { | |||
| while a smooth scroll is for those with arbitrary scroll direction freedom, like some touchpads. | |||
| */ | |||
| enum ScrollDirection { | |||
| kScrollUp, ///< Scroll up | |||
| kScrollDown, ///< Scroll down | |||
| kScrollLeft, ///< Scroll left | |||
| kScrollRight, ///< Scroll right | |||
| kScrollSmooth ///< Smooth scroll in any direction | |||
| kScrollUp, ///< Scroll up | |||
| kScrollDown, ///< Scroll down | |||
| kScrollLeft, ///< Scroll left | |||
| kScrollRight, ///< Scroll right | |||
| kScrollSmooth, ///< Smooth scroll in any direction | |||
| }; | |||
| /** | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -68,7 +68,41 @@ struct Color { | |||
| /** | |||
| Create a new color based on this one but with a different alpha value. | |||
| */ | |||
| Color withAlpha(float alpha) noexcept; | |||
| Color withAlpha(float alpha) const noexcept; | |||
| /** | |||
| Create a new color based on this one but with subtracted numeric value on all elements. | |||
| Value must be in [0..255] range. | |||
| */ | |||
| Color minus(int value) const noexcept; | |||
| /** | |||
| Create a new color based on this one but with subtracted floating-point value on all elements. | |||
| Value must be in [0..1] range. | |||
| */ | |||
| Color minus(float value) const noexcept; | |||
| /** | |||
| Create a new color based on this one but with added numeric value on all elements. | |||
| Value must be in [0..255] range. | |||
| */ | |||
| Color plus(int value) const noexcept; | |||
| /** | |||
| Create a new color based on this one but with added floating-point value on all elements. | |||
| Value must be in [0..1] range. | |||
| */ | |||
| Color plus(float value) const noexcept; | |||
| /** | |||
| Create a new color based on this one but colors inverted. | |||
| */ | |||
| Color invert() const noexcept; | |||
| /** | |||
| Create a new color based on this one but in grayscale (using weighted average). | |||
| */ | |||
| Color asGrayscale() const noexcept; | |||
| /** | |||
| Create a color specified by hue, saturation and lightness. | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -63,6 +63,9 @@ public: | |||
| bool isCheckable() const noexcept; | |||
| void setCheckable(bool checkable) noexcept; | |||
| bool isEnabled() const noexcept; | |||
| void setEnabled(bool enabled, bool appliesToEventInput = true) noexcept; | |||
| Point<double> getLastClickPosition() const noexcept; | |||
| Point<double> getLastMotionPosition() const noexcept; | |||
| @@ -94,7 +97,8 @@ class KnobEventHandler | |||
| public: | |||
| enum Orientation { | |||
| Horizontal, | |||
| Vertical | |||
| Vertical, | |||
| Both | |||
| }; | |||
| // NOTE hover not implemented yet | |||
| @@ -112,6 +116,7 @@ public: | |||
| virtual void knobDragStarted(SubWidget* widget) = 0; | |||
| virtual void knobDragFinished(SubWidget* widget) = 0; | |||
| virtual void knobValueChanged(SubWidget* widget, float value) = 0; | |||
| virtual void knobDoubleClicked(SubWidget*) {}; | |||
| }; | |||
| explicit KnobEventHandler(SubWidget* self); | |||
| @@ -119,6 +124,12 @@ public: | |||
| KnobEventHandler& operator=(const KnobEventHandler& other); | |||
| virtual ~KnobEventHandler(); | |||
| bool isEnabled() const noexcept; | |||
| void setEnabled(bool enabled, bool appliesToEventInput = true) noexcept; | |||
| // if setStep(1) has been called before, this returns true | |||
| bool isInteger() const noexcept; | |||
| // returns raw value, is assumed to be scaled if using log | |||
| float getValue() const noexcept; | |||
| @@ -128,9 +139,14 @@ public: | |||
| // returns 0-1 ranged value, already with log scale as needed | |||
| float getNormalizedValue() const noexcept; | |||
| float getDefault() const noexcept; | |||
| // NOTE: value is assumed to be scaled if using log | |||
| void setDefault(float def) noexcept; | |||
| float getMinimum() const noexcept; | |||
| float getMaximum() const noexcept; | |||
| // NOTE: value is assumed to be scaled if using log | |||
| void setRange(float min, float max) noexcept; | |||
| @@ -139,16 +155,19 @@ public: | |||
| void setUsingLogScale(bool yesNo) noexcept; | |||
| Orientation getOrientation() const noexcept; | |||
| void setOrientation(const Orientation orientation) noexcept; | |||
| void setOrientation(Orientation orientation) noexcept; | |||
| void setCallback(Callback* callback) noexcept; | |||
| bool mouseEvent(const Widget::MouseEvent& ev); | |||
| bool motionEvent(const Widget::MotionEvent& ev); | |||
| // default 200, higher means slower | |||
| void setMouseDeceleration(float accel) noexcept; | |||
| bool mouseEvent(const Widget::MouseEvent& ev, double scaleFactor = 1.0); | |||
| bool motionEvent(const Widget::MotionEvent& ev, double scaleFactor = 1.0); | |||
| bool scrollEvent(const Widget::ScrollEvent& ev); | |||
| protected: | |||
| State getState() const noexcept; | |||
| State getState() const noexcept; | |||
| private: | |||
| struct PrivateData; | |||
| @@ -168,6 +187,21 @@ private: | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| class SliderEventHandler | |||
| { | |||
| public: | |||
| explicit SliderEventHandler(SubWidget* self); | |||
| virtual ~SliderEventHandler(); | |||
| private: | |||
| struct PrivateData; | |||
| PrivateData* const pData; | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SliderEventHandler) | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| END_NAMESPACE_DGL | |||
| #endif // DGL_EVENT_HANDLERS_HPP_INCLUDED | |||
| @@ -138,6 +138,7 @@ public: | |||
| virtual void imageKnobDragStarted(ImageBaseKnob* imageKnob) = 0; | |||
| virtual void imageKnobDragFinished(ImageBaseKnob* imageKnob) = 0; | |||
| virtual void imageKnobValueChanged(ImageBaseKnob* imageKnob, float value) = 0; | |||
| virtual void imageKnobDoubleClicked(ImageBaseKnob*) {}; | |||
| }; | |||
| explicit ImageBaseKnob(Widget* parentWidget, const ImageType& image, Orientation orientation = Vertical) noexcept; | |||
| @@ -192,6 +193,7 @@ public: | |||
| void setEndPos(const Point<int>& endPos) noexcept; | |||
| void setEndPos(int x, int y) noexcept; | |||
| void setCheckable(bool checkable) noexcept; | |||
| void setInverted(bool inverted) noexcept; | |||
| void setRange(float min, float max) noexcept; | |||
| void setStep(float step) noexcept; | |||
| @@ -112,6 +112,11 @@ public: | |||
| */ | |||
| GLuint getTextureHandle() const; | |||
| /** | |||
| Update the image data in-place. | |||
| */ | |||
| void update(const uchar* data); | |||
| private: | |||
| Handle fHandle; | |||
| Size<uint> fSize; | |||
| @@ -319,10 +324,9 @@ public: | |||
| /** | |||
| Constructor reusing a NanoVG context, used for subwidgets. | |||
| Context will not be deleted on class destructor. | |||
| */ | |||
| /* | |||
| NanoVG(NanoWidget* groupWidget); | |||
| */ | |||
| explicit NanoVG(NVGcontext* context); | |||
| /** | |||
| Destructor. | |||
| @@ -591,14 +595,14 @@ public: | |||
| /** | |||
| Creates image by loading it from the specified chunk of memory. | |||
| */ | |||
| NanoImage::Handle createImageFromMemory(uchar* data, uint dataSize, ImageFlags imageFlags); | |||
| NanoImage::Handle createImageFromMemory(const uchar* data, uint dataSize, ImageFlags imageFlags); | |||
| /** | |||
| Creates image by loading it from the specified chunk of memory. | |||
| Overloaded function for convenience. | |||
| @see ImageFlags | |||
| */ | |||
| NanoImage::Handle createImageFromMemory(uchar* data, uint dataSize, int imageFlags); | |||
| NanoImage::Handle createImageFromMemory(const uchar* data, uint dataSize, int imageFlags); | |||
| /** | |||
| Creates image from specified raw format image data. | |||
| @@ -917,7 +921,17 @@ public: | |||
| Constructor for a NanoSubWidget. | |||
| @see CreateFlags | |||
| */ | |||
| explicit NanoBaseWidget(Widget* parentGroupWidget, int flags = CREATE_ANTIALIAS); | |||
| explicit NanoBaseWidget(Widget* parentWidget, int flags = CREATE_ANTIALIAS); | |||
| /** | |||
| Constructor for a NanoSubWidget reusing a parent subwidget nanovg context. | |||
| */ | |||
| explicit NanoBaseWidget(NanoBaseWidget<SubWidget>* parentWidget); | |||
| /** | |||
| Constructor for a NanoSubWidget reusing a parent top-level-widget nanovg context. | |||
| */ | |||
| explicit NanoBaseWidget(NanoBaseWidget<TopLevelWidget>* parentWidget); | |||
| /** | |||
| Constructor for a NanoTopLevelWidget. | |||
| @@ -954,13 +968,7 @@ private: | |||
| Widget display function. | |||
| Implemented internally to wrap begin/endFrame() automatically. | |||
| */ | |||
| inline void onDisplay() override | |||
| { | |||
| // NOTE maybe should use BaseWidget::getWindow().getScaleFactor() as 3rd arg ? | |||
| NanoVG::beginFrame(BaseWidget::getWidth(), BaseWidget::getHeight()); | |||
| onNanoDisplay(); | |||
| NanoVG::endFrame(); | |||
| } | |||
| void onDisplay() override; | |||
| // these should not be used | |||
| void beginFrame(uint,uint) {} | |||
| @@ -969,6 +977,12 @@ private: | |||
| void cancelFrame() {} | |||
| void endFrame() {} | |||
| /** @internal */ | |||
| const bool fUsingParentContext; | |||
| void displayChildren(); | |||
| friend class NanoBaseWidget<TopLevelWidget>; | |||
| friend class NanoBaseWidget<StandaloneWindow>; | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NanoBaseWidget) | |||
| }; | |||
| @@ -22,13 +22,16 @@ | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Fix OpenGL includes for Windows, based on glfw code (part 1) | |||
| #undef DGL_CALLBACK_DEFINED | |||
| #undef DGL_WINGDIAPI_DEFINED | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| #ifndef WINAPI | |||
| # define WINAPI __stdcall | |||
| #endif | |||
| #ifndef APIENTRY | |||
| # define APIENTRY __stdcall | |||
| # define APIENTRY WINAPI | |||
| #endif // APIENTRY | |||
| /* We need WINGDIAPI defined */ | |||
| @@ -43,20 +46,6 @@ | |||
| # define DGL_WINGDIAPI_DEFINED | |||
| #endif // WINGDIAPI | |||
| /* Some <GL/glu.h> files also need CALLBACK defined */ | |||
| #ifndef CALLBACK | |||
| # if defined(_MSC_VER) | |||
| # if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS) | |||
| # define CALLBACK __stdcall | |||
| # else | |||
| # define CALLBACK | |||
| # endif | |||
| # else | |||
| # define CALLBACK __stdcall | |||
| # endif | |||
| # define DGL_CALLBACK_DEFINED | |||
| #endif // CALLBACK | |||
| #endif // DISTRHO_OS_WINDOWS | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -97,11 +86,6 @@ | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Fix OpenGL includes for Windows, based on glfw code (part 2) | |||
| #ifdef DGL_CALLBACK_DEFINED | |||
| # undef CALLBACK | |||
| # undef DGL_CALLBACK_DEFINED | |||
| #endif | |||
| #ifdef DGL_WINGDIAPI_DEFINED | |||
| # undef WINGDIAPI | |||
| # undef DGL_WINGDIAPI_DEFINED | |||
| @@ -202,8 +202,9 @@ public: | |||
| GLenum getType() const noexcept { return GL_UNSIGNED_BYTE; } | |||
| private: | |||
| GLuint textureId; | |||
| bool setupCalled; | |||
| bool textureInit; | |||
| GLuint textureId; | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -53,6 +53,15 @@ public: | |||
| sgc.done(); | |||
| } | |||
| /** | |||
| Get a graphics context back again. | |||
| Called when a valid graphics context is needed outside the constructor. | |||
| */ | |||
| void reinit() | |||
| { | |||
| sgc.reinit(); | |||
| } | |||
| /** | |||
| Overloaded functions to ensure they apply to the Window class. | |||
| */ | |||
| @@ -64,6 +73,7 @@ public: | |||
| uint getHeight() const noexcept { return Window::getHeight(); } | |||
| const Size<uint> getSize() const noexcept { return Window::getSize(); } | |||
| void repaint() noexcept { Window::repaint(); } | |||
| void repaint(const Rectangle<uint>& rect) noexcept { Window::repaint(rect); } | |||
| void setWidth(uint width) { Window::setWidth(width); } | |||
| void setHeight(uint height) { Window::setHeight(height); } | |||
| void setSize(uint width, uint height) { Window::setSize(width, height); } | |||
| @@ -137,6 +137,12 @@ public: | |||
| */ | |||
| void repaint() noexcept override; | |||
| /** | |||
| Pushes this widget to the "bottom" of the parent widget. | |||
| Makes the widget behave as if it was the first to be registered on the parent widget, thus being "on bottom". | |||
| */ | |||
| virtual void toBottom(); | |||
| /** | |||
| Bring this widget to the "front" of the parent widget. | |||
| Makes the widget behave as if it was the last to be registered on the parent widget, thus being "in front". | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -19,6 +19,8 @@ | |||
| #include "Geometry.hpp" | |||
| #include <list> | |||
| START_NAMESPACE_DGL | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -62,7 +64,7 @@ public: | |||
| uint mod; | |||
| /** Event flags. @see EventFlag */ | |||
| uint flags; | |||
| /** Event timestamp (if any). */ | |||
| /** Event timestamp in milliseconds (if any). */ | |||
| uint time; | |||
| /** Constructor for default/null values */ | |||
| @@ -331,16 +333,33 @@ public: | |||
| /** | |||
| Get the Id associated with this widget. | |||
| Returns 0 by default. | |||
| @see setId | |||
| */ | |||
| uint getId() const noexcept; | |||
| /** | |||
| Get the name associated with this widget. | |||
| This is complately optional, mostly useful for debugging purposes. | |||
| Returns an empty string by default. | |||
| @see setName | |||
| */ | |||
| const char* getName() const noexcept; | |||
| /** | |||
| Set an Id to be associated with this widget. | |||
| @see getId | |||
| */ | |||
| void setId(uint id) noexcept; | |||
| /** | |||
| Set a name to be associated with this widget. | |||
| This is complately optional, only useful for debugging purposes. | |||
| @note name must not be null | |||
| @see getName | |||
| */ | |||
| void setName(const char* name) noexcept; | |||
| /** | |||
| Get the application associated with this widget's window. | |||
| This is the same as calling `getTopLevelWidget()->getApp()`. | |||
| @@ -367,6 +386,11 @@ public: | |||
| */ | |||
| TopLevelWidget* getTopLevelWidget() const noexcept; | |||
| /** | |||
| Get list of children (a subwidgets) that belong to this widget. | |||
| */ | |||
| std::list<SubWidget*> getChildren() const noexcept; | |||
| /** | |||
| Request repaint of this widget's area to the window this widget belongs to. | |||
| On the raw Widget class this function does nothing. | |||
| @@ -383,7 +407,7 @@ protected: | |||
| /** | |||
| A function called to draw the widget contents. | |||
| */ | |||
| virtual void onDisplay() = 0; | |||
| virtual void onDisplay() {}; | |||
| /** | |||
| A function called when a key is pressed or released. | |||
| @@ -424,19 +448,24 @@ protected: | |||
| A function called when a special key is pressed or released. | |||
| DEPRECATED use onKeyboard or onCharacterInput | |||
| */ | |||
| #if defined(__clang__) | |||
| # pragma clang diagnostic push | |||
| # pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
| #elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 | |||
| # pragma GCC diagnostic push | |||
| # pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |||
| #endif | |||
| #if defined(_MSC_VER) | |||
| #pragma warning(push) | |||
| #pragma warning(disable:4996) | |||
| #elif defined(__clang__) | |||
| #pragma clang diagnostic push | |||
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
| #elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 | |||
| #pragma GCC diagnostic push | |||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |||
| #endif | |||
| virtual bool onSpecial(const SpecialEvent&) { return false; } | |||
| #if defined(__clang__) | |||
| # pragma clang diagnostic pop | |||
| #elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 | |||
| # pragma GCC diagnostic pop | |||
| #endif | |||
| #if defined(_MSC_VER) | |||
| #pragma warning(pop) | |||
| #elif defined(__clang__) | |||
| #pragma clang diagnostic pop | |||
| #elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 | |||
| #pragma GCC diagnostic pop | |||
| #endif | |||
| private: | |||
| struct PrivateData; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -19,10 +19,14 @@ | |||
| #include "Geometry.hpp" | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| # include "FileBrowserDialog.hpp" | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| # include "WebView.hpp" | |||
| #endif | |||
| #include <vector> | |||
| #ifdef DISTRHO_NAMESPACE | |||
| @@ -105,13 +109,17 @@ public: | |||
| /** Early context clearing, useful for standalone windows not created by you. */ | |||
| void done(); | |||
| /** Get a valid context back again. */ | |||
| void reinit(); | |||
| DISTRHO_DECLARE_NON_COPYABLE(ScopedGraphicsContext) | |||
| DISTRHO_PREVENT_HEAP_ALLOCATION | |||
| private: | |||
| Window& window; | |||
| Window::PrivateData* ppData; | |||
| Window::PrivateData* const ppData; | |||
| bool active; | |||
| bool reenter; | |||
| }; | |||
| /** | |||
| @@ -390,7 +398,7 @@ public: | |||
| */ | |||
| void focus(); | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| /** | |||
| Open a file browser dialog with this window as transient parent. | |||
| A few options can be specified to setup the dialog. | |||
| @@ -401,7 +409,28 @@ public: | |||
| This function does not block the event loop. | |||
| */ | |||
| bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions()); | |||
| #endif | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| /** | |||
| Create a new web view. | |||
| The web view will be added on top of this window. | |||
| This means it will draw on top of whatever is below it, | |||
| something to take into consideration if mixing regular widgets with web views. | |||
| Provided metrics in @p options must have scale factor pre-applied. | |||
| @p url: The URL to open, assumed to be in encoded form (e.g spaces converted to %20) | |||
| @p options: Extra options, optional | |||
| */ | |||
| bool createWebView(const char* url, const DGL_NAMESPACE::WebViewOptions& options = WebViewOptions()); | |||
| /** | |||
| Evaluate/run JavaScript on the web view. | |||
| */ | |||
| void evaluateJS(const char* js); | |||
| #endif | |||
| /** | |||
| Request repaint of this window, for the entire area. | |||
| @@ -513,7 +542,7 @@ protected: | |||
| */ | |||
| virtual void onScaleFactorChanged(double scaleFactor); | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| /** | |||
| A function called when a path is selected by the user, as triggered by openFileBrowser(). | |||
| This action happens after the user confirms the action, so the file browser dialog will be closed at this point. | |||
| @@ -524,7 +553,7 @@ protected: | |||
| /** DEPRECATED Use onFileSelected(). */ | |||
| DISTRHO_DEPRECATED_BY("onFileSelected(const char*)") | |||
| inline virtual void fileBrowserSelected(const char* filename) { return onFileSelected(filename); } | |||
| #endif | |||
| #endif | |||
| private: | |||
| PrivateData* const pData; | |||
| @@ -541,7 +570,8 @@ private: | |||
| uint height, | |||
| double scaleFactor, | |||
| bool resizable, | |||
| bool isVST3, | |||
| bool usesScheduledRepaints, | |||
| bool usesSizeRequest, | |||
| bool doPostInit); | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Window) | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -16,12 +16,79 @@ | |||
| #include "ApplicationPrivateData.hpp" | |||
| #ifdef __EMSCRIPTEN__ | |||
| #if defined(__EMSCRIPTEN__) | |||
| # include <emscripten/emscripten.h> | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| # include <CoreFoundation/CoreFoundation.h> | |||
| #endif | |||
| START_NAMESPACE_DGL | |||
| /* define webview start */ | |||
| #if defined(HAVE_X11) && defined(DISTRHO_OS_LINUX) && defined(DGL_USE_WEB_VIEW) | |||
| int dpf_webview_start(int argc, char* argv[]); | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // build config sentinels | |||
| #define BUILD_CONFIG_SENTINEL(NAME) \ | |||
| DISTRHO_JOIN_MACRO(_, NAME)::DISTRHO_JOIN_MACRO(_, NAME)() noexcept : ok(false) {} | |||
| #ifdef DPF_DEBUG | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dpf_debug_on) | |||
| #else | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dpf_debug_off) | |||
| #endif | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_on) | |||
| #else | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_file_browser_off) | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_web_view_on) | |||
| #else | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_use_web_view_off) | |||
| #endif | |||
| #ifdef DGL_NO_SHARED_RESOURCES | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_no_shared_resources_on) | |||
| #else | |||
| BUILD_CONFIG_SENTINEL(fail_to_link_is_mismatch_dgl_no_shared_resources_off) | |||
| #endif | |||
| #undef BUILD_CONFIG_SENTINEL | |||
| static inline | |||
| bool dpf_check_build_status() noexcept | |||
| { | |||
| return ( | |||
| #ifdef DPF_DEBUG | |||
| fail_to_link_is_mismatch_dpf_debug_on.ok && | |||
| #else | |||
| fail_to_link_is_mismatch_dpf_debug_off.ok && | |||
| #endif | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| fail_to_link_is_mismatch_dgl_use_file_browser_on.ok && | |||
| #else | |||
| fail_to_link_is_mismatch_dgl_use_file_browser_off.ok && | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| fail_to_link_is_mismatch_dgl_use_web_view_on.ok && | |||
| #else | |||
| fail_to_link_is_mismatch_dgl_use_web_view_off.ok && | |||
| #endif | |||
| #ifdef DGL_NO_SHARED_RESOURCES | |||
| fail_to_link_is_mismatch_dgl_no_shared_resources_on.ok && | |||
| #else | |||
| fail_to_link_is_mismatch_dgl_no_shared_resources_off.ok && | |||
| #endif | |||
| true | |||
| ); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| #ifdef __EMSCRIPTEN__ | |||
| @@ -32,7 +99,67 @@ static void app_idle(void* const app) | |||
| #endif | |||
| Application::Application(const bool isStandalone) | |||
| : pData(new PrivateData(isStandalone)) {} | |||
| : pData(new PrivateData(isStandalone)) | |||
| { | |||
| // build config sentinels | |||
| #ifdef DPF_DEBUG | |||
| fail_to_link_is_mismatch_dpf_debug_on.ok = true; | |||
| #else | |||
| fail_to_link_is_mismatch_dpf_debug_off.ok = true; | |||
| #endif | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| fail_to_link_is_mismatch_dgl_use_file_browser_on.ok = true; | |||
| #else | |||
| fail_to_link_is_mismatch_dgl_use_file_browser_off.ok = true; | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| fail_to_link_is_mismatch_dgl_use_web_view_on.ok = true; | |||
| #else | |||
| fail_to_link_is_mismatch_dgl_use_web_view_off.ok = true; | |||
| #endif | |||
| #ifdef DGL_NO_SHARED_RESOURCES | |||
| fail_to_link_is_mismatch_dgl_no_shared_resources_on.ok = true; | |||
| #else | |||
| fail_to_link_is_mismatch_dgl_no_shared_resources_off.ok = true; | |||
| #endif | |||
| DISTRHO_SAFE_ASSERT(dpf_check_build_status()); | |||
| } | |||
| Application::Application(int argc, char* argv[]) | |||
| : pData(new PrivateData(true)) | |||
| { | |||
| #if defined(HAVE_X11) && defined(DISTRHO_OS_LINUX) && defined(DGL_USE_WEB_VIEW) | |||
| if (argc >= 2 && std::strcmp(argv[1], "dpf-ld-linux-webview") == 0) | |||
| std::exit(dpf_webview_start(argc, argv)); | |||
| #else | |||
| // unused | |||
| (void)argc; | |||
| (void)argv; | |||
| #endif | |||
| // build config sentinels | |||
| #ifdef DPF_DEBUG | |||
| fail_to_link_is_mismatch_dpf_debug_on.ok = true; | |||
| #else | |||
| fail_to_link_is_mismatch_dpf_debug_off.ok = true; | |||
| #endif | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| fail_to_link_is_mismatch_dgl_use_file_browser_on.ok = true; | |||
| #else | |||
| fail_to_link_is_mismatch_dgl_use_file_browser_off.ok = true; | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| fail_to_link_is_mismatch_dgl_use_web_view_on.ok = true; | |||
| #else | |||
| fail_to_link_is_mismatch_dgl_use_web_view_off.ok = true; | |||
| #endif | |||
| #ifdef DGL_NO_SHARED_RESOURCES | |||
| fail_to_link_is_mismatch_dgl_no_shared_resources_on.ok = true; | |||
| #else | |||
| fail_to_link_is_mismatch_dgl_no_shared_resources_off.ok = true; | |||
| #endif | |||
| DISTRHO_SAFE_ASSERT(dpf_check_build_status()); | |||
| } | |||
| Application::~Application() | |||
| { | |||
| @@ -48,8 +175,18 @@ void Application::exec(const uint idleTimeInMs) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(pData->isStandalone,); | |||
| #ifdef __EMSCRIPTEN__ | |||
| #if defined(__EMSCRIPTEN__) | |||
| emscripten_set_main_loop_arg(app_idle, this, 0, true); | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| const CFTimeInterval idleTimeInSecs = static_cast<CFTimeInterval>(idleTimeInMs) / 1000; | |||
| while (! pData->isQuitting) | |||
| { | |||
| pData->idle(0); | |||
| if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, idleTimeInSecs, true) == kCFRunLoopRunFinished) | |||
| break; | |||
| } | |||
| #else | |||
| while (! pData->isQuitting) | |||
| pData->idle(idleTimeInMs); | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -23,31 +23,32 @@ | |||
| START_NAMESPACE_DGL | |||
| typedef std::list<DGL_NAMESPACE::Window*>::iterator WindowListIterator; | |||
| typedef std::list<DGL_NAMESPACE::Window*>::reverse_iterator WindowListReverseIterator; | |||
| static d_ThreadHandle getCurrentThreadHandle() noexcept | |||
| { | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| return GetCurrentThread(); | |||
| #else | |||
| #else | |||
| return pthread_self(); | |||
| #endif | |||
| #endif | |||
| } | |||
| static bool isThisTheMainThread(const d_ThreadHandle mainThreadHandle) noexcept | |||
| { | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| return GetCurrentThread() == mainThreadHandle; // IsGUIThread ? | |||
| #else | |||
| #else | |||
| return pthread_equal(getCurrentThreadHandle(), mainThreadHandle) != 0; | |||
| #endif | |||
| #endif | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| const char* Application::getClassName() const noexcept | |||
| { | |||
| return puglGetClassName(pData->world); | |||
| return puglGetWorldString(pData->world, PUGL_CLASS_NAME); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -59,6 +60,7 @@ Application::PrivateData::PrivateData(const bool standalone) | |||
| isQuitting(false), | |||
| isQuittingInNextCycle(false), | |||
| isStarting(true), | |||
| needsRepaint(false), | |||
| visibleWindows(0), | |||
| mainThreadHandle(getCurrentThreadHandle()), | |||
| windows(), | |||
| @@ -66,10 +68,16 @@ Application::PrivateData::PrivateData(const bool standalone) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,); | |||
| #ifdef DGL_USING_SDL | |||
| SDL_Init(SDL_INIT_EVENTS|SDL_INIT_TIMER|SDL_INIT_VIDEO); | |||
| #else | |||
| puglSetWorldHandle(world, this); | |||
| #ifndef __EMSCRIPTEN__ | |||
| puglSetClassName(world, DISTRHO_MACRO_AS_STRING(DGL_NAMESPACE)); | |||
| #endif | |||
| #ifdef __EMSCRIPTEN__ | |||
| puglSetWorldString(world, PUGL_CLASS_NAME, "canvas"); | |||
| #else | |||
| puglSetWorldString(world, PUGL_CLASS_NAME, DISTRHO_MACRO_AS_STRING(DGL_NAMESPACE)); | |||
| #endif | |||
| #endif | |||
| } | |||
| Application::PrivateData::~PrivateData() | |||
| @@ -80,8 +88,12 @@ Application::PrivateData::~PrivateData() | |||
| windows.clear(); | |||
| idleCallbacks.clear(); | |||
| #ifdef DGL_USING_SDL | |||
| SDL_Quit(); | |||
| #else | |||
| if (world != nullptr) | |||
| puglFreeWorld(world); | |||
| #endif | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -134,6 +146,20 @@ void Application::PrivateData::triggerIdleCallbacks() | |||
| } | |||
| } | |||
| void Application::PrivateData::repaintIfNeeeded() | |||
| { | |||
| if (needsRepaint) | |||
| { | |||
| needsRepaint = false; | |||
| for (WindowListIterator it = windows.begin(), ite = windows.end(); it != ite; ++it) | |||
| { | |||
| DGL_NAMESPACE::Window* const window(*it); | |||
| window->repaint(); | |||
| } | |||
| } | |||
| } | |||
| void Application::PrivateData::quit() | |||
| { | |||
| if (! isThisTheMainThread(mainThreadHandle)) | |||
| @@ -147,28 +173,26 @@ void Application::PrivateData::quit() | |||
| isQuitting = true; | |||
| #ifndef DPF_TEST_APPLICATION_CPP | |||
| #ifndef DPF_TEST_APPLICATION_CPP | |||
| for (WindowListReverseIterator rit = windows.rbegin(), rite = windows.rend(); rit != rite; ++rit) | |||
| { | |||
| DGL_NAMESPACE::Window* const window(*rit); | |||
| window->close(); | |||
| } | |||
| #endif | |||
| #endif | |||
| } | |||
| double Application::PrivateData::getTime() const | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, 0.0); | |||
| return puglGetTime(world); | |||
| return world != nullptr ? puglGetTime(world) : 0.0; | |||
| } | |||
| void Application::PrivateData::setClassName(const char* const name) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0',); | |||
| puglSetClassName(world, name); | |||
| if (world != nullptr) | |||
| puglSetWorldString(world, PUGL_CLASS_NAME, name); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -63,6 +63,9 @@ struct Application::PrivateData { | |||
| /** Whether the applicating is starting up, that is, no windows have been made visible yet. Defaults to true. */ | |||
| bool isStarting; | |||
| /** When true force all windows to be repainted on next idle. */ | |||
| bool needsRepaint; | |||
| /** Counter of visible windows, only used in standalone mode. | |||
| If 0->1, application is starting. If 1->0, application is quitting/stopping. */ | |||
| uint visibleWindows; | |||
| @@ -96,6 +99,9 @@ struct Application::PrivateData { | |||
| /** Run each idle callback without updating pugl world. */ | |||
| void triggerIdleCallbacks(); | |||
| /** Trigger a repaint of all windows if @a needsRepaint is true. */ | |||
| void repaintIfNeeeded(); | |||
| /** Set flag indicating application is quitting, and close all windows in reverse order of registration. | |||
| For standalone mode only. */ | |||
| void quit(); | |||
| @@ -114,13 +114,72 @@ Color::Color(const Color& color1, const Color& color2, const float u) noexcept | |||
| interpolate(color2, u); | |||
| } | |||
| Color Color::withAlpha(const float alpha2) noexcept | |||
| Color Color::withAlpha(const float alpha2) const noexcept | |||
| { | |||
| Color color(*this); | |||
| color.alpha = alpha2; | |||
| return color; | |||
| } | |||
| Color Color::minus(const int value) const noexcept | |||
| { | |||
| const float fvalue = static_cast<float>(value)/255.f; | |||
| Color color(*this); | |||
| color.red -= fvalue; | |||
| color.green -= fvalue; | |||
| color.blue -= fvalue; | |||
| color.fixBounds(); | |||
| return color; | |||
| } | |||
| Color Color::minus(const float value) const noexcept | |||
| { | |||
| Color color(*this); | |||
| color.red -= value; | |||
| color.green -= value; | |||
| color.blue -= value; | |||
| color.fixBounds(); | |||
| return color; | |||
| } | |||
| Color Color::plus(const int value) const noexcept | |||
| { | |||
| const float fvalue = static_cast<float>(value)/255.f; | |||
| Color color(*this); | |||
| color.red += fvalue; | |||
| color.green += fvalue; | |||
| color.blue += fvalue; | |||
| color.fixBounds(); | |||
| return color; | |||
| } | |||
| Color Color::plus(const float value) const noexcept | |||
| { | |||
| Color color(*this); | |||
| color.red += value; | |||
| color.green += value; | |||
| color.blue += value; | |||
| color.fixBounds(); | |||
| return color; | |||
| } | |||
| Color Color::invert() const noexcept | |||
| { | |||
| Color color(*this); | |||
| color.red = 1.f - color.red; | |||
| color.green = 1.f - color.green; | |||
| color.blue = 1.f - color.blue; | |||
| return color; | |||
| } | |||
| Color Color::asGrayscale() const noexcept | |||
| { | |||
| Color color(*this); | |||
| // values taken from https://goodcalculators.com/rgb-to-grayscale-conversion-calculator/ | |||
| color.red = color.green = color.blue = 0.299f * color.red + 0.587f * color.green + 0.114f * color.blue; | |||
| return color; | |||
| } | |||
| Color Color::fromHSL(float hue, float saturation, float lightness, float alpha) | |||
| { | |||
| float m1, m2; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -31,6 +31,8 @@ struct ButtonEventHandler::PrivateData { | |||
| int state; | |||
| bool checkable; | |||
| bool checked; | |||
| bool enabled; | |||
| bool enabledInput; | |||
| Point<double> lastClickPos; | |||
| Point<double> lastMotionPos; | |||
| @@ -44,11 +46,16 @@ struct ButtonEventHandler::PrivateData { | |||
| state(kButtonStateDefault), | |||
| checkable(false), | |||
| checked(false), | |||
| enabled(true), | |||
| enabledInput(true), | |||
| lastClickPos(0, 0), | |||
| lastMotionPos(0, 0) {} | |||
| bool mouseEvent(const Widget::MouseEvent& ev) | |||
| { | |||
| if (! enabledInput) | |||
| return false; | |||
| lastClickPos = ev.pos; | |||
| // button was released, handle it now | |||
| @@ -98,6 +105,9 @@ struct ButtonEventHandler::PrivateData { | |||
| bool motionEvent(const Widget::MotionEvent& ev) | |||
| { | |||
| if (! enabledInput) | |||
| return false; | |||
| // keep pressed | |||
| if (button != -1) | |||
| { | |||
| @@ -171,6 +181,27 @@ struct ButtonEventHandler::PrivateData { | |||
| } | |||
| } | |||
| void setEnabled(const bool enabled2, const bool appliesToEventInput) noexcept | |||
| { | |||
| if (appliesToEventInput) | |||
| enabledInput = enabled2; | |||
| if (enabled == enabled2) | |||
| return; | |||
| // reset temp vars if disabling | |||
| if (! enabled2) | |||
| { | |||
| button = -1; | |||
| state = kButtonStateDefault; | |||
| lastClickPos = Point<double>(); | |||
| lastMotionPos = Point<double>(); | |||
| } | |||
| enabled = enabled2; | |||
| widget->repaint(); | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE(PrivateData) | |||
| }; | |||
| @@ -217,6 +248,16 @@ void ButtonEventHandler::setCheckable(const bool checkable) noexcept | |||
| pData->checkable = checkable; | |||
| } | |||
| bool ButtonEventHandler::isEnabled() const noexcept | |||
| { | |||
| return pData->enabled; | |||
| } | |||
| void ButtonEventHandler::setEnabled(const bool enabled, const bool appliesToEventInput) noexcept | |||
| { | |||
| pData->setEnabled(enabled, appliesToEventInput); | |||
| } | |||
| Point<double> ButtonEventHandler::getLastClickPosition() const noexcept | |||
| { | |||
| return pData->lastClickPos; | |||
| @@ -274,12 +315,15 @@ struct KnobEventHandler::PrivateData { | |||
| SubWidget* const widget; | |||
| KnobEventHandler::Callback* callback; | |||
| float accel; | |||
| float minimum; | |||
| float maximum; | |||
| float step; | |||
| float value; | |||
| float valueDef; | |||
| float valueTmp; | |||
| bool enabled; | |||
| bool enabledInput; | |||
| bool usingDefault; | |||
| bool usingLog; | |||
| Orientation orientation; | |||
| @@ -287,56 +331,69 @@ struct KnobEventHandler::PrivateData { | |||
| double lastX; | |||
| double lastY; | |||
| uint lastClickTime; | |||
| PrivateData(KnobEventHandler* const s, SubWidget* const w) | |||
| : self(s), | |||
| widget(w), | |||
| callback(nullptr), | |||
| minimum(0.0f), | |||
| maximum(1.0f), | |||
| accel(200.f), | |||
| minimum(0.f), | |||
| maximum(1.f), | |||
| step(0.0f), | |||
| value(0.5f), | |||
| valueDef(value), | |||
| valueTmp(value), | |||
| enabled(true), | |||
| enabledInput(true), | |||
| usingDefault(false), | |||
| usingLog(false), | |||
| orientation(Vertical), | |||
| state(kKnobStateDefault), | |||
| lastX(0.0), | |||
| lastY(0.0) {} | |||
| lastY(0.0), | |||
| lastClickTime(0) {} | |||
| PrivateData(KnobEventHandler* const s, SubWidget* const w, PrivateData* const other) | |||
| : self(s), | |||
| widget(w), | |||
| callback(other->callback), | |||
| accel(other->accel), | |||
| minimum(other->minimum), | |||
| maximum(other->maximum), | |||
| step(other->step), | |||
| value(other->value), | |||
| valueDef(other->valueDef), | |||
| valueTmp(value), | |||
| enabled(other->enabled), | |||
| enabledInput(other->enabledInput), | |||
| usingDefault(other->usingDefault), | |||
| usingLog(other->usingLog), | |||
| orientation(other->orientation), | |||
| state(kKnobStateDefault), | |||
| lastX(0.0), | |||
| lastY(0.0) {} | |||
| lastY(0.0), | |||
| lastClickTime(0) {} | |||
| void assignFrom(PrivateData* const other) | |||
| { | |||
| callback = other->callback; | |||
| accel = other->accel; | |||
| minimum = other->minimum; | |||
| maximum = other->maximum; | |||
| step = other->step; | |||
| value = other->value; | |||
| valueDef = other->valueDef; | |||
| valueTmp = value; | |||
| enabled = other->enabled; | |||
| enabledInput = other->enabledInput; | |||
| usingDefault = other->usingDefault; | |||
| usingLog = other->usingLog; | |||
| orientation = other->orientation; | |||
| state = kKnobStateDefault; | |||
| lastX = 0.0; | |||
| lastY = 0.0; | |||
| lastClickTime = 0; | |||
| } | |||
| inline float logscale(const float v) const | |||
| @@ -353,8 +410,11 @@ struct KnobEventHandler::PrivateData { | |||
| return std::log(v/a)/b; | |||
| } | |||
| bool mouseEvent(const Widget::MouseEvent& ev) | |||
| bool mouseEvent(const Widget::MouseEvent& ev, const double scaleFactor) | |||
| { | |||
| if (! enabledInput) | |||
| return false; | |||
| if (ev.button != 1) | |||
| return false; | |||
| @@ -370,9 +430,21 @@ struct KnobEventHandler::PrivateData { | |||
| return true; | |||
| } | |||
| lastX = ev.pos.getX() / scaleFactor; | |||
| lastY = ev.pos.getY() / scaleFactor; | |||
| if (lastClickTime > 0 && ev.time > lastClickTime && ev.time - lastClickTime <= 300) | |||
| { | |||
| lastClickTime = 0; | |||
| if (callback != nullptr) | |||
| callback->knobDoubleClicked(widget); | |||
| return true; | |||
| } | |||
| lastClickTime = ev.time; | |||
| state |= kKnobStateDragging; | |||
| lastX = ev.pos.getX(); | |||
| lastY = ev.pos.getY(); | |||
| widget->repaint(); | |||
| if (callback != nullptr) | |||
| @@ -394,73 +466,104 @@ struct KnobEventHandler::PrivateData { | |||
| return false; | |||
| } | |||
| bool motionEvent(const Widget::MotionEvent& ev) | |||
| bool motionEvent(const Widget::MotionEvent& ev, const double scaleFactor) | |||
| { | |||
| if (! enabledInput) | |||
| return false; | |||
| if ((state & kKnobStateDragging) == 0x0) | |||
| return false; | |||
| bool doVal = false; | |||
| float d, value2 = 0.0f; | |||
| double movDiff; | |||
| if (orientation == Horizontal) | |||
| switch (orientation) | |||
| { | |||
| if (const double movX = ev.pos.getX() - lastX) | |||
| case Horizontal: | |||
| movDiff = ev.pos.getX() / scaleFactor - lastX; | |||
| break; | |||
| case Vertical: | |||
| movDiff = lastY - ev.pos.getY() / scaleFactor; | |||
| break; | |||
| case Both: | |||
| { | |||
| d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; | |||
| value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + (float(maximum - minimum) / d * float(movX)); | |||
| doVal = true; | |||
| } | |||
| } | |||
| else if (orientation == Vertical) | |||
| { | |||
| if (const double movY = lastY - ev.pos.getY()) | |||
| { | |||
| d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; | |||
| value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) + (float(maximum - minimum) / d * float(movY)); | |||
| doVal = true; | |||
| const double movDiffX = ev.pos.getX() / scaleFactor - lastX; | |||
| const double movDiffY = lastY - ev.pos.getY() / scaleFactor; | |||
| movDiff = std::abs(movDiffX) > std::abs(movDiffY) ? movDiffX : movDiffY; | |||
| } | |||
| break; | |||
| default: | |||
| return false; | |||
| } | |||
| if (! doVal) | |||
| return false; | |||
| if (d_isZero(movDiff)) | |||
| return true; | |||
| const float divisor = (ev.mod & kModifierControl) ? accel * 10.f : accel; | |||
| valueTmp += (maximum - minimum) / divisor * static_cast<float>(movDiff); | |||
| if (usingLog) | |||
| value2 = logscale(value2); | |||
| valueTmp = logscale(valueTmp); | |||
| if (value2 < minimum) | |||
| float value2; | |||
| bool valueChanged = false; | |||
| if (valueTmp < minimum) | |||
| { | |||
| valueTmp = value2 = minimum; | |||
| valueChanged = true; | |||
| } | |||
| else if (value2 > maximum) | |||
| else if (valueTmp > maximum) | |||
| { | |||
| valueTmp = value2 = maximum; | |||
| valueChanged = true; | |||
| } | |||
| else | |||
| { | |||
| valueTmp = value2; | |||
| if (d_isNotZero(step)) | |||
| { | |||
| const float rest = std::fmod(value2, step); | |||
| value2 -= rest + (rest > step/2.0f ? step : 0.0f); | |||
| if (std::abs(valueTmp - value) >= step) | |||
| { | |||
| const float rest = std::fmod(valueTmp, step); | |||
| valueChanged = true; | |||
| value2 = valueTmp - rest; | |||
| if (rest < 0 && rest < step * -0.5f) | |||
| value2 -= step; | |||
| else if (rest > 0 && rest > step * 0.5f) | |||
| value2 += step; | |||
| if (value2 < minimum) | |||
| value2 = minimum; | |||
| else if (value2 > maximum) | |||
| value2 = maximum; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| value2 = valueTmp; | |||
| valueChanged = true; | |||
| } | |||
| } | |||
| setValue(value2, true); | |||
| if (valueChanged) | |||
| setValue(value2, true); | |||
| lastX = ev.pos.getX(); | |||
| lastY = ev.pos.getY(); | |||
| lastX = ev.pos.getX() / scaleFactor; | |||
| lastY = ev.pos.getY() / scaleFactor; | |||
| return true; | |||
| } | |||
| bool scrollEvent(const Widget::ScrollEvent& ev) | |||
| { | |||
| if (! enabledInput) | |||
| return false; | |||
| if (! widget->contains(ev.pos)) | |||
| return false; | |||
| const float dir = (ev.delta.getY() > 0.f) ? 1.f : -1.f; | |||
| const float d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; | |||
| const float d = (ev.mod & kModifierControl) ? accel * 10.f : accel; | |||
| float value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) | |||
| + ((maximum - minimum) / d * 10.f * dir); | |||
| @@ -496,6 +599,28 @@ struct KnobEventHandler::PrivateData { | |||
| return ((usingLog ? invlogscale(value) : value) - minimum) / diff; | |||
| } | |||
| void setEnabled(const bool enabled2, const bool appliesToEventInput) noexcept | |||
| { | |||
| if (appliesToEventInput) | |||
| enabledInput = enabled2; | |||
| if (enabled == enabled2) | |||
| return; | |||
| // reset temp vars if disabling | |||
| if (! enabled2) | |||
| { | |||
| state = kKnobStateDefault; | |||
| lastX = 0.0; | |||
| lastY = 0.0; | |||
| lastClickTime = 0; | |||
| valueTmp = value; | |||
| } | |||
| enabled = enabled2; | |||
| widget->repaint(); | |||
| } | |||
| void setRange(const float min, const float max) noexcept | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(max > min,); | |||
| @@ -553,6 +678,21 @@ KnobEventHandler::~KnobEventHandler() | |||
| delete pData; | |||
| } | |||
| bool KnobEventHandler::isEnabled() const noexcept | |||
| { | |||
| return pData->enabled; | |||
| } | |||
| void KnobEventHandler::setEnabled(const bool enabled, const bool appliesToEventInput) noexcept | |||
| { | |||
| pData->setEnabled(enabled, appliesToEventInput); | |||
| } | |||
| bool KnobEventHandler::isInteger() const noexcept | |||
| { | |||
| return d_isEqual(pData->step, 1.f); | |||
| } | |||
| float KnobEventHandler::getValue() const noexcept | |||
| { | |||
| return pData->value; | |||
| @@ -568,12 +708,27 @@ float KnobEventHandler::getNormalizedValue() const noexcept | |||
| return pData->getNormalizedValue(); | |||
| } | |||
| float KnobEventHandler::getDefault() const noexcept | |||
| { | |||
| return pData->valueDef; | |||
| } | |||
| void KnobEventHandler::setDefault(const float def) noexcept | |||
| { | |||
| pData->valueDef = def; | |||
| pData->usingDefault = true; | |||
| } | |||
| float KnobEventHandler::getMinimum() const noexcept | |||
| { | |||
| return pData->minimum; | |||
| } | |||
| float KnobEventHandler::getMaximum() const noexcept | |||
| { | |||
| return pData->maximum; | |||
| } | |||
| void KnobEventHandler::setRange(const float min, const float max) noexcept | |||
| { | |||
| pData->setRange(min, max); | |||
| @@ -596,9 +751,6 @@ KnobEventHandler::Orientation KnobEventHandler::getOrientation() const noexcept | |||
| void KnobEventHandler::setOrientation(const Orientation orientation) noexcept | |||
| { | |||
| if (pData->orientation == orientation) | |||
| return; | |||
| pData->orientation = orientation; | |||
| } | |||
| @@ -607,14 +759,19 @@ void KnobEventHandler::setCallback(Callback* const callback) noexcept | |||
| pData->callback = callback; | |||
| } | |||
| bool KnobEventHandler::mouseEvent(const Widget::MouseEvent& ev) | |||
| void KnobEventHandler::setMouseDeceleration(float accel) noexcept | |||
| { | |||
| return pData->mouseEvent(ev); | |||
| pData->accel = accel; | |||
| } | |||
| bool KnobEventHandler::motionEvent(const Widget::MotionEvent& ev) | |||
| bool KnobEventHandler::mouseEvent(const Widget::MouseEvent& ev, const double scaleFactor) | |||
| { | |||
| return pData->motionEvent(ev); | |||
| return pData->mouseEvent(ev, scaleFactor); | |||
| } | |||
| bool KnobEventHandler::motionEvent(const Widget::MotionEvent& ev, const double scaleFactor) | |||
| { | |||
| return pData->motionEvent(ev, scaleFactor); | |||
| } | |||
| bool KnobEventHandler::scrollEvent(const Widget::ScrollEvent& ev) | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -61,13 +61,20 @@ void ImageBaseAboutWindow<ImageType>::setImage(const ImageType& image) | |||
| if (img == image) | |||
| return; | |||
| img = image; | |||
| if (image.isInvalid()) | |||
| { | |||
| img = image; | |||
| return; | |||
| } | |||
| reinit(); | |||
| img = image; | |||
| setSize(image.getSize()); | |||
| setGeometryConstraints(image.getWidth(), image.getHeight(), true, true); | |||
| done(); | |||
| } | |||
| template <class ImageType> | |||
| @@ -180,12 +187,24 @@ void ImageBaseButton<ImageType>::onDisplay() | |||
| const State state = ButtonEventHandler::getState(); | |||
| if (state & kButtonStateActive) | |||
| pData->imageDown.draw(context); | |||
| else if (state & kButtonStateHover) | |||
| pData->imageHover.draw(context); | |||
| if (ButtonEventHandler::isCheckable()) | |||
| { | |||
| if (ButtonEventHandler::isChecked()) | |||
| pData->imageDown.draw(context); | |||
| else if (state & kButtonStateHover) | |||
| pData->imageHover.draw(context); | |||
| else | |||
| pData->imageNormal.draw(context); | |||
| } | |||
| else | |||
| pData->imageNormal.draw(context); | |||
| { | |||
| if (state & kButtonStateActive) | |||
| pData->imageDown.draw(context); | |||
| else if (state & kButtonStateHover) | |||
| pData->imageHover.draw(context); | |||
| else | |||
| pData->imageNormal.draw(context); | |||
| } | |||
| } | |||
| template <class ImageType> | |||
| @@ -297,6 +316,13 @@ struct ImageBaseKnob<ImageType>::PrivateData : public KnobEventHandler::Callback | |||
| callback->imageKnobValueChanged(imageKnob, value); | |||
| } | |||
| void knobDoubleClicked(SubWidget* const widget) override | |||
| { | |||
| if (callback != nullptr) | |||
| if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) | |||
| callback->imageKnobDoubleClicked(imageKnob); | |||
| } | |||
| // implemented independently per graphics backend | |||
| void init(); | |||
| void cleanup(); | |||
| @@ -395,7 +421,7 @@ bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev) | |||
| { | |||
| if (SubWidget::onMouse(ev)) | |||
| return true; | |||
| return KnobEventHandler::mouseEvent(ev); | |||
| return KnobEventHandler::mouseEvent(ev, getTopLevelWidget()->getScaleFactor()); | |||
| } | |||
| template <class ImageType> | |||
| @@ -403,7 +429,7 @@ bool ImageBaseKnob<ImageType>::onMotion(const MotionEvent& ev) | |||
| { | |||
| if (SubWidget::onMotion(ev)) | |||
| return true; | |||
| return KnobEventHandler::motionEvent(ev); | |||
| return KnobEventHandler::motionEvent(ev, getTopLevelWidget()->getScaleFactor()); | |||
| } | |||
| template <class ImageType> | |||
| @@ -428,6 +454,7 @@ struct ImageBaseSlider<ImageType>::PrivateData { | |||
| bool usingDefault; | |||
| bool dragging; | |||
| bool checkable; | |||
| bool inverted; | |||
| bool valueIsSet; | |||
| double startedX; | |||
| @@ -449,6 +476,7 @@ struct ImageBaseSlider<ImageType>::PrivateData { | |||
| valueTmp(value), | |||
| usingDefault(false), | |||
| dragging(false), | |||
| checkable(false), | |||
| inverted(false), | |||
| valueIsSet(false), | |||
| startedX(0.0), | |||
| @@ -553,6 +581,16 @@ void ImageBaseSlider<ImageType>::setEndPos(int x, int y) noexcept | |||
| setEndPos(Point<int>(x, y)); | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseSlider<ImageType>::setCheckable(bool checkable) noexcept | |||
| { | |||
| if (pData->checkable == checkable) | |||
| return; | |||
| pData->checkable = checkable; | |||
| repaint(); | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseSlider<ImageType>::setInverted(bool inverted) noexcept | |||
| { | |||
| @@ -674,6 +712,14 @@ bool ImageBaseSlider<ImageType>::onMouse(const MouseEvent& ev) | |||
| return true; | |||
| } | |||
| if (pData->checkable) | |||
| { | |||
| const float value = d_isEqual(pData->valueTmp, pData->minimum) ? pData->maximum : pData->minimum; | |||
| setValue(value, true); | |||
| pData->valueTmp = pData->value; | |||
| return true; | |||
| } | |||
| float vper; | |||
| const double x = ev.pos.getX(); | |||
| const double y = ev.pos.getY(); | |||
| @@ -14,6 +14,11 @@ | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #ifdef _MSC_VER | |||
| // instantiated template classes whose methods are defined elsewhere | |||
| # pragma warning(disable:4661) | |||
| #endif | |||
| #include "../NanoVG.hpp" | |||
| #include "SubWidgetPrivateData.hpp" | |||
| @@ -274,6 +279,14 @@ GLuint NanoImage::getTextureHandle() const | |||
| return nvglImageHandle(fHandle.context, fHandle.imageId); | |||
| } | |||
| void NanoImage::update(const uchar* const data) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(fHandle.context != nullptr && fHandle.imageId != 0,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(data != nullptr,); | |||
| nvgUpdateImage(fHandle.context, fHandle.imageId, data); | |||
| } | |||
| void NanoImage::_updateSize() | |||
| { | |||
| int w=0, h=0; | |||
| @@ -327,6 +340,14 @@ NanoVG::NanoVG(int flags) | |||
| DISTRHO_CUSTOM_SAFE_ASSERT("Failed to create NanoVG context, expect a black screen", fContext != nullptr); | |||
| } | |||
| NanoVG::NanoVG(NVGcontext* const context) | |||
| : fContext(context), | |||
| fInFrame(false), | |||
| fIsSubWidget(true) | |||
| { | |||
| DISTRHO_CUSTOM_SAFE_ASSERT("Failed to create NanoVG context, expect a black screen", fContext != nullptr); | |||
| } | |||
| NanoVG::~NanoVG() | |||
| { | |||
| DISTRHO_CUSTOM_SAFE_ASSERT("Destroying NanoVG context with still active frame", ! fInFrame); | |||
| @@ -664,18 +685,18 @@ NanoImage::Handle NanoVG::createImageFromFile(const char* filename, int imageFla | |||
| return NanoImage::Handle(fContext, nvgCreateImage(fContext, filename, imageFlags)); | |||
| } | |||
| NanoImage::Handle NanoVG::createImageFromMemory(uchar* data, uint dataSize, ImageFlags imageFlags) | |||
| NanoImage::Handle NanoVG::createImageFromMemory(const uchar* data, uint dataSize, ImageFlags imageFlags) | |||
| { | |||
| return createImageFromMemory(data, dataSize, static_cast<int>(imageFlags)); | |||
| } | |||
| NanoImage::Handle NanoVG::createImageFromMemory(uchar* data, uint dataSize, int imageFlags) | |||
| NanoImage::Handle NanoVG::createImageFromMemory(const uchar* data, uint dataSize, int imageFlags) | |||
| { | |||
| if (fContext == nullptr) return NanoImage::Handle(); | |||
| DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, NanoImage::Handle()); | |||
| DISTRHO_SAFE_ASSERT_RETURN(dataSize > 0, NanoImage::Handle()); | |||
| return NanoImage::Handle(fContext, nvgCreateImageMem(fContext, imageFlags, data,static_cast<int>(dataSize))); | |||
| return NanoImage::Handle(fContext, nvgCreateImageMem(fContext, imageFlags, data, static_cast<int>(dataSize))); | |||
| } | |||
| NanoImage::Handle NanoVG::createImageFromRawMemory(uint w, uint h, const uchar* data, | |||
| @@ -1057,17 +1078,73 @@ bool NanoVG::loadSharedResources() | |||
| } | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| template <class BaseWidget> | |||
| void NanoBaseWidget<BaseWidget>::displayChildren() | |||
| { | |||
| std::list<SubWidget*> children(BaseWidget::getChildren()); | |||
| for (std::list<SubWidget*>::iterator it = children.begin(); it != children.end(); ++it) | |||
| { | |||
| if (NanoSubWidget* const subwidget = dynamic_cast<NanoSubWidget*>(*it)) | |||
| { | |||
| if (subwidget->fUsingParentContext && subwidget->isVisible()) | |||
| subwidget->onDisplay(); | |||
| } | |||
| } | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| // NanoSubWidget | |||
| template <> | |||
| NanoBaseWidget<SubWidget>::NanoBaseWidget(Widget* const parent, int flags) | |||
| : SubWidget(parent), | |||
| NanoVG(flags) | |||
| NanoBaseWidget<SubWidget>::NanoBaseWidget(Widget* const parentWidget, int flags) | |||
| : SubWidget(parentWidget), | |||
| NanoVG(flags), | |||
| fUsingParentContext(false) | |||
| { | |||
| setNeedsViewportScaling(); | |||
| } | |||
| template <> | |||
| NanoBaseWidget<SubWidget>::NanoBaseWidget(NanoSubWidget* const parentWidget) | |||
| : SubWidget(parentWidget), | |||
| NanoVG(parentWidget->getContext()), | |||
| fUsingParentContext(true) | |||
| { | |||
| setSkipDrawing(); | |||
| } | |||
| template <> | |||
| NanoBaseWidget<SubWidget>::NanoBaseWidget(NanoTopLevelWidget* const parentWidget) | |||
| : SubWidget(parentWidget), | |||
| NanoVG(parentWidget->getContext()), | |||
| fUsingParentContext(true) | |||
| { | |||
| setSkipDrawing(); | |||
| } | |||
| template <> | |||
| inline void NanoBaseWidget<SubWidget>::onDisplay() | |||
| { | |||
| if (fUsingParentContext) | |||
| { | |||
| NanoVG::save(); | |||
| translate(SubWidget::getAbsoluteX(), SubWidget::getAbsoluteY()); | |||
| onNanoDisplay(); | |||
| NanoVG::restore(); | |||
| displayChildren(); | |||
| } | |||
| else | |||
| { | |||
| NanoVG::beginFrame(SubWidget::getWidth(), SubWidget::getHeight()); | |||
| onNanoDisplay(); | |||
| displayChildren(); | |||
| NanoVG::endFrame(); | |||
| } | |||
| } | |||
| template class NanoBaseWidget<SubWidget>; | |||
| // ----------------------------------------------------------------------- | |||
| @@ -1076,7 +1153,17 @@ template class NanoBaseWidget<SubWidget>; | |||
| template <> | |||
| NanoBaseWidget<TopLevelWidget>::NanoBaseWidget(Window& windowToMapTo, int flags) | |||
| : TopLevelWidget(windowToMapTo), | |||
| NanoVG(flags) {} | |||
| NanoVG(flags), | |||
| fUsingParentContext(false) {} | |||
| template <> | |||
| inline void NanoBaseWidget<TopLevelWidget>::onDisplay() | |||
| { | |||
| NanoVG::beginFrame(TopLevelWidget::getWidth(), TopLevelWidget::getHeight()); | |||
| onNanoDisplay(); | |||
| displayChildren(); | |||
| NanoVG::endFrame(); | |||
| } | |||
| template class NanoBaseWidget<TopLevelWidget>; | |||
| @@ -1086,12 +1173,23 @@ template class NanoBaseWidget<TopLevelWidget>; | |||
| template <> | |||
| NanoBaseWidget<StandaloneWindow>::NanoBaseWidget(Application& app, int flags) | |||
| : StandaloneWindow(app), | |||
| NanoVG(flags) {} | |||
| NanoVG(flags), | |||
| fUsingParentContext(false) {} | |||
| template <> | |||
| NanoBaseWidget<StandaloneWindow>::NanoBaseWidget(Application& app, Window& parentWindow, int flags) | |||
| : StandaloneWindow(app, parentWindow), | |||
| NanoVG(flags) {} | |||
| NanoVG(flags), | |||
| fUsingParentContext(false) {} | |||
| template <> | |||
| inline void NanoBaseWidget<StandaloneWindow>::onDisplay() | |||
| { | |||
| NanoVG::beginFrame(Window::getWidth(), Window::getHeight()); | |||
| onNanoDisplay(); | |||
| displayChildren(); | |||
| NanoVG::endFrame(); | |||
| } | |||
| template class NanoBaseWidget<StandaloneWindow>; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -35,17 +35,7 @@ START_NAMESPACE_DGL | |||
| // ----------------------------------------------------------------------- | |||
| #if defined(DGL_USE_GLES2) | |||
| static void notImplemented(const char* const name) | |||
| { | |||
| // d_stderr2("GLES2 function not implemented: %s", name); | |||
| } | |||
| #elif defined(DGL_USE_GLES3) | |||
| static void notImplemented(const char* const name) | |||
| { | |||
| d_stderr2("GLES3 function not implemented: %s", name); | |||
| } | |||
| #elif defined(DGL_USE_OPENGL3) | |||
| #ifdef DGL_USE_OPENGL3 | |||
| static void notImplemented(const char* const name) | |||
| { | |||
| d_stderr2("OpenGL3 function not implemented: %s", name); | |||
| @@ -445,17 +435,17 @@ static void drawOpenGLImage(const OpenGLImage& image, const Point<int>& pos, con | |||
| OpenGLImage::OpenGLImage() | |||
| : ImageBase(), | |||
| textureId(0), | |||
| setupCalled(false) | |||
| setupCalled(false), | |||
| textureInit(false), | |||
| textureId(0) | |||
| { | |||
| glGenTextures(1, &textureId); | |||
| DISTRHO_SAFE_ASSERT(textureId != 0); | |||
| } | |||
| OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, const ImageFormat fmt) | |||
| : ImageBase(rdata, w, h, fmt), | |||
| textureId(0), | |||
| setupCalled(false) | |||
| setupCalled(false), | |||
| textureInit(true), | |||
| textureId(0) | |||
| { | |||
| glGenTextures(1, &textureId); | |||
| DISTRHO_SAFE_ASSERT(textureId != 0); | |||
| @@ -463,8 +453,9 @@ OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, co | |||
| OpenGLImage::OpenGLImage(const char* const rdata, const Size<uint>& s, const ImageFormat fmt) | |||
| : ImageBase(rdata, s, fmt), | |||
| textureId(0), | |||
| setupCalled(false) | |||
| setupCalled(false), | |||
| textureInit(true), | |||
| textureId(0) | |||
| { | |||
| glGenTextures(1, &textureId); | |||
| DISTRHO_SAFE_ASSERT(textureId != 0); | |||
| @@ -472,8 +463,9 @@ OpenGLImage::OpenGLImage(const char* const rdata, const Size<uint>& s, const Ima | |||
| OpenGLImage::OpenGLImage(const OpenGLImage& image) | |||
| : ImageBase(image), | |||
| textureId(0), | |||
| setupCalled(false) | |||
| setupCalled(false), | |||
| textureInit(true), | |||
| textureId(0) | |||
| { | |||
| glGenTextures(1, &textureId); | |||
| DISTRHO_SAFE_ASSERT(textureId != 0); | |||
| @@ -487,6 +479,12 @@ OpenGLImage::~OpenGLImage() | |||
| void OpenGLImage::loadFromMemory(const char* const rdata, const Size<uint>& s, const ImageFormat fmt) noexcept | |||
| { | |||
| if (!textureInit) | |||
| { | |||
| textureInit = true; | |||
| glGenTextures(1, &textureId); | |||
| DISTRHO_SAFE_ASSERT(textureId != 0); | |||
| } | |||
| setupCalled = false; | |||
| ImageBase::loadFromMemory(rdata, s, fmt); | |||
| } | |||
| @@ -502,14 +500,23 @@ OpenGLImage& OpenGLImage::operator=(const OpenGLImage& image) noexcept | |||
| size = image.size; | |||
| format = image.format; | |||
| setupCalled = false; | |||
| if (image.isValid() && !textureInit) | |||
| { | |||
| textureInit = true; | |||
| glGenTextures(1, &textureId); | |||
| DISTRHO_SAFE_ASSERT(textureId != 0); | |||
| } | |||
| return *this; | |||
| } | |||
| // deprecated calls | |||
| OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, const GLenum fmt) | |||
| : ImageBase(rdata, w, h, asDISTRHOImageFormat(fmt)), | |||
| textureId(0), | |||
| setupCalled(false) | |||
| setupCalled(false), | |||
| textureInit(true), | |||
| textureId(0) | |||
| { | |||
| glGenTextures(1, &textureId); | |||
| DISTRHO_SAFE_ASSERT(textureId != 0); | |||
| @@ -517,8 +524,9 @@ OpenGLImage::OpenGLImage(const char* const rdata, const uint w, const uint h, co | |||
| OpenGLImage::OpenGLImage(const char* const rdata, const Size<uint>& s, const GLenum fmt) | |||
| : ImageBase(rdata, s, asDISTRHOImageFormat(fmt)), | |||
| textureId(0), | |||
| setupCalled(false) | |||
| setupCalled(false), | |||
| textureInit(true), | |||
| textureId(0) | |||
| { | |||
| glGenTextures(1, &textureId); | |||
| DISTRHO_SAFE_ASSERT(textureId != 0); | |||
| @@ -683,12 +691,12 @@ void SubWidget::PrivateData::display(const uint width, const uint height, const | |||
| const int w = static_cast<int>(self->getWidth()); | |||
| const int h = static_cast<int>(self->getHeight()); | |||
| if (viewportScaleFactor != 0.0 && viewportScaleFactor != 1.0) | |||
| if (d_isNotZero(viewportScaleFactor) && d_isNotEqual(viewportScaleFactor, 1.0)) | |||
| { | |||
| glViewport(x, | |||
| -static_cast<int>(height * viewportScaleFactor - height + absolutePos.getY() + 0.5), | |||
| static_cast<int>(width * viewportScaleFactor + 0.5), | |||
| static_cast<int>(height * viewportScaleFactor + 0.5)); | |||
| -d_roundToIntPositive(height * viewportScaleFactor - height + absolutePos.getY()), | |||
| d_roundToIntPositive(width * viewportScaleFactor), | |||
| d_roundToIntPositive(height * viewportScaleFactor)); | |||
| } | |||
| else | |||
| { | |||
| @@ -699,26 +707,21 @@ void SubWidget::PrivateData::display(const uint width, const uint height, const | |||
| else if (needsFullViewportForDrawing || (absolutePos.isZero() && self->getSize() == Size<uint>(width, height))) | |||
| { | |||
| // full viewport size | |||
| glViewport(0, | |||
| -static_cast<int>(height * autoScaleFactor - height + 0.5), | |||
| static_cast<int>(width * autoScaleFactor + 0.5), | |||
| static_cast<int>(height * autoScaleFactor + 0.5)); | |||
| glViewport(0, 0, static_cast<int>(width), static_cast<int>(height)); | |||
| } | |||
| else | |||
| { | |||
| // set viewport pos | |||
| glViewport(static_cast<int>(absolutePos.getX() * autoScaleFactor + 0.5), | |||
| -static_cast<int>(std::round((height * autoScaleFactor - height) | |||
| + (absolutePos.getY() * autoScaleFactor))), | |||
| static_cast<int>(std::round(width * autoScaleFactor)), | |||
| static_cast<int>(std::round(height * autoScaleFactor))); | |||
| glViewport(d_roundToIntPositive(absolutePos.getX() * autoScaleFactor), | |||
| -d_roundToIntPositive(absolutePos.getY() * autoScaleFactor), | |||
| static_cast<int>(width), | |||
| static_cast<int>(height)); | |||
| // then cut the outer bounds | |||
| glScissor(static_cast<int>(absolutePos.getX() * autoScaleFactor + 0.5), | |||
| static_cast<int>(height - std::round((static_cast<int>(self->getHeight()) + absolutePos.getY()) | |||
| * autoScaleFactor)), | |||
| static_cast<int>(std::round(self->getWidth() * autoScaleFactor)), | |||
| static_cast<int>(std::round(self->getHeight() * autoScaleFactor))); | |||
| glScissor(d_roundToIntPositive(absolutePos.getX() * autoScaleFactor), | |||
| d_roundToIntPositive(height - (static_cast<int>(self->getHeight()) + absolutePos.getY()) * autoScaleFactor), | |||
| d_roundToIntPositive(self->getWidth() * autoScaleFactor), | |||
| d_roundToIntPositive(self->getHeight() * autoScaleFactor)); | |||
| glEnable(GL_SCISSOR_TEST); | |||
| needsDisableScissor = true; | |||
| @@ -744,26 +747,14 @@ void TopLevelWidget::PrivateData::display() | |||
| const uint width = size.getWidth(); | |||
| const uint height = size.getHeight(); | |||
| const double autoScaleFactor = window.pData->autoScaleFactor; | |||
| // full viewport size | |||
| if (window.pData->autoScaling) | |||
| { | |||
| glViewport(0, | |||
| -static_cast<int>(height * autoScaleFactor - height + 0.5), | |||
| static_cast<int>(width * autoScaleFactor + 0.5), | |||
| static_cast<int>(height * autoScaleFactor + 0.5)); | |||
| } | |||
| else | |||
| { | |||
| glViewport(0, 0, static_cast<int>(width), static_cast<int>(height)); | |||
| } | |||
| glViewport(0, 0, static_cast<int>(width), static_cast<int>(height)); | |||
| // main widget drawing | |||
| self->onDisplay(); | |||
| // now draw subwidgets if there are any | |||
| selfw->pData->displaySubWidgets(width, height, autoScaleFactor); | |||
| selfw->pData->displaySubWidgets(width, height, window.pData->autoScaleFactor); | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -71,7 +71,7 @@ Rectangle<uint> SubWidget::getConstrainedAbsoluteArea() const noexcept | |||
| const int y = getAbsoluteY(); | |||
| if (x >= 0 && y >= 0) | |||
| return Rectangle<uint>(x, y, getSize()); | |||
| return Rectangle<uint>(static_cast<uint>(x), static_cast<uint>(y), getSize()); | |||
| const int xOffset = std::min(0, x); | |||
| const int yOffset = std::min(0, y); | |||
| @@ -139,12 +139,21 @@ void SubWidget::repaint() noexcept | |||
| if (TopLevelWidget* const topw = getTopLevelWidget()) | |||
| { | |||
| if (pData->needsFullViewportForDrawing) | |||
| topw->repaint(); | |||
| // repaint is virtual and we want precisely the top-level specific implementation, not any higher level | |||
| topw->TopLevelWidget::repaint(); | |||
| else | |||
| topw->repaint(getConstrainedAbsoluteArea()); | |||
| } | |||
| } | |||
| void SubWidget::toBottom() | |||
| { | |||
| std::list<SubWidget*>& subwidgets(pData->parentWidget->pData->subWidgets); | |||
| subwidgets.remove(this); | |||
| subwidgets.insert(subwidgets.begin(), this); | |||
| } | |||
| void SubWidget::toFront() | |||
| { | |||
| std::list<SubWidget*>& subwidgets(pData->parentWidget->pData->subWidgets); | |||
| @@ -28,6 +28,17 @@ TopLevelWidget::PrivateData::PrivateData(TopLevelWidget* const s, Window& w) | |||
| selfw(s), | |||
| window(w) | |||
| { | |||
| /* if window already has a top-level-widget, make the new one match the first one in size | |||
| * this is needed because window creation and resize is a synchronous operation in some systems. | |||
| * as such, there's a chance the non-1st top-level-widgets would never get a valid size. | |||
| */ | |||
| if (!window.pData->topLevelWidgets.empty()) | |||
| { | |||
| TopLevelWidget* const first = window.pData->topLevelWidgets.front(); | |||
| selfw->pData->size = first->getSize(); | |||
| } | |||
| window.pData->topLevelWidgets.push_back(self); | |||
| } | |||
| @@ -124,9 +135,9 @@ bool TopLevelWidget::PrivateData::scrollEvent(const ScrollEvent& ev) | |||
| return selfw->pData->giveScrollEventForSubWidgets(rev); | |||
| } | |||
| void TopLevelWidget::PrivateData::fallbackOnResize() | |||
| void TopLevelWidget::PrivateData::fallbackOnResize(const uint width, const uint height) | |||
| { | |||
| puglFallbackOnResize(window.pData->view); | |||
| puglFallbackOnResize(window.pData->view, width, height); | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| @@ -38,7 +38,7 @@ struct TopLevelWidget::PrivateData { | |||
| bool mouseEvent(const MouseEvent& ev); | |||
| bool motionEvent(const MotionEvent& ev); | |||
| bool scrollEvent(const ScrollEvent& ev); | |||
| void fallbackOnResize(); | |||
| void fallbackOnResize(uint width, uint height); | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) | |||
| }; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -148,6 +148,11 @@ TopLevelWidget* Widget::getTopLevelWidget() const noexcept | |||
| return pData->topLevelWidget; | |||
| } | |||
| std::list<SubWidget*> Widget::getChildren() const noexcept | |||
| { | |||
| return pData->subWidgets; | |||
| } | |||
| void Widget::repaint() noexcept | |||
| { | |||
| } | |||
| @@ -157,11 +162,22 @@ uint Widget::getId() const noexcept | |||
| return pData->id; | |||
| } | |||
| const char* Widget::getName() const noexcept | |||
| { | |||
| return pData->name != nullptr ? pData->name : ""; | |||
| } | |||
| void Widget::setId(uint id) noexcept | |||
| { | |||
| pData->id = id; | |||
| } | |||
| void Widget::setName(const char* const name) noexcept | |||
| { | |||
| std::free(pData->name); | |||
| pData->name = strdup(name); | |||
| } | |||
| bool Widget::onKeyboard(const KeyboardEvent& ev) | |||
| { | |||
| return pData->giveKeyboardEventForSubWidgets(ev); | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -33,6 +33,7 @@ Widget::PrivateData::PrivateData(Widget* const s, TopLevelWidget* const tlw) | |||
| topLevelWidget(tlw), | |||
| parentWidget(nullptr), | |||
| id(0), | |||
| name(nullptr), | |||
| needsScaling(false), | |||
| visible(true), | |||
| size(0, 0), | |||
| @@ -43,6 +44,7 @@ Widget::PrivateData::PrivateData(Widget* const s, Widget* const pw) | |||
| topLevelWidget(findTopLevelWidget(pw)), | |||
| parentWidget(pw), | |||
| id(0), | |||
| name(nullptr), | |||
| needsScaling(false), | |||
| visible(true), | |||
| size(0, 0), | |||
| @@ -51,6 +53,7 @@ Widget::PrivateData::PrivateData(Widget* const s, Widget* const pw) | |||
| Widget::PrivateData::~PrivateData() | |||
| { | |||
| subWidgets.clear(); | |||
| std::free(name); | |||
| } | |||
| void Widget::PrivateData::displaySubWidgets(const uint width, const uint height, const double autoScaleFactor) | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -30,6 +30,7 @@ struct Widget::PrivateData { | |||
| TopLevelWidget* const topLevelWidget; | |||
| Widget* const parentWidget; | |||
| uint id; | |||
| char* name; | |||
| bool needsScaling; | |||
| bool visible; | |||
| Size<uint> size; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -27,15 +27,20 @@ START_NAMESPACE_DGL | |||
| Window::ScopedGraphicsContext::ScopedGraphicsContext(Window& win) | |||
| : window(win), | |||
| ppData(nullptr), | |||
| active(puglBackendEnter(window.pData->view)) {} | |||
| active(window.pData->view != nullptr && puglBackendEnter(window.pData->view)), | |||
| reenter(false) {} | |||
| Window::ScopedGraphicsContext::ScopedGraphicsContext(Window& win, Window& transientWin) | |||
| : window(win), | |||
| ppData(transientWin.pData), | |||
| active(false) | |||
| active(false), | |||
| reenter(window.pData->view != nullptr) | |||
| { | |||
| puglBackendLeave(ppData->view); | |||
| active = puglBackendEnter(window.pData->view); | |||
| if (reenter) | |||
| { | |||
| puglBackendLeave(ppData->view); | |||
| active = puglBackendEnter(window.pData->view); | |||
| } | |||
| } | |||
| Window::ScopedGraphicsContext::~ScopedGraphicsContext() | |||
| @@ -51,13 +56,26 @@ void Window::ScopedGraphicsContext::done() | |||
| active = false; | |||
| } | |||
| if (ppData != nullptr) | |||
| if (reenter) | |||
| { | |||
| reenter = false; | |||
| DISTRHO_SAFE_ASSERT_RETURN(ppData != nullptr,); | |||
| puglBackendEnter(ppData->view); | |||
| ppData = nullptr; | |||
| } | |||
| } | |||
| void Window::ScopedGraphicsContext::reinit() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(!active,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(!reenter,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(ppData != nullptr,); | |||
| reenter = true; | |||
| puglBackendLeave(ppData->view); | |||
| active = puglBackendEnter(window.pData->view); | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| // Window | |||
| @@ -88,7 +106,7 @@ Window::Window(Application& app, | |||
| const uint height, | |||
| const double scaleFactor, | |||
| const bool resizable) | |||
| : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, false)) | |||
| : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, false, false)) | |||
| { | |||
| pData->initPost(); | |||
| } | |||
| @@ -99,9 +117,11 @@ Window::Window(Application& app, | |||
| const uint height, | |||
| const double scaleFactor, | |||
| const bool resizable, | |||
| const bool isVST3, | |||
| const bool usesScheduledRepaints, | |||
| const bool usesSizeRequest, | |||
| const bool doPostInit) | |||
| : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, isVST3)) | |||
| : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, | |||
| usesScheduledRepaints, usesSizeRequest)) | |||
| { | |||
| if (doPostInit) | |||
| pData->initPost(); | |||
| @@ -147,7 +167,8 @@ void Window::close() | |||
| bool Window::isResizable() const noexcept | |||
| { | |||
| return puglGetViewHint(pData->view, PUGL_RESIZABLE) == PUGL_TRUE; | |||
| return pData->view != nullptr | |||
| && puglGetViewHint(pData->view, PUGL_RESIZABLE) == PUGL_TRUE; | |||
| } | |||
| void Window::setResizable(const bool resizable) | |||
| @@ -189,7 +210,11 @@ void Window::setOffsetY(const int y) | |||
| void Window::setOffset(const int x, const int y) | |||
| { | |||
| puglSetPosition(pData->view, x, y); | |||
| // do not call this for embed windows! | |||
| DISTRHO_SAFE_ASSERT_RETURN(!pData->isEmbed,); | |||
| if (pData->view != nullptr) | |||
| puglSetPosition(pData->view, x, y); | |||
| } | |||
| void Window::setOffset(const Point<int>& offset) | |||
| @@ -202,7 +227,7 @@ uint Window::getWidth() const noexcept | |||
| DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0); | |||
| const double width = puglGetFrame(pData->view).width; | |||
| DISTRHO_SAFE_ASSERT_RETURN(width >= 0.0, 0); | |||
| DISTRHO_SAFE_ASSERT_RETURN(width > 0.0, 0); | |||
| return static_cast<uint>(width + 0.5); | |||
| } | |||
| @@ -211,7 +236,7 @@ uint Window::getHeight() const noexcept | |||
| DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0); | |||
| const double height = puglGetFrame(pData->view).height; | |||
| DISTRHO_SAFE_ASSERT_RETURN(height >= 0.0, 0); | |||
| DISTRHO_SAFE_ASSERT_RETURN(height > 0.0, 0); | |||
| return static_cast<uint>(height + 0.5); | |||
| } | |||
| @@ -220,8 +245,8 @@ Size<uint> Window::getSize() const noexcept | |||
| DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, Size<uint>()); | |||
| const PuglRect rect = puglGetFrame(pData->view); | |||
| DISTRHO_SAFE_ASSERT_RETURN(rect.width >= 0.0, Size<uint>()); | |||
| DISTRHO_SAFE_ASSERT_RETURN(rect.height >= 0.0, Size<uint>()); | |||
| DISTRHO_SAFE_ASSERT_RETURN(rect.width > 0.0, Size<uint>()); | |||
| DISTRHO_SAFE_ASSERT_RETURN(rect.height > 0.0, Size<uint>()); | |||
| return Size<uint>(static_cast<uint>(rect.width + 0.5), | |||
| static_cast<uint>(rect.height + 0.5)); | |||
| } | |||
| @@ -246,10 +271,10 @@ void Window::setSize(uint width, uint height) | |||
| uint minWidth = pData->minWidth; | |||
| uint minHeight = pData->minHeight; | |||
| if (pData->autoScaling && scaleFactor != 1.0) | |||
| if (pData->autoScaling && d_isNotEqual(scaleFactor, 1.0)) | |||
| { | |||
| minWidth *= scaleFactor; | |||
| minHeight *= scaleFactor; | |||
| minWidth = d_roundToUnsignedInt(minWidth * scaleFactor); | |||
| minHeight = d_roundToUnsignedInt(minHeight * scaleFactor); | |||
| } | |||
| // handle geometry constraints here | |||
| @@ -270,10 +295,10 @@ void Window::setSize(uint width, uint height) | |||
| { | |||
| // fix width | |||
| if (reqRatio > ratio) | |||
| width = static_cast<uint>(height * ratio + 0.5); | |||
| width = d_roundToUnsignedInt(height * ratio); | |||
| // fix height | |||
| else | |||
| height = static_cast<uint>(static_cast<double>(width) / ratio + 0.5); | |||
| height = d_roundToUnsignedInt(static_cast<double>(width) / ratio); | |||
| } | |||
| } | |||
| } | |||
| @@ -287,9 +312,19 @@ void Window::setSize(uint width, uint height) | |||
| topLevelWidget->requestSizeChange(width, height); | |||
| } | |||
| else | |||
| else if (pData->view != nullptr) | |||
| { | |||
| puglSetSizeAndDefault(pData->view, width, height); | |||
| // there are no resize events for closed windows, so short-circuit the top-level widgets here | |||
| if (pData->isClosed) | |||
| { | |||
| for (std::list<TopLevelWidget*>::iterator it = pData->topLevelWidgets.begin(), | |||
| end = pData->topLevelWidgets.end(); it != end; ++it) | |||
| { | |||
| ((Widget*)*it)->setSize(width, height); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -300,23 +335,25 @@ void Window::setSize(const Size<uint>& size) | |||
| const char* Window::getTitle() const noexcept | |||
| { | |||
| return puglGetWindowTitle(pData->view); | |||
| return pData->view != nullptr ? puglGetViewString(pData->view, PUGL_WINDOW_TITLE) : ""; | |||
| } | |||
| void Window::setTitle(const char* const title) | |||
| { | |||
| if (pData->view != nullptr) | |||
| puglSetWindowTitle(pData->view, title); | |||
| puglSetViewString(pData->view, PUGL_WINDOW_TITLE, title); | |||
| } | |||
| bool Window::isIgnoringKeyRepeat() const noexcept | |||
| { | |||
| return puglGetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT) == PUGL_TRUE; | |||
| return pData->view != nullptr | |||
| && puglGetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT) == PUGL_TRUE; | |||
| } | |||
| void Window::setIgnoringKeyRepeat(const bool ignore) noexcept | |||
| { | |||
| puglSetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT, ignore); | |||
| if (pData->view != nullptr) | |||
| puglSetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT, ignore); | |||
| } | |||
| const void* Window::getClipboard(size_t& dataSize) | |||
| @@ -326,12 +363,14 @@ const void* Window::getClipboard(size_t& dataSize) | |||
| bool Window::setClipboard(const char* const mimeType, const void* const data, const size_t dataSize) | |||
| { | |||
| return puglSetClipboard(pData->view, mimeType != nullptr ? mimeType : "text/plain", data, dataSize) == PUGL_SUCCESS; | |||
| return pData->view != nullptr | |||
| && puglSetClipboard(pData->view, mimeType != nullptr ? mimeType : "text/plain", data, dataSize) == PUGL_SUCCESS; | |||
| } | |||
| bool Window::setCursor(const MouseCursor cursor) | |||
| { | |||
| return puglSetCursor(pData->view, static_cast<PuglCursor>(cursor)) == PUGL_SUCCESS; | |||
| return pData->view != nullptr | |||
| && puglSetCursor(pData->view, static_cast<PuglCursor>(cursor)) == PUGL_SUCCESS; | |||
| } | |||
| bool Window::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs) | |||
| @@ -362,7 +401,7 @@ const GraphicsContext& Window::getGraphicsContext() const noexcept | |||
| uintptr_t Window::getNativeWindowHandle() const noexcept | |||
| { | |||
| return puglGetNativeView(pData->view); | |||
| return pData->view != nullptr ? puglGetNativeView(pData->view) : 0; | |||
| } | |||
| double Window::getScaleFactor() const noexcept | |||
| @@ -375,18 +414,35 @@ void Window::focus() | |||
| pData->focus(); | |||
| } | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| bool Window::openFileBrowser(const FileBrowserOptions& options) | |||
| { | |||
| return pData->openFileBrowser(options); | |||
| } | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| bool Window::createWebView(const char* const url, const DGL_NAMESPACE::WebViewOptions& options) | |||
| { | |||
| return pData->createWebView(url, options); | |||
| } | |||
| void Window::evaluateJS(const char* const js) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(pData->webViewHandle != nullptr,); | |||
| webViewEvaluateJS(pData->webViewHandle, js); | |||
| } | |||
| #endif | |||
| void Window::repaint() noexcept | |||
| { | |||
| if (pData->view == nullptr) | |||
| return; | |||
| if (pData->usesScheduledRepaints) | |||
| pData->appData->needsRepaint = true; | |||
| puglPostRedisplay(pData->view); | |||
| } | |||
| @@ -395,6 +451,9 @@ void Window::repaint(const Rectangle<uint>& rect) noexcept | |||
| if (pData->view == nullptr) | |||
| return; | |||
| if (pData->usesScheduledRepaints) | |||
| pData->appData->needsRepaint = true; | |||
| PuglRect prect = { | |||
| static_cast<PuglCoord>(rect.getX()), | |||
| static_cast<PuglCoord>(rect.getY()), | |||
| @@ -405,10 +464,10 @@ void Window::repaint(const Rectangle<uint>& rect) noexcept | |||
| { | |||
| const double autoScaleFactor = pData->autoScaleFactor; | |||
| prect.x *= autoScaleFactor; | |||
| prect.y *= autoScaleFactor; | |||
| prect.width *= autoScaleFactor; | |||
| prect.height *= autoScaleFactor; | |||
| prect.x = static_cast<PuglCoord>(prect.x * autoScaleFactor); | |||
| prect.y = static_cast<PuglCoord>(prect.y * autoScaleFactor); | |||
| prect.width = static_cast<PuglSpan>(prect.width * autoScaleFactor + 0.5); | |||
| prect.height = static_cast<PuglSpan>(prect.height * autoScaleFactor + 0.5); | |||
| } | |||
| puglPostRedisplayRect(pData->view, prect); | |||
| } | |||
| @@ -433,11 +492,15 @@ void Window::setGeometryConstraints(uint minimumWidth, | |||
| uint minimumHeight, | |||
| const bool keepAspectRatio, | |||
| const bool automaticallyScale, | |||
| const bool resizeNowIfAutoScaling) | |||
| bool resizeNowIfAutoScaling) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(minimumWidth > 0,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(minimumHeight > 0,); | |||
| // prevent auto-scaling up 2x | |||
| if (resizeNowIfAutoScaling && automaticallyScale && pData->autoScaling == automaticallyScale) | |||
| resizeNowIfAutoScaling = false; | |||
| pData->minWidth = minimumWidth; | |||
| pData->minHeight = minimumHeight; | |||
| pData->autoScaling = automaticallyScale; | |||
| @@ -450,8 +513,8 @@ void Window::setGeometryConstraints(uint minimumWidth, | |||
| if (automaticallyScale && scaleFactor != 1.0) | |||
| { | |||
| minimumWidth *= scaleFactor; | |||
| minimumHeight *= scaleFactor; | |||
| minimumWidth = d_roundToUnsignedInt(minimumWidth * scaleFactor); | |||
| minimumHeight = d_roundToUnsignedInt(minimumHeight * scaleFactor); | |||
| } | |||
| puglSetGeometryConstraints(pData->view, minimumWidth, minimumHeight, keepAspectRatio); | |||
| @@ -467,13 +530,17 @@ void Window::setGeometryConstraints(uint minimumWidth, | |||
| void Window::setTransientParent(const uintptr_t transientParentWindowHandle) | |||
| { | |||
| puglSetTransientParent(pData->view, transientParentWindowHandle); | |||
| if (pData->view != nullptr) | |||
| puglSetTransientParent(pData->view, transientParentWindowHandle); | |||
| } | |||
| std::vector<ClipboardDataOffer> Window::getClipboardDataOfferTypes() | |||
| { | |||
| std::vector<ClipboardDataOffer> offerTypes; | |||
| if (pData->view == nullptr) | |||
| return offerTypes; | |||
| if (const uint32_t numTypes = puglGetNumClipboardTypes(pData->view)) | |||
| { | |||
| offerTypes.reserve(numTypes); | |||
| @@ -511,16 +578,17 @@ void Window::onFocus(bool, CrossingMode) | |||
| { | |||
| } | |||
| void Window::onReshape(uint, uint) | |||
| void Window::onReshape(const uint width, const uint height) | |||
| { | |||
| puglFallbackOnResize(pData->view); | |||
| if (pData->view != nullptr) | |||
| puglFallbackOnResize(pData->view, width, height); | |||
| } | |||
| void Window::onScaleFactorChanged(double) | |||
| { | |||
| } | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| void Window::onFileSelected(const char*) | |||
| { | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -29,16 +29,22 @@ | |||
| # endif | |||
| #endif | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| # include <windows.h> | |||
| #endif | |||
| START_NAMESPACE_DGL | |||
| #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) | |||
| # define DGL_DBG(msg) std::fprintf(stderr, "%s", msg); | |||
| # define DGL_DBGp(...) std::fprintf(stderr, __VA_ARGS__); | |||
| # define DGL_DBGF std::fflush(stderr); | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| # include "pugl-upstream/src/win.h" | |||
| #endif | |||
| #ifdef DGL_DEBUG_EVENTS | |||
| # define DGL_DBG(msg) d_stdout("%s", msg); | |||
| # define DGL_DBGp(...) d_stdout(__VA_ARGS__); | |||
| #else | |||
| # define DGL_DBG(msg) | |||
| # define DGL_DBGp(...) | |||
| # define DGL_DBGF | |||
| #endif | |||
| #define DEFAULT_WIDTH 640 | |||
| @@ -52,21 +58,22 @@ START_NAMESPACE_DGL | |||
| // ----------------------------------------------------------------------- | |||
| static double getScaleFactorFromParent(const PuglView* const view) | |||
| static double getScaleFactor(const PuglView* const view) | |||
| { | |||
| // allow custom scale for testing | |||
| if (const char* const scale = getenv("DPF_SCALE_FACTOR")) | |||
| return std::max(1.0, std::atof(scale)); | |||
| if (view != nullptr) | |||
| return puglGetScaleFactorFromParent(view); | |||
| return puglGetScaleFactor(view); | |||
| return 1.0; | |||
| } | |||
| static PuglView* puglNewViewWithTransientParent(PuglWorld* const world, PuglView* const transientParentView) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, nullptr); | |||
| if (world == nullptr) | |||
| return nullptr; | |||
| if (PuglView* const view = puglNewView(world)) | |||
| { | |||
| @@ -79,11 +86,16 @@ static PuglView* puglNewViewWithTransientParent(PuglWorld* const world, PuglView | |||
| static PuglView* puglNewViewWithParentWindow(PuglWorld* const world, const uintptr_t parentWindowHandle) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, nullptr); | |||
| if (world == nullptr) | |||
| return nullptr; | |||
| if (PuglView* const view = puglNewView(world)) | |||
| { | |||
| puglSetParentWindow(view, parentWindowHandle); | |||
| if (parentWindowHandle != 0) | |||
| puglSetPosition(view, 0, 0); | |||
| return view; | |||
| } | |||
| @@ -101,8 +113,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s) | |||
| isClosed(true), | |||
| isVisible(false), | |||
| isEmbed(false), | |||
| usesScheduledRepaints(false), | |||
| usesSizeRequest(false), | |||
| scaleFactor(getScaleFactorFromParent(view)), | |||
| scaleFactor(DGL_NAMESPACE::getScaleFactor(view)), | |||
| autoScaling(false), | |||
| autoScaleFactor(1.0), | |||
| minWidth(0), | |||
| @@ -113,9 +126,12 @@ Window::PrivateData::PrivateData(Application& a, Window* const s) | |||
| waitingForClipboardEvents(false), | |||
| clipboardTypeId(0), | |||
| filenameToRenderInto(nullptr), | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| fileBrowserHandle(nullptr), | |||
| #endif | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| webViewHandle(nullptr), | |||
| #endif | |||
| modal() | |||
| { | |||
| initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false); | |||
| @@ -130,6 +146,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c | |||
| isClosed(true), | |||
| isVisible(false), | |||
| isEmbed(false), | |||
| usesScheduledRepaints(false), | |||
| usesSizeRequest(false), | |||
| scaleFactor(ppData->scaleFactor), | |||
| autoScaling(false), | |||
| @@ -142,9 +159,12 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c | |||
| waitingForClipboardEvents(false), | |||
| clipboardTypeId(0), | |||
| filenameToRenderInto(nullptr), | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| fileBrowserHandle(nullptr), | |||
| #endif | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| webViewHandle(nullptr), | |||
| #endif | |||
| modal(ppData) | |||
| { | |||
| initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false); | |||
| @@ -161,8 +181,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
| isClosed(parentWindowHandle == 0), | |||
| isVisible(parentWindowHandle != 0), | |||
| isEmbed(parentWindowHandle != 0), | |||
| usesScheduledRepaints(false), | |||
| usesSizeRequest(false), | |||
| scaleFactor(scale != 0.0 ? scale : getScaleFactorFromParent(view)), | |||
| scaleFactor(scale != 0.0 ? scale : DGL_NAMESPACE::getScaleFactor(view)), | |||
| autoScaling(false), | |||
| autoScaleFactor(1.0), | |||
| minWidth(0), | |||
| @@ -173,9 +194,12 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
| waitingForClipboardEvents(false), | |||
| clipboardTypeId(0), | |||
| filenameToRenderInto(nullptr), | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| fileBrowserHandle(nullptr), | |||
| #endif | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| webViewHandle(nullptr), | |||
| #endif | |||
| modal() | |||
| { | |||
| initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, resizable); | |||
| @@ -184,7 +208,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
| Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
| const uintptr_t parentWindowHandle, | |||
| const uint width, const uint height, | |||
| const double scale, const bool resizable, const bool isVST3) | |||
| const double scale, const bool resizable, | |||
| const bool _usesScheduledRepaints, | |||
| const bool _usesSizeRequest) | |||
| : app(a), | |||
| appData(a.pData), | |||
| self(s), | |||
| @@ -193,8 +219,9 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
| isClosed(parentWindowHandle == 0), | |||
| isVisible(parentWindowHandle != 0 && view != nullptr), | |||
| isEmbed(parentWindowHandle != 0), | |||
| usesSizeRequest(isVST3), | |||
| scaleFactor(scale != 0.0 ? scale : getScaleFactorFromParent(view)), | |||
| usesScheduledRepaints(_usesScheduledRepaints), | |||
| usesSizeRequest(_usesSizeRequest), | |||
| scaleFactor(scale != 0.0 ? scale : DGL_NAMESPACE::getScaleFactor(view)), | |||
| autoScaling(false), | |||
| autoScaleFactor(1.0), | |||
| minWidth(0), | |||
| @@ -205,14 +232,14 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
| waitingForClipboardEvents(false), | |||
| clipboardTypeId(0), | |||
| filenameToRenderInto(nullptr), | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| fileBrowserHandle(nullptr), | |||
| #endif | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| webViewHandle(nullptr), | |||
| #endif | |||
| modal() | |||
| { | |||
| if (isEmbed) | |||
| puglSetParentWindow(view, parentWindowHandle); | |||
| initPre(width != 0 ? width : DEFAULT_WIDTH, height != 0 ? height : DEFAULT_HEIGHT, resizable); | |||
| } | |||
| @@ -227,10 +254,14 @@ Window::PrivateData::~PrivateData() | |||
| if (isEmbed) | |||
| { | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| if (fileBrowserHandle != nullptr) | |||
| fileBrowserClose(fileBrowserHandle); | |||
| #endif | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| if (webViewHandle != nullptr) | |||
| webViewDestroy(webViewHandle); | |||
| #endif | |||
| puglHide(view); | |||
| appData->oneWindowClosed(); | |||
| isClosed = true; | |||
| @@ -259,29 +290,18 @@ void Window::PrivateData::initPre(const uint width, const uint height, const boo | |||
| puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE); | |||
| puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_FALSE); | |||
| #if DGL_USE_RGBA | |||
| #if defined(DGL_USE_RGBA) && DGL_USE_RGBA | |||
| puglSetViewHint(view, PUGL_DEPTH_BITS, 24); | |||
| #else | |||
| #else | |||
| puglSetViewHint(view, PUGL_DEPTH_BITS, 16); | |||
| #endif | |||
| #endif | |||
| puglSetViewHint(view, PUGL_STENCIL_BITS, 8); | |||
| #if defined(DGL_USE_OPENGL3) || defined(DGL_USE_GLES3) | |||
| puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); | |||
| puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3); | |||
| #elif defined(DGL_USE_GLES2) | |||
| puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); | |||
| puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2); | |||
| #else | |||
| puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_TRUE); | |||
| puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2); | |||
| #endif | |||
| // PUGL_SAMPLES ?? | |||
| puglSetEventFunc(view, puglEventCallback); | |||
| // setting default size triggers system-level calls, do it last | |||
| puglSetSizeHint(view, PUGL_DEFAULT_SIZE, width, height); | |||
| puglSetSizeHint(view, PUGL_DEFAULT_SIZE, static_cast<PuglSpan>(width), static_cast<PuglSpan>(height)); | |||
| } | |||
| bool Window::PrivateData::initPost() | |||
| @@ -300,7 +320,7 @@ bool Window::PrivateData::initPost() | |||
| if (isEmbed) | |||
| { | |||
| appData->oneWindowShown(); | |||
| puglShow(view); | |||
| puglShow(view, PUGL_SHOW_PASSIVE); | |||
| } | |||
| return true; | |||
| @@ -355,7 +375,7 @@ void Window::PrivateData::show() | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| puglMacOSShowCentered(view); | |||
| #else | |||
| puglShow(view); | |||
| puglShow(view, PUGL_SHOW_RAISE); | |||
| #endif | |||
| } | |||
| else | |||
| @@ -363,7 +383,7 @@ void Window::PrivateData::show() | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| puglWin32RestoreWindow(view); | |||
| #else | |||
| puglShow(view); | |||
| puglShow(view, PUGL_SHOW_RAISE); | |||
| #endif | |||
| } | |||
| @@ -388,13 +408,21 @@ void Window::PrivateData::hide() | |||
| if (modal.enabled) | |||
| stopModal(); | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| if (fileBrowserHandle != nullptr) | |||
| { | |||
| fileBrowserClose(fileBrowserHandle); | |||
| fileBrowserHandle = nullptr; | |||
| } | |||
| #endif | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| if (webViewHandle != nullptr) | |||
| { | |||
| webViewDestroy(webViewHandle); | |||
| webViewHandle = nullptr; | |||
| } | |||
| #endif | |||
| puglHide(view); | |||
| @@ -429,7 +457,7 @@ void Window::PrivateData::setResizable(const bool resizable) | |||
| void Window::PrivateData::idleCallback() | |||
| { | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| if (fileBrowserHandle != nullptr && fileBrowserIdle(fileBrowserHandle)) | |||
| { | |||
| self->onFileSelected(fileBrowserGetPath(fileBrowserHandle)); | |||
| @@ -437,6 +465,11 @@ void Window::PrivateData::idleCallback() | |||
| fileBrowserHandle = nullptr; | |||
| } | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| if (webViewHandle != nullptr) | |||
| webViewIdle(webViewHandle); | |||
| #endif | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| @@ -444,7 +477,7 @@ void Window::PrivateData::idleCallback() | |||
| bool Window::PrivateData::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs) | |||
| { | |||
| if (ignoreIdleCallbacks) | |||
| if (ignoreIdleCallbacks || view == nullptr) | |||
| return false; | |||
| if (timerFrequencyInMs == 0) | |||
| @@ -458,7 +491,7 @@ bool Window::PrivateData::addIdleCallback(IdleCallback* const callback, const ui | |||
| bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback) | |||
| { | |||
| if (ignoreIdleCallbacks) | |||
| if (ignoreIdleCallbacks || view == nullptr) | |||
| return false; | |||
| if (std::find(appData->idleCallbacks.begin(), | |||
| @@ -471,9 +504,9 @@ bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback) | |||
| return puglStopTimer(view, (uintptr_t)callback) == PUGL_SUCCESS; | |||
| } | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| // ----------------------------------------------------------------------- | |||
| // file handling | |||
| // file browser dialog | |||
| bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options) | |||
| { | |||
| @@ -483,7 +516,9 @@ bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options) | |||
| FileBrowserOptions options2 = options; | |||
| if (options2.title == nullptr) | |||
| options2.title = puglGetWindowTitle(view); | |||
| options2.title = puglGetViewString(view, PUGL_WINDOW_TITLE); | |||
| options2.className = puglGetViewString(view, PUGL_CLASS_NAME); | |||
| fileBrowserHandle = fileBrowserCreate(isEmbed, | |||
| puglGetNativeView(view), | |||
| @@ -492,14 +527,40 @@ bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options) | |||
| return fileBrowserHandle != nullptr; | |||
| } | |||
| #endif // ! DGL_FILE_BROWSER_DISABLED | |||
| #endif // DGL_USE_FILE_BROWSER | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| // ----------------------------------------------------------------------- | |||
| // file browser dialog | |||
| bool Window::PrivateData::createWebView(const char* const url, const DGL_NAMESPACE::WebViewOptions& options) | |||
| { | |||
| if (webViewHandle != nullptr) | |||
| webViewDestroy(webViewHandle); | |||
| const PuglRect rect = puglGetFrame(view); | |||
| uint initialWidth = static_cast<uint>(rect.width) - options.offset.x; | |||
| uint initialHeight = static_cast<uint>(rect.height) - options.offset.y; | |||
| webViewOffset = Point<int>(options.offset.x, options.offset.y); | |||
| webViewHandle = webViewCreate(url, | |||
| puglGetNativeView(view), | |||
| initialWidth, | |||
| initialHeight, | |||
| autoScaling ? autoScaleFactor : scaleFactor, | |||
| options); | |||
| return webViewHandle != nullptr; | |||
| } | |||
| #endif // DGL_USE_WEB_VIEW | |||
| // ----------------------------------------------------------------------- | |||
| // modal handling | |||
| void Window::PrivateData::startModal() | |||
| { | |||
| DGL_DBG("Window modal loop starting..."); DGL_DBGF; | |||
| DGL_DBG("Window modal loop starting..."); | |||
| DISTRHO_SAFE_ASSERT_RETURN(modal.parent != nullptr, show()); | |||
| // activate modal mode for this window | |||
| @@ -521,7 +582,7 @@ void Window::PrivateData::startModal() | |||
| void Window::PrivateData::stopModal() | |||
| { | |||
| DGL_DBG("Window modal loop stopping..."); DGL_DBGF; | |||
| DGL_DBG("Window modal loop stopping..."); | |||
| // deactivate modal mode | |||
| modal.enabled = false; | |||
| @@ -573,11 +634,11 @@ void Window::PrivateData::runAsModal(const bool blockWait) | |||
| // ----------------------------------------------------------------------- | |||
| // pugl events | |||
| void Window::PrivateData::onPuglConfigure(const double width, const double height) | |||
| void Window::PrivateData::onPuglConfigure(const uint width, const uint height) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_INT2_RETURN(width > 1 && height > 1, width, height,); | |||
| DGL_DBGp("PUGL: onReshape : %f %f\n", width, height); | |||
| DGL_DBGp("PUGL: onReshape : %d %d\n", width, height); | |||
| if (autoScaling) | |||
| { | |||
| @@ -585,16 +646,28 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh | |||
| const double scaleVertical = height / static_cast<double>(minHeight); | |||
| autoScaleFactor = scaleHorizontal < scaleVertical ? scaleHorizontal : scaleVertical; | |||
| } | |||
| else | |||
| { | |||
| autoScaleFactor = 1.0; | |||
| } | |||
| const uint uwidth = static_cast<uint>(width + 0.5); | |||
| const uint uheight = static_cast<uint>(height + 0.5); | |||
| const uint uwidth = d_roundToUnsignedInt(width / autoScaleFactor); | |||
| const uint uheight = d_roundToUnsignedInt(height / autoScaleFactor); | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| if (webViewHandle != nullptr) | |||
| webViewResize(webViewHandle, | |||
| uwidth - webViewOffset.getX(), | |||
| uheight - webViewOffset.getY(), | |||
| autoScaling ? autoScaleFactor : scaleFactor); | |||
| #endif | |||
| self->onReshape(uwidth, uheight); | |||
| #ifndef DPF_TEST_WINDOW_CPP | |||
| FOR_EACH_TOP_LEVEL_WIDGET(it) | |||
| { | |||
| TopLevelWidget* const widget(*it); | |||
| TopLevelWidget* const widget = *it; | |||
| /* Some special care here, we call Widget::setSize instead of the TopLevelWidget one. | |||
| * This is because we want TopLevelWidget::setSize to handle both window and widget size, | |||
| @@ -614,7 +687,7 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh | |||
| void Window::PrivateData::onPuglExpose() | |||
| { | |||
| DGL_DBG("PUGL: onPuglExpose\n"); | |||
| // DGL_DBG("PUGL: onPuglExpose\n"); | |||
| puglOnDisplayPrepare(view); | |||
| @@ -850,7 +923,7 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu | |||
| { | |||
| Window::PrivateData* const pData = (Window::PrivateData*)puglGetHandle(view); | |||
| #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) | |||
| if (event->type != PUGL_TIMER) { | |||
| if (event->type != PUGL_TIMER && event->type != PUGL_EXPOSE && event->type != PUGL_MOTION) { | |||
| printEvent(event, "pugl event: ", true); | |||
| } | |||
| #endif | |||
| @@ -891,65 +964,68 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu | |||
| case PUGL_NOTHING: | |||
| break; | |||
| ///< View created, a #PuglEventCreate | |||
| case PUGL_CREATE: | |||
| #ifdef DGL_USING_X11 | |||
| if (! pData->isEmbed) | |||
| ///< View realized, a #PuglRealizeEvent | |||
| case PUGL_REALIZE: | |||
| if (! pData->isEmbed && ! puglGetTransientParent(view)) | |||
| { | |||
| #if defined(DISTRHO_OS_WINDOWS) && defined(DGL_WINDOWS_ICON_ID) | |||
| WNDCLASSEX wClass = {}; | |||
| const HINSTANCE hInstance = GetModuleHandle(nullptr); | |||
| if (GetClassInfoEx(hInstance, view->world->strings[PUGL_CLASS_NAME], &wClass)) | |||
| wClass.hIcon = LoadIcon(nullptr, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID)); | |||
| SetClassLongPtr(view->impl->hwnd, GCLP_HICON, (LONG_PTR) LoadIcon(hInstance, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID))); | |||
| #endif | |||
| #ifdef DGL_USING_X11 | |||
| puglX11SetWindowTypeAndPID(view, pData->appData->isStandalone); | |||
| #endif | |||
| #endif | |||
| } | |||
| break; | |||
| ///< View destroyed, a #PuglEventDestroy | |||
| case PUGL_DESTROY: | |||
| ///< View unrealizeed, a #PuglUnrealizeEvent | |||
| case PUGL_UNREALIZE: | |||
| break; | |||
| ///< View moved/resized, a #PuglEventConfigure | |||
| ///< View configured, a #PuglConfigureEvent | |||
| case PUGL_CONFIGURE: | |||
| // unused x, y (double) | |||
| pData->onPuglConfigure(event->configure.width, event->configure.height); | |||
| break; | |||
| ///< View made visible, a #PuglEventMap | |||
| case PUGL_MAP: | |||
| break; | |||
| ///< View made invisible, a #PuglEventUnmap | |||
| case PUGL_UNMAP: | |||
| break; | |||
| ///< View ready to draw, a #PuglEventUpdate | |||
| ///< View ready to draw, a #PuglUpdateEvent | |||
| case PUGL_UPDATE: | |||
| break; | |||
| ///< View must be drawn, a #PuglEventExpose | |||
| ///< View must be drawn, a #PuglExposeEvent | |||
| case PUGL_EXPOSE: | |||
| // unused x, y, width, height (double) | |||
| pData->onPuglExpose(); | |||
| break; | |||
| ///< View will be closed, a #PuglEventClose | |||
| ///< View will be closed, a #PuglCloseEvent | |||
| case PUGL_CLOSE: | |||
| pData->onPuglClose(); | |||
| break; | |||
| ///< Keyboard focus entered view, a #PuglEventFocus | |||
| ///< Keyboard focus entered view, a #PuglFocusEvent | |||
| case PUGL_FOCUS_IN: | |||
| ///< Keyboard focus left view, a #PuglEventFocus | |||
| ///< Keyboard focus left view, a #PuglFocusEvent | |||
| case PUGL_FOCUS_OUT: | |||
| pData->onPuglFocus(event->type == PUGL_FOCUS_IN, | |||
| static_cast<CrossingMode>(event->focus.mode)); | |||
| break; | |||
| ///< Key pressed, a #PuglEventKey | |||
| ///< Key pressed, a #PuglKeyEvent | |||
| case PUGL_KEY_PRESS: | |||
| ///< Key released, a #PuglEventKey | |||
| ///< Key released, a #PuglKeyEvent | |||
| case PUGL_KEY_RELEASE: | |||
| { | |||
| // unused x, y, xRoot, yRoot (double) | |||
| Widget::KeyboardEvent ev; | |||
| ev.mod = event->key.state; | |||
| ev.flags = event->key.flags; | |||
| ev.time = static_cast<uint>(event->key.time * 1000.0 + 0.5); | |||
| ev.time = d_roundToUnsignedInt(event->key.time * 1000.0); | |||
| ev.press = event->type == PUGL_KEY_PRESS; | |||
| ev.key = event->key.key; | |||
| ev.keycode = event->key.keycode; | |||
| @@ -965,14 +1041,14 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu | |||
| break; | |||
| } | |||
| ///< Character entered, a #PuglEventText | |||
| ///< Character entered, a #PuglTextEvent | |||
| case PUGL_TEXT: | |||
| { | |||
| // unused x, y, xRoot, yRoot (double) | |||
| Widget::CharacterInputEvent ev; | |||
| ev.mod = event->text.state; | |||
| ev.flags = event->text.flags; | |||
| ev.time = static_cast<uint>(event->text.time * 1000.0 + 0.5); | |||
| ev.time = d_roundToUnsignedInt(event->text.time * 1000.0); | |||
| ev.keycode = event->text.keycode; | |||
| ev.character = event->text.character; | |||
| std::strncpy(ev.string, event->text.string, sizeof(ev.string)); | |||
| @@ -980,69 +1056,94 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu | |||
| break; | |||
| } | |||
| ///< Pointer entered view, a #PuglEventCrossing | |||
| ///< Pointer entered view, a #PuglCrossingEvent | |||
| case PUGL_POINTER_IN: | |||
| break; | |||
| ///< Pointer left view, a #PuglEventCrossing | |||
| ///< Pointer left view, a #PuglCrossingEvent | |||
| case PUGL_POINTER_OUT: | |||
| break; | |||
| ///< Mouse button pressed, a #PuglEventButton | |||
| ///< Mouse button pressed, a #PuglButtonEvent | |||
| case PUGL_BUTTON_PRESS: | |||
| ///< Mouse button released, a #PuglEventButton | |||
| ///< Mouse button released, a #PuglButtonEvent | |||
| case PUGL_BUTTON_RELEASE: | |||
| { | |||
| Widget::MouseEvent ev; | |||
| ev.mod = event->button.state; | |||
| ev.flags = event->button.flags; | |||
| ev.time = static_cast<uint>(event->button.time * 1000.0 + 0.5); | |||
| ev.time = d_roundToUnsignedInt(event->button.time * 1000.0); | |||
| ev.button = event->button.button + 1; | |||
| ev.press = event->type == PUGL_BUTTON_PRESS; | |||
| ev.pos = Point<double>(event->button.x, event->button.y); | |||
| if (pData->autoScaling && 0) | |||
| { | |||
| const double scaleFactor = pData->autoScaleFactor; | |||
| ev.pos = Point<double>(event->button.x / scaleFactor, event->button.y / scaleFactor); | |||
| } | |||
| else | |||
| { | |||
| ev.pos = Point<double>(event->button.x, event->button.y); | |||
| } | |||
| ev.absolutePos = ev.pos; | |||
| pData->onPuglMouse(ev); | |||
| break; | |||
| } | |||
| ///< Pointer moved, a #PuglEventMotion | |||
| ///< Pointer moved, a #PuglMotionEvent | |||
| case PUGL_MOTION: | |||
| { | |||
| Widget::MotionEvent ev; | |||
| ev.mod = event->motion.state; | |||
| ev.flags = event->motion.flags; | |||
| ev.time = static_cast<uint>(event->motion.time * 1000.0 + 0.5); | |||
| ev.pos = Point<double>(event->motion.x, event->motion.y); | |||
| ev.time = d_roundToUnsignedInt(event->motion.time * 1000.0); | |||
| if (pData->autoScaling && 0) | |||
| { | |||
| const double scaleFactor = pData->autoScaleFactor; | |||
| ev.pos = Point<double>(event->motion.x / scaleFactor, event->motion.y / scaleFactor); | |||
| } | |||
| else | |||
| { | |||
| ev.pos = Point<double>(event->motion.x, event->motion.y); | |||
| } | |||
| ev.absolutePos = ev.pos; | |||
| pData->onPuglMotion(ev); | |||
| break; | |||
| } | |||
| ///< Scrolled, a #PuglEventScroll | |||
| ///< Scrolled, a #PuglScrollEvent | |||
| case PUGL_SCROLL: | |||
| { | |||
| Widget::ScrollEvent ev; | |||
| ev.mod = event->scroll.state; | |||
| ev.flags = event->scroll.flags; | |||
| ev.time = static_cast<uint>(event->scroll.time * 1000.0 + 0.5); | |||
| ev.pos = Point<double>(event->scroll.x, event->scroll.y); | |||
| ev.delta = Point<double>(event->scroll.dx, event->scroll.dy); | |||
| ev.time = d_roundToUnsignedInt(event->scroll.time * 1000.0); | |||
| if (pData->autoScaling && 0) | |||
| { | |||
| const double scaleFactor = pData->autoScaleFactor; | |||
| ev.pos = Point<double>(event->scroll.x / scaleFactor, event->scroll.y / scaleFactor); | |||
| ev.delta = Point<double>(event->scroll.dx / scaleFactor, event->scroll.dy / scaleFactor); | |||
| } | |||
| else | |||
| { | |||
| ev.pos = Point<double>(event->scroll.x, event->scroll.y); | |||
| ev.delta = Point<double>(event->scroll.dx, event->scroll.dy); | |||
| } | |||
| ev.direction = static_cast<ScrollDirection>(event->scroll.direction); | |||
| ev.absolutePos = ev.pos; | |||
| pData->onPuglScroll(ev); | |||
| break; | |||
| } | |||
| ///< Custom client message, a #PuglEventClient | |||
| ///< Custom client message, a #PuglClientEvent | |||
| case PUGL_CLIENT: | |||
| break; | |||
| ///< Timer triggered, a #PuglEventTimer | |||
| ///< Timer triggered, a #PuglTimerEvent | |||
| case PUGL_TIMER: | |||
| if (IdleCallback* const idleCallback = reinterpret_cast<IdleCallback*>(event->timer.id)) | |||
| idleCallback->idleCallback(); | |||
| break; | |||
| ///< Recursive loop entered, a #PuglEventLoopEnter | |||
| ///< Recursive loop left, a #PuglLoopLeaveEvent | |||
| case PUGL_LOOP_ENTER: | |||
| break; | |||
| @@ -1081,7 +1182,7 @@ static int printEvent(const PuglEvent* event, const char* prefix, const bool ver | |||
| { | |||
| #define FFMT "%6.1f" | |||
| #define PFMT FFMT " " FFMT | |||
| #define PRINT(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) | |||
| #define PRINT(fmt, ...) d_stdout(fmt, __VA_ARGS__), 1 | |||
| switch (event->type) { | |||
| case PUGL_NOTHING: | |||
| @@ -1150,25 +1251,21 @@ static int printEvent(const PuglEvent* event, const char* prefix, const bool ver | |||
| if (verbose) { | |||
| switch (event->type) { | |||
| case PUGL_CREATE: | |||
| return fprintf(stderr, "%sCreate\n", prefix); | |||
| case PUGL_DESTROY: | |||
| return fprintf(stderr, "%sDestroy\n", prefix); | |||
| case PUGL_MAP: | |||
| return fprintf(stderr, "%sMap\n", prefix); | |||
| case PUGL_UNMAP: | |||
| return fprintf(stderr, "%sUnmap\n", prefix); | |||
| case PUGL_UPDATE: | |||
| return 0; // fprintf(stderr, "%sUpdate\n", prefix); | |||
| case PUGL_REALIZE: | |||
| return PRINT("%sRealize\n", prefix); | |||
| case PUGL_UNREALIZE: | |||
| return PRINT("%sUnrealize\n", prefix); | |||
| case PUGL_CONFIGURE: | |||
| return PRINT("%sConfigure " PFMT " " PFMT "\n", | |||
| return PRINT("%sConfigure %d %d %d %d\n", | |||
| prefix, | |||
| event->configure.x, | |||
| event->configure.y, | |||
| event->configure.width, | |||
| event->configure.height); | |||
| case PUGL_UPDATE: | |||
| return 0; // fprintf(stderr, "%sUpdate\n", prefix); | |||
| case PUGL_EXPOSE: | |||
| return PRINT("%sExpose " PFMT " " PFMT "\n", | |||
| return PRINT("%sExpose %d %d %d %d\n", | |||
| prefix, | |||
| event->expose.x, | |||
| event->expose.y, | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -60,7 +60,10 @@ struct Window::PrivateData : IdleCallback { | |||
| /** Whether this Window is embed into another (usually not DGL-controlled) Window. */ | |||
| const bool isEmbed; | |||
| /** Whether to ignore resize requests and feed them into the host instead. used for VST3 */ | |||
| /** Whether to schedule repaints on the next idle call, used for AU */ | |||
| const bool usesScheduledRepaints; | |||
| /** Whether to ignore resize requests and feed them into the host instead, used for CLAP and VST3 */ | |||
| const bool usesSizeRequest; | |||
| /** Scale factor to report to widgets on request, purely informational. */ | |||
| @@ -87,10 +90,16 @@ struct Window::PrivateData : IdleCallback { | |||
| /** Render to a picture file when non-null, automatically free+unset after saving. */ | |||
| char* filenameToRenderInto; | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| /** Handle for file browser dialog operations. */ | |||
| DGL_NAMESPACE::FileBrowserHandle fileBrowserHandle; | |||
| #endif | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| /** Handle for web view operations. */ | |||
| DGL_NAMESPACE::WebViewHandle webViewHandle; | |||
| DGL_NAMESPACE::Point<int> webViewOffset; | |||
| #endif | |||
| /** Modal window setup. */ | |||
| struct Modal { | |||
| @@ -131,7 +140,8 @@ struct Window::PrivateData : IdleCallback { | |||
| /** Constructor for an embed Window, with a few extra hints from the host side. */ | |||
| explicit PrivateData(Application& app, Window* self, uintptr_t parentWindowHandle, | |||
| uint width, uint height, double scaling, bool resizable, bool isVST3); | |||
| uint width, uint height, double scaling, bool resizable, | |||
| bool usesScheduledRepaints, bool usesSizeRequest); | |||
| /** Destructor. */ | |||
| ~PrivateData() override; | |||
| @@ -164,10 +174,15 @@ struct Window::PrivateData : IdleCallback { | |||
| bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs); | |||
| bool removeIdleCallback(IdleCallback* callback); | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| // file handling | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| // file browser dialog | |||
| bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options); | |||
| #endif | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| // web view | |||
| bool createWebView(const char* url, const DGL_NAMESPACE::WebViewOptions& options); | |||
| #endif | |||
| static void renderToPicture(const char* filename, const GraphicsContext& context, uint width, uint height); | |||
| @@ -177,7 +192,7 @@ struct Window::PrivateData : IdleCallback { | |||
| void runAsModal(bool blockWait); | |||
| // pugl events | |||
| void onPuglConfigure(double width, double height); | |||
| void onPuglConfigure(uint width, uint height); | |||
| void onPuglExpose(); | |||
| void onPuglClose(); | |||
| void onPuglFocus(bool focus, CrossingMode mode); | |||
| @@ -23,10 +23,46 @@ | |||
| #include "nanovg.h" | |||
| #define FONTSTASH_IMPLEMENTATION | |||
| #define stbtt_fontinfo dpf_nvg_stbtt_fontinfo | |||
| #define stbrp_context dpf_nvg_stbrp_context | |||
| #define stbrp_rect dpf_nvg_stbrp_rect | |||
| #define stbrp_node dpf_nvg_stbrp_node | |||
| #define stbrp_coord dpf_nvg_stbrp_coord | |||
| #include "fontstash.h" | |||
| #ifndef NVG_NO_STB | |||
| #define STB_IMAGE_IMPLEMENTATION | |||
| #define stbi_convert_iphone_png_to_rgb dpf_stbi_convert_iphone_png_to_rgb | |||
| #define stbi_failure_reason dpf_stbi_failure_reason | |||
| #define stbi_hdr_to_ldr_gamma dpf_stbi_hdr_to_ldr_gamma | |||
| #define stbi_hdr_to_ldr_scale dpf_stbi_hdr_to_ldr_scale | |||
| #define stbi_image_free dpf_stbi_image_free | |||
| #define stbi_info dpf_stbi_info | |||
| #define stbi_info_from_callbacks dpf_stbi_info_from_callbacks | |||
| #define stbi_info_from_file dpf_stbi_info_from_file | |||
| #define stbi_info_from_memory dpf_stbi_info_from_memory | |||
| #define stbi_is_hdr dpf_stbi_is_hdr | |||
| #define stbi_is_hdr_from_callbacks dpf_stbi_is_hdr_from_callbacks | |||
| #define stbi_is_hdr_from_file dpf_stbi_is_hdr_from_file | |||
| #define stbi_is_hdr_from_memory dpf_stbi_is_hdr_from_memory | |||
| #define stbi_ldr_to_hdr_gamma dpf_stbi_ldr_to_hdr_gamma | |||
| #define stbi_ldr_to_hdr_scale dpf_stbi_ldr_to_hdr_scale | |||
| #define stbi_load dpf_stbi_load | |||
| #define stbi_load_from_callbacks dpf_stbi_load_from_callbacks | |||
| #define stbi_load_from_file dpf_stbi_load_from_file | |||
| #define stbi_load_from_memory dpf_stbi_load_from_memory | |||
| #define stbi_loadf dpf_stbi_loadf | |||
| #define stbi_loadf_from_callbacks dpf_stbi_loadf_from_callbacks | |||
| #define stbi_loadf_from_file dpf_stbi_loadf_from_file | |||
| #define stbi_loadf_from_memory dpf_stbi_loadf_from_memory | |||
| #define stbi_set_flip_vertically_on_load dpf_stbi_set_flip_vertically_on_load | |||
| #define stbi_set_unpremultiply_on_load dpf_stbi_set_unpremultiply_on_load | |||
| #define stbi_zlib_decode_buffer dpf_stbi_zlib_decode_buffer | |||
| #define stbi_zlib_decode_malloc dpf_stbi_zlib_decode_malloc | |||
| #define stbi_zlib_decode_malloc_guesssize dpf_stbi_zlib_decode_malloc_guesssize | |||
| #define stbi_zlib_decode_malloc_guesssize_headerflag dpf_stbi_zlib_decode_malloc_guesssize_headerflag | |||
| #define stbi_zlib_decode_noheader_buffer dpf_stbi_zlib_decode_noheader_buffer | |||
| #define stbi_zlib_decode_noheader_malloc dpf_stbi_zlib_decode_noheader_malloc | |||
| #include "stb_image.h" | |||
| #endif | |||
| @@ -869,7 +905,7 @@ int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags) | |||
| return image; | |||
| } | |||
| int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata) | |||
| int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, const unsigned char* data, int ndata) | |||
| { | |||
| int w, h, n, image; | |||
| unsigned char* img = stbi_load_from_memory(data, ndata, &w, &h, &n, 4); | |||
| @@ -2592,6 +2628,11 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* | |||
| nvgTransformPoint(&c[6],&c[7], state->xform, q.x0*invscale, q.y1*invscale); | |||
| // Create triangles | |||
| if (nverts+6 <= cverts) { | |||
| #if NVG_FONT_TEXTURE_FLAGS | |||
| // align font kerning to integer pixel positions | |||
| for (int i = 0; i < 8; ++i) | |||
| c[i] = (int)(c[i] + 0.5f); | |||
| #endif | |||
| nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++; | |||
| nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++; | |||
| nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); nverts++; | |||
| @@ -385,7 +385,7 @@ int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags); | |||
| // Creates image by loading it from the specified chunk of memory. | |||
| // Returns handle to the image. | |||
| int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata); | |||
| int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, const unsigned char* data, int ndata); | |||
| // Creates image from specified image data and texture format. | |||
| // Returns handle to the image. | |||
| @@ -18,6 +18,28 @@ | |||
| #ifndef NANOVG_GL_H | |||
| #define NANOVG_GL_H | |||
| #if defined NANOVG_GL2_FORCED | |||
| # undef NANOVG_GL3 | |||
| # undef NANOVG_GLES2 | |||
| # undef NANOVG_GLES3 | |||
| # define NANOVG_GL2 1 | |||
| #elif defined NANOVG_GL3_FORCED | |||
| # undef NANOVG_GL2 | |||
| # undef NANOVG_GLES2 | |||
| # undef NANOVG_GLES3 | |||
| # define NANOVG_GL3 1 | |||
| #elif defined NANOVG_GLES2_FORCED | |||
| # undef NANOVG_GL2 | |||
| # undef NANOVG_GL3 | |||
| # undef NANOVG_GLES3 | |||
| # define NANOVG_GLES2 1 | |||
| #elif defined NANOVG_GLES3_FORCED | |||
| # undef NANOVG_GL2 | |||
| # undef NANOVG_GL3 | |||
| # undef NANOVG_GLES2 | |||
| # define NANOVG_GLES3 1 | |||
| #endif | |||
| #ifdef __cplusplus | |||
| extern "C" { | |||
| #endif | |||
| @@ -1,4 +1,4 @@ | |||
| Copyright 2011-2021 David Robillard <d@drobilla.net> | |||
| Copyright 2011-2022 David Robillard <d@drobilla.net> | |||
| Permission to use, copy, modify, and/or distribute this software for any | |||
| purpose with or without fee is hereby granted, provided that the above | |||
| @@ -0,0 +1,59 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_ATTRIBUTES_H | |||
| #define PUGL_ATTRIBUTES_H | |||
| // Public declaration scope | |||
| #ifdef __cplusplus | |||
| # define PUGL_BEGIN_DECLS extern "C" { | |||
| # define PUGL_END_DECLS } | |||
| #else | |||
| # define PUGL_BEGIN_DECLS ///< Begin public API definitions | |||
| # define PUGL_END_DECLS ///< End public API definitions | |||
| #endif | |||
| // Symbol exposed in the public API | |||
| #ifndef PUGL_API | |||
| # if defined(_WIN32) && !defined(PUGL_STATIC) && defined(PUGL_INTERNAL) | |||
| # define PUGL_API __declspec(dllexport) | |||
| # elif defined(_WIN32) && !defined(PUGL_STATIC) | |||
| # define PUGL_API __declspec(dllimport) | |||
| # elif defined(__GNUC__) | |||
| # define PUGL_API __attribute__((visibility("default"))) | |||
| # else | |||
| # define PUGL_API | |||
| # endif | |||
| #endif | |||
| // Deprecated API | |||
| #ifndef PUGL_DISABLE_DEPRECATED | |||
| # if defined(__clang__) | |||
| # define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("", rep))) | |||
| # elif defined(__GNUC__) | |||
| # define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("Use " rep))) | |||
| # else | |||
| # define PUGL_DEPRECATED_BY(rep) | |||
| # endif | |||
| #endif | |||
| // GCC function attributes | |||
| #if defined(__GNUC__) | |||
| # define PUGL_CONST_FUNC __attribute__((const)) | |||
| # define PUGL_MALLOC_FUNC __attribute__((malloc)) | |||
| #else | |||
| # define PUGL_CONST_FUNC ///< Only reads its parameters | |||
| # define PUGL_MALLOC_FUNC ///< Allocates memory | |||
| #endif | |||
| /// A const function in the public API that only reads parameters | |||
| #define PUGL_CONST_API \ | |||
| PUGL_API \ | |||
| PUGL_CONST_FUNC | |||
| /// A malloc function in the public API that returns allocated memory | |||
| #define PUGL_MALLOC_API \ | |||
| PUGL_API \ | |||
| PUGL_MALLOC_FUNC | |||
| #endif // PUGL_ATTRIBUTES_H | |||
| @@ -1,9 +1,10 @@ | |||
| // Copyright 2012-2020 David Robillard <d@drobilla.net> | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_GL_H | |||
| #define PUGL_GL_H | |||
| #include "pugl/attributes.h" | |||
| #include "pugl/pugl.h" | |||
| // IWYU pragma: begin_exports | |||
| @@ -22,23 +23,12 @@ | |||
| # endif | |||
| #endif | |||
| #ifndef PUGL_NO_INCLUDE_GLU_H | |||
| # ifdef __APPLE__ | |||
| # include <OpenGL/glu.h> | |||
| # else | |||
| # ifdef _WIN32 | |||
| # include <windows.h> | |||
| # endif | |||
| # include <GL/glu.h> | |||
| # endif | |||
| #endif | |||
| // IWYU pragma: end_exports | |||
| PUGL_BEGIN_DECLS | |||
| /** | |||
| @defgroup gl OpenGL | |||
| @defgroup pugl_gl OpenGL | |||
| OpenGL graphics support. | |||
| @ingroup pugl | |||
| @{ | |||
| @@ -0,0 +1,22 @@ | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_GLU_H | |||
| #define PUGL_GLU_H | |||
| // IWYU pragma: begin_exports | |||
| #ifndef PUGL_NO_INCLUDE_GLU_H | |||
| # ifdef __APPLE__ | |||
| # include <OpenGL/glu.h> | |||
| # else | |||
| # ifdef _WIN32 | |||
| # include <windows.h> | |||
| # endif | |||
| # include <GL/glu.h> | |||
| # endif | |||
| #endif | |||
| // IWYU pragma: end_exports | |||
| #endif // PUGL_GLU_H | |||
| @@ -1,15 +1,16 @@ | |||
| // Copyright 2019-2020 David Robillard <d@drobilla.net> | |||
| // Copyright 2019-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_STUB_H | |||
| #define PUGL_STUB_H | |||
| #include "pugl/attributes.h" | |||
| #include "pugl/pugl.h" | |||
| PUGL_BEGIN_DECLS | |||
| /** | |||
| @defgroup stub Stub | |||
| @defgroup pugl_stub Stub | |||
| Native graphics support. | |||
| @ingroup pugl | |||
| @{ | |||
| @@ -0,0 +1,59 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_ATTRIBUTES_H | |||
| #define PUGL_ATTRIBUTES_H | |||
| // Public declaration scope | |||
| #ifdef __cplusplus | |||
| # define PUGL_BEGIN_DECLS extern "C" { | |||
| # define PUGL_END_DECLS } | |||
| #else | |||
| # define PUGL_BEGIN_DECLS ///< Begin public API definitions | |||
| # define PUGL_END_DECLS ///< End public API definitions | |||
| #endif | |||
| // Symbol exposed in the public API | |||
| #ifndef PUGL_API | |||
| # if defined(_WIN32) && !defined(PUGL_STATIC) && defined(PUGL_INTERNAL) | |||
| # define PUGL_API __declspec(dllexport) | |||
| # elif defined(_WIN32) && !defined(PUGL_STATIC) | |||
| # define PUGL_API __declspec(dllimport) | |||
| # elif defined(__GNUC__) | |||
| # define PUGL_API __attribute__((visibility("default"))) | |||
| # else | |||
| # define PUGL_API | |||
| # endif | |||
| #endif | |||
| // Deprecated API | |||
| #ifndef PUGL_DISABLE_DEPRECATED | |||
| # if defined(__clang__) | |||
| # define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("", rep))) | |||
| # elif defined(__GNUC__) | |||
| # define PUGL_DEPRECATED_BY(rep) __attribute__((deprecated("Use " rep))) | |||
| # else | |||
| # define PUGL_DEPRECATED_BY(rep) | |||
| # endif | |||
| #endif | |||
| // GCC function attributes | |||
| #if defined(__GNUC__) | |||
| # define PUGL_CONST_FUNC __attribute__((const)) | |||
| # define PUGL_MALLOC_FUNC __attribute__((malloc)) | |||
| #else | |||
| # define PUGL_CONST_FUNC ///< Only reads its parameters | |||
| # define PUGL_MALLOC_FUNC ///< Allocates memory | |||
| #endif | |||
| /// A const function in the public API that only reads parameters | |||
| #define PUGL_CONST_API \ | |||
| PUGL_API \ | |||
| PUGL_CONST_FUNC | |||
| /// A malloc function in the public API that returns allocated memory | |||
| #define PUGL_MALLOC_API \ | |||
| PUGL_API \ | |||
| PUGL_MALLOC_FUNC | |||
| #endif // PUGL_ATTRIBUTES_H | |||
| @@ -0,0 +1,34 @@ | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_CAIRO_H | |||
| #define PUGL_CAIRO_H | |||
| #include "pugl/attributes.h" | |||
| #include "pugl/pugl.h" | |||
| PUGL_BEGIN_DECLS | |||
| /** | |||
| @defgroup pugl_cairo Cairo | |||
| Cairo graphics support. | |||
| @ingroup pugl | |||
| @{ | |||
| */ | |||
| /** | |||
| Cairo graphics backend accessor. | |||
| Pass the returned value to puglSetBackend() to draw to a view with Cairo. | |||
| */ | |||
| PUGL_CONST_API | |||
| const PuglBackend* | |||
| puglCairoBackend(void); | |||
| /** | |||
| @} | |||
| */ | |||
| PUGL_END_DECLS | |||
| #endif // PUGL_CAIRO_H | |||
| @@ -0,0 +1,84 @@ | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_GL_H | |||
| #define PUGL_GL_H | |||
| #include "pugl/attributes.h" | |||
| #include "pugl/pugl.h" | |||
| // IWYU pragma: begin_exports | |||
| /* Unfortunately, GL includes vary across platforms, so include them here to | |||
| enable pure portable programs. */ | |||
| #ifndef PUGL_NO_INCLUDE_GL_H | |||
| # ifdef __APPLE__ | |||
| # include <OpenGL/gl.h> | |||
| # else | |||
| # ifdef _WIN32 | |||
| # include <windows.h> | |||
| # endif | |||
| # include <GL/gl.h> | |||
| # endif | |||
| #endif | |||
| // IWYU pragma: end_exports | |||
| PUGL_BEGIN_DECLS | |||
| /** | |||
| @defgroup pugl_gl OpenGL | |||
| OpenGL graphics support. | |||
| @ingroup pugl | |||
| @{ | |||
| */ | |||
| /** | |||
| OpenGL extension function. | |||
| */ | |||
| typedef void (*PuglGlFunc)(void); | |||
| /** | |||
| Return the address of an OpenGL extension function. | |||
| */ | |||
| PUGL_API | |||
| PuglGlFunc | |||
| puglGetProcAddress(const char* name); | |||
| /** | |||
| Enter the OpenGL context. | |||
| This can be used to enter the graphics context in unusual situations, for | |||
| doing things like loading textures. Note that this must not be used for | |||
| drawing, which may only be done while processing an expose event. | |||
| */ | |||
| PUGL_API | |||
| PuglStatus | |||
| puglEnterContext(PuglView* view); | |||
| /** | |||
| Leave the OpenGL context. | |||
| This must only be called after puglEnterContext(). | |||
| */ | |||
| PUGL_API | |||
| PuglStatus | |||
| puglLeaveContext(PuglView* view); | |||
| /** | |||
| OpenGL graphics backend. | |||
| Pass the returned value to puglSetBackend() to draw to a view with OpenGL. | |||
| */ | |||
| PUGL_CONST_API | |||
| const PuglBackend* | |||
| puglGlBackend(void); | |||
| PUGL_END_DECLS | |||
| /** | |||
| @} | |||
| */ | |||
| #endif // PUGL_GL_H | |||
| @@ -0,0 +1,22 @@ | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_GLU_H | |||
| #define PUGL_GLU_H | |||
| // IWYU pragma: begin_exports | |||
| #ifndef PUGL_NO_INCLUDE_GLU_H | |||
| # ifdef __APPLE__ | |||
| # include <OpenGL/glu.h> | |||
| # else | |||
| # ifdef _WIN32 | |||
| # include <windows.h> | |||
| # endif | |||
| # include <GL/glu.h> | |||
| # endif | |||
| #endif | |||
| // IWYU pragma: end_exports | |||
| #endif // PUGL_GLU_H | |||
| @@ -0,0 +1,35 @@ | |||
| // Copyright 2019-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_STUB_H | |||
| #define PUGL_STUB_H | |||
| #include "pugl/attributes.h" | |||
| #include "pugl/pugl.h" | |||
| PUGL_BEGIN_DECLS | |||
| /** | |||
| @defgroup pugl_stub Stub | |||
| Native graphics support. | |||
| @ingroup pugl | |||
| @{ | |||
| */ | |||
| /** | |||
| Stub graphics backend accessor. | |||
| This backend just creates a simple native window without setting up any | |||
| portable graphics API. | |||
| */ | |||
| PUGL_CONST_API | |||
| const PuglBackend* | |||
| puglStubBackend(void); | |||
| /** | |||
| @} | |||
| */ | |||
| PUGL_END_DECLS | |||
| #endif // PUGL_STUB_H | |||
| @@ -0,0 +1,153 @@ | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| /* | |||
| Note that this header includes Vulkan headers, so if you are writing a | |||
| program or plugin that dynamically loads vulkan, you should first define | |||
| `VK_NO_PROTOTYPES` before including it. | |||
| */ | |||
| #ifndef PUGL_VULKAN_H | |||
| #define PUGL_VULKAN_H | |||
| #include "pugl/attributes.h" | |||
| #include "pugl/pugl.h" | |||
| #include <vulkan/vulkan_core.h> | |||
| #include <stdint.h> | |||
| PUGL_BEGIN_DECLS | |||
| /** | |||
| @defgroup pugl_vulkan Vulkan | |||
| Vulkan graphics support. | |||
| Vulkan support differs from OpenGL because almost all most configuration is | |||
| done using the Vulkan API itself, rather than by setting view hints to | |||
| configure the context. Pugl only provides a minimal loader for loading the | |||
| Vulkan library, and a portable function to create a Vulkan surface for a | |||
| view, which hides the platform-specific implementation details. | |||
| @ingroup pugl | |||
| @{ | |||
| */ | |||
| /** | |||
| Dynamic Vulkan loader. | |||
| This can be used to dynamically load the Vulkan library. Applications or | |||
| plugins should not link against the Vulkan library, but instead use this at | |||
| runtime. This ensures that things will work on as many systems as possible, | |||
| and allows errors to be handled gracefully. | |||
| This is not a "loader" in the sense of loading all the required Vulkan | |||
| functions (which is the application's responsibility), but just a minimal | |||
| implementation to portably load the Vulkan library and get the two functions | |||
| that are used to load everything else. | |||
| Note that this owns the loaded Vulkan library, so it must outlive all use of | |||
| the Vulkan API. | |||
| @see https://www.khronos.org/registry/vulkan/specs/1.0/html/chap4.html | |||
| */ | |||
| typedef struct PuglVulkanLoaderImpl PuglVulkanLoader; | |||
| /** | |||
| Create a new dynamic loader for Vulkan functions. | |||
| This dynamically loads the Vulkan library and gets the load functions from | |||
| it. | |||
| @param world The world the returned loader is a part of. | |||
| @param libraryName The name of the Vulkan library to load, or null. | |||
| Typically, this is left unset, which will load the standard Vulkan library | |||
| for the current platform. It can be set to an alternative name, or an | |||
| absolute path, to support special packaging scenarios or unusual system | |||
| configurations. This name is passed directly to the underlying platform | |||
| library loading function (`dlopen` or `LoadLibrary`). | |||
| @return A new Vulkan loader, or null on failure. | |||
| */ | |||
| PUGL_API | |||
| PuglVulkanLoader* | |||
| puglNewVulkanLoader(PuglWorld* world, const char* libraryName); | |||
| /** | |||
| Free a loader created with puglNewVulkanLoader(). | |||
| Note that this closes the Vulkan library, so no Vulkan objects or API may be | |||
| used after this is called. | |||
| */ | |||
| PUGL_API | |||
| void | |||
| puglFreeVulkanLoader(PuglVulkanLoader* loader); | |||
| /** | |||
| Return the `vkGetInstanceProcAddr` function. | |||
| @return Null if the Vulkan library does not contain this function (which is | |||
| unlikely and indicates a broken system). | |||
| */ | |||
| PUGL_API | |||
| PFN_vkGetInstanceProcAddr | |||
| puglGetInstanceProcAddrFunc(const PuglVulkanLoader* loader); | |||
| /** | |||
| Return the `vkGetDeviceProcAddr` function. | |||
| @return Null if the Vulkan library does not contain this function (which is | |||
| unlikely and indicates a broken system). | |||
| */ | |||
| PUGL_API | |||
| PFN_vkGetDeviceProcAddr | |||
| puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader); | |||
| /** | |||
| Return the Vulkan instance extensions required to draw to a PuglView. | |||
| This simply returns static strings, it does not access Vulkan or the window | |||
| system. The returned array always contains at least "VK_KHR_surface". | |||
| @param[out] count The number of extensions in the returned array. | |||
| @return An array of extension name strings. | |||
| */ | |||
| PUGL_API | |||
| const char* const* | |||
| puglGetInstanceExtensions(uint32_t* count); | |||
| /** | |||
| Create a Vulkan surface for a Pugl view. | |||
| @param vkGetInstanceProcAddr Accessor for Vulkan functions. | |||
| @param view The view the surface is to be displayed on. | |||
| @param instance The Vulkan instance. | |||
| @param allocator Vulkan allocation callbacks, may be NULL. | |||
| @param[out] surface Pointed to a newly created Vulkan surface. | |||
| @return `VK_SUCCESS` on success, or a Vulkan error code. | |||
| */ | |||
| PUGL_API | |||
| VkResult | |||
| puglCreateSurface(PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr, | |||
| PuglView* view, | |||
| VkInstance instance, | |||
| const VkAllocationCallbacks* allocator, | |||
| VkSurfaceKHR* surface); | |||
| /** | |||
| Vulkan graphics backend. | |||
| Pass the returned value to puglSetBackend() to draw to a view with Vulkan. | |||
| */ | |||
| PUGL_CONST_API | |||
| const PuglBackend* | |||
| puglVulkanBackend(void); | |||
| /** | |||
| @} | |||
| */ | |||
| PUGL_END_DECLS | |||
| #endif // PUGL_VULKAN_H | |||
| @@ -1,4 +1,4 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| // Common implementations of public API functions in the core library | |||
| @@ -10,7 +10,9 @@ | |||
| #include "pugl/pugl.h" | |||
| #include <limits.h> | |||
| #include <stdbool.h> | |||
| #include <stdint.h> | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| @@ -48,12 +50,9 @@ puglNewWorld(PuglWorldType type, PuglWorldFlags flags) | |||
| } | |||
| world->startTime = puglGetTime(world); | |||
| world->type = type; | |||
| #ifdef __EMSCRIPTEN__ | |||
| puglSetString(&world->className, "canvas"); | |||
| #else | |||
| puglSetString(&world->className, "Pugl"); | |||
| #endif | |||
| puglSetString(&world->strings[PUGL_CLASS_NAME], "Pugl"); | |||
| return world; | |||
| } | |||
| @@ -62,7 +61,11 @@ void | |||
| puglFreeWorld(PuglWorld* const world) | |||
| { | |||
| puglFreeWorldInternals(world); | |||
| free(world->className); | |||
| for (size_t i = 0; i < PUGL_NUM_STRING_HINTS; ++i) { | |||
| free(world->strings[i]); | |||
| } | |||
| free(world->views); | |||
| free(world); | |||
| } | |||
| @@ -80,36 +83,50 @@ puglGetWorldHandle(PuglWorld* world) | |||
| } | |||
| PuglStatus | |||
| puglSetClassName(PuglWorld* const world, const char* const name) | |||
| puglSetWorldString(PuglWorld* const world, | |||
| const PuglStringHint key, | |||
| const char* const value) | |||
| { | |||
| puglSetString(&world->className, name); | |||
| if ((unsigned)key >= PUGL_NUM_STRING_HINTS) { | |||
| return PUGL_BAD_PARAMETER; | |||
| } | |||
| puglSetString(&world->strings[key], value); | |||
| return PUGL_SUCCESS; | |||
| } | |||
| const char* | |||
| puglGetClassName(const PuglWorld* world) | |||
| puglGetWorldString(const PuglWorld* const world, const PuglStringHint key) | |||
| { | |||
| return world->className; | |||
| if ((unsigned)key >= PUGL_NUM_STRING_HINTS) { | |||
| return NULL; | |||
| } | |||
| return world->strings[key]; | |||
| } | |||
| static void | |||
| puglSetDefaultHints(PuglHints hints) | |||
| { | |||
| hints[PUGL_USE_COMPAT_PROFILE] = PUGL_TRUE; | |||
| hints[PUGL_CONTEXT_API] = PUGL_OPENGL_API; | |||
| hints[PUGL_CONTEXT_VERSION_MAJOR] = 2; | |||
| hints[PUGL_CONTEXT_VERSION_MINOR] = 0; | |||
| hints[PUGL_CONTEXT_PROFILE] = PUGL_OPENGL_CORE_PROFILE; | |||
| hints[PUGL_CONTEXT_DEBUG] = PUGL_FALSE; | |||
| hints[PUGL_RED_BITS] = 8; | |||
| hints[PUGL_GREEN_BITS] = 8; | |||
| hints[PUGL_BLUE_BITS] = 8; | |||
| hints[PUGL_ALPHA_BITS] = 8; | |||
| hints[PUGL_DEPTH_BITS] = 0; | |||
| hints[PUGL_STENCIL_BITS] = 0; | |||
| hints[PUGL_SAMPLE_BUFFERS] = PUGL_DONT_CARE; | |||
| hints[PUGL_SAMPLES] = 0; | |||
| hints[PUGL_DOUBLE_BUFFER] = PUGL_TRUE; | |||
| hints[PUGL_SWAP_INTERVAL] = PUGL_DONT_CARE; | |||
| hints[PUGL_RESIZABLE] = PUGL_FALSE; | |||
| hints[PUGL_IGNORE_KEY_REPEAT] = PUGL_FALSE; | |||
| hints[PUGL_REFRESH_RATE] = PUGL_DONT_CARE; | |||
| hints[PUGL_VIEW_TYPE] = PUGL_DONT_CARE; | |||
| } | |||
| PuglView* | |||
| @@ -124,15 +141,25 @@ puglNewView(PuglWorld* const world) | |||
| view->world = world; | |||
| view->sizeHints[PUGL_MIN_SIZE].width = 1; | |||
| view->sizeHints[PUGL_MIN_SIZE].height = 1; | |||
| view->defaultX = INT_MIN; | |||
| view->defaultY = INT_MIN; | |||
| puglSetDefaultHints(view->hints); | |||
| // Add to world view list | |||
| ++world->numViews; | |||
| world->views = | |||
| (PuglView**)realloc(world->views, world->numViews * sizeof(PuglView*)); | |||
| // Enlarge world view list | |||
| const size_t newNumViews = world->numViews + 1U; | |||
| PuglView** const views = | |||
| (PuglView**)realloc(world->views, newNumViews * sizeof(PuglView*)); | |||
| if (!views) { | |||
| free(view); | |||
| return NULL; | |||
| } | |||
| world->views[world->numViews - 1] = view; | |||
| // Add to world view list | |||
| world->views = views; | |||
| world->views[world->numViews] = view; | |||
| world->numViews = newNumViews; | |||
| return view; | |||
| } | |||
| @@ -140,10 +167,6 @@ puglNewView(PuglWorld* const world) | |||
| void | |||
| puglFreeView(PuglView* view) | |||
| { | |||
| if (view->eventFunc && view->backend) { | |||
| puglDispatchSimpleEvent(view, PUGL_DESTROY); | |||
| } | |||
| // Remove from world view list | |||
| PuglWorld* world = view->world; | |||
| for (size_t i = 0; i < world->numViews; ++i) { | |||
| @@ -160,7 +183,10 @@ puglFreeView(PuglView* view) | |||
| } | |||
| } | |||
| free(view->title); | |||
| for (size_t i = 0; i < PUGL_NUM_STRING_HINTS; ++i) { | |||
| free(view->strings[i]); | |||
| } | |||
| puglFreeViewInternals(view); | |||
| free(view); | |||
| } | |||
| @@ -208,10 +234,11 @@ puglSetViewHint(PuglView* view, PuglViewHint hint, int value) | |||
| { | |||
| if (value == PUGL_DONT_CARE) { | |||
| switch (hint) { | |||
| case PUGL_USE_COMPAT_PROFILE: | |||
| case PUGL_USE_DEBUG_CONTEXT: | |||
| case PUGL_CONTEXT_API: | |||
| case PUGL_CONTEXT_VERSION_MAJOR: | |||
| case PUGL_CONTEXT_VERSION_MINOR: | |||
| case PUGL_CONTEXT_PROFILE: | |||
| case PUGL_CONTEXT_DEBUG: | |||
| case PUGL_SWAP_INTERVAL: | |||
| return PUGL_BAD_PARAMETER; | |||
| default: | |||
| @@ -219,26 +246,73 @@ puglSetViewHint(PuglView* view, PuglViewHint hint, int value) | |||
| } | |||
| } | |||
| view->hints[hint] = value; | |||
| return PUGL_SUCCESS; | |||
| if ((unsigned)hint < PUGL_NUM_VIEW_HINTS) { | |||
| view->hints[hint] = value; | |||
| return PUGL_SUCCESS; | |||
| } | |||
| return PUGL_BAD_PARAMETER; | |||
| } | |||
| int | |||
| puglGetViewHint(const PuglView* view, PuglViewHint hint) | |||
| { | |||
| return view->hints[hint]; | |||
| if ((unsigned)hint < PUGL_NUM_VIEW_HINTS) { | |||
| return view->hints[hint]; | |||
| } | |||
| return PUGL_DONT_CARE; | |||
| } | |||
| PuglRect | |||
| puglGetFrame(const PuglView* view) | |||
| PuglStatus | |||
| puglSetViewString(PuglView* const view, | |||
| const PuglStringHint key, | |||
| const char* const value) | |||
| { | |||
| return view->frame; | |||
| if ((unsigned)key >= PUGL_NUM_STRING_HINTS) { | |||
| return PUGL_BAD_PARAMETER; | |||
| } | |||
| puglSetString(&view->strings[key], value); | |||
| return puglViewStringChanged(view, key, view->strings[key]); | |||
| } | |||
| const char* | |||
| puglGetWindowTitle(const PuglView* const view) | |||
| puglGetViewString(const PuglView* const view, const PuglStringHint key) | |||
| { | |||
| return view->title; | |||
| if ((unsigned)key >= PUGL_NUM_STRING_HINTS) { | |||
| return NULL; | |||
| } | |||
| return view->strings[key]; | |||
| } | |||
| PuglRect | |||
| puglGetFrame(const PuglView* view) | |||
| { | |||
| if (view->lastConfigure.type == PUGL_CONFIGURE) { | |||
| // Return the last configured frame | |||
| const PuglRect frame = {view->lastConfigure.x, | |||
| view->lastConfigure.y, | |||
| view->lastConfigure.width, | |||
| view->lastConfigure.height}; | |||
| return frame; | |||
| } | |||
| // Get the default position if set, or fallback to (0, 0) | |||
| int x = view->defaultX; | |||
| int y = view->defaultY; | |||
| if (x < INT16_MIN || x > INT16_MAX || y < INT16_MIN || y > INT16_MAX) { | |||
| x = 0; | |||
| y = 0; | |||
| } | |||
| // Return the default frame, sanitized if necessary | |||
| const PuglRect frame = {(PuglCoord)x, | |||
| (PuglCoord)y, | |||
| view->sizeHints[PUGL_DEFAULT_SIZE].width, | |||
| view->sizeHints[PUGL_DEFAULT_SIZE].height}; | |||
| return frame; | |||
| } | |||
| PuglStatus | |||
| @@ -263,11 +337,18 @@ puglGetTransientParent(const PuglView* const view) | |||
| bool | |||
| puglGetVisible(const PuglView* view) | |||
| { | |||
| return view->visible; | |||
| return (view->lastConfigure.style & PUGL_VIEW_STYLE_MAPPED) && | |||
| !(view->lastConfigure.style & PUGL_VIEW_STYLE_HIDDEN); | |||
| } | |||
| void* | |||
| puglGetContext(PuglView* view) | |||
| puglGetContext(PuglView* const view) | |||
| { | |||
| return view->backend->getContext(view); | |||
| } | |||
| PuglViewStyleFlags | |||
| puglGetViewStyle(const PuglView* const view) | |||
| { | |||
| return view->lastConfigure.style; | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #include "internal.h" | |||
| @@ -12,6 +12,20 @@ | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| bool | |||
| puglIsValidSize(const PuglViewSize size) | |||
| { | |||
| return size.width && size.height; | |||
| } | |||
| void | |||
| puglEnsureHint(PuglView* const view, const PuglViewHint hint, const int value) | |||
| { | |||
| if (view->hints[hint] == PUGL_DONT_CARE) { | |||
| view->hints[hint] = value; | |||
| } | |||
| } | |||
| PuglStatus | |||
| puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len) | |||
| { | |||
| @@ -39,11 +53,18 @@ puglSetBlob(PuglBlob* const dest, const void* const data, const size_t len) | |||
| void | |||
| puglSetString(char** dest, const char* string) | |||
| { | |||
| if (*dest != string) { | |||
| const size_t len = strlen(string); | |||
| if (*dest == string) { | |||
| return; | |||
| } | |||
| const size_t len = string ? strlen(string) : 0U; | |||
| *dest = (char*)realloc(*dest, len + 1); | |||
| strncpy(*dest, string, len + 1); | |||
| if (!len) { | |||
| free(*dest); | |||
| *dest = NULL; | |||
| } else { | |||
| *dest = (char*)realloc(*dest, len + 1U); | |||
| strncpy(*dest, string, len + 1U); | |||
| } | |||
| } | |||
| @@ -67,40 +88,90 @@ puglDecodeUTF8(const uint8_t* buf) | |||
| } | |||
| if (buf[0] < 0xE0) { | |||
| FAIL_IF((buf[1] & 0xC0u) != 0x80); | |||
| return ((uint32_t)buf[0] << 6u) + buf[1] - 0x3080u; | |||
| FAIL_IF((buf[1] & 0xC0U) != 0x80); | |||
| return ((uint32_t)buf[0] << 6U) + buf[1] - 0x3080U; | |||
| } | |||
| if (buf[0] < 0xF0) { | |||
| FAIL_IF((buf[1] & 0xC0u) != 0x80); | |||
| FAIL_IF((buf[1] & 0xC0U) != 0x80); | |||
| FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0); | |||
| FAIL_IF((buf[2] & 0xC0u) != 0x80); | |||
| return ((uint32_t)buf[0] << 12u) + // | |||
| ((uint32_t)buf[1] << 6u) + // | |||
| ((uint32_t)buf[2] - 0xE2080u); | |||
| FAIL_IF((buf[2] & 0xC0U) != 0x80); | |||
| return ((uint32_t)buf[0] << 12U) + // | |||
| ((uint32_t)buf[1] << 6U) + // | |||
| ((uint32_t)buf[2] - 0xE2080U); | |||
| } | |||
| if (buf[0] < 0xF5) { | |||
| FAIL_IF((buf[1] & 0xC0u) != 0x80); | |||
| FAIL_IF((buf[1] & 0xC0U) != 0x80); | |||
| FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90); | |||
| FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90); | |||
| FAIL_IF((buf[2] & 0xC0u) != 0x80u); | |||
| FAIL_IF((buf[3] & 0xC0u) != 0x80u); | |||
| return (((uint32_t)buf[0] << 18u) + // | |||
| ((uint32_t)buf[1] << 12u) + // | |||
| ((uint32_t)buf[2] << 6u) + // | |||
| ((uint32_t)buf[3] - 0x3C82080u)); | |||
| FAIL_IF((buf[2] & 0xC0U) != 0x80U); | |||
| FAIL_IF((buf[3] & 0xC0U) != 0x80U); | |||
| return (((uint32_t)buf[0] << 18U) + // | |||
| ((uint32_t)buf[1] << 12U) + // | |||
| ((uint32_t)buf[2] << 6U) + // | |||
| ((uint32_t)buf[3] - 0x3C82080U)); | |||
| } | |||
| return 0xFFFD; | |||
| } | |||
| PuglMods | |||
| puglFilterMods(const PuglMods state, const PuglKey key) | |||
| { | |||
| switch (key) { | |||
| case PUGL_KEY_SHIFT_L: | |||
| case PUGL_KEY_SHIFT_R: | |||
| return state & ~(PuglMods)PUGL_MOD_SHIFT; | |||
| case PUGL_KEY_CTRL_L: | |||
| case PUGL_KEY_CTRL_R: | |||
| return state & ~(PuglMods)PUGL_MOD_CTRL; | |||
| case PUGL_KEY_ALT_L: | |||
| case PUGL_KEY_ALT_R: | |||
| return state & ~(PuglMods)PUGL_MOD_ALT; | |||
| case PUGL_KEY_SUPER_L: | |||
| case PUGL_KEY_SUPER_R: | |||
| return state & ~(PuglMods)PUGL_MOD_SUPER; | |||
| case PUGL_KEY_NUM_LOCK: | |||
| return state & ~(PuglMods)PUGL_MOD_NUM_LOCK; | |||
| case PUGL_KEY_SCROLL_LOCK: | |||
| return state & ~(PuglMods)PUGL_MOD_SCROLL_LOCK; | |||
| case PUGL_KEY_CAPS_LOCK: | |||
| return state & ~(PuglMods)PUGL_MOD_CAPS_LOCK; | |||
| default: | |||
| break; | |||
| } | |||
| return state; | |||
| } | |||
| PuglStatus | |||
| puglPreRealize(PuglView* const view) | |||
| { | |||
| // Ensure that a backend with at least a configure method has been set | |||
| if (!view->backend || !view->backend->configure) { | |||
| return PUGL_BAD_BACKEND; | |||
| } | |||
| // Ensure that the view has an event handler | |||
| if (!view->eventFunc) { | |||
| return PUGL_BAD_CONFIGURATION; | |||
| } | |||
| // Ensure that the default size is set to a valid size | |||
| if (!puglIsValidSize(view->sizeHints[PUGL_DEFAULT_SIZE])) { | |||
| return PUGL_BAD_CONFIGURATION; | |||
| } | |||
| return PUGL_SUCCESS; | |||
| } | |||
| PuglStatus | |||
| puglDispatchSimpleEvent(PuglView* view, const PuglEventType type) | |||
| { | |||
| assert(type == PUGL_CREATE || type == PUGL_DESTROY || type == PUGL_MAP || | |||
| type == PUGL_UNMAP || type == PUGL_UPDATE || type == PUGL_CLOSE || | |||
| type == PUGL_LOOP_ENTER || type == PUGL_LOOP_LEAVE); | |||
| assert(type == PUGL_REALIZE || type == PUGL_UNREALIZE || | |||
| type == PUGL_UPDATE || type == PUGL_CLOSE || type == PUGL_LOOP_ENTER || | |||
| type == PUGL_LOOP_LEAVE); | |||
| const PuglEvent event = {{type, 0}}; | |||
| return puglDispatchEvent(view, &event); | |||
| @@ -118,12 +189,6 @@ puglConfigure(PuglView* view, const PuglEvent* event) | |||
| PuglStatus st = PUGL_SUCCESS; | |||
| assert(event->type == PUGL_CONFIGURE); | |||
| view->frame.x = event->configure.x; | |||
| view->frame.y = event->configure.y; | |||
| view->frame.width = event->configure.width; | |||
| view->frame.height = event->configure.height; | |||
| if (puglMustConfigure(view, &event->configure)) { | |||
| st = view->eventFunc(view, event); | |||
| view->lastConfigure = event->configure; | |||
| @@ -132,14 +197,6 @@ puglConfigure(PuglView* view, const PuglEvent* event) | |||
| return st; | |||
| } | |||
| PuglStatus | |||
| puglExpose(PuglView* view, const PuglEvent* event) | |||
| { | |||
| return (event->expose.width > 0.0 && event->expose.height > 0.0) | |||
| ? view->eventFunc(view, event) | |||
| : PUGL_SUCCESS; | |||
| } | |||
| PuglStatus | |||
| puglDispatchEvent(PuglView* view, const PuglEvent* event) | |||
| { | |||
| @@ -149,13 +206,25 @@ puglDispatchEvent(PuglView* view, const PuglEvent* event) | |||
| switch (event->type) { | |||
| case PUGL_NOTHING: | |||
| break; | |||
| case PUGL_CREATE: | |||
| case PUGL_DESTROY: | |||
| case PUGL_REALIZE: | |||
| assert(view->stage == PUGL_VIEW_STAGE_ALLOCATED); | |||
| if (!(st0 = view->backend->enter(view, NULL))) { | |||
| st0 = view->eventFunc(view, event); | |||
| st1 = view->backend->leave(view, NULL); | |||
| } | |||
| view->stage = PUGL_VIEW_STAGE_REALIZED; | |||
| break; | |||
| case PUGL_UNREALIZE: | |||
| assert(view->stage >= PUGL_VIEW_STAGE_REALIZED); | |||
| if (!(st0 = view->backend->enter(view, NULL))) { | |||
| st0 = view->eventFunc(view, event); | |||
| st1 = view->backend->leave(view, NULL); | |||
| } | |||
| view->stage = PUGL_VIEW_STAGE_ALLOCATED; | |||
| break; | |||
| case PUGL_CONFIGURE: | |||
| if (puglMustConfigure(view, &event->configure)) { | |||
| if (!(st0 = view->backend->enter(view, NULL))) { | |||
| @@ -163,25 +232,19 @@ puglDispatchEvent(PuglView* view, const PuglEvent* event) | |||
| st1 = view->backend->leave(view, NULL); | |||
| } | |||
| } | |||
| break; | |||
| case PUGL_MAP: | |||
| if (!view->visible) { | |||
| view->visible = true; | |||
| st0 = view->eventFunc(view, event); | |||
| } | |||
| break; | |||
| case PUGL_UNMAP: | |||
| if (view->visible) { | |||
| view->visible = false; | |||
| st0 = view->eventFunc(view, event); | |||
| if (view->stage == PUGL_VIEW_STAGE_REALIZED) { | |||
| view->stage = PUGL_VIEW_STAGE_CONFIGURED; | |||
| } | |||
| break; | |||
| case PUGL_EXPOSE: | |||
| assert(view->stage == PUGL_VIEW_STAGE_CONFIGURED); | |||
| if (!(st0 = view->backend->enter(view, &event->expose))) { | |||
| st0 = puglExpose(view, event); | |||
| st0 = view->eventFunc(view, event); | |||
| st1 = view->backend->leave(view, &event->expose); | |||
| } | |||
| break; | |||
| default: | |||
| st0 = view->eventFunc(view, event); | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| // Internal utilities available to platform implementations | |||
| @@ -9,13 +9,23 @@ | |||
| #include "attributes.h" | |||
| #include "types.h" | |||
| #include "pugl/attributes.h" | |||
| #include "pugl/pugl.h" | |||
| #include <stdbool.h> | |||
| #include <stddef.h> | |||
| #include <stdint.h> | |||
| PUGL_BEGIN_DECLS | |||
| /// Return true if `size` is a valid view size | |||
| bool | |||
| puglIsValidSize(PuglViewSize size); | |||
| /// Set hint to a default value if it is unset (PUGL_DONT_CARE) | |||
| void | |||
| puglEnsureHint(PuglView* view, PuglViewHint hint, int value); | |||
| /// Set `blob` to `data` with length `len`, reallocating if necessary | |||
| PuglStatus | |||
| puglSetBlob(PuglBlob* dest, const void* data, size_t len); | |||
| @@ -24,10 +34,23 @@ puglSetBlob(PuglBlob* dest, const void* data, size_t len); | |||
| void | |||
| puglSetString(char** dest, const char* string); | |||
| /// Handle a changed string property | |||
| PUGL_API | |||
| PuglStatus | |||
| puglViewStringChanged(PuglView* view, PuglStringHint key, const char* value); | |||
| /// Return the Unicode code point for `buf` or the replacement character | |||
| uint32_t | |||
| puglDecodeUTF8(const uint8_t* buf); | |||
| /// Return `state` with any flags related to `key` removed | |||
| PuglMods | |||
| puglFilterMods(PuglMods state, PuglKey key); | |||
| /// Prepare a view to be realized by the platform implementation if possible | |||
| PuglStatus | |||
| puglPreRealize(PuglView* view); | |||
| /// Dispatch an event with a simple `type` to `view` | |||
| PuglStatus | |||
| puglDispatchSimpleEvent(PuglView* view, PuglEventType type); | |||
| @@ -37,11 +60,6 @@ PUGL_WARN_UNUSED_RESULT | |||
| PuglStatus | |||
| puglConfigure(PuglView* view, const PuglEvent* event); | |||
| /// Process expose event while already in the graphics context | |||
| PUGL_WARN_UNUSED_RESULT | |||
| PuglStatus | |||
| puglExpose(PuglView* view, const PuglEvent* event); | |||
| /// Dispatch `event` to `view`, entering graphics context if necessary | |||
| PuglStatus | |||
| puglDispatchEvent(PuglView* view, const PuglEvent* event); | |||
| @@ -9,6 +9,8 @@ | |||
| #import <Cocoa/Cocoa.h> | |||
| #include <mach/mach_time.h> | |||
| #include <stdint.h> | |||
| @interface PuglWrapperView : NSView<NSTextInputClient> | |||
| @@ -19,14 +21,12 @@ | |||
| @end | |||
| @interface PuglWindow : NSWindow | |||
| - (void)setPuglview:(PuglView*)view; | |||
| @end | |||
| struct PuglWorldInternalsImpl { | |||
| NSApplication* app; | |||
| NSAutoreleasePool* autoreleasePool; | |||
| NSApplication* app; | |||
| NSAutoreleasePool* autoreleasePool; | |||
| struct mach_timebase_info timebaseInfo; | |||
| }; | |||
| struct PuglInternalsImpl { | |||
| @@ -90,13 +90,11 @@ puglMacCairoEnter(PuglView* view, const PuglExposeEvent* expose) | |||
| CGContextRef context = | |||
| (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; | |||
| const CGSize sizePx = {(CGFloat)view->frame.width, | |||
| (CGFloat)view->frame.height}; | |||
| const CGSize sizePt = CGContextConvertSizeToUserSpace(context, sizePx); | |||
| const CGSize sizePx = {(CGFloat)view->lastConfigure.width, | |||
| (CGFloat)view->lastConfigure.height}; | |||
| // Convert coordinates to standard Cairo space | |||
| CGContextTranslateCTM(context, 0.0, -sizePt.height); | |||
| CGContextTranslateCTM(context, 0.0, sizePx.height * scale); | |||
| CGContextScaleCTM(context, scale, -scale); | |||
| drawView->surface = cairo_quartz_surface_create_for_cg_context( | |||
| @@ -11,6 +11,14 @@ | |||
| # define NSOpenGLProfileVersion4_1Core NSOpenGLProfileVersion3_2Core | |||
| #endif | |||
| static void | |||
| ensureHint(PuglView* const view, const PuglViewHint hint, const int value) | |||
| { | |||
| if (view->hints[hint] == PUGL_DONT_CARE) { | |||
| view->hints[hint] = value; | |||
| } | |||
| } | |||
| @interface PuglOpenGLView : NSOpenGLView | |||
| @end | |||
| @@ -21,7 +29,9 @@ | |||
| - (id)initWithFrame:(NSRect)frame | |||
| { | |||
| const bool compat = puglview->hints[PUGL_USE_COMPAT_PROFILE]; | |||
| const bool compat = | |||
| puglview->hints[PUGL_CONTEXT_PROFILE] == PUGL_OPENGL_COMPATIBILITY_PROFILE; | |||
| const unsigned samples = (unsigned)puglview->hints[PUGL_SAMPLES]; | |||
| const int major = puglview->hints[PUGL_CONTEXT_VERSION_MAJOR]; | |||
| const unsigned profile = | |||
| @@ -31,21 +41,12 @@ | |||
| // Set attributes to default if they are unset | |||
| // (There is no GLX_DONT_CARE equivalent on MacOS) | |||
| if (puglview->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) { | |||
| puglview->hints[PUGL_DEPTH_BITS] = 0; | |||
| } | |||
| if (puglview->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) { | |||
| puglview->hints[PUGL_STENCIL_BITS] = 0; | |||
| } | |||
| if (puglview->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) { | |||
| puglview->hints[PUGL_SAMPLES] = 1; | |||
| } | |||
| if (puglview->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) { | |||
| puglview->hints[PUGL_DOUBLE_BUFFER] = 1; | |||
| } | |||
| if (puglview->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) { | |||
| puglview->hints[PUGL_SWAP_INTERVAL] = 1; | |||
| } | |||
| ensureHint(puglview, PUGL_DEPTH_BITS, 0); | |||
| ensureHint(puglview, PUGL_STENCIL_BITS, 0); | |||
| ensureHint(puglview, PUGL_SAMPLES, 1); | |||
| ensureHint(puglview, PUGL_SAMPLE_BUFFERS, puglview->hints[PUGL_SAMPLES] > 0); | |||
| ensureHint(puglview, PUGL_DOUBLE_BUFFER, 1); | |||
| ensureHint(puglview, PUGL_SWAP_INTERVAL, 1); | |||
| const unsigned colorSize = (unsigned)(puglview->hints[PUGL_RED_BITS] + | |||
| puglview->hints[PUGL_BLUE_BITS] + | |||
| @@ -60,8 +61,8 @@ | |||
| NSOpenGLPFAColorSize, colorSize, | |||
| NSOpenGLPFADepthSize, (unsigned)puglview->hints[PUGL_DEPTH_BITS], | |||
| NSOpenGLPFAStencilSize, (unsigned)puglview->hints[PUGL_STENCIL_BITS], | |||
| NSOpenGLPFAMultisample, samples ? 1u : 0u, | |||
| NSOpenGLPFASampleBuffers, samples ? 1u : 0u, | |||
| NSOpenGLPFAMultisample, samples ? 1U : 0U, | |||
| NSOpenGLPFASampleBuffers, samples ? 1U : 0U, | |||
| NSOpenGLPFASamples, samples, | |||
| 0}; | |||
| // clang-format on | |||
| @@ -41,8 +41,11 @@ puglMacStubCreate(PuglView* view) | |||
| PuglStubView* drawView = [PuglStubView alloc]; | |||
| drawView->puglview = view; | |||
| [drawView | |||
| initWithFrame:NSMakeRect(0, 0, view->frame.width, view->frame.height)]; | |||
| [drawView initWithFrame:NSMakeRect(0, | |||
| 0, | |||
| view->lastConfigure.width, | |||
| view->lastConfigure.height)]; | |||
| if (view->hints[PUGL_RESIZABLE]) { | |||
| [drawView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; | |||
| } else { | |||
| @@ -75,7 +75,8 @@ puglMacVulkanCreate(PuglView* view) | |||
| { | |||
| PuglInternals* impl = view->impl; | |||
| PuglVulkanView* drawView = [PuglVulkanView alloc]; | |||
| const NSRect rect = NSMakeRect(0, 0, view->frame.width, view->frame.height); | |||
| const NSRect rect = | |||
| NSMakeRect(0, 0, view->lastConfigure.width, view->lastConfigure.height); | |||
| drawView->puglview = view; | |||
| [drawView initWithFrame:rect]; | |||
| @@ -107,7 +108,8 @@ struct PuglVulkanLoaderImpl { | |||
| }; | |||
| PuglVulkanLoader* | |||
| puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) | |||
| puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world), | |||
| const char* const libraryName) | |||
| { | |||
| PuglVulkanLoader* loader = | |||
| (PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader)); | |||
| @@ -115,7 +117,8 @@ puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) | |||
| return NULL; | |||
| } | |||
| if (!(loader->libvulkan = dlopen("libvulkan.dylib", RTLD_LAZY))) { | |||
| const char* const filename = libraryName ? libraryName : "libvulkan.dylib"; | |||
| if (!(loader->libvulkan = dlopen(filename, RTLD_LAZY))) { | |||
| free(loader); | |||
| return NULL; | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| // Copyright 2016-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_SRC_MACROS_H | |||
| #define PUGL_SRC_MACROS_H | |||
| #ifndef MIN | |||
| # define MIN(a, b) (((a) < (b)) ? (a) : (b)) | |||
| #endif | |||
| #ifndef MAX | |||
| # define MAX(a, b) (((a) > (b)) ? (a) : (b)) | |||
| #endif | |||
| #endif // PUGL_SRC_MACROS_H | |||
| @@ -13,6 +13,7 @@ | |||
| PUGL_BEGIN_DECLS | |||
| /// Allocate and initialise world internals (implemented once per platform) | |||
| PUGL_MALLOC_FUNC | |||
| PuglWorldInternals* | |||
| puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags); | |||
| @@ -21,6 +22,7 @@ void | |||
| puglFreeWorldInternals(PuglWorld* world); | |||
| /// Allocate and initialise view internals (implemented once per platform) | |||
| PUGL_MALLOC_FUNC | |||
| PuglInternals* | |||
| puglInitViewInternals(PuglWorld* world); | |||
| @@ -21,6 +21,12 @@ typedef struct PuglInternalsImpl PuglInternals; | |||
| /// View hints | |||
| typedef int PuglHints[PUGL_NUM_VIEW_HINTS]; | |||
| /// View position (both X and Y coordinates) or general point | |||
| typedef struct { | |||
| PuglCoord x; | |||
| PuglCoord y; | |||
| } PuglPoint; | |||
| /// View size (both X and Y coordinates) | |||
| typedef struct { | |||
| PuglSpan width; | |||
| @@ -33,6 +39,13 @@ typedef struct { | |||
| size_t len; ///< Length of data in bytes | |||
| } PuglBlob; | |||
| /// Stage of a view along its lifespan | |||
| typedef enum { | |||
| PUGL_VIEW_STAGE_ALLOCATED, | |||
| PUGL_VIEW_STAGE_REALIZED, | |||
| PUGL_VIEW_STAGE_CONFIGURED, | |||
| } PuglViewStage; | |||
| /// Cross-platform view definition | |||
| struct PuglViewImpl { | |||
| PuglWorld* world; | |||
| @@ -40,24 +53,27 @@ struct PuglViewImpl { | |||
| PuglInternals* impl; | |||
| PuglHandle handle; | |||
| PuglEventFunc eventFunc; | |||
| char* title; | |||
| PuglNativeView parent; | |||
| uintptr_t transientParent; | |||
| PuglRect frame; | |||
| PuglConfigureEvent lastConfigure; | |||
| PuglHints hints; | |||
| PuglViewSize sizeHints[PUGL_NUM_SIZE_HINTS]; | |||
| bool visible; | |||
| char* strings[PUGL_NUM_STRING_HINTS]; | |||
| int defaultX; | |||
| int defaultY; | |||
| PuglViewStage stage; | |||
| bool resizing; | |||
| }; | |||
| /// Cross-platform world definition | |||
| struct PuglWorldImpl { | |||
| PuglWorldInternals* impl; | |||
| PuglWorldHandle handle; | |||
| char* className; | |||
| double startTime; | |||
| size_t numViews; | |||
| PuglView** views; | |||
| char* strings[PUGL_NUM_STRING_HINTS]; | |||
| PuglWorldType type; | |||
| }; | |||
| /// Opaque surface used by graphics backend | |||
| @@ -1,734 +0,0 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| // SPDX-License-Identifier: ISC | |||
| #include "wasm.h" | |||
| #include "internal.h" | |||
| #include <stdio.h> | |||
| #include <emscripten/html5.h> | |||
| #ifdef __cplusplus | |||
| # define PUGL_INIT_STRUCT \ | |||
| {} | |||
| #else | |||
| # define PUGL_INIT_STRUCT \ | |||
| { \ | |||
| 0 \ | |||
| } | |||
| #endif | |||
| PuglWorldInternals* | |||
| puglInitWorldInternals(const PuglWorldType type, const PuglWorldFlags flags) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| PuglWorldInternals* impl = | |||
| (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals)); | |||
| impl->scaleFactor = emscripten_get_device_pixel_ratio(); | |||
| return impl; | |||
| } | |||
| void* | |||
| puglGetNativeWorld(PuglWorld*) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| return NULL; | |||
| } | |||
| PuglInternals* | |||
| puglInitViewInternals(PuglWorld* const world) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); | |||
| return impl; | |||
| } | |||
| static PuglStatus | |||
| puglDispatchEventWithContext(PuglView* const view, const PuglEvent* event) | |||
| { | |||
| PuglStatus st0 = PUGL_SUCCESS; | |||
| PuglStatus st1 = PUGL_SUCCESS; | |||
| if (!(st0 = view->backend->enter(view, NULL))) { | |||
| st0 = view->eventFunc(view, event); | |||
| st1 = view->backend->leave(view, NULL); | |||
| } | |||
| return st0 ? st0 : st1; | |||
| } | |||
| static PuglKey | |||
| keyCodeToSpecial(const unsigned long code, const unsigned long location) | |||
| { | |||
| switch (code) { | |||
| case 0x08: return PUGL_KEY_BACKSPACE; | |||
| case 0x1B: return PUGL_KEY_ESCAPE; | |||
| case 0x2E: return PUGL_KEY_DELETE; | |||
| case 0x70: return PUGL_KEY_F1; | |||
| case 0x71: return PUGL_KEY_F2; | |||
| case 0x72: return PUGL_KEY_F3; | |||
| case 0x73: return PUGL_KEY_F4; | |||
| case 0x74: return PUGL_KEY_F5; | |||
| case 0x75: return PUGL_KEY_F6; | |||
| case 0x76: return PUGL_KEY_F7; | |||
| case 0x77: return PUGL_KEY_F8; | |||
| case 0x78: return PUGL_KEY_F9; | |||
| case 0x79: return PUGL_KEY_F10; | |||
| case 0x7A: return PUGL_KEY_F11; | |||
| case 0x7B: return PUGL_KEY_F12; | |||
| case 0x25: return PUGL_KEY_LEFT; | |||
| case 0x26: return PUGL_KEY_UP; | |||
| case 0x27: return PUGL_KEY_RIGHT; | |||
| case 0x28: return PUGL_KEY_DOWN; | |||
| case 0x21: return PUGL_KEY_PAGE_UP; | |||
| case 0x22: return PUGL_KEY_PAGE_DOWN; | |||
| case 0x24: return PUGL_KEY_HOME; | |||
| case 0x23: return PUGL_KEY_END; | |||
| case 0x2D: return PUGL_KEY_INSERT; | |||
| case 0x10: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SHIFT_R : PUGL_KEY_SHIFT_L; | |||
| case 0x11: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_CTRL_R : PUGL_KEY_CTRL_L; | |||
| case 0x12: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_ALT_R : PUGL_KEY_ALT_L; | |||
| case 0xE0: return location == DOM_KEY_LOCATION_RIGHT ? PUGL_KEY_SUPER_R : PUGL_KEY_SUPER_L; | |||
| case 0x5D: return PUGL_KEY_MENU; | |||
| case 0x14: return PUGL_KEY_CAPS_LOCK; | |||
| case 0x91: return PUGL_KEY_SCROLL_LOCK; | |||
| case 0x90: return PUGL_KEY_NUM_LOCK; | |||
| case 0x2C: return PUGL_KEY_PRINT_SCREEN; | |||
| case 0x13: return PUGL_KEY_PAUSE; | |||
| case '\r': return (PuglKey)'\r'; | |||
| default: break; | |||
| } | |||
| return (PuglKey)0; | |||
| } | |||
| static PuglMods | |||
| translateModifiers(const EM_BOOL ctrlKey, | |||
| const EM_BOOL shiftKey, | |||
| const EM_BOOL altKey, | |||
| const EM_BOOL metaKey) | |||
| { | |||
| return (ctrlKey ? PUGL_MOD_CTRL : 0u) | | |||
| (shiftKey ? PUGL_MOD_SHIFT : 0u) | | |||
| (altKey ? PUGL_MOD_ALT : 0u) | | |||
| (metaKey ? PUGL_MOD_SUPER : 0u); | |||
| } | |||
| static bool | |||
| decodeCharacterString(const unsigned long keyCode, | |||
| const EM_UTF8 key[EM_HTML5_SHORT_STRING_LEN_BYTES], | |||
| char str[8]) | |||
| { | |||
| if (key[1] == 0) | |||
| { | |||
| str[0] = key[0]; | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| static EM_BOOL | |||
| puglKeyCallback(const int eventType, const EmscriptenKeyboardEvent* const keyEvent, void* const userData) | |||
| { | |||
| PuglView* const view = (PuglView*)userData; | |||
| if (!view->visible) { | |||
| return EM_FALSE; | |||
| } | |||
| if (keyEvent->repeat && view->hints[PUGL_IGNORE_KEY_REPEAT]) | |||
| return EM_TRUE; | |||
| PuglStatus st0 = PUGL_SUCCESS; | |||
| PuglStatus st1 = PUGL_SUCCESS; | |||
| const uint state = translateModifiers(keyEvent->ctrlKey, | |||
| keyEvent->shiftKey, | |||
| keyEvent->altKey, | |||
| keyEvent->metaKey); | |||
| const PuglKey special = keyCodeToSpecial(keyEvent->keyCode, keyEvent->location); | |||
| uint key = keyEvent->keyCode; | |||
| if (key >= 'A' && key <= 'Z' && !keyEvent->shiftKey) | |||
| key += 'a' - 'A'; | |||
| PuglEvent event = {{PUGL_NOTHING, 0}}; | |||
| event.key.type = eventType == EMSCRIPTEN_EVENT_KEYDOWN ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE; | |||
| event.key.time = keyEvent->timestamp / 1000; | |||
| // event.key.x = xevent.xkey.x; | |||
| // event.key.y = xevent.xkey.y; | |||
| // event.key.xRoot = xevent.xkey.x_root; | |||
| // event.key.yRoot = xevent.xkey.y_root; | |||
| event.key.key = special ? special : key; | |||
| event.key.keycode = keyEvent->keyCode; | |||
| event.key.state = state; | |||
| st0 = puglDispatchEventWithContext(view, &event); | |||
| d_debug("key event \n" | |||
| "\tdown: %d\n" | |||
| "\trepeat: %d\n" | |||
| "\tlocation: %d\n" | |||
| "\tstate: 0x%x\n" | |||
| "\tkey[]: '%s'\n" | |||
| "\tcode[]: '%s'\n" | |||
| "\tlocale[]: '%s'\n" | |||
| "\tkeyCode: 0x%lx:'%c' [deprecated, use key]\n" | |||
| "\twhich: 0x%lx:'%c' [deprecated, use key, same as keycode?]\n" | |||
| "\tspecial: 0x%x", | |||
| eventType == EMSCRIPTEN_EVENT_KEYDOWN, | |||
| keyEvent->repeat, | |||
| keyEvent->location, | |||
| state, | |||
| keyEvent->key, | |||
| keyEvent->code, | |||
| keyEvent->locale, | |||
| keyEvent->keyCode, keyEvent->keyCode >= ' ' && keyEvent->keyCode <= '~' ? keyEvent->keyCode : 0, | |||
| keyEvent->which, keyEvent->which >= ' ' && keyEvent->which <= '~' ? keyEvent->which : 0, | |||
| special); | |||
| if (event.type == PUGL_KEY_PRESS && !special && !(keyEvent->ctrlKey|keyEvent->altKey|keyEvent->metaKey)) { | |||
| char str[8] = PUGL_INIT_STRUCT; | |||
| if (decodeCharacterString(keyEvent->keyCode, keyEvent->key, str)) { | |||
| d_debug("resulting string is '%s'", str); | |||
| event.text.type = PUGL_TEXT; | |||
| event.text.character = event.key.key; | |||
| memcpy(event.text.string, str, sizeof(event.text.string)); | |||
| st1 = puglDispatchEventWithContext(view, &event); | |||
| } | |||
| } | |||
| return (st0 ? st0 : st1) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE; | |||
| } | |||
| static EM_BOOL | |||
| puglMouseCallback(const int eventType, const EmscriptenMouseEvent* const mouseEvent, void* const userData) | |||
| { | |||
| PuglView* const view = (PuglView*)userData; | |||
| if (!view->visible) { | |||
| return EM_FALSE; | |||
| } | |||
| PuglEvent event = {{PUGL_NOTHING, 0}}; | |||
| const double time = mouseEvent->timestamp / 1000; | |||
| const PuglMods state = translateModifiers(mouseEvent->ctrlKey, | |||
| mouseEvent->shiftKey, | |||
| mouseEvent->altKey, | |||
| mouseEvent->metaKey); | |||
| const double scaleFactor = view->world->impl->scaleFactor; | |||
| switch (eventType) { | |||
| case EMSCRIPTEN_EVENT_MOUSEDOWN: | |||
| case EMSCRIPTEN_EVENT_MOUSEUP: | |||
| event.button.type = eventType == EMSCRIPTEN_EVENT_MOUSEDOWN ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE; | |||
| event.button.time = time; | |||
| event.button.x = mouseEvent->targetX * scaleFactor; | |||
| event.button.y = mouseEvent->targetY * scaleFactor; | |||
| event.button.xRoot = mouseEvent->screenX * scaleFactor; | |||
| event.button.yRoot = mouseEvent->screenY * scaleFactor; | |||
| event.button.state = state; | |||
| switch (mouseEvent->button) { | |||
| case 1: | |||
| event.button.button = 2; | |||
| break; | |||
| case 2: | |||
| event.button.button = 1; | |||
| break; | |||
| default: | |||
| event.button.button = mouseEvent->button; | |||
| break; | |||
| } | |||
| break; | |||
| case EMSCRIPTEN_EVENT_MOUSEMOVE: | |||
| event.motion.type = PUGL_MOTION; | |||
| event.motion.time = time; | |||
| if (view->impl->lastX == mouseEvent->targetX && view->impl->lastY == mouseEvent->targetY) { | |||
| // adjust local values for delta | |||
| const double movementX = mouseEvent->movementX * scaleFactor; | |||
| const double movementY = mouseEvent->movementY * scaleFactor; | |||
| view->impl->lockedX += movementX; | |||
| view->impl->lockedY += movementY; | |||
| view->impl->lockedRootX += movementX; | |||
| view->impl->lockedRootY += movementY; | |||
| // now set x, y, xRoot and yRoot | |||
| event.motion.x = view->impl->lockedX; | |||
| event.motion.y = view->impl->lockedY; | |||
| event.motion.xRoot = view->impl->lockedRootX; | |||
| event.motion.yRoot = view->impl->lockedRootY; | |||
| } else { | |||
| // cache unmodified value first, for pointer lock detection | |||
| view->impl->lastX = mouseEvent->targetX; | |||
| view->impl->lastY = mouseEvent->targetY; | |||
| // now set x, y, xRoot and yRoot | |||
| view->impl->lockedX = event.motion.x = mouseEvent->targetX * scaleFactor; | |||
| view->impl->lockedY = event.motion.y = mouseEvent->targetY * scaleFactor; | |||
| view->impl->lockedRootX = event.motion.xRoot = mouseEvent->screenX * scaleFactor; | |||
| view->impl->lockedRootY = event.motion.yRoot = mouseEvent->screenY * scaleFactor; | |||
| } | |||
| event.motion.state = state; | |||
| break; | |||
| case EMSCRIPTEN_EVENT_MOUSEENTER: | |||
| case EMSCRIPTEN_EVENT_MOUSELEAVE: | |||
| event.crossing.type = eventType == EMSCRIPTEN_EVENT_MOUSEENTER ? PUGL_POINTER_IN : PUGL_POINTER_OUT; | |||
| event.crossing.time = time; | |||
| event.crossing.x = mouseEvent->targetX * scaleFactor; | |||
| event.crossing.y = mouseEvent->targetY * scaleFactor; | |||
| event.crossing.xRoot = mouseEvent->screenX * scaleFactor; | |||
| event.crossing.yRoot = mouseEvent->screenY * scaleFactor; | |||
| event.crossing.state = state; | |||
| event.crossing.mode = PUGL_CROSSING_NORMAL; | |||
| break; | |||
| } | |||
| if (event.type == PUGL_NOTHING) | |||
| return EM_FALSE; | |||
| puglDispatchEventWithContext(view, &event); | |||
| // note: we must always return false, otherwise canvas never gets keyboard input | |||
| return EM_FALSE; | |||
| } | |||
| static EM_BOOL | |||
| puglFocusCallback(const int eventType, const EmscriptenFocusEvent* /*const focusEvent*/, void* const userData) | |||
| { | |||
| PuglView* const view = (PuglView*)userData; | |||
| if (!view->visible) { | |||
| return EM_FALSE; | |||
| } | |||
| PuglEvent event = {{eventType == EMSCRIPTEN_EVENT_FOCUSIN ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT, 0}}; | |||
| event.focus.mode = PUGL_CROSSING_NORMAL; | |||
| puglDispatchEventWithContext(view, &event); | |||
| // note: we must always return false, otherwise canvas never gets proper focus | |||
| return EM_FALSE; | |||
| } | |||
| static EM_BOOL | |||
| puglPointerLockChangeCallback(const int eventType, const EmscriptenPointerlockChangeEvent* event, void* const userData) | |||
| { | |||
| PuglView* const view = (PuglView*)userData; | |||
| printf("puglPointerLockChangeCallback %d\n", event->isActive); | |||
| view->impl->pointerLocked = event->isActive; | |||
| return EM_TRUE; | |||
| } | |||
| static EM_BOOL | |||
| puglWheelCallback(const int eventType, const EmscriptenWheelEvent* const wheelEvent, void* const userData) | |||
| { | |||
| PuglView* const view = (PuglView*)userData; | |||
| if (!view->visible) { | |||
| return EM_FALSE; | |||
| } | |||
| const double scaleFactor = view->world->impl->scaleFactor; | |||
| PuglEvent event = {{PUGL_SCROLL, 0}}; | |||
| event.scroll.time = wheelEvent->mouse.timestamp / 1000; | |||
| event.scroll.x = wheelEvent->mouse.targetX; | |||
| event.scroll.y = wheelEvent->mouse.targetY; | |||
| event.scroll.xRoot = wheelEvent->mouse.screenX; | |||
| event.scroll.yRoot = wheelEvent->mouse.screenY; | |||
| event.scroll.state = translateModifiers(wheelEvent->mouse.ctrlKey, | |||
| wheelEvent->mouse.shiftKey, | |||
| wheelEvent->mouse.altKey, | |||
| wheelEvent->mouse.metaKey); | |||
| event.scroll.direction = PUGL_SCROLL_SMOOTH; | |||
| // FIXME handle wheelEvent->deltaMode | |||
| event.scroll.dx = wheelEvent->deltaX * 0.01 * scaleFactor; | |||
| event.scroll.dy = -wheelEvent->deltaY * 0.01 * scaleFactor; | |||
| return puglDispatchEventWithContext(view, &event) == PUGL_SUCCESS ? EM_TRUE : EM_FALSE; | |||
| } | |||
| static EM_BOOL | |||
| puglUiCallback(const int eventType, const EmscriptenUiEvent* const uiEvent, void* const userData) | |||
| { | |||
| PuglView* const view = (PuglView*)userData; | |||
| // FIXME | |||
| const int width = EM_ASM_INT({ return canvas.parentElement.clientWidth; }); | |||
| const int height = EM_ASM_INT({ return canvas.parentElement.clientHeight; }); | |||
| if (!width || !height) | |||
| return EM_FALSE; | |||
| const double scaleFactor = view->world->impl->scaleFactor = emscripten_get_device_pixel_ratio(); | |||
| emscripten_set_canvas_element_size(view->world->className, width * scaleFactor, height * scaleFactor); | |||
| PuglEvent event = {{PUGL_CONFIGURE, 0}}; | |||
| event.configure.x = view->frame.x; | |||
| event.configure.y = view->frame.y; | |||
| event.configure.width = width * scaleFactor; | |||
| event.configure.height = height * scaleFactor; | |||
| puglDispatchEvent(view, &event); | |||
| return EM_TRUE; | |||
| } | |||
| PuglStatus | |||
| puglRealize(PuglView* const view) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| PuglStatus st = PUGL_SUCCESS; | |||
| // Ensure that we do not have a parent | |||
| if (view->parent) { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return PUGL_FAILURE; | |||
| } | |||
| if (!view->backend || !view->backend->configure) { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return PUGL_BAD_BACKEND; | |||
| } | |||
| const char* const className = view->world->className; | |||
| d_stdout("className is %s", className); | |||
| // Set the size to the default if it has not already been set | |||
| if (view->frame.width <= 0.0 && view->frame.height <= 0.0) { | |||
| PuglViewSize defaultSize = view->sizeHints[PUGL_DEFAULT_SIZE]; | |||
| if (!defaultSize.width || !defaultSize.height) { | |||
| return PUGL_BAD_CONFIGURATION; | |||
| } | |||
| view->frame.width = defaultSize.width; | |||
| view->frame.height = defaultSize.height; | |||
| } | |||
| // Configure and create the backend | |||
| if ((st = view->backend->configure(view)) || (st = view->backend->create(view))) { | |||
| view->backend->destroy(view); | |||
| return st; | |||
| } | |||
| if (view->title) { | |||
| puglSetWindowTitle(view, view->title); | |||
| } | |||
| puglDispatchSimpleEvent(view, PUGL_CREATE); | |||
| PuglEvent event = {{PUGL_CONFIGURE, 0}}; | |||
| event.configure.x = view->frame.x; | |||
| event.configure.y = view->frame.y; | |||
| event.configure.width = view->frame.width; | |||
| event.configure.height = view->frame.height; | |||
| puglDispatchEvent(view, &event); | |||
| emscripten_set_canvas_element_size(className, view->frame.width, view->frame.height); | |||
| // emscripten_set_keypress_callback(className, view, false, puglKeyCallback); | |||
| emscripten_set_keydown_callback(className, view, false, puglKeyCallback); | |||
| emscripten_set_keyup_callback(className, view, false, puglKeyCallback); | |||
| emscripten_set_mousedown_callback(className, view, false, puglMouseCallback); | |||
| emscripten_set_mouseup_callback(className, view, false, puglMouseCallback); | |||
| emscripten_set_mousemove_callback(className, view, false, puglMouseCallback); | |||
| emscripten_set_mouseenter_callback(className, view, false, puglMouseCallback); | |||
| emscripten_set_mouseleave_callback(className, view, false, puglMouseCallback); | |||
| emscripten_set_focusin_callback(className, view, false, puglFocusCallback); | |||
| emscripten_set_focusout_callback(className, view, false, puglFocusCallback); | |||
| emscripten_set_pointerlockchange_callback(className, view, false, puglPointerLockChangeCallback); | |||
| emscripten_set_wheel_callback(className, view, false, puglWheelCallback); | |||
| emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, view, false, puglUiCallback); | |||
| view->impl->pointerLocked = true; | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return PUGL_SUCCESS; | |||
| } | |||
| PuglStatus | |||
| puglShow(PuglView* const view) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| view->visible = true; | |||
| return puglPostRedisplay(view); | |||
| } | |||
| PuglStatus | |||
| puglHide(PuglView* const view) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| view->visible = false; | |||
| return PUGL_FAILURE; | |||
| } | |||
| void | |||
| puglFreeViewInternals(PuglView* const view) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| if (view && view->impl) { | |||
| if (view->backend) { | |||
| view->backend->destroy(view); | |||
| } | |||
| free(view->impl->timers); | |||
| free(view->impl); | |||
| } | |||
| } | |||
| void | |||
| puglFreeWorldInternals(PuglWorld* const world) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| free(world->impl); | |||
| } | |||
| PuglStatus | |||
| puglGrabFocus(PuglView*) | |||
| { | |||
| return PUGL_FAILURE; | |||
| } | |||
| PuglStatus | |||
| puglAcceptOffer(PuglView* const view, | |||
| const PuglDataOfferEvent* const offer, | |||
| const uint32_t typeIndex) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return PUGL_FAILURE; | |||
| } | |||
| PuglStatus | |||
| puglPaste(PuglView* const view) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return PUGL_FAILURE; | |||
| } | |||
| uint32_t | |||
| puglGetNumClipboardTypes(const PuglView* const view) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return 0; | |||
| } | |||
| const char* | |||
| puglGetClipboardType(const PuglView* const view, const uint32_t typeIndex) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return NULL; | |||
| } | |||
| double | |||
| puglGetScaleFactor(const PuglView* const view) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| return view->world->impl->scaleFactor; | |||
| } | |||
| double | |||
| puglGetTime(const PuglWorld*) | |||
| { | |||
| // d_stdout("DONE %s %d", __func__, __LINE__); | |||
| return emscripten_get_now() / 1000; | |||
| } | |||
| PuglStatus | |||
| puglUpdate(PuglWorld* const world, const double timeout) | |||
| { | |||
| // printf("TODO: %s %d\n", __func__, __LINE__); | |||
| for (size_t i = 0; i < world->numViews; ++i) { | |||
| PuglView* const view = world->views[i]; | |||
| if (view->visible) { | |||
| puglDispatchSimpleEvent(view, PUGL_UPDATE); | |||
| } | |||
| if (!view->impl->needsRepaint) { | |||
| continue; | |||
| } | |||
| view->impl->needsRepaint = false; | |||
| PuglEvent event = {{PUGL_EXPOSE, 0}}; | |||
| event.expose.x = view->frame.x; | |||
| event.expose.y = view->frame.y; | |||
| event.expose.width = view->frame.width; | |||
| event.expose.height = view->frame.height; | |||
| puglDispatchEvent(view, &event); | |||
| static bool p = true; | |||
| if (p) { | |||
| p = false; | |||
| d_stdout("drawing at %d %d %u %u", (int)view->frame.x, (int)view->frame.y, | |||
| (uint)view->frame.width, (uint)view->frame.height); | |||
| } | |||
| } | |||
| return PUGL_SUCCESS; | |||
| } | |||
| PuglStatus | |||
| puglPostRedisplay(PuglView* const view) | |||
| { | |||
| // printf("TODO: %s %d\n", __func__, __LINE__); | |||
| view->impl->needsRepaint = true; | |||
| return PUGL_SUCCESS; | |||
| } | |||
| PuglStatus | |||
| puglPostRedisplayRect(PuglView* const view, const PuglRect rect) | |||
| { | |||
| // printf("TODO: %s %d\n", __func__, __LINE__); | |||
| view->impl->needsRepaint = true; | |||
| return PUGL_FAILURE; | |||
| } | |||
| PuglNativeView | |||
| puglGetNativeView(PuglView* const view) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return 0; | |||
| } | |||
| PuglStatus | |||
| puglSetWindowTitle(PuglView* const view, const char* const title) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| puglSetString(&view->title, title); | |||
| emscripten_set_window_title(title); | |||
| return PUGL_SUCCESS; | |||
| } | |||
| PuglStatus | |||
| puglSetSizeHint(PuglView* const view, | |||
| const PuglSizeHint hint, | |||
| const PuglSpan width, | |||
| const PuglSpan height) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| view->sizeHints[hint].width = width; | |||
| view->sizeHints[hint].height = height; | |||
| return PUGL_SUCCESS; | |||
| } | |||
| static EM_BOOL | |||
| puglTimerLoopCallback(double timeout, void* const arg) | |||
| { | |||
| PuglTimer* const timer = (PuglTimer*)arg; | |||
| PuglInternals* const impl = timer->view->impl; | |||
| // only handle active timers | |||
| for (uint32_t i=0; i<impl->numTimers; ++i) | |||
| { | |||
| if (impl->timers[i].id == timer->id) | |||
| { | |||
| PuglEvent event = {{PUGL_TIMER, 0}}; | |||
| event.timer.id = timer->id; | |||
| puglDispatchEventWithContext(timer->view, &event); | |||
| return EM_TRUE; | |||
| } | |||
| } | |||
| return EM_FALSE; | |||
| // unused | |||
| (void)timeout; | |||
| } | |||
| PuglStatus | |||
| puglStartTimer(PuglView* const view, const uintptr_t id, const double timeout) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| PuglInternals* const impl = view->impl; | |||
| const uint32_t timerIndex = impl->numTimers++; | |||
| if (impl->timers == NULL) | |||
| impl->timers = (PuglTimer*)malloc(sizeof(PuglTimer)); | |||
| else | |||
| impl->timers = (PuglTimer*)realloc(impl->timers, sizeof(PuglTimer) * timerIndex); | |||
| PuglTimer* const timer = &impl->timers[timerIndex]; | |||
| timer->view = view; | |||
| timer->id = id; | |||
| emscripten_set_timeout_loop(puglTimerLoopCallback, timeout * 1000, timer); | |||
| return PUGL_SUCCESS; | |||
| } | |||
| PuglStatus | |||
| puglStopTimer(PuglView* const view, const uintptr_t id) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| PuglInternals* const impl = view->impl; | |||
| if (impl->timers == NULL || impl->numTimers == 0) | |||
| return PUGL_FAILURE; | |||
| for (uint32_t i=0; i<impl->numTimers; ++i) | |||
| { | |||
| if (impl->timers[i].id == id) | |||
| { | |||
| memmove(impl->timers + i, impl->timers + (i + 1), sizeof(PuglTimer) * (impl->numTimers - 1)); | |||
| --impl->numTimers; | |||
| return PUGL_SUCCESS; | |||
| } | |||
| } | |||
| return PUGL_FAILURE; | |||
| } | |||
| const void* | |||
| puglGetClipboard(PuglView* const view, | |||
| const uint32_t typeIndex, | |||
| size_t* const len) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return NULL; | |||
| } | |||
| PuglStatus | |||
| puglSetClipboard(PuglView* const view, | |||
| const char* const type, | |||
| const void* const data, | |||
| const size_t len) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return PUGL_FAILURE; | |||
| } | |||
| PuglStatus | |||
| puglSetCursor(PuglView* const view, const PuglCursor cursor) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| return PUGL_FAILURE; | |||
| } | |||
| PuglStatus | |||
| puglSetTransientParent(PuglView* const view, const PuglNativeView parent) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| view->transientParent = parent; | |||
| return PUGL_FAILURE; | |||
| } | |||
| PuglStatus | |||
| puglSetPosition(PuglView* const view, const int x, const int y) | |||
| { | |||
| printf("TODO: %s %d\n", __func__, __LINE__); | |||
| if (x > INT16_MAX || y > INT16_MAX) { | |||
| return PUGL_BAD_PARAMETER; | |||
| } | |||
| view->frame.x = (PuglCoord)x; | |||
| view->frame.y = (PuglCoord)y; | |||
| return PUGL_FAILURE; | |||
| } | |||
| @@ -1,33 +0,0 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| // SPDX-License-Identifier: ISC | |||
| #ifndef PUGL_SRC_WASM_H | |||
| #define PUGL_SRC_WASM_H | |||
| // #include "attributes.h" | |||
| #include "types.h" | |||
| #include "pugl/pugl.h" | |||
| struct PuglTimer { | |||
| PuglView* view; | |||
| uintptr_t id; | |||
| }; | |||
| struct PuglWorldInternalsImpl { | |||
| double scaleFactor; | |||
| }; | |||
| struct PuglInternalsImpl { | |||
| PuglSurface* surface; | |||
| bool needsRepaint; | |||
| bool pointerLocked; | |||
| uint32_t numTimers; | |||
| long lastX, lastY; | |||
| double lockedX, lockedY; | |||
| double lockedRootX, lockedRootY; | |||
| struct PuglTimer* timers; | |||
| }; | |||
| #endif // PUGL_SRC_WASM_H | |||
| @@ -1,255 +0,0 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| // SPDX-License-Identifier: ISC | |||
| // #include "attributes.h" | |||
| #include "stub.h" | |||
| // #include "types.h" | |||
| #include "wasm.h" | |||
| // #include "pugl/gl.h" | |||
| #include "pugl/pugl.h" | |||
| #include <stdio.h> | |||
| #include <stdlib.h> | |||
| #include <EGL/egl.h> | |||
| typedef struct { | |||
| EGLDisplay display; | |||
| EGLConfig config; | |||
| EGLContext context; | |||
| EGLSurface surface; | |||
| } PuglWasmGlSurface; | |||
| static EGLint | |||
| puglWasmGlHintValue(const int value) | |||
| { | |||
| return value == PUGL_DONT_CARE ? EGL_DONT_CARE : value; | |||
| } | |||
| static int | |||
| puglWasmGlGetAttrib(const EGLDisplay display, | |||
| const EGLConfig config, | |||
| const EGLint attrib) | |||
| { | |||
| EGLint value = 0; | |||
| eglGetConfigAttrib(display, config, attrib, &value); | |||
| return value; | |||
| } | |||
| static PuglStatus | |||
| puglWasmGlConfigure(PuglView* view) | |||
| { | |||
| PuglInternals* const impl = view->impl; | |||
| // const int screen = impl->screen; | |||
| // Display* const display = view->world->impl->display; | |||
| printf("TODO: %s %d | start\n", __func__, __LINE__); | |||
| const EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); | |||
| if (display == EGL_NO_DISPLAY) { | |||
| printf("eglGetDisplay Failed\n"); | |||
| return PUGL_CREATE_CONTEXT_FAILED; | |||
| } | |||
| int major, minor; | |||
| if (eglInitialize(display, &major, &minor) != EGL_TRUE) { | |||
| printf("eglInitialize Failed\n"); | |||
| return PUGL_CREATE_CONTEXT_FAILED; | |||
| } | |||
| EGLConfig config; | |||
| int numConfigs; | |||
| if (eglGetConfigs(display, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) { | |||
| printf("eglGetConfigs Failed\n"); | |||
| eglTerminate(display); | |||
| return PUGL_CREATE_CONTEXT_FAILED; | |||
| } | |||
| // clang-format off | |||
| const EGLint attrs[] = { | |||
| // GLX_X_RENDERABLE, True, | |||
| // GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, | |||
| // GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, | |||
| // GLX_RENDER_TYPE, GLX_RGBA_BIT, | |||
| // GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]), | |||
| EGL_SAMPLES, puglWasmGlHintValue(view->hints[PUGL_SAMPLES]), | |||
| EGL_RED_SIZE, puglWasmGlHintValue(view->hints[PUGL_RED_BITS]), | |||
| EGL_GREEN_SIZE, puglWasmGlHintValue(view->hints[PUGL_GREEN_BITS]), | |||
| EGL_BLUE_SIZE, puglWasmGlHintValue(view->hints[PUGL_BLUE_BITS]), | |||
| EGL_ALPHA_SIZE, puglWasmGlHintValue(view->hints[PUGL_ALPHA_BITS]), | |||
| EGL_DEPTH_SIZE, puglWasmGlHintValue(view->hints[PUGL_DEPTH_BITS]), | |||
| EGL_STENCIL_SIZE, puglWasmGlHintValue(view->hints[PUGL_STENCIL_BITS]), | |||
| EGL_NONE | |||
| }; | |||
| // clang-format on | |||
| if (eglChooseConfig(display, attrs, &config, 1, &numConfigs) != EGL_TRUE || numConfigs != 1) { | |||
| printf("eglChooseConfig Failed\n"); | |||
| eglTerminate(display); | |||
| return PUGL_CREATE_CONTEXT_FAILED; | |||
| } | |||
| PuglWasmGlSurface* const surface = | |||
| (PuglWasmGlSurface*)calloc(1, sizeof(PuglWasmGlSurface)); | |||
| impl->surface = surface; | |||
| surface->display = display; | |||
| surface->config = config; | |||
| surface->context = EGL_NO_SURFACE; | |||
| surface->surface = EGL_NO_CONTEXT; | |||
| view->hints[PUGL_RED_BITS] = | |||
| puglWasmGlGetAttrib(display, config, EGL_RED_SIZE); | |||
| view->hints[PUGL_GREEN_BITS] = | |||
| puglWasmGlGetAttrib(display, config, EGL_GREEN_SIZE); | |||
| view->hints[PUGL_BLUE_BITS] = | |||
| puglWasmGlGetAttrib(display, config, EGL_BLUE_SIZE); | |||
| view->hints[PUGL_ALPHA_BITS] = | |||
| puglWasmGlGetAttrib(display, config, EGL_ALPHA_SIZE); | |||
| view->hints[PUGL_DEPTH_BITS] = | |||
| puglWasmGlGetAttrib(display, config, EGL_DEPTH_SIZE); | |||
| view->hints[PUGL_STENCIL_BITS] = | |||
| puglWasmGlGetAttrib(display, config, EGL_STENCIL_SIZE); | |||
| view->hints[PUGL_SAMPLES] = | |||
| puglWasmGlGetAttrib(display, config, EGL_SAMPLES); | |||
| // always enabled for EGL | |||
| view->hints[PUGL_DOUBLE_BUFFER] = 1; | |||
| printf("TODO: %s %d | ok\n", __func__, __LINE__); | |||
| return PUGL_SUCCESS; | |||
| } | |||
| PUGL_WARN_UNUSED_RESULT | |||
| static PuglStatus | |||
| puglWasmGlEnter(PuglView* view, const PuglExposeEvent* PUGL_UNUSED(expose)) | |||
| { | |||
| // printf("DONE: %s %d\n", __func__, __LINE__); | |||
| PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface; | |||
| if (!surface || !surface->context || !surface->surface) { | |||
| return PUGL_FAILURE; | |||
| } | |||
| // TESTING: is it faster if we never unset context? | |||
| return PUGL_SUCCESS; | |||
| return eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context) ? PUGL_SUCCESS : PUGL_FAILURE; | |||
| } | |||
| PUGL_WARN_UNUSED_RESULT | |||
| static PuglStatus | |||
| puglWasmGlLeave(PuglView* view, const PuglExposeEvent* expose) | |||
| { | |||
| // printf("DONE: %s %d\n", __func__, __LINE__); | |||
| PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface; | |||
| if (expose) { // note: swap buffers always enabled for EGL | |||
| eglSwapBuffers(surface->display, surface->surface); | |||
| } | |||
| // TESTING: is it faster if we never unset context? | |||
| return PUGL_SUCCESS; | |||
| return eglMakeCurrent(surface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) ? PUGL_SUCCESS : PUGL_FAILURE; | |||
| } | |||
| static PuglStatus | |||
| puglWasmGlCreate(PuglView* view) | |||
| { | |||
| printf("TODO: %s %d | start\n", __func__, __LINE__); | |||
| PuglWasmGlSurface* const surface = (PuglWasmGlSurface*)view->impl->surface; | |||
| const EGLDisplay display = surface->display; | |||
| const EGLConfig config = surface->config; | |||
| const EGLint attrs[] = { | |||
| EGL_CONTEXT_CLIENT_VERSION, | |||
| view->hints[PUGL_CONTEXT_VERSION_MAJOR], | |||
| EGL_CONTEXT_MAJOR_VERSION, | |||
| view->hints[PUGL_CONTEXT_VERSION_MAJOR], | |||
| /* | |||
| EGL_CONTEXT_MINOR_VERSION, | |||
| view->hints[PUGL_CONTEXT_VERSION_MINOR], | |||
| EGL_CONTEXT_OPENGL_DEBUG, | |||
| (view->hints[PUGL_USE_DEBUG_CONTEXT] ? EGL_TRUE : EGL_FALSE), | |||
| EGL_CONTEXT_OPENGL_PROFILE_MASK, | |||
| (view->hints[PUGL_USE_COMPAT_PROFILE] | |||
| ? EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT | |||
| : EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT), | |||
| */ | |||
| EGL_NONE | |||
| }; | |||
| surface->context = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs); | |||
| if (surface->context == EGL_NO_CONTEXT) { | |||
| printf("eglCreateContext Failed\n"); | |||
| return PUGL_CREATE_CONTEXT_FAILED; | |||
| } | |||
| #if 0 | |||
| eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context); | |||
| printf("GL_VENDOR=%s\n", glGetString(GL_VENDOR)); | |||
| printf("GL_RENDERER=%s\n", glGetString(GL_RENDERER)); | |||
| printf("GL_VERSION=%s\n", glGetString(GL_VERSION)); | |||
| eglMakeCurrent(surface->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | |||
| #endif | |||
| surface->surface = eglCreateWindowSurface(display, config, 0, NULL); | |||
| if (surface->surface == EGL_NO_SURFACE) { | |||
| printf("eglCreateWindowSurface Failed\n"); | |||
| return PUGL_CREATE_CONTEXT_FAILED; | |||
| } | |||
| printf("TODO: %s %d | ok\n", __func__, __LINE__); | |||
| // TESTING: is it faster if we never unset context? | |||
| eglMakeCurrent(surface->display, surface->surface, surface->surface, surface->context); | |||
| return PUGL_SUCCESS; | |||
| } | |||
| static void | |||
| puglWasmGlDestroy(PuglView* view) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| PuglWasmGlSurface* surface = (PuglWasmGlSurface*)view->impl->surface; | |||
| if (surface) { | |||
| const EGLDisplay display = surface->display; | |||
| if (surface->surface != EGL_NO_SURFACE) | |||
| eglDestroySurface(display, surface->surface); | |||
| if (surface->context != EGL_NO_CONTEXT) | |||
| eglDestroyContext(display, surface->context); | |||
| eglTerminate(display); | |||
| free(surface); | |||
| view->impl->surface = NULL; | |||
| } | |||
| } | |||
| const PuglBackend* | |||
| puglGlBackend(void) | |||
| { | |||
| printf("DONE: %s %d\n", __func__, __LINE__); | |||
| static const PuglBackend backend = {puglWasmGlConfigure, | |||
| puglWasmGlCreate, | |||
| puglWasmGlDestroy, | |||
| puglWasmGlEnter, | |||
| puglWasmGlLeave, | |||
| puglStubGetContext}; | |||
| return &backend; | |||
| } | |||
| @@ -1,26 +0,0 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // Copyright 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| // SPDX-License-Identifier: ISC | |||
| #include "pugl/stub.h" | |||
| #include "stub.h" | |||
| // #include "types.h" | |||
| // #include "wasm.h" | |||
| #include "pugl/pugl.h" | |||
| const PuglBackend* | |||
| puglStubBackend(void) | |||
| { | |||
| static const PuglBackend backend = { | |||
| puglStubConfigure, | |||
| puglStubCreate, | |||
| puglStubDestroy, | |||
| puglStubEnter, | |||
| puglStubLeave, | |||
| puglStubGetContext, | |||
| }; | |||
| return &backend; | |||
| } | |||
| @@ -19,16 +19,22 @@ struct PuglWorldInternalsImpl { | |||
| }; | |||
| struct PuglInternalsImpl { | |||
| PuglWinPFD pfd; | |||
| int pfId; | |||
| HWND hwnd; | |||
| HCURSOR cursor; | |||
| HDC hdc; | |||
| PuglBlob clipboard; | |||
| PuglSurface* surface; | |||
| double scaleFactor; | |||
| bool flashing; | |||
| bool mouseTracked; | |||
| PuglWinPFD pfd; | |||
| int pfId; | |||
| HWND hwnd; | |||
| HCURSOR cursor; | |||
| HDC hdc; | |||
| WINDOWPLACEMENT oldPlacement; | |||
| PAINTSTRUCT paint; | |||
| PuglBlob clipboard; | |||
| PuglSurface* surface; | |||
| double scaleFactor; | |||
| bool mapped; | |||
| bool flashing; | |||
| bool mouseTracked; | |||
| bool minimized; | |||
| bool maximized; | |||
| bool fullscreen; | |||
| }; | |||
| PUGL_API | |||
| @@ -27,7 +27,7 @@ puglWinCairoCreateDrawContext(PuglView* view) | |||
| surface->drawDc = CreateCompatibleDC(impl->hdc); | |||
| surface->drawBitmap = CreateCompatibleBitmap( | |||
| impl->hdc, (int)view->frame.width, (int)view->frame.height); | |||
| impl->hdc, (int)view->lastConfigure.width, (int)view->lastConfigure.height); | |||
| DeleteObject(SelectObject(surface->drawDc, surface->drawBitmap)); | |||
| @@ -108,8 +108,7 @@ puglWinCairoEnter(PuglView* view, const PuglExposeEvent* expose) | |||
| if (expose && !(st = puglWinCairoCreateDrawContext(view)) && | |||
| !(st = puglWinCairoOpen(view))) { | |||
| PAINTSTRUCT ps; | |||
| BeginPaint(view->impl->hwnd, &ps); | |||
| st = puglWinEnter(view, expose); | |||
| } | |||
| return st; | |||
| @@ -126,8 +125,8 @@ puglWinCairoLeave(PuglView* view, const PuglExposeEvent* expose) | |||
| BitBlt(impl->hdc, | |||
| 0, | |||
| 0, | |||
| (int)view->frame.width, | |||
| (int)view->frame.height, | |||
| (int)view->lastConfigure.width, | |||
| (int)view->lastConfigure.height, | |||
| surface->drawDc, | |||
| 0, | |||
| 0, | |||
| @@ -135,12 +134,9 @@ puglWinCairoLeave(PuglView* view, const PuglExposeEvent* expose) | |||
| puglWinCairoClose(view); | |||
| puglWinCairoDestroyDrawContext(view); | |||
| PAINTSTRUCT ps; | |||
| EndPaint(view->impl->hwnd, &ps); | |||
| } | |||
| return PUGL_SUCCESS; | |||
| return puglWinLeave(view, expose); | |||
| } | |||
| static void* | |||
| @@ -150,7 +146,7 @@ puglWinCairoGetContext(PuglView* view) | |||
| } | |||
| const PuglBackend* | |||
| puglCairoBackend() | |||
| puglCairoBackend(void) | |||
| { | |||
| static const PuglBackend backend = {puglWinCairoConfigure, | |||
| puglStubCreate, | |||
| @@ -89,6 +89,14 @@ puglWinGlGetProcs(void) | |||
| return procs; | |||
| } | |||
| static void | |||
| ensureHint(PuglView* const view, const PuglViewHint hint, const int value) | |||
| { | |||
| if (view->hints[hint] == PUGL_DONT_CARE) { | |||
| view->hints[hint] = value; | |||
| } | |||
| } | |||
| static PuglStatus | |||
| puglWinGlConfigure(PuglView* view) | |||
| { | |||
| @@ -96,21 +104,12 @@ puglWinGlConfigure(PuglView* view) | |||
| // Set attributes to default if they are unset | |||
| // (There is no GLX_DONT_CARE equivalent on Windows) | |||
| if (view->hints[PUGL_DEPTH_BITS] == PUGL_DONT_CARE) { | |||
| view->hints[PUGL_DEPTH_BITS] = 0; | |||
| } | |||
| if (view->hints[PUGL_STENCIL_BITS] == PUGL_DONT_CARE) { | |||
| view->hints[PUGL_STENCIL_BITS] = 0; | |||
| } | |||
| if (view->hints[PUGL_SAMPLES] == PUGL_DONT_CARE) { | |||
| view->hints[PUGL_SAMPLES] = 1; | |||
| } | |||
| if (view->hints[PUGL_DOUBLE_BUFFER] == PUGL_DONT_CARE) { | |||
| view->hints[PUGL_DOUBLE_BUFFER] = 1; | |||
| } | |||
| if (view->hints[PUGL_SWAP_INTERVAL] == PUGL_DONT_CARE) { | |||
| view->hints[PUGL_SWAP_INTERVAL] = 1; | |||
| } | |||
| ensureHint(view, PUGL_DEPTH_BITS, 0); | |||
| ensureHint(view, PUGL_STENCIL_BITS, 0); | |||
| ensureHint(view, PUGL_SAMPLES, 0); | |||
| ensureHint(view, PUGL_SAMPLE_BUFFERS, view->hints[PUGL_SAMPLES] > 0); | |||
| ensureHint(view, PUGL_DOUBLE_BUFFER, 1); | |||
| ensureHint(view, PUGL_SWAP_INTERVAL, 1); | |||
| // clang-format off | |||
| const int pixelAttrs[] = { | |||
| @@ -119,7 +118,7 @@ puglWinGlConfigure(PuglView* view) | |||
| WGL_SUPPORT_OPENGL_ARB, GL_TRUE, | |||
| WGL_DOUBLE_BUFFER_ARB, view->hints[PUGL_DOUBLE_BUFFER], | |||
| WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, | |||
| WGL_SAMPLE_BUFFERS_ARB, view->hints[PUGL_SAMPLES] ? 1 : 0, | |||
| WGL_SAMPLE_BUFFERS_ARB, view->hints[PUGL_SAMPLE_BUFFERS], | |||
| WGL_SAMPLES_ARB, view->hints[PUGL_SAMPLES], | |||
| WGL_RED_BITS_ARB, view->hints[PUGL_RED_BITS], | |||
| WGL_GREEN_BITS_ARB, view->hints[PUGL_GREEN_BITS], | |||
| @@ -164,7 +163,7 @@ puglWinGlConfigure(PuglView* view) | |||
| // Choose pixel format based on attributes | |||
| UINT numFormats = 0; | |||
| if (!surface->procs.wglChoosePixelFormat( | |||
| fakeWin.hdc, pixelAttrs, NULL, 1u, &impl->pfId, &numFormats)) { | |||
| fakeWin.hdc, pixelAttrs, NULL, 1U, &impl->pfId, &numFormats)) { | |||
| return puglWinError(&fakeWin, PUGL_SET_FORMAT_FAILED); | |||
| } | |||
| @@ -199,12 +198,12 @@ puglWinGlCreate(PuglView* view) | |||
| view->hints[PUGL_CONTEXT_VERSION_MINOR], | |||
| WGL_CONTEXT_FLAGS_ARB, | |||
| (view->hints[PUGL_USE_DEBUG_CONTEXT] ? WGL_CONTEXT_DEBUG_BIT_ARB : 0), | |||
| (view->hints[PUGL_CONTEXT_DEBUG] ? WGL_CONTEXT_DEBUG_BIT_ARB : 0), | |||
| WGL_CONTEXT_PROFILE_MASK_ARB, | |||
| (view->hints[PUGL_USE_COMPAT_PROFILE] | |||
| ? WGL_CONTEXT_CORE_PROFILE_BIT_ARB | |||
| : WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB), | |||
| ((view->hints[PUGL_CONTEXT_PROFILE] == PUGL_OPENGL_COMPATIBILITY_PROFILE) | |||
| ? WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB | |||
| : WGL_CONTEXT_CORE_PROFILE_BIT_ARB), | |||
| 0}; | |||
| @@ -262,27 +261,20 @@ puglWinGlEnter(PuglView* view, const PuglExposeEvent* expose) | |||
| return PUGL_FAILURE; | |||
| } | |||
| const PuglStatus st = puglWinEnter(view, expose); | |||
| wglMakeCurrent(view->impl->hdc, surface->hglrc); | |||
| if (expose) { | |||
| PAINTSTRUCT ps; | |||
| BeginPaint(view->impl->hwnd, &ps); | |||
| } | |||
| return PUGL_SUCCESS; | |||
| return st; | |||
| } | |||
| static PuglStatus | |||
| puglWinGlLeave(PuglView* view, const PuglExposeEvent* expose) | |||
| { | |||
| if (expose) { | |||
| PAINTSTRUCT ps; | |||
| EndPaint(view->impl->hwnd, &ps); | |||
| SwapBuffers(view->impl->hdc); | |||
| } | |||
| wglMakeCurrent(NULL, NULL); | |||
| return PUGL_SUCCESS; | |||
| return puglWinLeave(view, expose); | |||
| } | |||
| PuglGlFunc | |||
| @@ -296,7 +288,7 @@ puglGetProcAddress(const char* name) | |||
| return func | |||
| ? func | |||
| : (PuglGlFunc)GetProcAddress(GetModuleHandle("opengl32.dll"), name); | |||
| : (PuglGlFunc)GetProcAddress(GetModuleHandleA("opengl32.dll"), name); | |||
| } | |||
| PuglStatus | |||
| @@ -21,7 +21,8 @@ struct PuglVulkanLoaderImpl { | |||
| }; | |||
| PuglVulkanLoader* | |||
| puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) | |||
| puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world), | |||
| const char* const libraryName) | |||
| { | |||
| PuglVulkanLoader* loader = | |||
| (PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader)); | |||
| @@ -29,7 +30,9 @@ puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) | |||
| return NULL; | |||
| } | |||
| if (!(loader->libvulkan = LoadLibrary("vulkan-1.dll"))) { | |||
| const char* const filename = libraryName ? libraryName : "vulkan-1.dll"; | |||
| if (!(loader->libvulkan = | |||
| LoadLibraryExA(filename, NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS))) { | |||
| free(loader); | |||
| return NULL; | |||
| } | |||
| @@ -65,7 +68,7 @@ puglGetDeviceProcAddrFunc(const PuglVulkanLoader* loader) | |||
| } | |||
| const PuglBackend* | |||
| puglVulkanBackend() | |||
| puglVulkanBackend(void) | |||
| { | |||
| static const PuglBackend backend = {puglWinConfigure, | |||
| puglStubCreate, | |||
| @@ -7,6 +7,7 @@ | |||
| #include "attributes.h" | |||
| #include "types.h" | |||
| #include "pugl/attributes.h" | |||
| #include "pugl/pugl.h" | |||
| #include <X11/X.h> | |||
| @@ -20,13 +21,28 @@ | |||
| typedef struct { | |||
| Atom CLIPBOARD; | |||
| Atom UTF8_STRING; | |||
| Atom WM_CLIENT_MACHINE; | |||
| Atom WM_PROTOCOLS; | |||
| Atom WM_DELETE_WINDOW; | |||
| Atom PUGL_CLIENT_MSG; | |||
| Atom NET_CLOSE_WINDOW; | |||
| Atom NET_FRAME_EXTENTS; | |||
| Atom NET_WM_NAME; | |||
| Atom NET_WM_PID; | |||
| Atom NET_WM_PING; | |||
| Atom NET_WM_STATE; | |||
| Atom NET_WM_STATE_ABOVE; | |||
| Atom NET_WM_STATE_BELOW; | |||
| Atom NET_WM_STATE_DEMANDS_ATTENTION; | |||
| Atom NET_WM_STATE_FULLSCREEN; | |||
| Atom NET_WM_STATE_HIDDEN; | |||
| Atom NET_WM_STATE_MAXIMIZED_HORZ; | |||
| Atom NET_WM_STATE_MAXIMIZED_VERT; | |||
| Atom NET_WM_STATE_MODAL; | |||
| Atom NET_WM_WINDOW_TYPE; | |||
| Atom NET_WM_WINDOW_TYPE_DIALOG; | |||
| Atom NET_WM_WINDOW_TYPE_NORMAL; | |||
| Atom NET_WM_WINDOW_TYPE_UTILITY; | |||
| Atom TARGETS; | |||
| Atom text_uri_list; | |||
| } PuglX11Atoms; | |||
| @@ -70,8 +86,11 @@ struct PuglInternalsImpl { | |||
| PuglEvent pendingConfigure; | |||
| PuglEvent pendingExpose; | |||
| PuglX11Clipboard clipboard; | |||
| long frameExtentLeft; | |||
| long frameExtentTop; | |||
| int screen; | |||
| const char* cursorName; | |||
| bool mapped; | |||
| }; | |||
| PUGL_WARN_UNUSED_RESULT | |||
| @@ -1,13 +1,13 @@ | |||
| // Copyright 2012-2022 David Robillard <d@drobilla.net> | |||
| // Copyright 2012-2023 David Robillard <d@drobilla.net> | |||
| // SPDX-License-Identifier: ISC | |||
| #include "macros.h" | |||
| #include "types.h" | |||
| #include "x11.h" | |||
| #include "pugl/cairo.h" | |||
| #include "pugl/pugl.h" | |||
| #include <X11/Xutil.h> | |||
| #include <cairo-xlib.h> | |||
| #include <cairo.h> | |||
| @@ -19,6 +19,24 @@ typedef struct { | |||
| cairo_t* cr; | |||
| } PuglX11CairoSurface; | |||
| static PuglViewSize | |||
| puglX11CairoGetViewSize(const PuglView* const view) | |||
| { | |||
| PuglViewSize size = {0U, 0U}; | |||
| if (view->lastConfigure.type == PUGL_CONFIGURE) { | |||
| // Use the size of the last configured frame | |||
| size.width = view->lastConfigure.width; | |||
| size.height = view->lastConfigure.height; | |||
| } else { | |||
| // Use the default size | |||
| size.width = view->sizeHints[PUGL_DEFAULT_SIZE].width; | |||
| size.height = view->sizeHints[PUGL_DEFAULT_SIZE].height; | |||
| } | |||
| return size; | |||
| } | |||
| static void | |||
| puglX11CairoClose(PuglView* view) | |||
| { | |||
| @@ -31,22 +49,16 @@ puglX11CairoClose(PuglView* view) | |||
| } | |||
| static PuglStatus | |||
| puglX11CairoOpen(PuglView* view) | |||
| puglX11CairoOpen(PuglView* view, const PuglSpan width, const PuglSpan height) | |||
| { | |||
| PuglInternals* const impl = view->impl; | |||
| PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; | |||
| surface->back = cairo_xlib_surface_create(view->world->impl->display, | |||
| impl->win, | |||
| impl->vi->visual, | |||
| (int)view->frame.width, | |||
| (int)view->frame.height); | |||
| surface->back = cairo_xlib_surface_create( | |||
| view->world->impl->display, impl->win, impl->vi->visual, width, height); | |||
| surface->front = | |||
| cairo_surface_create_similar(surface->back, | |||
| cairo_surface_get_content(surface->back), | |||
| (int)view->frame.width, | |||
| (int)view->frame.height); | |||
| surface->front = cairo_surface_create_similar( | |||
| surface->back, cairo_surface_get_content(surface->back), width, height); | |||
| if (cairo_surface_status(surface->back) || | |||
| cairo_surface_status(surface->front)) { | |||
| @@ -84,9 +96,20 @@ puglX11CairoEnter(PuglView* view, const PuglExposeEvent* expose) | |||
| PuglX11CairoSurface* const surface = (PuglX11CairoSurface*)impl->surface; | |||
| PuglStatus st = PUGL_SUCCESS; | |||
| if (expose && !(st = puglX11CairoOpen(view))) { | |||
| surface->cr = cairo_create(surface->front); | |||
| st = cairo_status(surface->cr) ? PUGL_CREATE_CONTEXT_FAILED : PUGL_SUCCESS; | |||
| if (expose) { | |||
| const PuglViewSize viewSize = puglX11CairoGetViewSize(view); | |||
| const PuglSpan right = (PuglSpan)(expose->x + expose->width); | |||
| const PuglSpan bottom = (PuglSpan)(expose->y + expose->height); | |||
| const PuglSpan surfaceWidth = MAX(right, viewSize.width); | |||
| const PuglSpan surfaceHeight = MAX(bottom, viewSize.height); | |||
| if (!(st = puglX11CairoOpen(view, surfaceWidth, surfaceHeight))) { | |||
| surface->cr = cairo_create(surface->front); | |||
| if (cairo_status(surface->cr)) { | |||
| cairo_destroy(surface->cr); | |||
| surface->cr = NULL; | |||
| st = PUGL_CREATE_CONTEXT_FAILED; | |||
| } | |||
| } | |||
| } | |||
| return st; | |||
| @@ -52,18 +52,19 @@ puglX11GlConfigure(PuglView* view) | |||
| // clang-format off | |||
| const int attrs[] = { | |||
| GLX_X_RENDERABLE, True, | |||
| GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, | |||
| GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, | |||
| GLX_RENDER_TYPE, GLX_RGBA_BIT, | |||
| GLX_SAMPLES, puglX11GlHintValue(view->hints[PUGL_SAMPLES]), | |||
| GLX_RED_SIZE, puglX11GlHintValue(view->hints[PUGL_RED_BITS]), | |||
| GLX_GREEN_SIZE, puglX11GlHintValue(view->hints[PUGL_GREEN_BITS]), | |||
| GLX_BLUE_SIZE, puglX11GlHintValue(view->hints[PUGL_BLUE_BITS]), | |||
| GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints[PUGL_ALPHA_BITS]), | |||
| GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints[PUGL_DEPTH_BITS]), | |||
| GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints[PUGL_STENCIL_BITS]), | |||
| GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]), | |||
| GLX_X_RENDERABLE, True, | |||
| GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, | |||
| GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, | |||
| GLX_RENDER_TYPE, GLX_RGBA_BIT, | |||
| GLX_SAMPLE_BUFFERS, puglX11GlHintValue(view->hints[PUGL_SAMPLE_BUFFERS]), | |||
| GLX_SAMPLES, puglX11GlHintValue(view->hints[PUGL_SAMPLES]), | |||
| GLX_RED_SIZE, puglX11GlHintValue(view->hints[PUGL_RED_BITS]), | |||
| GLX_GREEN_SIZE, puglX11GlHintValue(view->hints[PUGL_GREEN_BITS]), | |||
| GLX_BLUE_SIZE, puglX11GlHintValue(view->hints[PUGL_BLUE_BITS]), | |||
| GLX_ALPHA_SIZE, puglX11GlHintValue(view->hints[PUGL_ALPHA_BITS]), | |||
| GLX_DEPTH_SIZE, puglX11GlHintValue(view->hints[PUGL_DEPTH_BITS]), | |||
| GLX_STENCIL_SIZE, puglX11GlHintValue(view->hints[PUGL_STENCIL_BITS]), | |||
| GLX_DOUBLEBUFFER, puglX11GlHintValue(view->hints[PUGL_DOUBLE_BUFFER]), | |||
| None | |||
| }; | |||
| // clang-format on | |||
| @@ -89,6 +90,8 @@ puglX11GlConfigure(PuglView* view) | |||
| puglX11GlGetAttrib(display, fbc[0], GLX_DEPTH_SIZE); | |||
| view->hints[PUGL_STENCIL_BITS] = | |||
| puglX11GlGetAttrib(display, fbc[0], GLX_STENCIL_SIZE); | |||
| view->hints[PUGL_SAMPLE_BUFFERS] = | |||
| puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLE_BUFFERS); | |||
| view->hints[PUGL_SAMPLES] = puglX11GlGetAttrib(display, fbc[0], GLX_SAMPLES); | |||
| view->hints[PUGL_DOUBLE_BUFFER] = | |||
| puglX11GlGetAttrib(display, fbc[0], GLX_DOUBLEBUFFER); | |||
| @@ -142,12 +145,14 @@ puglX11GlCreate(PuglView* view) | |||
| view->hints[PUGL_CONTEXT_VERSION_MINOR], | |||
| GLX_CONTEXT_FLAGS_ARB, | |||
| (view->hints[PUGL_USE_DEBUG_CONTEXT] ? GLX_CONTEXT_DEBUG_BIT_ARB : 0), | |||
| (view->hints[PUGL_CONTEXT_DEBUG] ? GLX_CONTEXT_DEBUG_BIT_ARB : 0), | |||
| GLX_CONTEXT_PROFILE_MASK_ARB, | |||
| (view->hints[PUGL_USE_COMPAT_PROFILE] | |||
| ? GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB | |||
| : GLX_CONTEXT_CORE_PROFILE_BIT_ARB), | |||
| (view->hints[PUGL_CONTEXT_API] == PUGL_OPENGL_ES_API | |||
| ? GLX_CONTEXT_ES2_PROFILE_BIT_EXT | |||
| : (view->hints[PUGL_CONTEXT_PROFILE] == PUGL_OPENGL_COMPATIBILITY_PROFILE | |||
| ? GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB | |||
| : GLX_CONTEXT_CORE_PROFILE_BIT_ARB)), | |||
| 0}; | |||
| const char* const extensions = | |||
| @@ -26,16 +26,25 @@ struct PuglVulkanLoaderImpl { | |||
| }; | |||
| PuglVulkanLoader* | |||
| puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world)) | |||
| puglNewVulkanLoader(PuglWorld* PUGL_UNUSED(world), | |||
| const char* const libraryName) | |||
| { | |||
| const char* const filename = libraryName ? libraryName : "libvulkan.so"; | |||
| void* const libvulkan = dlopen(filename, RTLD_LAZY); | |||
| if (!libvulkan) { | |||
| return NULL; | |||
| } | |||
| PuglVulkanLoader* const loader = | |||
| (PuglVulkanLoader*)calloc(1, sizeof(PuglVulkanLoader)); | |||
| if (!loader || !(loader->libvulkan = dlopen("libvulkan.so", RTLD_LAZY))) { | |||
| free(loader); | |||
| if (!loader) { | |||
| dlclose(libvulkan); | |||
| return NULL; | |||
| } | |||
| loader->libvulkan = libvulkan; | |||
| loader->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)dlsym( | |||
| loader->libvulkan, "vkGetInstanceProcAddr"); | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -37,16 +37,20 @@ | |||
| #include <cstring> | |||
| #include <ctime> | |||
| #if defined(DISTRHO_OS_MAC) | |||
| #if defined(DISTRHO_OS_HAIKU) | |||
| # include <Application.h> | |||
| # include <Window.h> | |||
| # ifdef DGL_OPENGL | |||
| # include <GL/gl.h> | |||
| # include <opengl/GLView.h> | |||
| # endif | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| # import <Cocoa/Cocoa.h> | |||
| # include <dlfcn.h> | |||
| # include <mach/mach_time.h> | |||
| # ifdef DGL_CAIRO | |||
| # include <cairo-quartz.h> | |||
| # endif | |||
| # ifdef DGL_OPENGL | |||
| # include <OpenGL/gl.h> | |||
| # endif | |||
| # ifdef DGL_VULKAN | |||
| # import <QuartzCore/CAMetalLayer.h> | |||
| # include <vulkan/vulkan_macos.h> | |||
| @@ -106,33 +110,56 @@ | |||
| # endif | |||
| #endif | |||
| #ifndef DGL_FILE_BROWSER_DISABLED | |||
| #ifdef DGL_USE_FILE_BROWSER | |||
| # define DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED | |||
| # define FILE_BROWSER_DIALOG_DGL_NAMESPACE | |||
| # define FILE_BROWSER_DIALOG_NAMESPACE DGL_NAMESPACE | |||
| # define DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED | |||
| START_NAMESPACE_DGL | |||
| # include "../../distrho/extra/FileBrowserDialogImpl.hpp" | |||
| END_NAMESPACE_DGL | |||
| # include "../../distrho/extra/FileBrowserDialogImpl.cpp" | |||
| #endif | |||
| #ifdef DGL_USE_WEB_VIEW | |||
| # define DGL_WEB_VIEW_HPP_INCLUDED | |||
| # define WEB_VIEW_NAMESPACE DGL_NAMESPACE | |||
| # define WEB_VIEW_DGL_NAMESPACE | |||
| START_NAMESPACE_DGL | |||
| # include "../../distrho/extra/WebViewImpl.hpp" | |||
| END_NAMESPACE_DGL | |||
| # include "../../distrho/extra/WebViewImpl.cpp" | |||
| #endif | |||
| #if defined(DGL_USING_X11) && defined(DGL_X11_WINDOW_ICON_NAME) | |||
| extern const ulong* DGL_X11_WINDOW_ICON_NAME; | |||
| #endif | |||
| #ifndef DISTRHO_OS_MAC | |||
| START_NAMESPACE_DGL | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| #if defined(DISTRHO_OS_MAC) | |||
| #if defined(DISTRHO_OS_HAIKU) | |||
| # include "pugl-extra/haiku.cpp" | |||
| # include "pugl-extra/haiku_stub.cpp" | |||
| # ifdef DGL_OPENGL | |||
| # include "pugl-extra/haiku_gl.cpp" | |||
| # endif | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| # ifndef DISTRHO_MACOS_NAMESPACE_MACRO | |||
| # define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, SEP, INTERFACE) NS ## SEP ## INTERFACE | |||
| # define DISTRHO_MACOS_NAMESPACE_MACRO(NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, _, INTERFACE) | |||
| # define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglCairoView) | |||
| # define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglOpenGLView) | |||
| # define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglStubView) | |||
| # define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglVulkanView) | |||
| # define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWindow) | |||
| # define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWindowDelegate) | |||
| # define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWrapperView) | |||
| # ifndef DISTRHO_MACOS_NAMESPACE_TIME | |||
| # define DISTRHO_MACOS_NAMESPACE_TIME __apple_build_version__ | |||
| # endif | |||
| # define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, SEP, TIME, INTERFACE) NS ## SEP ## TIME ## SEP ## INTERFACE | |||
| # define DISTRHO_MACOS_NAMESPACE_MACRO(NS, TIME, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, _, TIME, INTERFACE) | |||
| # define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglCairoView) | |||
| # define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglOpenGLView) | |||
| # define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglStubView) | |||
| # define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglVulkanView) | |||
| # define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglWindow) | |||
| # define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglWindowDelegate) | |||
| # define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, DISTRHO_MACOS_NAMESPACE_TIME, PuglWrapperView) | |||
| # endif | |||
| # pragma clang diagnostic push | |||
| # pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
| @@ -149,10 +176,10 @@ START_NAMESPACE_DGL | |||
| # endif | |||
| # pragma clang diagnostic pop | |||
| #elif defined(DISTRHO_OS_WASM) | |||
| # include "pugl-upstream/src/wasm.c" | |||
| # include "pugl-upstream/src/wasm_stub.c" | |||
| # include "pugl-extra/wasm.c" | |||
| # include "pugl-extra/wasm_stub.c" | |||
| # ifdef DGL_OPENGL | |||
| # include "pugl-upstream/src/wasm_gl.c" | |||
| # include "pugl-extra/wasm_gl.c" | |||
| # endif | |||
| #elif defined(DISTRHO_OS_WINDOWS) | |||
| # include "pugl-upstream/src/win.c" | |||
| @@ -167,7 +194,14 @@ START_NAMESPACE_DGL | |||
| # include "pugl-upstream/src/win_vulkan.c" | |||
| # endif | |||
| #elif defined(HAVE_X11) | |||
| # if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) | |||
| # pragma GCC diagnostic push | |||
| # pragma GCC diagnostic ignored "-Wsign-conversion" | |||
| # endif | |||
| # include "pugl-upstream/src/x11.c" | |||
| # if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) | |||
| # pragma GCC diagnostic pop | |||
| # endif | |||
| # include "pugl-upstream/src/x11_stub.c" | |||
| # ifdef DGL_CAIRO | |||
| # include "pugl-upstream/src/x11_cairo.c" | |||
| @@ -204,17 +238,38 @@ bool puglBackendLeave(PuglView* const view) | |||
| void puglSetMatchingBackendForCurrentBuild(PuglView* const view) | |||
| { | |||
| #ifdef DGL_CAIRO | |||
| #ifdef DGL_CAIRO | |||
| puglSetBackend(view, puglCairoBackend()); | |||
| #endif | |||
| #ifdef DGL_OPENGL | |||
| #endif | |||
| #ifdef DGL_OPENGL | |||
| puglSetBackend(view, puglGlBackend()); | |||
| #endif | |||
| #ifdef DGL_VULKAN | |||
| #endif | |||
| #ifdef DGL_VULKAN | |||
| puglSetBackend(view, puglVulkanBackend()); | |||
| #endif | |||
| if (view->backend == nullptr) | |||
| #endif | |||
| if (view->backend != nullptr) | |||
| { | |||
| #ifdef DGL_OPENGL | |||
| #if defined(DGL_USE_GLES2) | |||
| puglSetViewHint(view, PUGL_CONTEXT_API, PUGL_OPENGL_ES_API); | |||
| puglSetViewHint(view, PUGL_CONTEXT_PROFILE, PUGL_OPENGL_CORE_PROFILE); | |||
| puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2); | |||
| #elif defined(DGL_USE_OPENGL3) | |||
| puglSetViewHint(view, PUGL_CONTEXT_API, PUGL_OPENGL_API); | |||
| puglSetViewHint(view, PUGL_CONTEXT_PROFILE, PUGL_OPENGL_CORE_PROFILE); | |||
| puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3); | |||
| #else | |||
| puglSetViewHint(view, PUGL_CONTEXT_API, PUGL_OPENGL_API); | |||
| puglSetViewHint(view, PUGL_CONTEXT_PROFILE, PUGL_OPENGL_COMPATIBILITY_PROFILE); | |||
| puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2); | |||
| #endif | |||
| #endif | |||
| } | |||
| else | |||
| { | |||
| puglSetBackend(view, puglStubBackend()); | |||
| } | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -222,7 +277,8 @@ void puglSetMatchingBackendForCurrentBuild(PuglView* const view) | |||
| void puglRaiseWindow(PuglView* const view) | |||
| { | |||
| #if defined(DISTRHO_OS_MAC) | |||
| #if defined(DISTRHO_OS_HAIKU) | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| if (NSWindow* const window = view->impl->window ? view->impl->window | |||
| : [view->impl->wrapperView window]) | |||
| [window orderFrontRegardless]; | |||
| @@ -236,60 +292,28 @@ void puglRaiseWindow(PuglView* const view) | |||
| #endif | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // get scale factor from parent window if possible, fallback to puglGetScaleFactor | |||
| double puglGetScaleFactorFromParent(const PuglView* const view) | |||
| { | |||
| const PuglNativeView parent = view->parent ? view->parent : view->transientParent ? view->transientParent : 0; | |||
| #if defined(DISTRHO_OS_MAC) | |||
| // some of these can return 0 as backingScaleFactor, pick the most relevant valid one | |||
| const NSWindow* possibleWindows[] = { | |||
| parent != 0 ? [(NSView*)parent window] : nullptr, | |||
| view->impl->window, | |||
| [view->impl->wrapperView window] | |||
| }; | |||
| for (size_t i=0; i<ARRAY_SIZE(possibleWindows); ++i) | |||
| { | |||
| if (possibleWindows[i] == nullptr) | |||
| continue; | |||
| if (const double scaleFactor = [[possibleWindows[i] screen] backingScaleFactor]) | |||
| return scaleFactor; | |||
| } | |||
| return [[NSScreen mainScreen] backingScaleFactor]; | |||
| #elif defined(DISTRHO_OS_WINDOWS) | |||
| const HWND hwnd = parent != 0 ? (HWND)parent : view->impl->hwnd; | |||
| return puglWinGetViewScaleFactor(hwnd); | |||
| #else | |||
| return puglGetScaleFactor(view); | |||
| // unused | |||
| (void)parent; | |||
| #endif | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Combined puglSetSizeHint using PUGL_MIN_SIZE and PUGL_FIXED_ASPECT | |||
| PuglStatus puglSetGeometryConstraints(PuglView* const view, const uint width, const uint height, const bool aspect) | |||
| { | |||
| view->sizeHints[PUGL_MIN_SIZE].width = width; | |||
| view->sizeHints[PUGL_MIN_SIZE].height = height; | |||
| view->sizeHints[PUGL_MIN_SIZE].width = static_cast<PuglSpan>(width); | |||
| view->sizeHints[PUGL_MIN_SIZE].height = static_cast<PuglSpan>(height); | |||
| if (aspect) | |||
| { | |||
| view->sizeHints[PUGL_FIXED_ASPECT].width = width; | |||
| view->sizeHints[PUGL_FIXED_ASPECT].height = height; | |||
| view->sizeHints[PUGL_FIXED_ASPECT].width = static_cast<PuglSpan>(width); | |||
| view->sizeHints[PUGL_FIXED_ASPECT].height = static_cast<PuglSpan>(height); | |||
| } | |||
| #if defined(DISTRHO_OS_MAC) | |||
| #if defined(DISTRHO_OS_HAIKU) | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| if (view->impl->window) | |||
| { | |||
| PuglStatus status; | |||
| if ((status = updateSizeHint(view, PUGL_MIN_SIZE)) != PUGL_SUCCESS) | |||
| if (const PuglStatus status = updateSizeHint(view, PUGL_MIN_SIZE)) | |||
| return status; | |||
| if (aspect && (status = updateSizeHint(view, PUGL_FIXED_ASPECT)) != PUGL_SUCCESS) | |||
| if (const PuglStatus status = updateSizeHint(view, PUGL_FIXED_ASPECT)) | |||
| return status; | |||
| } | |||
| #elif defined(DISTRHO_OS_WASM) | |||
| @@ -297,10 +321,13 @@ PuglStatus puglSetGeometryConstraints(PuglView* const view, const uint width, co | |||
| #elif defined(DISTRHO_OS_WINDOWS) | |||
| // nothing | |||
| #elif defined(HAVE_X11) | |||
| if (const PuglStatus status = updateSizeHints(view)) | |||
| return status; | |||
| if (view->impl->win) | |||
| { | |||
| if (const PuglStatus status = updateSizeHints(view)) | |||
| return status; | |||
| XFlush(view->world->impl->display); | |||
| XFlush(view->world->impl->display); | |||
| } | |||
| #endif | |||
| return PUGL_SUCCESS; | |||
| @@ -313,7 +340,8 @@ void puglSetResizable(PuglView* const view, const bool resizable) | |||
| { | |||
| puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE); | |||
| #if defined(DISTRHO_OS_MAC) | |||
| #if defined(DISTRHO_OS_HAIKU) | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| if (PuglWindow* const window = view->impl->window) | |||
| { | |||
| const uint style = (NSClosableWindowMask | NSTitledWindowMask | NSMiniaturizableWindowMask) | |||
| @@ -343,58 +371,66 @@ PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height) | |||
| if (width > INT16_MAX || height > INT16_MAX) | |||
| return PUGL_BAD_PARAMETER; | |||
| view->sizeHints[PUGL_DEFAULT_SIZE].width = view->frame.width = static_cast<PuglSpan>(width); | |||
| view->sizeHints[PUGL_DEFAULT_SIZE].height = view->frame.height = static_cast<PuglSpan>(height); | |||
| #if defined(DISTRHO_OS_MAC) | |||
| // mostly matches upstream pugl, simplified | |||
| PuglInternals* const impl = view->impl; | |||
| const PuglRect frame = view->frame; | |||
| const NSRect framePx = rectToNsRect(frame); | |||
| const NSRect framePt = nsRectToPoints(view, framePx); | |||
| if (PuglWindow* const window = view->impl->window) | |||
| #ifdef DGL_USING_X11 | |||
| // workaround issues in fluxbox, see https://github.com/lv2/pugl/issues/118 | |||
| // NOTE troublesome if used under KDE | |||
| if (view->impl->win && !view->parent && !view->transientParent && std::getenv("KDE_SESSION_VERSION") == nullptr) | |||
| { | |||
| view->sizeHints[PUGL_DEFAULT_SIZE].width = view->sizeHints[PUGL_DEFAULT_SIZE].height = 0; | |||
| } | |||
| else | |||
| #endif | |||
| // set default size first | |||
| { | |||
| const NSRect screenPt = rectToScreen(viewScreen(view), framePt); | |||
| const NSRect winFrame = [window frameRectForContentRect:screenPt]; | |||
| [window setFrame:winFrame display:NO]; | |||
| view->sizeHints[PUGL_DEFAULT_SIZE].width = static_cast<PuglSpan>(width); | |||
| view->sizeHints[PUGL_DEFAULT_SIZE].height = static_cast<PuglSpan>(height); | |||
| } | |||
| const NSSize sizePx = NSMakeSize(frame.width, frame.height); | |||
| const NSSize sizePt = [impl->drawView convertSizeFromBacking:sizePx]; | |||
| [impl->wrapperView setFrameSize:sizePt]; | |||
| [impl->drawView setFrameSize:sizePt]; | |||
| #if defined(DISTRHO_OS_HAIKU) | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| // matches upstream pugl | |||
| if (view->impl->wrapperView) | |||
| { | |||
| if (const PuglStatus status = puglSetSize(view, width, height)) | |||
| return status; | |||
| // nothing to do for PUGL_DEFAULT_SIZE hint | |||
| } | |||
| #elif defined(DISTRHO_OS_WASM) | |||
| d_stdout("className is %s", view->world->className); | |||
| emscripten_set_canvas_element_size(view->world->className, width, height); | |||
| d_stdout("className is %s", view->world->strings[PUGL_CLASS_NAME]); | |||
| emscripten_set_canvas_element_size(view->world->strings[PUGL_CLASS_NAME], width, height); | |||
| #elif defined(DISTRHO_OS_WINDOWS) | |||
| // matches upstream pugl, except we re-enter context after resize | |||
| if (const HWND hwnd = view->impl->hwnd) | |||
| if (view->impl->hwnd) | |||
| { | |||
| const RECT rect = adjustedWindowRect(view, view->frame.x, view->frame.y, | |||
| static_cast<long>(width), static_cast<long>(height)); | |||
| if (const PuglStatus status = puglSetSize(view, width, height)) | |||
| return status; | |||
| if (!SetWindowPos(hwnd, HWND_TOP, 0, 0, rect.right - rect.left, rect.bottom - rect.top, | |||
| SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE)) | |||
| return PUGL_UNKNOWN_ERROR; | |||
| // nothing to do for PUGL_DEFAULT_SIZE hint | |||
| // make sure to return context back to ourselves | |||
| puglBackendEnter(view); | |||
| } | |||
| #elif defined(HAVE_X11) | |||
| // matches upstream pugl, all in one | |||
| if (const Window window = view->impl->win) | |||
| // matches upstream pugl, adds flush at the end | |||
| if (view->impl->win) | |||
| { | |||
| Display* const display = view->world->impl->display; | |||
| if (const PuglStatus status = puglSetSize(view, width, height)) | |||
| return status; | |||
| if (! XResizeWindow(display, window, width, height)) | |||
| return PUGL_UNKNOWN_ERROR; | |||
| // updateSizeHints will use last known size, which is not yet updated | |||
| const PuglSpan lastWidth = view->lastConfigure.width; | |||
| const PuglSpan lastHeight = view->lastConfigure.height; | |||
| view->lastConfigure.width = static_cast<PuglSpan>(width); | |||
| view->lastConfigure.height = static_cast<PuglSpan>(height); | |||
| if (const PuglStatus status = updateSizeHints(view)) | |||
| return status; | |||
| updateSizeHints(view); | |||
| XFlush(display); | |||
| view->lastConfigure.width = lastWidth; | |||
| view->lastConfigure.height = lastHeight; | |||
| // flush size changes | |||
| XFlush(view->world->impl->display); | |||
| } | |||
| #endif | |||
| @@ -406,42 +442,46 @@ PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height) | |||
| void puglOnDisplayPrepare(PuglView*) | |||
| { | |||
| #ifdef DGL_OPENGL | |||
| #ifdef DGL_OPENGL | |||
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |||
| # ifndef DGL_USE_GLES | |||
| #ifndef DGL_USE_OPENGL3 | |||
| glLoadIdentity(); | |||
| # endif | |||
| #endif | |||
| #endif | |||
| #endif | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // DGL specific, build-specific fallback resize | |||
| void puglFallbackOnResize(PuglView* const view) | |||
| void puglFallbackOnResize(PuglView* const view, const uint width, const uint height) | |||
| { | |||
| #ifdef DGL_OPENGL | |||
| #ifdef DGL_OPENGL | |||
| glEnable(GL_BLEND); | |||
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |||
| # ifndef DGL_USE_GLES | |||
| #ifdef DGL_USE_OPENGL3 | |||
| glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height)); | |||
| #else | |||
| glMatrixMode(GL_PROJECTION); | |||
| glLoadIdentity(); | |||
| glOrtho(0.0, static_cast<GLdouble>(view->frame.width), static_cast<GLdouble>(view->frame.height), 0.0, 0.0, 1.0); | |||
| glViewport(0, 0, static_cast<GLsizei>(view->frame.width), static_cast<GLsizei>(view->frame.height)); | |||
| glOrtho(0.0, static_cast<GLdouble>(width), static_cast<GLdouble>(height), 0.0, 0.0, 1.0); | |||
| glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height)); | |||
| glMatrixMode(GL_MODELVIEW); | |||
| glLoadIdentity(); | |||
| # else | |||
| glViewport(0, 0, static_cast<GLsizei>(view->frame.width), static_cast<GLsizei>(view->frame.height)); | |||
| # endif | |||
| #else | |||
| #endif | |||
| #else | |||
| return; | |||
| // unused | |||
| (void)view; | |||
| #endif | |||
| #endif | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| #if defined(DISTRHO_OS_MAC) | |||
| #if defined(DISTRHO_OS_HAIKU) | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // macOS specific, add another view's window as child | |||
| @@ -488,7 +528,7 @@ puglMacOSRemoveChildWindow(PuglView* const view, PuglView* const child) | |||
| void puglMacOSShowCentered(PuglView* const view) | |||
| { | |||
| if (puglShow(view) != PUGL_SUCCESS) | |||
| if (puglShow(view, PUGL_SHOW_RAISE) != PUGL_SUCCESS) | |||
| return; | |||
| if (view->transientParent != 0) | |||
| @@ -540,34 +580,21 @@ void puglWin32ShowCentered(PuglView* const view) | |||
| GetWindowRect(impl->hwnd, &rectChild) && | |||
| GetWindowRect((HWND)view->transientParent, &rectParent)) | |||
| { | |||
| SetWindowPos(impl->hwnd, (HWND)view->transientParent, | |||
| rectParent.left + (rectChild.right-rectChild.left)/2, | |||
| rectParent.top + (rectChild.bottom-rectChild.top)/2, | |||
| SetWindowPos(impl->hwnd, HWND_TOP, | |||
| rectParent.left + (rectParent.right-rectParent.left)/2 - (rectChild.right-rectChild.left)/2, | |||
| rectParent.top + (rectParent.bottom-rectParent.top)/2 - (rectChild.bottom-rectChild.top)/2, | |||
| 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE); | |||
| } | |||
| else | |||
| { | |||
| #ifdef DGL_WINDOWS_ICON_ID | |||
| WNDCLASSEX wClass; | |||
| std::memset(&wClass, 0, sizeof(wClass)); | |||
| const HINSTANCE hInstance = GetModuleHandle(nullptr); | |||
| if (GetClassInfoEx(hInstance, view->world->className, &wClass)) | |||
| wClass.hIcon = LoadIcon(nullptr, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID)); | |||
| SetClassLongPtr(impl->hwnd, GCLP_HICON, (LONG_PTR) LoadIcon(hInstance, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID))); | |||
| #endif | |||
| MONITORINFO mInfo; | |||
| std::memset(&mInfo, 0, sizeof(mInfo)); | |||
| mInfo.cbSize = sizeof(mInfo); | |||
| if (GetMonitorInfo(MonitorFromWindow(impl->hwnd, MONITOR_DEFAULTTOPRIMARY), &mInfo)) | |||
| SetWindowPos(impl->hwnd, | |||
| HWND_TOP, | |||
| mInfo.rcWork.left + (mInfo.rcWork.right - mInfo.rcWork.left - view->frame.width) / 2, | |||
| mInfo.rcWork.top + (mInfo.rcWork.bottom - mInfo.rcWork.top - view->frame.height) / 2, | |||
| SetWindowPos(impl->hwnd, HWND_TOP, | |||
| mInfo.rcWork.left + (mInfo.rcWork.right - mInfo.rcWork.left - view->lastConfigure.width) / 2, | |||
| mInfo.rcWork.top + (mInfo.rcWork.bottom - mInfo.rcWork.top - view->lastConfigure.height) / 2, | |||
| 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE); | |||
| else | |||
| ShowWindow(impl->hwnd, SW_NORMAL); | |||
| @@ -586,6 +613,9 @@ void puglWin32ShowCentered(PuglView* const view) | |||
| #elif defined(HAVE_X11) | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // X11 specific, update world without triggering exposure events | |||
| PuglStatus puglX11UpdateWithoutExposures(PuglWorld* const world) | |||
| { | |||
| const bool wasDispatchingEvents = world->impl->dispatchingEvents; | |||
| @@ -617,6 +647,15 @@ void puglX11SetWindowTypeAndPID(const PuglView* const view, const bool isStandal | |||
| const Atom _nwp = XInternAtom(display, "_NET_WM_PID", False); | |||
| XChangeProperty(display, impl->win, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1); | |||
| #if defined(DGL_X11_WINDOW_ICON_NAME) && defined(DGL_X11_WINDOW_ICON_SIZE) | |||
| if (isStandalone) | |||
| { | |||
| const Atom _nwi = XInternAtom(display, "_NET_WM_ICON", False); | |||
| XChangeProperty(display, impl->win, _nwi, XA_CARDINAL, 32, PropModeReplace, | |||
| (const uchar*)DGL_X11_WINDOW_ICON_NAME, DGL_X11_WINDOW_ICON_SIZE); | |||
| } | |||
| #endif | |||
| const Atom _wt = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); | |||
| Atom _wts[2]; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2023 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -19,7 +19,7 @@ | |||
| #include "../Base.hpp" | |||
| /* we will include all header files used in pugl.h in their C++ friendly form, then pugl stuff in custom namespace */ | |||
| // we will include all header files used in pugl.h in their C++ friendly form, then pugl stuff in custom namespace | |||
| #include <cstddef> | |||
| #ifdef DISTRHO_PROPER_CPP11_SUPPORT | |||
| # include <cstdbool> | |||
| @@ -29,9 +29,26 @@ | |||
| # include <stdint.h> | |||
| #endif | |||
| // hidden api | |||
| // custom attributes | |||
| #define PUGL_ATTRIBUTES_H | |||
| #define PUGL_BEGIN_DECLS | |||
| #define PUGL_END_DECLS | |||
| #define PUGL_API | |||
| #define PUGL_DISABLE_DEPRECATED | |||
| // GCC function attributes | |||
| #if defined(__GNUC__) && !defined(__clang__) | |||
| #define PUGL_CONST_FUNC __attribute__((const)) | |||
| #define PUGL_MALLOC_FUNC __attribute__((malloc)) | |||
| #else | |||
| #define PUGL_CONST_FUNC | |||
| #define PUGL_MALLOC_FUNC | |||
| #endif | |||
| #define PUGL_CONST_API PUGL_CONST_FUNC | |||
| #define PUGL_MALLOC_API PUGL_MALLOC_FUNC | |||
| // we do our own OpenGL inclusion | |||
| #define PUGL_NO_INCLUDE_GL_H | |||
| #define PUGL_NO_INCLUDE_GLU_H | |||
| @@ -39,7 +56,7 @@ | |||
| START_NAMESPACE_DGL | |||
| #endif | |||
| #include "pugl-upstream/include/pugl/pugl.h" | |||
| #include "pugl/pugl.h" | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -55,9 +72,6 @@ void puglSetMatchingBackendForCurrentBuild(PuglView* view); | |||
| // bring view window into the foreground, aka "raise" window | |||
| void puglRaiseWindow(PuglView* view); | |||
| // get scale factor from parent window if possible, fallback to puglGetScaleFactor | |||
| double puglGetScaleFactorFromParent(const PuglView* view); | |||
| // combined puglSetSizeHint using PUGL_MIN_SIZE, PUGL_MIN_ASPECT and PUGL_MAX_ASPECT | |||
| PuglStatus puglSetGeometryConstraints(PuglView* view, uint width, uint height, bool aspect); | |||
| @@ -71,9 +85,13 @@ PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height); | |||
| void puglOnDisplayPrepare(PuglView* view); | |||
| // DGL specific, build-specific fallback resize | |||
| void puglFallbackOnResize(PuglView* view); | |||
| void puglFallbackOnResize(PuglView* view, uint width, uint height); | |||
| #if defined(DISTRHO_OS_HAIKU) | |||
| // nothing here yet | |||
| #if defined(DISTRHO_OS_MAC) | |||
| #elif defined(DISTRHO_OS_MAC) | |||
| // macOS specific, add another view's window as child | |||
| PuglStatus puglMacOSAddChildWindow(PuglView* view, PuglView* child); | |||
| @@ -100,7 +118,7 @@ void puglWin32ShowCentered(PuglView* view); | |||
| #define DGL_USING_X11 | |||
| // X11 specific, update world without triggering exposure evente | |||
| // X11 specific, update world without triggering exposure events | |||
| PuglStatus puglX11UpdateWithoutExposures(PuglWorld* world); | |||
| // X11 specific, set dialog window type and pid hints | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -17,857 +17,12 @@ | |||
| #ifndef DISTRHO_PLUGIN_HPP_INCLUDED | |||
| #define DISTRHO_PLUGIN_HPP_INCLUDED | |||
| #include "extra/String.hpp" | |||
| #include "DistrhoDetails.hpp" | |||
| #include "extra/LeakDetector.hpp" | |||
| #include "src/DistrhoPluginChecks.h" | |||
| START_NAMESPACE_DISTRHO | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * Audio Port Hints */ | |||
| /** | |||
| @defgroup AudioPortHints Audio Port Hints | |||
| Various audio port hints. | |||
| @see AudioPort::hints | |||
| @{ | |||
| */ | |||
| /** | |||
| Audio port can be used as control voltage (LV2 and JACK standalone only). | |||
| */ | |||
| static const uint32_t kAudioPortIsCV = 0x1; | |||
| /** | |||
| Audio port should be used as sidechan (LV2 and VST3 only). | |||
| This hint should not be used with CV style ports. | |||
| @note non-sidechain audio ports must exist in the plugin if this flag is set. | |||
| */ | |||
| static const uint32_t kAudioPortIsSidechain = 0x2; | |||
| /** | |||
| CV port has bipolar range (-1 to +1, or -5 to +5 if scaled). | |||
| This is merely a hint to tell the host what value range to expect. | |||
| */ | |||
| static const uint32_t kCVPortHasBipolarRange = 0x10; | |||
| /** | |||
| CV port has negative unipolar range (-1 to 0, or -10 to 0 if scaled). | |||
| This is merely a hint to tell the host what value range to expect. | |||
| */ | |||
| static const uint32_t kCVPortHasNegativeUnipolarRange = 0x20; | |||
| /** | |||
| CV port has positive unipolar range (0 to +1, or 0 to +10 if scaled). | |||
| This is merely a hint to tell the host what value range to expect. | |||
| */ | |||
| static const uint32_t kCVPortHasPositiveUnipolarRange = 0x40; | |||
| /** | |||
| CV port has scaled range to match real values (-5 to +5v bipolar, +/-10 to 0v unipolar). | |||
| One other range flag is required if this flag is set. | |||
| When enabled, this makes the port a mod:CVPort, compatible with the MOD Devices platform. | |||
| */ | |||
| static const uint32_t kCVPortHasScaledRange = 0x80; | |||
| /** @} */ | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * Parameter Hints */ | |||
| /** | |||
| @defgroup ParameterHints Parameter Hints | |||
| Various parameter hints. | |||
| @see Parameter::hints | |||
| @{ | |||
| */ | |||
| /** | |||
| Parameter is automatable (real-time safe). | |||
| @see Plugin::setParameterValue(uint32_t, float) | |||
| */ | |||
| static const uint32_t kParameterIsAutomatable = 0x01; | |||
| /** It was a typo, sorry.. */ | |||
| DISTRHO_DEPRECATED_BY("kParameterIsAutomatable") | |||
| static const uint32_t kParameterIsAutomable = kParameterIsAutomatable; | |||
| /** | |||
| Parameter value is boolean.@n | |||
| It's always at either minimum or maximum value. | |||
| */ | |||
| static const uint32_t kParameterIsBoolean = 0x02; | |||
| /** | |||
| Parameter value is integer. | |||
| */ | |||
| static const uint32_t kParameterIsInteger = 0x04; | |||
| /** | |||
| Parameter value is logarithmic. | |||
| */ | |||
| static const uint32_t kParameterIsLogarithmic = 0x08; | |||
| /** | |||
| Parameter is of output type.@n | |||
| When unset, parameter is assumed to be of input type. | |||
| Parameter inputs are changed by the host and typically should not be changed by the plugin.@n | |||
| One exception is when changing programs, see Plugin::loadProgram().@n | |||
| The other exception is with parameter change requests, see Plugin::requestParameterValueChange().@n | |||
| Outputs are changed by the plugin and never modified by the host. | |||
| If you are targetting VST2, make sure to order your parameters so that all inputs are before any outputs. | |||
| */ | |||
| static const uint32_t kParameterIsOutput = 0x10; | |||
| /** | |||
| Parameter value is a trigger.@n | |||
| This means the value resets back to its default after each process/run call.@n | |||
| Cannot be used for output parameters. | |||
| @note Only officially supported under LV2. For other formats DPF simulates the behaviour. | |||
| */ | |||
| static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean; | |||
| /** @} */ | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * State Hints */ | |||
| /** | |||
| @defgroup StateHints State Hints | |||
| Various state hints. | |||
| @see State::hints | |||
| @{ | |||
| */ | |||
| /** | |||
| State is visible and readable by hosts that support string-type plugin parameters. | |||
| */ | |||
| static const uint32_t kStateIsHostReadable = 0x01; | |||
| /** | |||
| State is writable by the host, allowing users to arbitrarily change the state.@n | |||
| For obvious reasons a writable state is also readable by the host. | |||
| */ | |||
| static const uint32_t kStateIsHostWritable = 0x02 | kStateIsHostReadable; | |||
| /** | |||
| State is a filename path instead of a regular string.@n | |||
| The readable and writable hints are required for filenames to work, and thus are automatically set. | |||
| */ | |||
| static const uint32_t kStateIsFilenamePath = 0x04 | kStateIsHostWritable; | |||
| /** | |||
| State is a base64 encoded string. | |||
| */ | |||
| static const uint32_t kStateIsBase64Blob = 0x08; | |||
| /** | |||
| State is for Plugin/DSP side only, meaning there is never a need to notify the UI when it changes. | |||
| */ | |||
| static const uint32_t kStateIsOnlyForDSP = 0x10; | |||
| /** | |||
| State is for UI side only.@n | |||
| If the DSP and UI are separate and the UI is not available, this property won't be saved. | |||
| */ | |||
| static const uint32_t kStateIsOnlyForUI = 0x20; | |||
| /** @} */ | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * Base Plugin structs */ | |||
| /** | |||
| @defgroup BasePluginStructs Base Plugin Structs | |||
| @{ | |||
| */ | |||
| /** | |||
| Parameter designation.@n | |||
| Allows a parameter to be specially designated for a task, like bypass. | |||
| Each designation is unique, there must be only one parameter that uses it.@n | |||
| The use of designated parameters is completely optional. | |||
| @note Designated parameters have strict ranges. | |||
| @see ParameterRanges::adjustForDesignation() | |||
| */ | |||
| enum ParameterDesignation { | |||
| /** | |||
| Null or unset designation. | |||
| */ | |||
| kParameterDesignationNull = 0, | |||
| /** | |||
| Bypass designation.@n | |||
| When on (> 0.5f), it means the plugin must run in a bypassed state. | |||
| */ | |||
| kParameterDesignationBypass = 1 | |||
| }; | |||
| /** | |||
| Predefined Port Groups Ids. | |||
| This enumeration provides a few commonly used groups for convenient use in plugins. | |||
| For preventing conflicts with user code, negative values are used here. | |||
| When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential. | |||
| @see PortGroup | |||
| */ | |||
| enum PredefinedPortGroupsIds { | |||
| /** | |||
| Null or unset port group. | |||
| */ | |||
| kPortGroupNone = (uint32_t)-1, | |||
| /** | |||
| A single channel audio group. | |||
| */ | |||
| kPortGroupMono = (uint32_t)-2, | |||
| /** | |||
| A 2-channel discrete stereo audio group, | |||
| where the 1st audio port is the left channel and the 2nd port is the right channel. | |||
| */ | |||
| kPortGroupStereo = (uint32_t)-3 | |||
| }; | |||
| /** | |||
| Audio Port. | |||
| Can be used as CV port by specifying kAudioPortIsCV in hints,@n | |||
| but this is only supported in LV2 and JACK standalone formats. | |||
| */ | |||
| struct AudioPort { | |||
| /** | |||
| Hints describing this audio port. | |||
| @see AudioPortHints | |||
| */ | |||
| uint32_t hints; | |||
| /** | |||
| The name of this audio port.@n | |||
| An audio port name can contain any character, but hosts might have a hard time with non-ascii ones.@n | |||
| The name doesn't have to be unique within a plugin instance, but it's recommended. | |||
| */ | |||
| String name; | |||
| /** | |||
| The symbol of this audio port.@n | |||
| An audio port symbol is a short restricted name used as a machine and human readable identifier.@n | |||
| The first character must be one of _, a-z or A-Z and subsequent characters can be from _, a-z, A-Z and 0-9. | |||
| @note Audio port and parameter symbols MUST be unique within a plugin instance. | |||
| */ | |||
| String symbol; | |||
| /** | |||
| The group id that this audio/cv port belongs to. | |||
| No group is assigned by default. | |||
| You can use a group from PredefinedPortGroups or roll your own.@n | |||
| When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential. | |||
| @see PortGroup, Plugin::initPortGroup | |||
| */ | |||
| uint32_t groupId; | |||
| /** | |||
| Default constructor for a regular audio port. | |||
| */ | |||
| AudioPort() noexcept | |||
| : hints(0x0), | |||
| name(), | |||
| symbol(), | |||
| groupId(kPortGroupNone) {} | |||
| }; | |||
| /** | |||
| Parameter ranges.@n | |||
| This is used to set the default, minimum and maximum values of a parameter. | |||
| By default a parameter has 0.0 as minimum, 1.0 as maximum and 0.0 as default.@n | |||
| When changing this struct values you must ensure maximum > minimum and default is within range. | |||
| */ | |||
| struct ParameterRanges { | |||
| /** | |||
| Default value. | |||
| */ | |||
| float def; | |||
| /** | |||
| Minimum value. | |||
| */ | |||
| float min; | |||
| /** | |||
| Maximum value. | |||
| */ | |||
| float max; | |||
| /** | |||
| Default constructor, using 0.0 as default, 0.0 as minimum, 1.0 as maximum. | |||
| */ | |||
| ParameterRanges() noexcept | |||
| : def(0.0f), | |||
| min(0.0f), | |||
| max(1.0f) {} | |||
| /** | |||
| Constructor using custom values. | |||
| */ | |||
| ParameterRanges(float df, float mn, float mx) noexcept | |||
| : def(df), | |||
| min(mn), | |||
| max(mx) {} | |||
| /** | |||
| Fix the default value within range. | |||
| */ | |||
| void fixDefault() noexcept | |||
| { | |||
| fixValue(def); | |||
| } | |||
| /** | |||
| Fix a value within range. | |||
| */ | |||
| void fixValue(float& value) const noexcept | |||
| { | |||
| if (value < min) | |||
| value = min; | |||
| else if (value > max) | |||
| value = max; | |||
| } | |||
| /** | |||
| Get a fixed value within range. | |||
| */ | |||
| float getFixedValue(const float& value) const noexcept | |||
| { | |||
| if (value <= min) | |||
| return min; | |||
| if (value >= max) | |||
| return max; | |||
| return value; | |||
| } | |||
| /** | |||
| Get a value normalized to 0.0<->1.0. | |||
| */ | |||
| float getNormalizedValue(const float& value) const noexcept | |||
| { | |||
| const float normValue((value - min) / (max - min)); | |||
| if (normValue <= 0.0f) | |||
| return 0.0f; | |||
| if (normValue >= 1.0f) | |||
| return 1.0f; | |||
| return normValue; | |||
| } | |||
| /** | |||
| Get a value normalized to 0.0<->1.0, fixed within range. | |||
| */ | |||
| float getFixedAndNormalizedValue(const float& value) const noexcept | |||
| { | |||
| if (value <= min) | |||
| return 0.0f; | |||
| if (value >= max) | |||
| return 1.0f; | |||
| const float normValue((value - min) / (max - min)); | |||
| if (normValue <= 0.0f) | |||
| return 0.0f; | |||
| if (normValue >= 1.0f) | |||
| return 1.0f; | |||
| return normValue; | |||
| } | |||
| /** | |||
| Get a proper value previously normalized to 0.0<->1.0. | |||
| */ | |||
| float getUnnormalizedValue(const float& value) const noexcept | |||
| { | |||
| if (value <= 0.0f) | |||
| return min; | |||
| if (value >= 1.0f) | |||
| return max; | |||
| return value * (max - min) + min; | |||
| } | |||
| }; | |||
| /** | |||
| Parameter enumeration value.@n | |||
| A string representation of a plugin parameter value.@n | |||
| Used together can be used to give meaning to parameter values, working as an enumeration. | |||
| */ | |||
| struct ParameterEnumerationValue { | |||
| /** | |||
| Parameter value. | |||
| */ | |||
| float value; | |||
| /** | |||
| String representation of this value. | |||
| */ | |||
| String label; | |||
| /** | |||
| Default constructor, using 0.0 as value and empty label. | |||
| */ | |||
| ParameterEnumerationValue() noexcept | |||
| : value(0.0f), | |||
| label() {} | |||
| /** | |||
| Constructor using custom values. | |||
| */ | |||
| ParameterEnumerationValue(float v, const char* l) noexcept | |||
| : value(v), | |||
| label(l) {} | |||
| }; | |||
| /** | |||
| Collection of parameter enumeration values.@n | |||
| Handy class to handle the lifetime and count of all enumeration values. | |||
| */ | |||
| struct ParameterEnumerationValues { | |||
| /** | |||
| Number of elements allocated in @values. | |||
| */ | |||
| uint8_t count; | |||
| /** | |||
| Wherever the host is to be restricted to only use enumeration values. | |||
| @note This mode is only a hint! Not all hosts and plugin formats support this mode. | |||
| */ | |||
| bool restrictedMode; | |||
| /** | |||
| Array of @ParameterEnumerationValue items.@n | |||
| This pointer must be null or have been allocated on the heap with `new ParameterEnumerationValue[count]`. | |||
| */ | |||
| ParameterEnumerationValue* values; | |||
| /** | |||
| Default constructor, for zero enumeration values. | |||
| */ | |||
| ParameterEnumerationValues() noexcept | |||
| : count(0), | |||
| restrictedMode(false), | |||
| values() {} | |||
| /** | |||
| Constructor using custom values.@n | |||
| The pointer to @values must have been allocated on the heap with `new`. | |||
| */ | |||
| ParameterEnumerationValues(uint32_t c, bool r, ParameterEnumerationValue* v) noexcept | |||
| : count(c), | |||
| restrictedMode(r), | |||
| values(v) {} | |||
| ~ParameterEnumerationValues() noexcept | |||
| { | |||
| count = 0; | |||
| restrictedMode = false; | |||
| if (values != nullptr) | |||
| { | |||
| delete[] values; | |||
| values = nullptr; | |||
| } | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE(ParameterEnumerationValues) | |||
| }; | |||
| /** | |||
| Parameter. | |||
| */ | |||
| struct Parameter { | |||
| /** | |||
| Hints describing this parameter. | |||
| @see ParameterHints | |||
| */ | |||
| uint32_t hints; | |||
| /** | |||
| The name of this parameter.@n | |||
| A parameter name can contain any character, but hosts might have a hard time with non-ascii ones.@n | |||
| The name doesn't have to be unique within a plugin instance, but it's recommended. | |||
| */ | |||
| String name; | |||
| /** | |||
| The short name of this parameter.@n | |||
| Used when displaying the parameter name in a very limited space. | |||
| @note This value is optional, the full name is used when the short one is missing. | |||
| */ | |||
| String shortName; | |||
| /** | |||
| The symbol of this parameter.@n | |||
| A parameter symbol is a short restricted name used as a machine and human readable identifier.@n | |||
| The first character must be one of _, a-z or A-Z and subsequent characters can be from _, a-z, A-Z and 0-9. | |||
| @note Parameter symbols MUST be unique within a plugin instance. | |||
| */ | |||
| String symbol; | |||
| /** | |||
| The unit of this parameter.@n | |||
| This means something like "dB", "kHz" and "ms".@n | |||
| Can be left blank if a unit does not apply to this parameter. | |||
| */ | |||
| String unit; | |||
| /** | |||
| An extensive description/comment about the parameter. | |||
| @note This value is optional and only used for LV2. | |||
| */ | |||
| String description; | |||
| /** | |||
| Ranges of this parameter.@n | |||
| The ranges describe the default, minimum and maximum values. | |||
| */ | |||
| ParameterRanges ranges; | |||
| /** | |||
| Enumeration values.@n | |||
| Can be used to give meaning to parameter values, working as an enumeration. | |||
| */ | |||
| ParameterEnumerationValues enumValues; | |||
| /** | |||
| Designation for this parameter. | |||
| */ | |||
| ParameterDesignation designation; | |||
| /** | |||
| MIDI CC to use by default on this parameter.@n | |||
| A value of 0 or 32 (bank change) is considered invalid.@n | |||
| Must also be less or equal to 120. | |||
| @note This value is only a hint! Hosts might map it automatically or completely ignore it. | |||
| */ | |||
| uint8_t midiCC; | |||
| /** | |||
| The group id that this parameter belongs to. | |||
| No group is assigned by default. | |||
| You can use a group from PredefinedPortGroups or roll your own.@n | |||
| When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential. | |||
| @see PortGroup, Plugin::initPortGroup | |||
| */ | |||
| uint32_t groupId; | |||
| /** | |||
| Default constructor for a null parameter. | |||
| */ | |||
| Parameter() noexcept | |||
| : hints(0x0), | |||
| name(), | |||
| shortName(), | |||
| symbol(), | |||
| unit(), | |||
| ranges(), | |||
| enumValues(), | |||
| designation(kParameterDesignationNull), | |||
| midiCC(0), | |||
| groupId(kPortGroupNone) {} | |||
| /** | |||
| Constructor using custom values. | |||
| */ | |||
| Parameter(uint32_t h, const char* n, const char* s, const char* u, float def, float min, float max) noexcept | |||
| : hints(h), | |||
| name(n), | |||
| shortName(), | |||
| symbol(s), | |||
| unit(u), | |||
| ranges(def, min, max), | |||
| enumValues(), | |||
| designation(kParameterDesignationNull), | |||
| midiCC(0), | |||
| groupId(kPortGroupNone) {} | |||
| /** | |||
| Initialize a parameter for a specific designation. | |||
| */ | |||
| void initDesignation(ParameterDesignation d) noexcept | |||
| { | |||
| designation = d; | |||
| switch (d) | |||
| { | |||
| case kParameterDesignationNull: | |||
| break; | |||
| case kParameterDesignationBypass: | |||
| hints = kParameterIsAutomatable|kParameterIsBoolean|kParameterIsInteger; | |||
| name = "Bypass"; | |||
| shortName = "Bypass"; | |||
| symbol = "dpf_bypass"; | |||
| unit = ""; | |||
| midiCC = 0; | |||
| groupId = kPortGroupNone; | |||
| ranges.def = 0.0f; | |||
| ranges.min = 0.0f; | |||
| ranges.max = 1.0f; | |||
| break; | |||
| } | |||
| } | |||
| }; | |||
| /** | |||
| Port Group.@n | |||
| Allows to group together audio/cv ports or parameters. | |||
| Each unique group MUST have an unique symbol and a name. | |||
| A group can be applied to both inputs and outputs (at the same time). | |||
| The same group cannot be used in audio ports and parameters. | |||
| When both audio and parameter groups are used, audio groups MUST be defined first. | |||
| That is, group indexes start with audio ports, then parameters. | |||
| An audio port group logically combines ports which should be considered part of the same stream.@n | |||
| For example, two audio ports in a group may form a stereo stream. | |||
| A parameter group provides meta-data to the host to indicate that some parameters belong together. | |||
| The use of port groups is completely optional. | |||
| @see Plugin::initPortGroup, AudioPort::group, Parameter::group | |||
| */ | |||
| struct PortGroup { | |||
| /** | |||
| The name of this port group.@n | |||
| A port group name can contain any character, but hosts might have a hard time with non-ascii ones.@n | |||
| The name doesn't have to be unique within a plugin instance, but it's recommended. | |||
| */ | |||
| String name; | |||
| /** | |||
| The symbol of this port group.@n | |||
| A port group symbol is a short restricted name used as a machine and human readable identifier.@n | |||
| The first character must be one of _, a-z or A-Z and subsequent characters can be from _, a-z, A-Z and 0-9. | |||
| @note Port group symbols MUST be unique within a plugin instance. | |||
| */ | |||
| String symbol; | |||
| }; | |||
| /** | |||
| State. | |||
| In DPF states refer to key:value string pairs, used to store arbitrary non-parameter data.@n | |||
| By default states are completely internal to the plugin and not visible by the host.@n | |||
| Flags can be set to allow hosts to see and/or change them. | |||
| TODO API under construction | |||
| */ | |||
| struct State { | |||
| /** | |||
| Hints describing this state. | |||
| @note Changing these hints can break compatibility with previously saved data. | |||
| @see StateHints | |||
| */ | |||
| uint32_t hints; | |||
| /** | |||
| The key or "symbol" of this state.@n | |||
| A state key is a short restricted name used as a machine and human readable identifier. | |||
| @note State keys MUST be unique within a plugin instance. | |||
| TODO define rules for allowed characters, must be usable as URI non-encoded parameters | |||
| */ | |||
| String key; | |||
| /** | |||
| The default value of this state.@n | |||
| Can be left empty if considered a valid initial state. | |||
| */ | |||
| String defaultValue; | |||
| /** | |||
| String representation of this state. | |||
| */ | |||
| String label; | |||
| /** | |||
| An extensive description/comment about this state. | |||
| @note This value is optional and only used for LV2. | |||
| */ | |||
| String description; | |||
| }; | |||
| /** | |||
| MIDI event. | |||
| */ | |||
| struct MidiEvent { | |||
| /** | |||
| Size of internal data. | |||
| */ | |||
| static const uint32_t kDataSize = 4; | |||
| /** | |||
| Time offset in frames. | |||
| */ | |||
| uint32_t frame; | |||
| /** | |||
| Number of bytes used. | |||
| */ | |||
| uint32_t size; | |||
| /** | |||
| MIDI data.@n | |||
| If size > kDataSize, dataExt is used (otherwise null). | |||
| When dataExt is used, the event holder is responsible for | |||
| keeping the pointer valid during the entirety of the run function. | |||
| */ | |||
| uint8_t data[kDataSize]; | |||
| const uint8_t* dataExt; | |||
| }; | |||
| /** | |||
| Time position.@n | |||
| The @a playing and @a frame values are always valid.@n | |||
| BBT values are only valid when @a bbt.valid is true. | |||
| This struct is inspired by the [JACK Transport API](https://jackaudio.org/api/structjack__position__t.html). | |||
| */ | |||
| struct TimePosition { | |||
| /** | |||
| Wherever the host transport is playing/rolling. | |||
| */ | |||
| bool playing; | |||
| /** | |||
| Current host transport position in frames. | |||
| @note This value is not always monotonic, | |||
| with some plugin hosts assigning it based on a source that can accumulate rounding errors. | |||
| */ | |||
| uint64_t frame; | |||
| /** | |||
| Bar-Beat-Tick time position. | |||
| */ | |||
| struct BarBeatTick { | |||
| /** | |||
| Wherever the host transport is using BBT.@n | |||
| If false you must not read from this struct. | |||
| */ | |||
| bool valid; | |||
| /** | |||
| Current bar.@n | |||
| Should always be > 0.@n | |||
| The first bar is bar '1'. | |||
| */ | |||
| int32_t bar; | |||
| /** | |||
| Current beat within bar.@n | |||
| Should always be > 0 and <= @a beatsPerBar.@n | |||
| The first beat is beat '1'. | |||
| */ | |||
| int32_t beat; | |||
| /** | |||
| Current tick within beat.@n | |||
| Should always be >= 0 and < @a ticksPerBeat.@n | |||
| The first tick is tick '0'. | |||
| @note Fraction part of tick is only available on some plugin formats. | |||
| */ | |||
| double tick; | |||
| /** | |||
| Number of ticks that have elapsed between frame 0 and the first beat of the current measure. | |||
| */ | |||
| double barStartTick; | |||
| /** | |||
| Time signature "numerator". | |||
| */ | |||
| float beatsPerBar; | |||
| /** | |||
| Time signature "denominator". | |||
| */ | |||
| float beatType; | |||
| /** | |||
| Number of ticks within a beat.@n | |||
| Usually a moderately large integer with many denominators, such as 1920.0. | |||
| */ | |||
| double ticksPerBeat; | |||
| /** | |||
| Number of beats per minute. | |||
| */ | |||
| double beatsPerMinute; | |||
| /** | |||
| Default constructor for a null BBT time position. | |||
| */ | |||
| BarBeatTick() noexcept | |||
| : valid(false), | |||
| bar(0), | |||
| beat(0), | |||
| tick(0), | |||
| barStartTick(0.0), | |||
| beatsPerBar(0.0f), | |||
| beatType(0.0f), | |||
| ticksPerBeat(0.0), | |||
| beatsPerMinute(0.0) {} | |||
| /** | |||
| Reinitialize this position using the default null initialization. | |||
| */ | |||
| void clear() noexcept | |||
| { | |||
| valid = false; | |||
| bar = 0; | |||
| beat = 0; | |||
| tick = 0; | |||
| barStartTick = 0.0; | |||
| beatsPerBar = 0.0f; | |||
| beatType = 0.0f; | |||
| ticksPerBeat = 0.0; | |||
| beatsPerMinute = 0.0; | |||
| } | |||
| } bbt; | |||
| /** | |||
| Default constructor for a time position. | |||
| */ | |||
| TimePosition() noexcept | |||
| : playing(false), | |||
| frame(0), | |||
| bbt() {} | |||
| /** | |||
| Reinitialize this position using the default null initialization. | |||
| */ | |||
| void clear() noexcept | |||
| { | |||
| playing = false; | |||
| frame = 0; | |||
| bbt.clear(); | |||
| } | |||
| }; | |||
| /** @} */ | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * DPF Plugin */ | |||
| @@ -897,7 +52,7 @@ struct TimePosition { | |||
| When enabled you need to implement initProgramName() and loadProgram(). | |||
| DISTRHO_PLUGIN_WANT_STATE activates internal state features.@n | |||
| When enabled you need to implement initStateKey() and setState(). | |||
| When enabled you need to implement initState() and setState(). | |||
| The process function run() changes wherever DISTRHO_PLUGIN_WANT_MIDI_INPUT is enabled or not.@n | |||
| When enabled it provides midi input events. | |||
| @@ -951,6 +106,16 @@ public: | |||
| */ | |||
| bool isDummyInstance() const noexcept; | |||
| /** | |||
| Check if this plugin instance is a "selftest" one used for automated plugin tests.@n | |||
| To enable this mode build with `DPF_RUNTIME_TESTING` macro defined (i.e. set as compiler build flag), | |||
| and run the JACK/Standalone executable with "selftest" as its only and single argument. | |||
| A few basic DSP and UI tests will run in self-test mode, with once instance having this function returning true.@n | |||
| You can use this chance to do a few tests of your own as well. | |||
| */ | |||
| bool isSelfTestInstance() const noexcept; | |||
| #if DISTRHO_PLUGIN_WANT_TIMEPOS | |||
| /** | |||
| Get the current host transport time position.@n | |||
| @@ -1024,30 +189,65 @@ protected: | |||
| Get the plugin label.@n | |||
| This label is a short restricted name consisting of only _, a-z, A-Z and 0-9 characters. | |||
| */ | |||
| #ifdef DISTRHO_PLUGIN_LABEL | |||
| virtual const char* getLabel() const | |||
| { | |||
| return DISTRHO_PLUGIN_LABEL; | |||
| } | |||
| #else | |||
| virtual const char* getLabel() const = 0; | |||
| #endif | |||
| /** | |||
| Get an extensive comment/description about the plugin.@n | |||
| Optional, returns nothing by default. | |||
| */ | |||
| virtual const char* getDescription() const { return ""; } | |||
| virtual const char* getDescription() const | |||
| { | |||
| #ifdef DISTRHO_PLUGIN_DESCRIPTION | |||
| return DISTRHO_PLUGIN_DESCRIPTION; | |||
| #else | |||
| return ""; | |||
| #endif | |||
| } | |||
| /** | |||
| Get the plugin author/maker. | |||
| */ | |||
| #ifdef DISTRHO_PLUGIN_MAKER | |||
| virtual const char* getMaker() const | |||
| { | |||
| return DISTRHO_PLUGIN_MAKER; | |||
| } | |||
| #else | |||
| virtual const char* getMaker() const = 0; | |||
| #endif | |||
| /** | |||
| Get the plugin homepage.@n | |||
| Optional, returns nothing by default. | |||
| */ | |||
| virtual const char* getHomePage() const { return ""; } | |||
| virtual const char* getHomePage() const | |||
| { | |||
| #ifdef DISTRHO_PLUGIN_HOMEPAGE | |||
| return DISTRHO_PLUGIN_HOMEPAGE; | |||
| #else | |||
| return ""; | |||
| #endif | |||
| } | |||
| /** | |||
| Get the plugin license (a single line of text or a URL).@n | |||
| For commercial plugins this should return some short copyright information. | |||
| */ | |||
| #ifdef DISTRHO_PLUGIN_LICENSE | |||
| virtual const char* getLicense() const | |||
| { | |||
| return DISTRHO_PLUGIN_LICENSE; | |||
| } | |||
| #else | |||
| virtual const char* getLicense() const = 0; | |||
| #endif | |||
| /** | |||
| Get the plugin version, in hexadecimal. | |||
| @@ -1057,10 +257,19 @@ protected: | |||
| /** | |||
| Get the plugin unique Id.@n | |||
| This value is used by LADSPA, DSSI and VST plugin formats. | |||
| This value is used by LADSPA, DSSI, VST2, VST3 and AUv2 plugin formats.@n | |||
| @note It is preferred that you set DISTRHO_PLUGIN_UNIQUE_ID macro instead of overriding this call, | |||
| as that is required for AUv2 plugins anyhow. | |||
| @see d_cconst() | |||
| */ | |||
| #ifdef DISTRHO_PLUGIN_UNIQUE_ID | |||
| virtual int64_t getUniqueId() const | |||
| { | |||
| return d_cconst(STRINGIFY(DISTRHO_PLUGIN_UNIQUE_ID)); | |||
| } | |||
| #else | |||
| virtual int64_t getUniqueId() const = 0; | |||
| #endif | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| * Init */ | |||
| @@ -1199,6 +408,14 @@ protected: | |||
| */ | |||
| virtual void sampleRateChanged(double newSampleRate); | |||
| /** | |||
| Optional callback to inform the plugin about audio port IO changes.@n | |||
| This function will only be called when the plugin is deactivated.@n | |||
| Only used in AU (AudioUnit) format when DISTRHO_PLUGIN_EXTRA_IO is defined. | |||
| @see DISTRHO_PLUGIN_EXTRA_IO | |||
| */ | |||
| virtual void ioChanged(uint16_t numInputs, uint16_t numOutputs); | |||
| // ------------------------------------------------------------------------------------------------------- | |||
| private: | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -16,8 +16,12 @@ | |||
| #include "src/DistrhoPlugin.cpp" | |||
| #if defined(DISTRHO_PLUGIN_TARGET_CARLA) | |||
| #if defined(DISTRHO_PLUGIN_TARGET_AU) | |||
| # include "src/DistrhoPluginAU.cpp" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_CARLA) | |||
| # include "src/DistrhoPluginCarla.cpp" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_CLAP) | |||
| # include "src/DistrhoPluginCLAP.cpp" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_JACK) | |||
| # include "src/DistrhoPluginJACK.cpp" | |||
| #elif (defined(DISTRHO_PLUGIN_TARGET_LADSPA) || defined(DISTRHO_PLUGIN_TARGET_DSSI)) | |||
| @@ -29,6 +33,8 @@ | |||
| # include "src/DistrhoPluginVST2.cpp" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_VST3) | |||
| # include "src/DistrhoPluginVST3.cpp" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_EXPORT) | |||
| # include "src/DistrhoPluginExport.cpp" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_SHARED) | |||
| DISTRHO_PLUGIN_EXPORT DISTRHO_NAMESPACE::Plugin* createSharedPlugin(); | |||
| DISTRHO_PLUGIN_EXPORT DISTRHO_NAMESPACE::Plugin* createSharedPlugin() { return DISTRHO_NAMESPACE::createPlugin(); } | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -37,9 +37,29 @@ START_NAMESPACE_DISTRHO | |||
| */ | |||
| const char* getBinaryFilename(); | |||
| /** | |||
| Get an OS-specific directory intended to store persistent configuration data about the plugin.@n | |||
| Calling this function will ensure the dictory exists on the filesystem.@n | |||
| The returned path already includes DISTRHO_PLUGIN_NAME and final OS separator. | |||
| */ | |||
| const char* getConfigDir(); | |||
| /** | |||
| Get an OS-specific directory intended to store "documents" for the plugin.@n | |||
| Calling this function will ensure the dictory exists on the filesystem.@n | |||
| The returned path already includes DISTRHO_PLUGIN_NAME and final OS separator. | |||
| */ | |||
| const char* getDocumentsDir(); | |||
| /** | |||
| Get the user "home" directory.@n | |||
| This function is provided only for convenience, it should not be needed under normal circunstances. | |||
| */ | |||
| const char* getHomeDir(); | |||
| /** | |||
| Get a string representation of the current plugin format we are building against.@n | |||
| This can be "JACK/Standalone", "LADSPA", "DSSI", "LV2", "VST2" or "VST3".@n | |||
| This can be "AudioUnit", "JACK/Standalone", "LADSPA", "DSSI", "LV2", "VST2" or "VST3" or "CLAP".@n | |||
| This string is purely informational and must not be used to tweak plugin behaviour. | |||
| @note DO NOT CHANGE PLUGIN BEHAVIOUR BASED ON PLUGIN FORMAT. | |||
| @@ -53,13 +73,15 @@ const char* getPluginFormatName() noexcept; | |||
| Returns a path inside the bundle where the plugin is meant to store its resources in.@n | |||
| This path varies between systems and plugin formats, like so: | |||
| - AU: <bundle>/Contents/Resources | |||
| - CLAP+VST2 macOS: <bundle>/Contents/Resources | |||
| - CLAP+VST2 non-macOS: <bundle>/resources (see note) | |||
| - LV2: <bundle>/resources (can be stored anywhere inside the bundle really, DPF just uses this one) | |||
| - VST2 macOS: <bundle>/Contents/Resources | |||
| - VST2 non-macOS: <bundle>/resources (see note) | |||
| - VST3: <bundle>/Contents/Resources | |||
| The other non-mentioned formats do not support bundles.@n | |||
| @note For VST2 on non-macOS systems, this assumes you have your plugin inside a dedicated directory | |||
| @note For CLAP and VST2 on non-macOS systems, this assumes you have your plugin inside a dedicated directory | |||
| rather than only shipping with the binary (e.g. <myplugin.vst>/myplugin.dll) | |||
| */ | |||
| const char* getResourcePath(const char* bundlePath) noexcept; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -17,6 +17,7 @@ | |||
| #ifndef DISTRHO_UI_HPP_INCLUDED | |||
| #define DISTRHO_UI_HPP_INCLUDED | |||
| #include "DistrhoDetails.hpp" | |||
| #include "extra/LeakDetector.hpp" | |||
| #include "src/DistrhoPluginChecks.h" | |||
| @@ -30,11 +31,7 @@ | |||
| # include "Vulkan.hpp" | |||
| #endif | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| # include "../dgl/Base.hpp" | |||
| # include "extra/ExternalWindow.hpp" | |||
| typedef DISTRHO_NAMESPACE::ExternalWindow UIWidget; | |||
| #elif DISTRHO_UI_USE_CUSTOM | |||
| #if DISTRHO_UI_USE_CUSTOM | |||
| # include DISTRHO_UI_CUSTOM_INCLUDE_PATH | |||
| typedef DISTRHO_UI_CUSTOM_WIDGET_TYPE UIWidget; | |||
| #elif DISTRHO_UI_USE_CAIRO | |||
| @@ -52,6 +49,8 @@ typedef DGL_NAMESPACE::TopLevelWidget UIWidget; | |||
| # include "extra/FileBrowserDialog.hpp" | |||
| #endif | |||
| #include <vector> | |||
| START_NAMESPACE_DISTRHO | |||
| class PluginWindow; | |||
| @@ -186,15 +185,15 @@ public: | |||
| #if DISTRHO_UI_FILE_BROWSER | |||
| /** | |||
| Open a file browser dialog with this window as transient parent.@n | |||
| A few options can be specified to setup the dialog. | |||
| A few options can be specified to setup the dialog.@n | |||
| The @a DISTRHO_NAMESPACE::FileBrowserOptions::className variable is automatically set in this call. | |||
| If a path is selected, onFileSelected() will be called with the user chosen path. | |||
| If the user cancels or does not pick a file, onFileSelected() will be called with nullptr as filename. | |||
| This function does not block the event loop. | |||
| @note This is exactly the same API as provided by the Window class, | |||
| but redeclared here so that non-embed/DGL based UIs can still use file browser related functions. | |||
| @note This is exactly the same API as provided by the Window class, but redeclared here for convenience. | |||
| */ | |||
| bool openFileBrowser(const DISTRHO_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions()); | |||
| #endif | |||
| @@ -210,34 +209,6 @@ public: | |||
| void* getPluginInstancePointer() const noexcept; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| * External UI helpers */ | |||
| /** | |||
| Get the bundle path that will be used for the next UI. | |||
| @note: This function is only valid during createUI(), | |||
| it will return null when called from anywhere else. | |||
| */ | |||
| static const char* getNextBundlePath() noexcept; | |||
| /** | |||
| Get the scale factor that will be used for the next UI. | |||
| @note: This function is only valid during createUI(), | |||
| it will return 1.0 when called from anywhere else. | |||
| */ | |||
| static double getNextScaleFactor() noexcept; | |||
| # if DISTRHO_PLUGIN_HAS_EMBED_UI | |||
| /** | |||
| Get the Window Id that will be used for the next created window. | |||
| @note: This function is only valid during createUI(), | |||
| it will return 0 when called from anywhere else. | |||
| */ | |||
| static uintptr_t getNextWindowId() noexcept; | |||
| # endif | |||
| #endif | |||
| protected: | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| * DSP/Plugin Callbacks */ | |||
| @@ -246,14 +217,14 @@ protected: | |||
| A parameter has changed on the plugin side.@n | |||
| This is called by the host to inform the UI about parameter changes. | |||
| */ | |||
| virtual void parameterChanged(uint32_t index, float value) = 0; | |||
| virtual void parameterChanged(uint32_t index, float value); | |||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
| /** | |||
| A program has been loaded on the plugin side.@n | |||
| This is called by the host to inform the UI about program changes. | |||
| */ | |||
| virtual void programLoaded(uint32_t index) = 0; | |||
| virtual void programLoaded(uint32_t index); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| @@ -261,7 +232,7 @@ protected: | |||
| A state has changed on the plugin side.@n | |||
| This is called by the host to inform the UI about state changes. | |||
| */ | |||
| virtual void stateChanged(const char* key, const char* value) = 0; | |||
| virtual void stateChanged(const char* key, const char* value); | |||
| #endif | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| @@ -293,7 +264,6 @@ protected: | |||
| */ | |||
| virtual void uiScaleFactorChanged(double scaleFactor); | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| /** | |||
| Get the types available for the data in a clipboard. | |||
| Must only be called within the context of uiClipboardDataOffer. | |||
| @@ -330,7 +300,6 @@ protected: | |||
| The most common exception is custom OpenGL setup, but only really needed for custom OpenGL drawing code. | |||
| */ | |||
| virtual void uiReshape(uint width, uint height); | |||
| #endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| #if DISTRHO_UI_FILE_BROWSER | |||
| /** | |||
| @@ -348,21 +317,12 @@ protected: | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| * UI Resize Handling, internal */ | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| /** | |||
| External Window resize function, called when the window is resized. | |||
| This is overriden here so the host knows when the UI is resized by you. | |||
| @see ExternalWindow::sizeChanged(uint,uint) | |||
| */ | |||
| void sizeChanged(uint width, uint height) override; | |||
| #else | |||
| /** | |||
| Widget resize function, called when the widget is resized. | |||
| This is overriden here so the host knows when the UI is resized by you. | |||
| @see Widget::onResize(const ResizeEvent&) | |||
| */ | |||
| void onResize(const ResizeEvent& ev) override; | |||
| #endif | |||
| // ------------------------------------------------------------------------------------------------------- | |||
| @@ -371,10 +331,8 @@ private: | |||
| PrivateData* const uiData; | |||
| friend class PluginWindow; | |||
| friend class UIExporter; | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| /** @internal */ | |||
| void requestSizeChange(uint width, uint height) override; | |||
| #endif | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UI) | |||
| }; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -16,29 +16,65 @@ | |||
| #include "src/DistrhoUI.cpp" | |||
| #if defined(DISTRHO_PLUGIN_TARGET_CARLA) | |||
| // nothing | |||
| // we might be building a plugin with external UI, which works on most formats except VST2/3 | |||
| #if ! DISTRHO_PLUGIN_HAS_UI && ! defined(DISTRHO_PLUGIN_VST_HPP_INCLUDED) | |||
| # error Trying to build UI without DISTRHO_PLUGIN_HAS_UI set to 1 | |||
| #endif | |||
| #if DISTRHO_PLUGIN_HAS_UI | |||
| #if defined(DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT) | |||
| # if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS | |||
| # warning Using single/monolithic LV2 target while DISTRHO_PLUGIN_WANT_DIRECT_ACCESS is 0 | |||
| # endif | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_AU) | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1 | |||
| # import "src/DistrhoUIAU.mm" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_CARLA) | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1 | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_CLAP) | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1 | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_JACK) | |||
| // nothing | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1 | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_DSSI) | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 0 | |||
| # include "src/DistrhoUIDSSI.cpp" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_LV2) | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT DISTRHO_PLUGIN_WANT_DIRECT_ACCESS | |||
| # include "src/DistrhoUILV2.cpp" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_VST2) | |||
| // nothing | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1 | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_VST3) | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1 | |||
| # include "src/DistrhoUIVST3.cpp" | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_EXPORT) | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1 | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_SHARED) || defined(DISTRHO_PLUGIN_TARGET_STATIC) | |||
| // nothing | |||
| # define DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT 1 | |||
| #else | |||
| # error unsupported format | |||
| #endif | |||
| #if !DISTRHO_PLUGIN_WANT_DIRECT_ACCESS && !defined(DISTRHO_PLUGIN_TARGET_CARLA) && !defined(DISTRHO_PLUGIN_TARGET_JACK) && !defined(DISTRHO_PLUGIN_TARGET_VST2) && !defined(DISTRHO_PLUGIN_TARGET_VST3) | |||
| #if !DISTRHO_PLUGIN_AND_UI_IN_SINGLE_OBJECT | |||
| # ifdef DISTRHO_PLUGIN_TARGET_DSSI | |||
| # define DISTRHO_IS_STANDALONE 1 | |||
| # else | |||
| # define DISTRHO_IS_STANDALONE 0 | |||
| # endif | |||
| # include "src/DistrhoUtils.cpp" | |||
| #else | |||
| # ifdef DISTRHO_PLUGIN_TARGET_JACK | |||
| # define DISTRHO_IS_STANDALONE 1 | |||
| # else | |||
| # define DISTRHO_IS_STANDALONE 0 | |||
| # endif | |||
| #endif | |||
| #if defined(DISTRHO_UI_LINUX_WEBVIEW_START) && !DISTRHO_IS_STANDALONE | |||
| int main(int argc, char* argv[]) | |||
| { | |||
| return DISTRHO_NAMESPACE::dpf_webview_start(argc, argv); | |||
| } | |||
| #endif | |||
| #endif | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -19,10 +19,8 @@ | |||
| #include "src/DistrhoDefines.h" | |||
| #if defined(DISTRHO_OS_WINDOWS) && !defined(_MSC_VER) | |||
| #include <winsock2.h> | |||
| #undef max | |||
| #undef min | |||
| #ifndef __STDC_LIMIT_MACROS | |||
| # define __STDC_LIMIT_MACROS | |||
| #endif | |||
| #include <cstdarg> | |||
| @@ -44,7 +42,7 @@ | |||
| typedef SSIZE_T ssize_t; | |||
| #endif | |||
| #if ! defined(CARLA_MATH_UTILS_HPP_INCLUDED) && ! defined(DISTRHO_PROPER_CPP11_SUPPORT) | |||
| #if ! defined(CARLA_MATH_UTILS_HPP_INCLUDED) && ! defined(DISTRHO_PROPER_CPP11_SUPPORT) && ! defined(DISTRHO_OS_MAC) | |||
| namespace std { | |||
| inline float fmin(float __x, float __y) | |||
| { return __builtin_fminf(__x, __y); } | |||
| @@ -61,10 +59,7 @@ inline float round(float __x) | |||
| # define M_PI 3.14159265358979323846 | |||
| #endif | |||
| #define DISTRHO_MACRO_AS_STRING_VALUE(MACRO) #MACRO | |||
| #define DISTRHO_MACRO_AS_STRING(MACRO) DISTRHO_MACRO_AS_STRING_VALUE(MACRO) | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| /* -------------------------------------------------------------------------------------------------------------------- | |||
| * misc functions */ | |||
| /** | |||
| @@ -83,6 +78,15 @@ int64_t d_cconst(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_ | |||
| return (a << 24) | (b << 16) | (c << 8) | (d << 0); | |||
| } | |||
| /** | |||
| Return a 32-bit number from 4 ASCII characters. | |||
| */ | |||
| static inline constexpr | |||
| uint32_t d_cconst(const char str[4]) | |||
| { | |||
| return (str[0] << 24) | (str[1] << 16) | (str[2] << 8) | str[3]; | |||
| } | |||
| /** | |||
| Return an hexadecimal representation of a MAJ.MIN.MICRO version number. | |||
| */ | |||
| @@ -100,7 +104,7 @@ void d_pass() noexcept {} | |||
| /** @} */ | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| /* -------------------------------------------------------------------------------------------------------------------- | |||
| * string print functions */ | |||
| /** | |||
| @@ -109,6 +113,27 @@ void d_pass() noexcept {} | |||
| @{ | |||
| */ | |||
| /* | |||
| * Internal noexcept-safe fopen function. | |||
| */ | |||
| static inline | |||
| FILE* __d_fopen(const char* const filename, FILE* const fallback) noexcept | |||
| { | |||
| if (std::getenv("DPF_CAPTURE_CONSOLE_OUTPUT") == nullptr) | |||
| return fallback; | |||
| FILE* ret = nullptr; | |||
| try { | |||
| ret = std::fopen(filename, "a+"); | |||
| } catch (...) {} | |||
| if (ret == nullptr) | |||
| ret = fallback; | |||
| return ret; | |||
| } | |||
| /** | |||
| Print a string to stdout with newline (gray color). | |||
| Does nothing if DEBUG is not defined. | |||
| @@ -119,12 +144,30 @@ void d_pass() noexcept {} | |||
| static inline | |||
| void d_debug(const char* const fmt, ...) noexcept | |||
| { | |||
| static FILE* const output = __d_fopen("/tmp/dpf.debug.log", stdout); | |||
| try { | |||
| va_list args; | |||
| va_start(args, fmt); | |||
| std::fprintf(stdout, "\x1b[30;1m"); | |||
| std::vfprintf(stdout, fmt, args); | |||
| std::fprintf(stdout, "\x1b[0m\n"); | |||
| if (output == stdout) | |||
| { | |||
| #ifdef DISTRHO_OS_MAC | |||
| std::fprintf(output, "\x1b[37;1m[dpf] "); | |||
| #else | |||
| std::fprintf(output, "\x1b[30;1m[dpf] "); | |||
| #endif | |||
| std::vfprintf(output, fmt, args); | |||
| std::fprintf(output, "\x1b[0m\n"); | |||
| } | |||
| else | |||
| { | |||
| std::fprintf(output, "[dpf] "); | |||
| std::vfprintf(output, fmt, args); | |||
| std::fprintf(output, "\n"); | |||
| } | |||
| std::fflush(output); | |||
| va_end(args); | |||
| } catch (...) {} | |||
| } | |||
| @@ -136,11 +179,18 @@ void d_debug(const char* const fmt, ...) noexcept | |||
| static inline | |||
| void d_stdout(const char* const fmt, ...) noexcept | |||
| { | |||
| static FILE* const output = __d_fopen("/tmp/dpf.stdout.log", stdout); | |||
| try { | |||
| va_list args; | |||
| va_start(args, fmt); | |||
| std::vfprintf(stdout, fmt, args); | |||
| std::fprintf(stdout, "\n"); | |||
| std::fprintf(output, "[dpf] "); | |||
| std::vfprintf(output, fmt, args); | |||
| std::fprintf(output, "\n"); | |||
| #ifndef DEBUG | |||
| if (output != stdout) | |||
| #endif | |||
| std::fflush(output); | |||
| va_end(args); | |||
| } catch (...) {} | |||
| } | |||
| @@ -151,11 +201,18 @@ void d_stdout(const char* const fmt, ...) noexcept | |||
| static inline | |||
| void d_stderr(const char* const fmt, ...) noexcept | |||
| { | |||
| static FILE* const output = __d_fopen("/tmp/dpf.stderr.log", stderr); | |||
| try { | |||
| va_list args; | |||
| va_start(args, fmt); | |||
| std::vfprintf(stderr, fmt, args); | |||
| std::fprintf(stderr, "\n"); | |||
| std::fprintf(output, "[dpf] "); | |||
| std::vfprintf(output, fmt, args); | |||
| std::fprintf(output, "\n"); | |||
| #ifndef DEBUG | |||
| if (output != stderr) | |||
| #endif | |||
| std::fflush(output); | |||
| va_end(args); | |||
| } catch (...) {} | |||
| } | |||
| @@ -166,12 +223,26 @@ void d_stderr(const char* const fmt, ...) noexcept | |||
| static inline | |||
| void d_stderr2(const char* const fmt, ...) noexcept | |||
| { | |||
| static FILE* const output = __d_fopen("/tmp/dpf.stderr2.log", stderr); | |||
| try { | |||
| va_list args; | |||
| va_start(args, fmt); | |||
| std::fprintf(stderr, "\x1b[31m"); | |||
| std::vfprintf(stderr, fmt, args); | |||
| std::fprintf(stderr, "\x1b[0m\n"); | |||
| if (output == stdout) | |||
| { | |||
| std::fprintf(output, "\x1b[31m[dpf] "); | |||
| std::vfprintf(output, fmt, args); | |||
| std::fprintf(output, "\x1b[0m\n"); | |||
| } | |||
| else | |||
| { | |||
| std::fprintf(output, "[dpf] "); | |||
| std::vfprintf(output, fmt, args); | |||
| std::fprintf(output, "\n"); | |||
| } | |||
| std::fflush(output); | |||
| va_end(args); | |||
| } catch (...) {} | |||
| } | |||
| @@ -246,7 +317,7 @@ void d_safe_exception(const char* const exception, const char* const file, const | |||
| /** @} */ | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| /* -------------------------------------------------------------------------------------------------------------------- | |||
| * math functions */ | |||
| /** | |||
| @@ -260,7 +331,7 @@ void d_safe_exception(const char* const exception, const char* const file, const | |||
| Returns true if they match. | |||
| */ | |||
| template<typename T> | |||
| static inline | |||
| static inline constexpr | |||
| bool d_isEqual(const T& v1, const T& v2) | |||
| { | |||
| return std::abs(v1-v2) < std::numeric_limits<T>::epsilon(); | |||
| @@ -271,7 +342,7 @@ bool d_isEqual(const T& v1, const T& v2) | |||
| Returns true if they don't match. | |||
| */ | |||
| template<typename T> | |||
| static inline | |||
| static inline constexpr | |||
| bool d_isNotEqual(const T& v1, const T& v2) | |||
| { | |||
| return std::abs(v1-v2) >= std::numeric_limits<T>::epsilon(); | |||
| @@ -281,7 +352,7 @@ bool d_isNotEqual(const T& v1, const T& v2) | |||
| Safely check if a floating point number is zero. | |||
| */ | |||
| template<typename T> | |||
| static inline | |||
| static inline constexpr | |||
| bool d_isZero(const T& value) | |||
| { | |||
| return std::abs(value) < std::numeric_limits<T>::epsilon(); | |||
| @@ -291,7 +362,7 @@ bool d_isZero(const T& value) | |||
| Safely check if a floating point number is not zero. | |||
| */ | |||
| template<typename T> | |||
| static inline | |||
| static inline constexpr | |||
| bool d_isNotZero(const T& value) | |||
| { | |||
| return std::abs(value) >= std::numeric_limits<T>::epsilon(); | |||
| @@ -315,17 +386,62 @@ uint32_t d_nextPowerOf2(uint32_t size) noexcept | |||
| return ++size; | |||
| } | |||
| /** | |||
| Round a floating point number to an integer. | |||
| Fast operation for values known to be 0 or positive. | |||
| */ | |||
| template<typename T> | |||
| static inline constexpr | |||
| int32_t d_roundToIntPositive(const T& value) | |||
| { | |||
| return static_cast<int32_t>(value + static_cast<T>(0.5)); | |||
| } | |||
| /** | |||
| Round a floating point number to an unsigned integer. | |||
| Fast operation for values known to be 0 or positive. | |||
| */ | |||
| template<typename T> | |||
| static inline constexpr | |||
| uint32_t d_roundToUnsignedInt(const T& value) | |||
| { | |||
| return static_cast<uint32_t>(value + static_cast<T>(0.5)); | |||
| } | |||
| /** | |||
| Round a floating point number to an integer. | |||
| Fast operation for values known to be 0 or negative. | |||
| */ | |||
| template<typename T> | |||
| static inline constexpr | |||
| int32_t d_roundToIntNegative(const T& value) | |||
| { | |||
| return static_cast<int32_t>(value - static_cast<T>(0.5)); | |||
| } | |||
| /** | |||
| Round a floating point number to integer. | |||
| */ | |||
| template<typename T> | |||
| static inline constexpr | |||
| int32_t d_roundToInt(const T& value) | |||
| { | |||
| return value >= 0 ? static_cast<int32_t>(value + static_cast<T>(0.5)) | |||
| : static_cast<int32_t>(value - static_cast<T>(0.5)); | |||
| } | |||
| /** @} */ | |||
| // ----------------------------------------------------------------------- | |||
| /* -------------------------------------------------------------------------------------------------------------------- | |||
| * other stuff */ | |||
| #ifndef DONT_SET_USING_DISTRHO_NAMESPACE | |||
| // If your code uses a lot of DISTRHO classes, then this will obviously save you | |||
| // a lot of typing, but can be disabled by setting DONT_SET_USING_DISTRHO_NAMESPACE. | |||
| namespace DISTRHO_NAMESPACE {} | |||
| using namespace DISTRHO_NAMESPACE; | |||
| /** | |||
| If your code uses a lot of DISTRHO classes, then this will obviously save you a lot of typing, | |||
| but can be disabled by setting DONT_SET_USING_DISTRHO_NAMESPACE. | |||
| */ | |||
| namespace DISTRHO_NAMESPACE {} | |||
| using namespace DISTRHO_NAMESPACE; | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| #endif // DISTRHO_UTILS_HPP_INCLUDED | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2016 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2023 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -50,7 +50,7 @@ START_NAMESPACE_DISTRHO | |||
| #define DISTRHO_JOIN_MACRO_HELPER(a, b) a ## b | |||
| #define DISTRHO_JOIN_MACRO(item1, item2) DISTRHO_JOIN_MACRO_HELPER(item1, item2) | |||
| #ifdef DEBUG | |||
| #if defined(DPF_DEBUG) && !defined(NDEBUG) | |||
| /** This macro lets you embed a leak-detecting object inside a class.\n | |||
| To use it, simply declare a DISTRHO_LEAK_DETECTOR(YourClassName) inside a private section | |||
| of the class declaration. E.g. | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -22,6 +22,10 @@ | |||
| #include <algorithm> | |||
| #if __cplusplus >= 201703L | |||
| # include <string_view> | |||
| #endif | |||
| START_NAMESPACE_DISTRHO | |||
| // ----------------------------------------------------------------------- | |||
| @@ -49,10 +53,7 @@ public: | |||
| fBufferLen(0), | |||
| fBufferAlloc(false) | |||
| { | |||
| char ch[2]; | |||
| ch[0] = c; | |||
| ch[1] = '\0'; | |||
| const char ch[2] = { c, '\0' }; | |||
| _dup(ch); | |||
| } | |||
| @@ -87,6 +88,19 @@ public: | |||
| _dup(strBuf); | |||
| } | |||
| #if __cplusplus >= 201703L | |||
| /* | |||
| * std::string_view compatible variant. | |||
| */ | |||
| explicit String(const std::string_view& strView) noexcept | |||
| : fBuffer(_null()), | |||
| fBufferLen(0), | |||
| fBufferAlloc(false) | |||
| { | |||
| _dup(strView.data(), strView.size()); | |||
| } | |||
| #endif | |||
| /* | |||
| * Integer. | |||
| */ | |||
| @@ -591,7 +605,7 @@ public: | |||
| */ | |||
| String& toLower() noexcept | |||
| { | |||
| static const char kCharDiff('a' - 'A'); | |||
| static constexpr const char kCharDiff = 'a' - 'A'; | |||
| for (std::size_t i=0; i < fBufferLen; ++i) | |||
| { | |||
| @@ -607,7 +621,7 @@ public: | |||
| */ | |||
| String& toUpper() noexcept | |||
| { | |||
| static const char kCharDiff('a' - 'A'); | |||
| static constexpr const char kCharDiff = 'a' - 'A'; | |||
| for (std::size_t i=0; i < fBufferLen; ++i) | |||
| { | |||
| @@ -676,16 +690,16 @@ public: | |||
| static String asBase64(const void* const data, const std::size_t dataSize) | |||
| { | |||
| static const char* const kBase64Chars = | |||
| static constexpr const char* const kBase64Chars = | |||
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |||
| "abcdefghijklmnopqrstuvwxyz" | |||
| "0123456789+/"; | |||
| #ifndef _MSC_VER | |||
| #ifndef _MSC_VER | |||
| const std::size_t kTmpBufSize = std::min(d_nextPowerOf2(static_cast<uint32_t>(dataSize/3)), 65536U); | |||
| #else | |||
| #else | |||
| constexpr std::size_t kTmpBufSize = 65536U; | |||
| #endif | |||
| #endif | |||
| const uchar* bytesToEncode((const uchar*)data); | |||
| @@ -749,6 +763,192 @@ public: | |||
| return ret; | |||
| } | |||
| /* | |||
| * Convert to a URL encoded string. | |||
| */ | |||
| String& urlEncode() noexcept | |||
| { | |||
| static constexpr const char* const kHexChars = "0123456789ABCDEF"; | |||
| if (fBufferLen == 0) | |||
| return *this; | |||
| char* const newbuf = static_cast<char*>(std::malloc(fBufferLen * 3 + 1)); | |||
| DISTRHO_SAFE_ASSERT_RETURN(newbuf != nullptr, *this); | |||
| char* newbufptr = newbuf; | |||
| for (std::size_t i=0; i < fBufferLen; ++i) | |||
| { | |||
| const char c = fBuffer[i]; | |||
| switch (c) | |||
| { | |||
| case '!': // 33 | |||
| case '#': // 35 | |||
| case '$': // 36 | |||
| case '&': // 38 | |||
| case '\'': // 39 | |||
| case '(': // 40 | |||
| case ')': // 41 | |||
| case '*': // 42 | |||
| case '+': // 43 | |||
| case ',': // 44 | |||
| case '-': // 45 | |||
| case '.': // 46 | |||
| case '/': // 47 | |||
| case '0': // 48 | |||
| case '1': // 49 | |||
| case '2': // 50 | |||
| case '3': // 51 | |||
| case '4': // 52 | |||
| case '5': // 53 | |||
| case '6': // 54 | |||
| case '7': // 55 | |||
| case '8': // 56 | |||
| case '9': // 57 | |||
| case ':': // 58 | |||
| case ';': // 59 | |||
| case '=': // 61 | |||
| case '?': // 63 | |||
| case '@': // 64 | |||
| case 'A': // 65 | |||
| case 'B': // 66 | |||
| case 'C': // 67 | |||
| case 'D': // 68 | |||
| case 'E': // 69 | |||
| case 'F': // 70 | |||
| case 'G': // 71 | |||
| case 'H': // 72 | |||
| case 'I': // 73 | |||
| case 'J': // 74 | |||
| case 'K': // 75 | |||
| case 'L': // 76 | |||
| case 'M': // 77 | |||
| case 'N': // 78 | |||
| case 'O': // 79 | |||
| case 'P': // 80 | |||
| case 'Q': // 81 | |||
| case 'R': // 82 | |||
| case 'S': // 83 | |||
| case 'T': // 84 | |||
| case 'U': // 85 | |||
| case 'V': // 86 | |||
| case 'W': // 87 | |||
| case 'X': // 88 | |||
| case 'Y': // 89 | |||
| case 'Z': // 90 | |||
| case '[': // 91 | |||
| case ']': // 93 | |||
| case '_': // 95 | |||
| case 'a': // 97 | |||
| case 'b': // 98 | |||
| case 'c': // 99 | |||
| case 'd': // 100 | |||
| case 'e': // 101 | |||
| case 'f': // 102 | |||
| case 'g': // 103 | |||
| case 'h': // 104 | |||
| case 'i': // 105 | |||
| case 'j': // 106 | |||
| case 'k': // 107 | |||
| case 'l': // 108 | |||
| case 'm': // 109 | |||
| case 'n': // 110 | |||
| case 'o': // 111 | |||
| case 'p': // 112 | |||
| case 'q': // 113 | |||
| case 'r': // 114 | |||
| case 's': // 115 | |||
| case 't': // 116 | |||
| case 'u': // 117 | |||
| case 'v': // 118 | |||
| case 'w': // 119 | |||
| case 'x': // 120 | |||
| case 'y': // 121 | |||
| case 'z': // 122 | |||
| case '~': // 126 | |||
| *newbufptr++ = c; | |||
| break; | |||
| default: | |||
| *newbufptr++ = '%'; | |||
| *newbufptr++ = kHexChars[(c >> 4) & 0xf]; | |||
| *newbufptr++ = kHexChars[c & 0xf]; | |||
| break; | |||
| } | |||
| } | |||
| *newbufptr = '\0'; | |||
| std::free(fBuffer); | |||
| fBuffer = newbuf; | |||
| fBufferLen = std::strlen(newbuf); | |||
| fBufferAlloc = true; | |||
| return *this; | |||
| } | |||
| /* | |||
| * Convert to a URL decoded string. | |||
| */ | |||
| String& urlDecode() noexcept | |||
| { | |||
| if (fBufferLen == 0) | |||
| return *this; | |||
| char* const newbuf = static_cast<char*>(std::malloc(fBufferLen + 1)); | |||
| DISTRHO_SAFE_ASSERT_RETURN(newbuf != nullptr, *this); | |||
| char* newbufptr = newbuf; | |||
| for (std::size_t i=0; i < fBufferLen; ++i) | |||
| { | |||
| const char c = fBuffer[i]; | |||
| if (c == '%') | |||
| { | |||
| DISTRHO_SAFE_ASSERT_CONTINUE(fBufferLen > i + 2); | |||
| char c1 = fBuffer[i + 1]; | |||
| char c2 = fBuffer[i + 2]; | |||
| i += 2; | |||
| /**/ if (c1 >= '0' && c1 <= '9') | |||
| c1 -= '0'; | |||
| else if (c1 >= 'A' && c1 <= 'Z') | |||
| c1 -= 'A' - 10; | |||
| else if (c1 >= 'a' && c1 <= 'z') | |||
| c1 -= 'a' - 10; | |||
| else | |||
| continue; | |||
| /**/ if (c2 >= '0' && c2 <= '9') | |||
| c2 -= '0'; | |||
| else if (c2 >= 'A' && c2 <= 'Z') | |||
| c2 -= 'A' - 10; | |||
| else if (c2 >= 'a' && c2 <= 'z') | |||
| c2 -= 'a' - 10; | |||
| else | |||
| continue; | |||
| *newbufptr++ = c1 << 4 | c2; | |||
| } | |||
| else | |||
| { | |||
| *newbufptr++ = c; | |||
| } | |||
| } | |||
| *newbufptr = '\0'; | |||
| std::free(fBuffer); | |||
| fBuffer = newbuf; | |||
| fBufferLen = std::strlen(newbuf); | |||
| fBufferAlloc = true; | |||
| return *this; | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // public operators | |||
| @@ -830,7 +1030,7 @@ public: | |||
| } | |||
| // we have some data ourselves, reallocate to add the new stuff | |||
| char* const newBuf = (char*)realloc(fBuffer, fBufferLen + strBufLen + 1); | |||
| char* const newBuf = static_cast<char*>(std::realloc(fBuffer, fBufferLen + strBufLen + 1)); | |||
| DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, *this); | |||
| std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1); | |||
| @@ -855,7 +1055,7 @@ public: | |||
| const std::size_t strBufLen = std::strlen(strBuf); | |||
| const std::size_t newBufSize = fBufferLen + strBufLen; | |||
| char* const newBuf = (char*)malloc(newBufSize + 1); | |||
| char* const newBuf = static_cast<char*>(std::malloc(newBufSize + 1)); | |||
| DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String()); | |||
| std::memcpy(newBuf, fBuffer, fBufferLen); | |||
| @@ -912,7 +1112,7 @@ private: | |||
| std::free(fBuffer); | |||
| fBufferLen = (size > 0) ? size : std::strlen(strBuf); | |||
| fBuffer = (char*)std::malloc(fBufferLen+1); | |||
| fBuffer = static_cast<char*>(std::malloc(fBufferLen + 1)); | |||
| if (fBuffer == nullptr) | |||
| { | |||
| @@ -960,7 +1160,7 @@ String operator+(const String& strBefore, const char* const strBufAfter) noexcep | |||
| const std::size_t strBeforeLen = strBefore.length(); | |||
| const std::size_t strBufAfterLen = std::strlen(strBufAfter); | |||
| const std::size_t newBufSize = strBeforeLen + strBufAfterLen; | |||
| char* const newBuf = (char*)malloc(newBufSize + 1); | |||
| char* const newBuf = static_cast<char*>(malloc(newBufSize + 1)); | |||
| DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String()); | |||
| std::memcpy(newBuf, strBefore.buffer(), strBeforeLen); | |||
| @@ -980,7 +1180,7 @@ String operator+(const char* const strBufBefore, const String& strAfter) noexcep | |||
| const std::size_t strBufBeforeLen = std::strlen(strBufBefore); | |||
| const std::size_t strAfterLen = strAfter.length(); | |||
| const std::size_t newBufSize = strBufBeforeLen + strAfterLen; | |||
| char* const newBuf = (char*)malloc(newBufSize + 1); | |||
| char* const newBuf = static_cast<char*>(malloc(newBufSize + 1)); | |||
| DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String()); | |||
| std::memcpy(newBuf, strBufBefore, strBufBeforeLen); | |||
| @@ -211,7 +211,15 @@ private: \ | |||
| #endif | |||
| /* Useful macros */ | |||
| #define ARRAY_SIZE(ARRAY) sizeof(ARRAY)/sizeof(ARRAY[0]) | |||
| #define ARRAY_SIZE(ARRAY) (sizeof(ARRAY)/sizeof(ARRAY[0])) | |||
| #define STRINGIFY2(s) #s | |||
| #define STRINGIFY(s) STRINGIFY2(s) | |||
| #ifdef DISTRHO_PROPER_CPP11_SUPPORT | |||
| #define CPP_AGGREGATE_INIT(ClassName) ClassName | |||
| #else | |||
| #define CPP_AGGREGATE_INIT(ClassName) (ClassName) | |||
| #endif | |||
| /* Useful typedefs */ | |||
| typedef unsigned char uchar; | |||
| @@ -223,5 +231,6 @@ typedef unsigned long long int ulonglong; | |||
| /* Deprecated macros */ | |||
| #define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) DISTRHO_DECLARE_NON_COPYABLE(ClassName) | |||
| #define DISTRHO_DECLARE_NON_COPY_STRUCT(StructName) DISTRHO_DECLARE_NON_COPYABLE(StructName) | |||
| #define DISTRHO_MACRO_AS_STRING(MACRO) STRINGIFY2(MACRO) | |||
| #endif // DISTRHO_DEFINES_H_INCLUDED | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -25,6 +25,7 @@ uint32_t d_nextBufferSize = 0; | |||
| double d_nextSampleRate = 0.0; | |||
| const char* d_nextBundlePath = nullptr; | |||
| bool d_nextPluginIsDummy = false; | |||
| bool d_nextPluginIsSelfTest = false; | |||
| bool d_nextCanRequestParameterValueChanges = false; | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| @@ -42,45 +43,45 @@ const PortGroupWithId PluginExporter::sFallbackPortGroup; | |||
| Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCount) | |||
| : pData(new PrivateData()) | |||
| { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| pData->audioPorts = new AudioPortWithBusId[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS]; | |||
| #endif | |||
| #endif | |||
| #ifdef DPF_ABORT_ON_ERROR | |||
| # define DPF_ABORT abort(); | |||
| #else | |||
| # define DPF_ABORT | |||
| #endif | |||
| #if defined(DPF_ABORT_ON_ERROR) || defined(DPF_RUNTIME_TESTING) | |||
| #define DPF_ABORT abort(); | |||
| #else | |||
| #define DPF_ABORT | |||
| #endif | |||
| if (parameterCount > 0) | |||
| { | |||
| pData->parameterCount = parameterCount; | |||
| pData->parameters = new Parameter[parameterCount]; | |||
| pData->parameters = new Parameter[parameterCount]; | |||
| } | |||
| if (programCount > 0) | |||
| { | |||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
| pData->programCount = programCount; | |||
| pData->programNames = new String[programCount]; | |||
| #else | |||
| #else | |||
| d_stderr2("DPF warning: Plugins with programs must define `DISTRHO_PLUGIN_WANT_PROGRAMS` to 1"); | |||
| DPF_ABORT | |||
| #endif | |||
| #endif | |||
| } | |||
| if (stateCount > 0) | |||
| { | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| pData->stateCount = stateCount; | |||
| pData->states = new State[stateCount]; | |||
| #else | |||
| pData->states = new State[stateCount]; | |||
| #else | |||
| d_stderr2("DPF warning: Plugins with state must define `DISTRHO_PLUGIN_WANT_STATE` to 1"); | |||
| DPF_ABORT | |||
| #endif | |||
| #endif | |||
| } | |||
| #undef DPF_ABORT | |||
| #undef DPF_ABORT | |||
| } | |||
| Plugin::~Plugin() | |||
| @@ -111,6 +112,11 @@ bool Plugin::isDummyInstance() const noexcept | |||
| return pData->isDummy; | |||
| } | |||
| bool Plugin::isSelfTestInstance() const noexcept | |||
| { | |||
| return pData->isSelfTest; | |||
| } | |||
| #if DISTRHO_PLUGIN_WANT_TIMEPOS | |||
| const TimePosition& Plugin::getTimePosition() const noexcept | |||
| { | |||
| @@ -119,7 +125,7 @@ const TimePosition& Plugin::getTimePosition() const noexcept | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_LATENCY | |||
| void Plugin::setLatency(uint32_t frames) noexcept | |||
| void Plugin::setLatency(const uint32_t frames) noexcept | |||
| { | |||
| pData->latency = frames; | |||
| } | |||
| @@ -189,7 +195,10 @@ void Plugin::initState(const uint32_t index, State& state) | |||
| uint hints = 0x0; | |||
| String stateKey, defaultStateValue; | |||
| #if defined(__clang__) | |||
| #if defined(_MSC_VER) | |||
| #pragma warning(push) | |||
| #pragma warning(disable:4996) | |||
| #elif defined(__clang__) | |||
| #pragma clang diagnostic push | |||
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
| #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) | |||
| @@ -199,7 +208,9 @@ void Plugin::initState(const uint32_t index, State& state) | |||
| initState(index, stateKey, defaultStateValue); | |||
| if (isStateFile(index)) | |||
| hints = kStateIsFilenamePath; | |||
| #if defined(__clang__) | |||
| #if defined(_MSC_VER) | |||
| #pragma warning(pop) | |||
| #elif defined(__clang__) | |||
| #pragma clang diagnostic pop | |||
| #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) | |||
| #pragma GCC diagnostic pop | |||
| @@ -235,6 +246,7 @@ void Plugin::setState(const char*, const char*) {} | |||
| void Plugin::bufferSizeChanged(uint32_t) {} | |||
| void Plugin::sampleRateChanged(double) {} | |||
| void Plugin::ioChanged(uint16_t, uint16_t) {} | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -17,9 +17,13 @@ | |||
| #ifndef DISTRHO_PLUGIN_CHECKS_H_INCLUDED | |||
| #define DISTRHO_PLUGIN_CHECKS_H_INCLUDED | |||
| #ifndef DISTRHO_DETAILS_HPP_INCLUDED | |||
| # error wrong include order | |||
| #endif | |||
| #include "DistrhoPluginInfo.h" | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Check if all required macros are defined | |||
| #ifndef DISTRHO_PLUGIN_NAME | |||
| @@ -38,17 +42,13 @@ | |||
| # error DISTRHO_PLUGIN_URI undefined! | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Define optional macros if not done yet | |||
| #ifndef DISTRHO_PLUGIN_HAS_UI | |||
| # define DISTRHO_PLUGIN_HAS_UI 0 | |||
| #endif | |||
| #ifndef DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| # define DISTRHO_PLUGIN_HAS_EXTERNAL_UI 0 | |||
| #endif | |||
| #ifndef DISTRHO_PLUGIN_IS_RT_SAFE | |||
| # define DISTRHO_PLUGIN_IS_RT_SAFE 0 | |||
| #endif | |||
| @@ -65,6 +65,10 @@ | |||
| # define DISTRHO_PLUGIN_WANT_LATENCY 0 | |||
| #endif | |||
| #ifndef DISTRHO_PLUGIN_WANT_MIDI_AS_MPE | |||
| # define DISTRHO_PLUGIN_WANT_MIDI_AS_MPE 0 | |||
| #endif | |||
| #ifndef DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| # define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0 | |||
| #endif | |||
| @@ -91,47 +95,101 @@ | |||
| #endif | |||
| #ifndef DISTRHO_UI_FILE_BROWSER | |||
| # if defined(DGL_FILE_BROWSER_DISABLED) || DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| # define DISTRHO_UI_FILE_BROWSER 0 | |||
| # else | |||
| # define DISTRHO_UI_FILE_BROWSER 1 | |||
| # endif | |||
| # define DISTRHO_UI_FILE_BROWSER 0 | |||
| #endif | |||
| #ifndef DISTRHO_UI_WEB_VIEW | |||
| # define DISTRHO_UI_WEB_VIEW 0 | |||
| #endif | |||
| #ifndef DISTRHO_UI_USER_RESIZABLE | |||
| # define DISTRHO_UI_USER_RESIZABLE 0 | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // set UI type | |||
| #ifndef DISTRHO_UI_USE_CAIRO | |||
| # define DISTRHO_UI_USE_CAIRO 0 | |||
| #endif | |||
| #ifndef DISTRHO_UI_USE_CUSTOM | |||
| # define DISTRHO_UI_USE_CUSTOM 0 | |||
| #endif | |||
| #ifndef DISTRHO_UI_USE_EXTERNAL | |||
| # define DISTRHO_UI_USE_EXTERNAL 0 | |||
| #endif | |||
| #ifndef DISTRHO_UI_USE_NANOVG | |||
| # define DISTRHO_UI_USE_NANOVG 0 | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // Define DISTRHO_PLUGIN_HAS_EMBED_UI if needed | |||
| #ifndef DISTRHO_UI_USE_WEB_VIEW | |||
| # define DISTRHO_UI_USE_WEB_VIEW 0 | |||
| #endif | |||
| #ifndef DISTRHO_PLUGIN_HAS_EMBED_UI | |||
| # if (defined(DGL_CAIRO) && defined(HAVE_CAIRO)) || (defined(DGL_OPENGL) && defined(HAVE_OPENGL)) | |||
| # define DISTRHO_PLUGIN_HAS_EMBED_UI 1 | |||
| # else | |||
| # define DISTRHO_PLUGIN_HAS_EMBED_UI 0 | |||
| # endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Define DISTRHO_UI_WEB_VIEW if needed | |||
| #if DISTRHO_UI_USE_WEB_VIEW && !DISTRHO_UI_WEB_VIEW | |||
| # undef DISTRHO_UI_WEB_VIEW | |||
| # define DISTRHO_UI_WEB_VIEW 1 | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Define DISTRHO_UI_URI if needed | |||
| #ifndef DISTRHO_UI_URI | |||
| # define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#DPF_UI" | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Test for wrong compiler macros | |||
| #if defined(DISTRHO_PLUGIN_HAS_EMBED_UI) | |||
| # warning DISTRHO_PLUGIN_HAS_EMBED_UI has been removed, it is now always on | |||
| #endif | |||
| #if defined(DISTRHO_PLUGIN_HAS_EXTERNAL_UI) | |||
| # error DISTRHO_PLUGIN_HAS_EXTERNAL_UI has been replaced by DISTRHO_UI_USE_EXTERNAL | |||
| #endif | |||
| #ifdef DISTRHO_UI_FILEBROWSER | |||
| # error typo detected use DISTRHO_UI_FILE_BROWSER instead of DISTRHO_UI_FILEBROWSER | |||
| #endif | |||
| #ifdef DISTRHO_UI_WEBVIEW | |||
| # error typo detected use DISTRHO_UI_WEB_VIEW instead of DISTRHO_UI_WEBVIEW | |||
| #endif | |||
| #ifdef DISTRHO_UI_USE_WEBVIEW | |||
| # error typo detected use DISTRHO_UI_USE_WEB_VIEW instead of DISTRHO_UI_USE_WEBVIEW | |||
| #endif | |||
| #if DISTRHO_UI_FILE_BROWSER && !defined(DGL_USE_FILE_BROWSER) | |||
| # error invalid build config: file browser requested but `USE_FILE_BROWSER` build option is not set | |||
| #endif | |||
| #if DISTRHO_UI_WEB_VIEW && !defined(DGL_USE_WEB_VIEW) | |||
| # error invalid build config: web view requested but `USE_WEB_VIEW` build option is not set | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Test if synth has audio outputs | |||
| #if DISTRHO_PLUGIN_IS_SYNTH && DISTRHO_PLUGIN_NUM_OUTPUTS == 0 | |||
| # error Synths need audio output to work! | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Test if MIDI as MPE enabled where it doesn't make sense | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_AS_MPE && ! (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) | |||
| # error MIDI as MPE needs MIDI input or output to work! | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Enable MIDI input if synth, test if midi-input disabled when synth | |||
| #ifndef DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| @@ -140,7 +198,7 @@ | |||
| # error Synths need MIDI input to work! | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Enable state if plugin wants state files (deprecated) | |||
| #ifdef DISTRHO_PLUGIN_WANT_STATEFILES | |||
| @@ -152,7 +210,7 @@ | |||
| # endif | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Enable full state if plugin exports presets | |||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS && DISTRHO_PLUGIN_WANT_STATE && defined(DISTRHO_PLUGIN_WANT_FULL_STATE_WAS_NOT_SET) | |||
| @@ -161,35 +219,77 @@ | |||
| # define DISTRHO_PLUGIN_WANT_FULL_STATE 1 | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // Disable file browser if using external UI | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Disable UI if DGL is not available | |||
| #if DISTRHO_UI_FILE_BROWSER && DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| # warning file browser APIs do not work for external UIs | |||
| # undef DISTRHO_UI_FILE_BROWSER 0 | |||
| # define DISTRHO_UI_FILE_BROWSER 0 | |||
| #if DISTRHO_PLUGIN_HAS_UI && !defined(HAVE_DGL) | |||
| # undef DISTRHO_PLUGIN_HAS_UI | |||
| # define DISTRHO_PLUGIN_HAS_UI 0 | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // Disable UI if DGL or external UI is not available | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Make sure both default width and height are provided | |||
| #if (defined(DGL_CAIRO) && ! defined(HAVE_CAIRO)) || (defined(DGL_OPENGL) && ! defined(HAVE_OPENGL)) | |||
| # undef DISTRHO_PLUGIN_HAS_EMBED_UI | |||
| # define DISTRHO_PLUGIN_HAS_EMBED_UI 0 | |||
| #if defined(DISTRHO_UI_DEFAULT_WIDTH) && !defined(DISTRHO_UI_DEFAULT_HEIGHT) | |||
| # error DISTRHO_UI_DEFAULT_WIDTH is defined but DISTRHO_UI_DEFAULT_HEIGHT is not | |||
| #endif | |||
| #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| # undef DISTRHO_PLUGIN_HAS_UI | |||
| # define DISTRHO_PLUGIN_HAS_UI 0 | |||
| #if defined(DISTRHO_UI_DEFAULT_HEIGHT) && !defined(DISTRHO_UI_DEFAULT_WIDTH) | |||
| # error DISTRHO_UI_DEFAULT_HEIGHT is defined but DISTRHO_UI_DEFAULT_WIDTH is not | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Define DISTRHO_PLUGIN_AU_TYPE if needed | |||
| #ifndef DISTRHO_PLUGIN_AU_TYPE | |||
| # if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) && DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0 | |||
| # define DISTRHO_PLUGIN_AU_TYPE aumf /* kAudioUnitType_MusicEffect */ | |||
| # elif (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) && DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS != 0 | |||
| # define DISTRHO_PLUGIN_AU_TYPE aumu /* kAudioUnitType_MusicDevice */ | |||
| # elif DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| # define DISTRHO_PLUGIN_AU_TYPE aumi /* kAudioUnitType_MIDIProcessor */ | |||
| # elif DISTRHO_PLUGIN_NUM_INPUTS == 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0 | |||
| # define DISTRHO_PLUGIN_AU_TYPE augn /* kAudioUnitType_Generator */ | |||
| # else | |||
| # define DISTRHO_PLUGIN_AU_TYPE aufx /* kAudioUnitType_Effect */ | |||
| # endif | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Check that symbol macros are well defined | |||
| #ifdef DISTRHO_PROPER_CPP11_SUPPORT | |||
| #ifdef DISTRHO_PLUGIN_AU_TYPE | |||
| static_assert(sizeof(STRINGIFY(DISTRHO_PLUGIN_AU_TYPE)) == 5, "The macro DISTRHO_PLUGIN_AU_TYPE has incorrect length"); | |||
| # if DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_OUTPUTS == 0 | |||
| static constexpr const char _aut[5] = STRINGIFY(DISTRHO_PLUGIN_AU_TYPE); | |||
| static_assert(_aut[0] != 'a' || _aut[0] != 'u' || _aut[0] != 'm' || _aut[0] != 'u', | |||
| "The 'aumu' type requires both audio input and output"); | |||
| # endif | |||
| #endif | |||
| #ifdef DISTRHO_PLUGIN_BRAND_ID | |||
| static_assert(sizeof(STRINGIFY(DISTRHO_PLUGIN_BRAND_ID)) == 5, "The macro DISTRHO_PLUGIN_BRAND_ID has incorrect length"); | |||
| #endif | |||
| #ifdef DISTRHO_PLUGIN_UNIQUE_ID | |||
| static_assert(sizeof(STRINGIFY(DISTRHO_PLUGIN_UNIQUE_ID)) == 5, "The macro DISTRHO_PLUGIN_UNIQUE_ID has incorrect length"); | |||
| #endif | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // Prevent users from messing about with DPF internals | |||
| #ifdef DISTRHO_UI_IS_STANDALONE | |||
| # error DISTRHO_UI_IS_STANDALONE must not be defined | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| #ifdef DISTRHO_UI_LINUX_WEBVIEW_START | |||
| # error DISTRHO_UI_LINUX_WEBVIEW_START must not be defined | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| #endif // DISTRHO_PLUGIN_CHECKS_H_INCLUDED | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -39,6 +39,7 @@ extern uint32_t d_nextBufferSize; | |||
| extern double d_nextSampleRate; | |||
| extern const char* d_nextBundlePath; | |||
| extern bool d_nextPluginIsDummy; | |||
| extern bool d_nextPluginIsSelfTest; | |||
| extern bool d_nextCanRequestParameterValueChanges; | |||
| // ----------------------------------------------------------------------- | |||
| @@ -67,7 +68,8 @@ struct PortGroupWithId : PortGroup { | |||
| groupId(kPortGroupNone) {} | |||
| }; | |||
| static void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& portGroup) | |||
| static inline | |||
| void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& portGroup) | |||
| { | |||
| switch (groupId) | |||
| { | |||
| @@ -86,12 +88,62 @@ static void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& por | |||
| } | |||
| } | |||
| static inline | |||
| void d_strncpy(char* const dst, const char* const src, const size_t length) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(length > 0,); | |||
| if (const size_t len = std::min(std::strlen(src), length-1U)) | |||
| { | |||
| std::memcpy(dst, src, len); | |||
| dst[len] = '\0'; | |||
| } | |||
| else | |||
| { | |||
| dst[0] = '\0'; | |||
| } | |||
| } | |||
| template<typename T> | |||
| static inline | |||
| void snprintf_t(char* const dst, const T value, const char* const format, const size_t size) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(size > 0,); | |||
| std::snprintf(dst, size-1, format, value); | |||
| dst[size-1] = '\0'; | |||
| } | |||
| static inline | |||
| void snprintf_f32(char* const dst, const float value, const size_t size) | |||
| { | |||
| return snprintf_t<float>(dst, value, "%f", size); | |||
| } | |||
| static inline | |||
| void snprintf_f32(char* const dst, const double value, const size_t size) | |||
| { | |||
| return snprintf_t<double>(dst, value, "%f", size); | |||
| } | |||
| static inline | |||
| void snprintf_i32(char* const dst, const int32_t value, const size_t size) | |||
| { | |||
| return snprintf_t<int32_t>(dst, value, "%d", size); | |||
| } | |||
| static inline | |||
| void snprintf_u32(char* const dst, const uint32_t value, const size_t size) | |||
| { | |||
| return snprintf_t<uint32_t>(dst, value, "%u", size); | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| // Plugin private data | |||
| struct Plugin::PrivateData { | |||
| const bool canRequestParameterValueChanges; | |||
| const bool isDummy; | |||
| const bool isSelfTest; | |||
| bool isProcessing; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| @@ -136,6 +188,7 @@ struct Plugin::PrivateData { | |||
| PrivateData() noexcept | |||
| : canRequestParameterValueChanges(d_nextCanRequestParameterValueChanges), | |||
| isDummy(d_nextPluginIsDummy), | |||
| isSelfTest(d_nextPluginIsSelfTest), | |||
| isProcessing(false), | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| audioPorts(nullptr), | |||
| @@ -256,6 +309,9 @@ struct Plugin::PrivateData { | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| bool updateStateValueCallback(const char* const key, const char* const value) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0', false); | |||
| DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, false); | |||
| d_stdout("updateStateValueCallback %p", updateStateValueCallbackFunc); | |||
| if (updateStateValueCallbackFunc != nullptr) | |||
| return updateStateValueCallbackFunc(callbacksPtr, key, value); | |||
| @@ -282,6 +338,64 @@ public: | |||
| DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| { | |||
| uint32_t j=0; | |||
| # if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i, ++j) | |||
| fPlugin->initAudioPort(true, i, fData->audioPorts[j]); | |||
| # endif | |||
| # if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i, ++j) | |||
| fPlugin->initAudioPort(false, i, fData->audioPorts[j]); | |||
| # endif | |||
| } | |||
| #endif // DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) | |||
| fPlugin->initParameter(i, fData->parameters[i]); | |||
| { | |||
| std::set<uint32_t> portGroupIndices; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) | |||
| portGroupIndices.insert(fData->audioPorts[i].groupId); | |||
| #endif | |||
| for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) | |||
| portGroupIndices.insert(fData->parameters[i].groupId); | |||
| portGroupIndices.erase(kPortGroupNone); | |||
| if (const uint32_t portGroupSize = static_cast<uint32_t>(portGroupIndices.size())) | |||
| { | |||
| fData->portGroups = new PortGroupWithId[portGroupSize]; | |||
| fData->portGroupCount = portGroupSize; | |||
| uint32_t index = 0; | |||
| for (std::set<uint32_t>::iterator it = portGroupIndices.begin(); it != portGroupIndices.end(); ++it, ++index) | |||
| { | |||
| PortGroupWithId& portGroup(fData->portGroups[index]); | |||
| portGroup.groupId = *it; | |||
| if (portGroup.groupId < portGroupSize) | |||
| fPlugin->initPortGroup(portGroup.groupId, portGroup); | |||
| else | |||
| fillInPredefinedPortGroupData(portGroup.groupId, portGroup); | |||
| } | |||
| } | |||
| } | |||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
| for (uint32_t i=0; i < fData->programCount; ++i) | |||
| fPlugin->initProgramName(i, fData->programNames[i]); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| for (uint32_t i=0; i < fData->stateCount; ++i) | |||
| fPlugin->initState(i, fData->states[i]); | |||
| #endif | |||
| #if defined(DPF_RUNTIME_TESTING) && defined(__GNUC__) && !defined(__clang__) | |||
| /* Run-time testing build. | |||
| * Verify that virtual functions are overriden if parameters, programs or states are in use. | |||
| @@ -324,16 +438,30 @@ public: | |||
| # if DISTRHO_PLUGIN_WANT_STATE | |||
| if (fData->stateCount != 0) | |||
| { | |||
| if ((void*)(fPlugin->*(&Plugin::initState)) == (void*)&Plugin::initState) | |||
| bool hasNonUiState = false; | |||
| for (uint32_t i=0; i < fData->stateCount; ++i) | |||
| { | |||
| d_stderr2("DPF warning: Plugins with state must implement `initState`"); | |||
| abort(); | |||
| if ((fData->states[i].hints & kStateIsOnlyForUI) == 0) | |||
| { | |||
| hasNonUiState = true; | |||
| break; | |||
| } | |||
| } | |||
| if ((void*)(fPlugin->*(&Plugin::setState)) == (void*)&Plugin::setState) | |||
| if (hasNonUiState) | |||
| { | |||
| d_stderr2("DPF warning: Plugins with state must implement `setState`"); | |||
| abort(); | |||
| if ((void*)(fPlugin->*(static_cast<void(Plugin::*)(uint32_t,State&)>(&Plugin::initState))) == | |||
| (void*)static_cast<void(Plugin::*)(uint32_t,State&)>(&Plugin::initState)) | |||
| { | |||
| d_stderr2("DPF warning: Plugins with state must implement `initState`"); | |||
| abort(); | |||
| } | |||
| if ((void*)(fPlugin->*(&Plugin::setState)) == (void*)&Plugin::setState) | |||
| { | |||
| d_stderr2("DPF warning: Plugins with state must implement `setState`"); | |||
| abort(); | |||
| } | |||
| } | |||
| } | |||
| # endif | |||
| @@ -355,64 +483,6 @@ public: | |||
| # endif | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| { | |||
| uint32_t j=0; | |||
| # if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i, ++j) | |||
| fPlugin->initAudioPort(true, i, fData->audioPorts[j]); | |||
| # endif | |||
| # if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i, ++j) | |||
| fPlugin->initAudioPort(false, i, fData->audioPorts[j]); | |||
| # endif | |||
| } | |||
| #endif // DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) | |||
| fPlugin->initParameter(i, fData->parameters[i]); | |||
| { | |||
| std::set<uint32_t> portGroupIndices; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) | |||
| portGroupIndices.insert(fData->audioPorts[i].groupId); | |||
| #endif | |||
| for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) | |||
| portGroupIndices.insert(fData->parameters[i].groupId); | |||
| portGroupIndices.erase(kPortGroupNone); | |||
| if (const uint32_t portGroupSize = static_cast<uint32_t>(portGroupIndices.size())) | |||
| { | |||
| fData->portGroups = new PortGroupWithId[portGroupSize]; | |||
| fData->portGroupCount = portGroupSize; | |||
| uint32_t index = 0; | |||
| for (std::set<uint32_t>::iterator it = portGroupIndices.begin(); it != portGroupIndices.end(); ++it, ++index) | |||
| { | |||
| PortGroupWithId& portGroup(fData->portGroups[index]); | |||
| portGroup.groupId = *it; | |||
| if (portGroup.groupId < portGroupSize) | |||
| fPlugin->initPortGroup(portGroup.groupId, portGroup); | |||
| else | |||
| fillInPredefinedPortGroupData(portGroup.groupId, portGroup); | |||
| } | |||
| } | |||
| } | |||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
| for (uint32_t i=0, count=fData->programCount; i < count; ++i) | |||
| fPlugin->initProgramName(i, fData->programNames[i]); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| for (uint32_t i=0, count=fData->stateCount; i < count; ++i) | |||
| fPlugin->initState(i, fData->states[i]); | |||
| #endif | |||
| fData->callbacksPtr = callbacksPtr; | |||
| fData->writeMidiCallbackFunc = writeMidiCall; | |||
| fData->requestParameterValueChangeCallbackFunc = requestParameterValueChangeCall; | |||
| @@ -593,6 +663,11 @@ public: | |||
| return (getParameterHints(index) & kParameterIsOutput) != 0x0; | |||
| } | |||
| bool isParameterInteger(const uint32_t index) const noexcept | |||
| { | |||
| return (getParameterHints(index) & kParameterIsInteger) != 0x0; | |||
| } | |||
| bool isParameterTrigger(const uint32_t index) const noexcept | |||
| { | |||
| return (getParameterHints(index) & kParameterIsTrigger) == kParameterIsTrigger; | |||
| @@ -697,6 +772,24 @@ public: | |||
| fPlugin->setParameterValue(index, value); | |||
| } | |||
| /* | |||
| bool getParameterIndexForSymbol(const char* const symbol, uint32_t& index) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, false); | |||
| for (uint32_t i=0; i < fData->parameterCount; ++i) | |||
| { | |||
| if (fData->parameters[i].symbol == symbol) | |||
| { | |||
| index = i; | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| */ | |||
| uint32_t getPortGroupCount() const noexcept | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0); | |||
| @@ -800,7 +893,16 @@ public: | |||
| return fData->states[index].description; | |||
| } | |||
| # if DISTRHO_PLUGIN_WANT_FULL_STATE | |||
| #ifdef __MOD_DEVICES__ | |||
| const String& getStateFileTypes(const uint32_t index) const noexcept | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); | |||
| return fData->states[index].fileTypes; | |||
| } | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_FULL_STATE | |||
| String getStateValue(const char* const key) const | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackString); | |||
| @@ -808,7 +910,7 @@ public: | |||
| return fPlugin->getState(key); | |||
| } | |||
| # endif | |||
| #endif | |||
| void setState(const char* const key, const char* const value) | |||
| { | |||
| @@ -879,7 +981,7 @@ public: | |||
| } | |||
| } | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| void run(const float** const inputs, float** const outputs, const uint32_t frames, | |||
| const MidiEvent* const midiEvents, const uint32_t midiEventCount) | |||
| { | |||
| @@ -896,7 +998,7 @@ public: | |||
| fPlugin->run(inputs, outputs, frames, midiEvents, midiEventCount); | |||
| fData->isProcessing = false; | |||
| } | |||
| #else | |||
| #else | |||
| void run(const float** const inputs, float** const outputs, const uint32_t frames) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); | |||
| @@ -912,7 +1014,21 @@ public: | |||
| fPlugin->run(inputs, outputs, frames); | |||
| fData->isProcessing = false; | |||
| } | |||
| #endif | |||
| #endif | |||
| // ------------------------------------------------------------------- | |||
| #ifdef DISTRHO_PLUGIN_TARGET_AU | |||
| void setAudioPortIO(const uint16_t numInputs, const uint16_t numOutputs) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); | |||
| if (fIsActive) fPlugin->deactivate(); | |||
| fPlugin->ioChanged(numInputs, numOutputs); | |||
| if (fIsActive) fPlugin->activate(); | |||
| } | |||
| #endif | |||
| // ------------------------------------------------------------------- | |||
| @@ -928,14 +1044,14 @@ public: | |||
| return fData->sampleRate; | |||
| } | |||
| void setBufferSize(const uint32_t bufferSize, const bool doCallback = false) | |||
| bool setBufferSize(const uint32_t bufferSize, const bool doCallback = false) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, false); | |||
| DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, false); | |||
| DISTRHO_SAFE_ASSERT(bufferSize >= 2); | |||
| if (fData->bufferSize == bufferSize) | |||
| return; | |||
| return false; | |||
| fData->bufferSize = bufferSize; | |||
| @@ -945,6 +1061,8 @@ public: | |||
| fPlugin->bufferSizeChanged(bufferSize); | |||
| if (fIsActive) fPlugin->activate(); | |||
| } | |||
| return true; | |||
| } | |||
| void setSampleRate(const double sampleRate, const bool doCallback = false) | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -14,8 +14,9 @@ | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #include "DistrhoDetails.hpp" | |||
| #include "DistrhoPluginUtils.hpp" | |||
| #include "src/DistrhoPluginChecks.h" | |||
| #include "src/DistrhoDefines.h" | |||
| #include <cstddef> | |||
| @@ -25,6 +26,17 @@ | |||
| # include <stdint.h> | |||
| #endif | |||
| #if defined(DISTRHO_OS_WASM) | |||
| # include <emscripten/emscripten.h> | |||
| #elif defined(DISTRHO_OS_WINDOWS) | |||
| # include <winsock2.h> | |||
| # include <windows.h> | |||
| #elif defined(HAVE_X11) | |||
| # define Window X11Window | |||
| # include <X11/Xresource.h> | |||
| # undef Window | |||
| #endif | |||
| #if DISTRHO_UI_FILE_BROWSER && !defined(DISTRHO_OS_MAC) | |||
| # define DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION | |||
| # define DISTRHO_PUGL_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION) | |||
| @@ -49,35 +61,29 @@ | |||
| START_NAMESPACE_DISTRHO | |||
| # include "../extra/FileBrowserDialogImpl.hpp" | |||
| END_NAMESPACE_DISTRHO | |||
| # define Window X11Window | |||
| # include "../extra/FileBrowserDialogImpl.cpp" | |||
| # undef Window | |||
| #endif | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| # if defined(DISTRHO_OS_WINDOWS) | |||
| # include <winsock2.h> | |||
| # include <windows.h> | |||
| # elif defined(HAVE_X11) | |||
| # include <X11/Xresource.h> | |||
| # endif | |||
| #else | |||
| # include "src/TopLevelWidgetPrivateData.hpp" | |||
| # include "src/WindowPrivateData.hpp" | |||
| #if DISTRHO_UI_WEB_VIEW && !defined(DISTRHO_OS_MAC) | |||
| # define DISTRHO_WEB_VIEW_HPP_INCLUDED | |||
| # define WEB_VIEW_NAMESPACE DISTRHO_NAMESPACE | |||
| # define WEB_VIEW_DISTRHO_NAMESPACE | |||
| START_NAMESPACE_DISTRHO | |||
| # include "../extra/WebViewImpl.hpp" | |||
| END_NAMESPACE_DISTRHO | |||
| # define Window X11Window | |||
| # include "../extra/WebViewImpl.cpp" | |||
| # undef Window | |||
| #endif | |||
| #include "src/TopLevelWidgetPrivateData.hpp" | |||
| #include "src/WindowPrivateData.hpp" | |||
| #include "DistrhoUIPrivateData.hpp" | |||
| START_NAMESPACE_DISTRHO | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * Static data, see DistrhoUIInternal.hpp */ | |||
| const char* g_nextBundlePath = nullptr; | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| uintptr_t g_nextWindowId = 0; | |||
| double g_nextScaleFactor = 1.0; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * get global scale factor */ | |||
| @@ -90,23 +96,25 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle) | |||
| if (const char* const scale = getenv("DPF_SCALE_FACTOR")) | |||
| return std::max(1.0, std::atof(scale)); | |||
| #if defined(DISTRHO_OS_WINDOWS) | |||
| #if defined(DISTRHO_OS_WASM) | |||
| return emscripten_get_device_pixel_ratio(); | |||
| #elif defined(DISTRHO_OS_WINDOWS) | |||
| if (const HMODULE Shcore = LoadLibraryA("Shcore.dll")) | |||
| { | |||
| typedef HRESULT(WINAPI* PFN_GetProcessDpiAwareness)(HANDLE, DWORD*); | |||
| typedef HRESULT(WINAPI* PFN_GetScaleFactorForMonitor)(HMONITOR, DWORD*); | |||
| # if defined(__GNUC__) && (__GNUC__ >= 9) | |||
| # pragma GCC diagnostic push | |||
| # pragma GCC diagnostic ignored "-Wcast-function-type" | |||
| # endif | |||
| #if defined(__GNUC__) && (__GNUC__ >= 9) | |||
| #pragma GCC diagnostic push | |||
| #pragma GCC diagnostic ignored "-Wcast-function-type" | |||
| #endif | |||
| const PFN_GetProcessDpiAwareness GetProcessDpiAwareness | |||
| = (PFN_GetProcessDpiAwareness)GetProcAddress(Shcore, "GetProcessDpiAwareness"); | |||
| const PFN_GetScaleFactorForMonitor GetScaleFactorForMonitor | |||
| = (PFN_GetScaleFactorForMonitor)GetProcAddress(Shcore, "GetScaleFactorForMonitor"); | |||
| # if defined(__GNUC__) && (__GNUC__ >= 9) | |||
| # pragma GCC diagnostic pop | |||
| # endif | |||
| #if defined(__GNUC__) && (__GNUC__ >= 9) | |||
| #pragma GCC diagnostic pop | |||
| #endif | |||
| DWORD dpiAware = 0; | |||
| DWORD scaleFactor = 100; | |||
| @@ -115,14 +123,14 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle) | |||
| { | |||
| const HMONITOR hMon = parentWindowHandle != 0 | |||
| ? MonitorFromWindow((HWND)parentWindowHandle, MONITOR_DEFAULTTOPRIMARY) | |||
| : MonitorFromPoint(POINT{0,0}, MONITOR_DEFAULTTOPRIMARY); | |||
| : MonitorFromPoint(POINT(), MONITOR_DEFAULTTOPRIMARY); | |||
| GetScaleFactorForMonitor(hMon, &scaleFactor); | |||
| } | |||
| FreeLibrary(Shcore); | |||
| return static_cast<double>(scaleFactor) / 100.0; | |||
| } | |||
| #elif defined(HAVE_X11) | |||
| #elif defined(HAVE_X11) | |||
| ::Display* const display = XOpenDisplay(nullptr); | |||
| DISTRHO_SAFE_ASSERT_RETURN(display != nullptr, 1.0); | |||
| @@ -153,7 +161,7 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle) | |||
| XCloseDisplay(display); | |||
| return dpi / 96; | |||
| #endif | |||
| #endif | |||
| return 1.0; | |||
| @@ -162,50 +170,173 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle) | |||
| } | |||
| #endif // !DISTRHO_OS_MAC | |||
| #endif | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * UI::PrivateData special handling */ | |||
| UI::PrivateData* UI::PrivateData::s_nextPrivateData = nullptr; | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| ExternalWindow::PrivateData | |||
| #else | |||
| PluginWindow& | |||
| #endif | |||
| UI::PrivateData::createNextWindow(UI* const ui, const uint width, const uint height) | |||
| PluginWindow& UI::PrivateData::createNextWindow(UI* const ui, uint width, uint height) | |||
| { | |||
| UI::PrivateData* const pData = s_nextPrivateData; | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| pData->window = new PluginWindow(ui, pData->app); | |||
| ExternalWindow::PrivateData ewData; | |||
| ewData.parentWindowHandle = pData->winId; | |||
| ewData.width = width; | |||
| ewData.height = height; | |||
| ewData.scaleFactor = pData->scaleFactor != 0.0 ? pData->scaleFactor : getDesktopScaleFactor(pData->winId); | |||
| ewData.title = DISTRHO_PLUGIN_NAME; | |||
| ewData.isStandalone = DISTRHO_UI_IS_STANDALONE; | |||
| return ewData; | |||
| #else | |||
| pData->window = new PluginWindow(ui, pData->app, pData->winId, width, height, pData->scaleFactor); | |||
| UI::PrivateData* const uiData = s_nextPrivateData; | |||
| const double scaleFactor = d_isNotZero(uiData->scaleFactor) ? uiData->scaleFactor : getDesktopScaleFactor(uiData->winId); | |||
| // If there are no callbacks, this is most likely a temporary window, so ignore idle callbacks | |||
| if (pData->callbacksPtr == nullptr) | |||
| pData->window->setIgnoreIdleCallbacks(); | |||
| if (d_isNotZero(scaleFactor) && d_isNotEqual(scaleFactor, 1.0)) | |||
| { | |||
| width *= scaleFactor; | |||
| height *= scaleFactor; | |||
| } | |||
| d_stdout("createNextWindow %u %u %f", width, height, scaleFactor); | |||
| uiData->window = new PluginWindow(ui, uiData->app, uiData->winId, width, height, scaleFactor); | |||
| if (uiData->callbacksPtr != nullptr) | |||
| { | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| String path; | |||
| if (uiData->bundlePath != nullptr) | |||
| { | |||
| path = getResourcePath(uiData->bundlePath); | |||
| } | |||
| else | |||
| { | |||
| path = getBinaryFilename(); | |||
| path.truncate(path.rfind(DISTRHO_OS_SEP)); | |||
| path += "/resources"; | |||
| } | |||
| return pData->window.getObject(); | |||
| path.urlEncode(); | |||
| // TODO convert win32 paths to web | |||
| WebViewOptions opts; | |||
| opts.initialJS = "" | |||
| "editParameter = function(index, started){ postMessage('editparam ' + index + ' ' + (started ? '1' : '0')) };" | |||
| "setParameterValue = function(index, value){ postMessage('setparam ' + index + ' ' + value) };" | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| "setState = function(key, value){ postMessage('setstate ' + key + ' ' + value) };" | |||
| "requestStateFile = function(key){ postMessage('reqstatefile ' + key) };" | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| "sendNote = function(channel, note, velocity){ postMessage('sendnote ' + channel + ' ' + note + ' ' + velocity) };" | |||
| #endif | |||
| ; | |||
| opts.callback = webViewMessageCallback; | |||
| opts.callbackPtr = uiData; | |||
| uiData->webview = webViewCreate("file://" + path + "/index.html", | |||
| uiData->winId != 0 ? uiData->winId : uiData->window->getNativeWindowHandle(), | |||
| width, | |||
| height, | |||
| scaleFactor, | |||
| opts); | |||
| #endif | |||
| } | |||
| // If there are no callbacks, this is most likely a temporary window, so ignore idle callbacks | |||
| else | |||
| { | |||
| uiData->window->setIgnoreIdleCallbacks(); | |||
| } | |||
| return uiData->window.getObject(); | |||
| } | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| void UI::PrivateData::webViewMessageCallback(void* const arg, char* const msg) | |||
| { | |||
| UI::PrivateData* const uiData = static_cast<UI::PrivateData*>(arg); | |||
| if (std::strncmp(msg, "setparam ", 9) == 0) | |||
| { | |||
| const char* const strindex = msg + 9; | |||
| char* strvalue = nullptr; | |||
| const ulong index = std::strtoul(strindex, &strvalue, 10); | |||
| DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,); | |||
| float value; | |||
| { | |||
| const ScopedSafeLocale ssl; | |||
| value = std::atof(strvalue); | |||
| } | |||
| uiData->setParamCallback(index + uiData->parameterOffset, value); | |||
| return; | |||
| } | |||
| if (std::strncmp(msg, "editparam ", 10) == 0) | |||
| { | |||
| const char* const strindex = msg + 10; | |||
| char* strvalue = nullptr; | |||
| const ulong index = std::strtoul(strindex, &strvalue, 10); | |||
| DISTRHO_SAFE_ASSERT_RETURN(strvalue != nullptr && strindex != strvalue,); | |||
| const bool started = strvalue[0] != '0'; | |||
| uiData->editParamCallback(index + uiData->parameterOffset, started); | |||
| return; | |||
| } | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| if (std::strncmp(msg, "setstate ", 9) == 0) | |||
| { | |||
| char* const key = msg + 9; | |||
| char* const sep = std::strchr(key, ' '); | |||
| DISTRHO_SAFE_ASSERT_RETURN(sep != nullptr,); | |||
| *sep = '\0'; | |||
| char* const value = sep + 1; | |||
| uiData->setStateCallback(key, value); | |||
| return; | |||
| } | |||
| if (std::strncmp(msg, "reqstatefile ", 13) == 0) | |||
| { | |||
| const char* const key = msg + 13; | |||
| uiData->fileRequestCallback(key); | |||
| return; | |||
| } | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| if (std::strncmp(msg, "sendnote ", 9) == 0) | |||
| { | |||
| const char* const strchannel = msg + 9; | |||
| char* strnote = nullptr; | |||
| char* strvelocity = nullptr; | |||
| char* end = nullptr; | |||
| const ulong channel = std::strtoul(strchannel, &strnote, 10); | |||
| DISTRHO_SAFE_ASSERT_RETURN(strnote != nullptr && strchannel != strnote,); | |||
| const ulong note = std::strtoul(strnote, &strvelocity, 10); | |||
| DISTRHO_SAFE_ASSERT_RETURN(strvelocity != nullptr && strchannel != strvelocity,); | |||
| const ulong velocity = std::strtoul(strvelocity, &end, 10); | |||
| DISTRHO_SAFE_ASSERT_RETURN(end != nullptr && strvelocity != end,); | |||
| uiData->sendNoteCallback(channel, note, velocity); | |||
| return; | |||
| } | |||
| #endif | |||
| d_stderr("UI received unknown message '%s'", msg); | |||
| } | |||
| #endif | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * UI */ | |||
| UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetAsMinimumSize) | |||
| : UIWidget(UI::PrivateData::createNextWindow(this, width, height)), | |||
| : UIWidget(UI::PrivateData::createNextWindow(this, | |||
| // width | |||
| #ifdef DISTRHO_UI_DEFAULT_WIDTH | |||
| width == 0 ? DISTRHO_UI_DEFAULT_WIDTH : | |||
| #endif | |||
| width, | |||
| // height | |||
| #ifdef DISTRHO_UI_DEFAULT_HEIGHT | |||
| height == 0 ? DISTRHO_UI_DEFAULT_HEIGHT : | |||
| #endif | |||
| height | |||
| )), | |||
| uiData(UI::PrivateData::s_nextPrivateData) | |||
| { | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| if (width != 0 && height != 0) | |||
| { | |||
| Widget::setSize(width, height); | |||
| @@ -213,14 +344,20 @@ UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetA | |||
| if (automaticallyScaleAndSetAsMinimumSize) | |||
| setGeometryConstraints(width, height, true, true, true); | |||
| } | |||
| #else | |||
| // unused | |||
| (void)automaticallyScaleAndSetAsMinimumSize; | |||
| #endif | |||
| #ifdef DISTRHO_UI_DEFAULT_WIDTH | |||
| else | |||
| { | |||
| Widget::setSize(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT); | |||
| } | |||
| #endif | |||
| } | |||
| UI::~UI() | |||
| { | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| if (uiData->webview != nullptr) | |||
| webViewDestroy(uiData->webview); | |||
| #endif | |||
| } | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| @@ -228,15 +365,11 @@ UI::~UI() | |||
| bool UI::isResizable() const noexcept | |||
| { | |||
| #if DISTRHO_UI_USER_RESIZABLE | |||
| # if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| return true; | |||
| # else | |||
| #if DISTRHO_UI_USER_RESIZABLE | |||
| return uiData->window->isResizable(); | |||
| # endif | |||
| #else | |||
| #else | |||
| return false; | |||
| #endif | |||
| #endif | |||
| } | |||
| uint UI::getBackgroundColor() const noexcept | |||
| @@ -307,33 +440,94 @@ void* UI::getPluginInstancePointer() const noexcept | |||
| } | |||
| #endif | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * External UI helpers (static calls) */ | |||
| * DSP/Plugin Callbacks */ | |||
| const char* UI::getNextBundlePath() noexcept | |||
| void UI::parameterChanged(const uint32_t index, const float value) | |||
| { | |||
| return g_nextBundlePath; | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| if (uiData->webview != nullptr) | |||
| { | |||
| char msg[128]; | |||
| { | |||
| const ScopedSafeLocale ssl; | |||
| std::snprintf(msg, sizeof(msg) - 1, | |||
| "typeof(parameterChanged) === 'function' && parameterChanged(%u,%f)", index, value); | |||
| } | |||
| webViewEvaluateJS(uiData->webview, msg); | |||
| } | |||
| #else | |||
| // unused | |||
| (void)index; | |||
| (void)value; | |||
| #endif | |||
| } | |||
| double UI::getNextScaleFactor() noexcept | |||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
| void UI::programLoaded(const uint32_t index) | |||
| { | |||
| return g_nextScaleFactor; | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| if (uiData->webview != nullptr) | |||
| { | |||
| char msg[128]; | |||
| std::snprintf(msg, sizeof(msg) - 1, | |||
| "typeof(programLoaded) === 'function' && programLoaded(%u)", index); | |||
| webViewEvaluateJS(uiData->webview, msg); | |||
| } | |||
| #else | |||
| // unused | |||
| (void)index; | |||
| #endif | |||
| } | |||
| #endif | |||
| # if DISTRHO_PLUGIN_HAS_EMBED_UI | |||
| uintptr_t UI::getNextWindowId() noexcept | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| void UI::stateChanged(const char* const key, const char* const value) | |||
| { | |||
| return g_nextWindowId; | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| if (uiData->webview != nullptr) | |||
| { | |||
| const size_t keylen = std::strlen(key); | |||
| const size_t valuelen = std::strlen(value); | |||
| const size_t msglen = keylen + valuelen + 60; | |||
| if (char* const msg = static_cast<char*>(std::malloc(msglen))) | |||
| { | |||
| // TODO escape \\' | |||
| std::snprintf(msg, msglen - 1, | |||
| "typeof(stateChanged) === 'function' && stateChanged('%s','%s')", key, value); | |||
| msg[msglen - 1] = '\0'; | |||
| webViewEvaluateJS(uiData->webview, msg); | |||
| std::free(msg); | |||
| } | |||
| } | |||
| #else | |||
| // unused | |||
| (void)key; | |||
| (void)value; | |||
| #endif | |||
| } | |||
| # endif | |||
| #endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| #endif | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * DSP/Plugin Callbacks (optional) */ | |||
| void UI::sampleRateChanged(double) | |||
| void UI::sampleRateChanged(const double sampleRate) | |||
| { | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| if (uiData->webview != nullptr) | |||
| { | |||
| char msg[128]; | |||
| { | |||
| const ScopedSafeLocale ssl; | |||
| std::snprintf(msg, sizeof(msg) - 1, | |||
| "typeof(sampleRateChanged) === 'function' && sampleRateChanged(%f)", sampleRate); | |||
| } | |||
| webViewEvaluateJS(uiData->webview, msg); | |||
| } | |||
| #else | |||
| // unused | |||
| (void)sampleRate; | |||
| #endif | |||
| } | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| @@ -343,7 +537,6 @@ void UI::uiScaleFactorChanged(double) | |||
| { | |||
| } | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| std::vector<DGL_NAMESPACE::ClipboardDataOffer> UI::getClipboardDataOfferTypes() | |||
| { | |||
| return uiData->window->getClipboardDataOfferTypes(); | |||
| @@ -367,12 +560,11 @@ void UI::uiFocus(bool, DGL_NAMESPACE::CrossingMode) | |||
| { | |||
| } | |||
| void UI::uiReshape(uint, uint) | |||
| void UI::uiReshape(const uint width, const uint height) | |||
| { | |||
| // NOTE this must be the same as Window::onReshape | |||
| pData->fallbackOnResize(); | |||
| pData->fallbackOnResize(width, height); | |||
| } | |||
| #endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| #if DISTRHO_UI_FILE_BROWSER | |||
| void UI::uiFileBrowserSelected(const char*) | |||
| @@ -383,43 +575,34 @@ void UI::uiFileBrowserSelected(const char*) | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| * UI Resize Handling, internal */ | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| void UI::sizeChanged(const uint width, const uint height) | |||
| { | |||
| UIWidget::sizeChanged(width, height); | |||
| uiData->setSizeCallback(width, height); | |||
| } | |||
| #else | |||
| void UI::onResize(const ResizeEvent& ev) | |||
| { | |||
| UIWidget::onResize(ev); | |||
| #ifndef DISTRHO_PLUGIN_TARGET_VST3 | |||
| #if ! DISTRHO_UI_USES_SIZE_REQUEST | |||
| if (uiData->initializing) | |||
| return; | |||
| const uint width = ev.size.getWidth(); | |||
| const uint height = ev.size.getHeight(); | |||
| uiData->setSizeCallback(width, height); | |||
| #endif | |||
| #endif | |||
| } | |||
| // NOTE: only used for VST3 | |||
| // NOTE: only used for CLAP and VST3 | |||
| void UI::requestSizeChange(const uint width, const uint height) | |||
| { | |||
| # ifdef DISTRHO_PLUGIN_TARGET_VST3 | |||
| #if DISTRHO_UI_USES_SIZE_REQUEST | |||
| if (uiData->initializing) | |||
| uiData->window->setSizeForVST3(width, height); | |||
| uiData->window->setSizeFromHost(width, height); | |||
| else | |||
| uiData->setSizeCallback(width, height); | |||
| # else | |||
| #else | |||
| // unused | |||
| (void)width; | |||
| (void)height; | |||
| # endif | |||
| #endif | |||
| } | |||
| #endif | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -21,15 +21,6 @@ | |||
| START_NAMESPACE_DISTRHO | |||
| // ----------------------------------------------------------------------- | |||
| // Static data, see DistrhoUI.cpp | |||
| extern const char* g_nextBundlePath; | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| extern uintptr_t g_nextWindowId; | |||
| extern double g_nextScaleFactor; | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // UI exporter class | |||
| @@ -57,9 +48,10 @@ public: | |||
| void* const dspPtr = nullptr, | |||
| const double scaleFactor = 0.0, | |||
| const uint32_t bgColor = 0, | |||
| const uint32_t fgColor = 0xffffffff) | |||
| const uint32_t fgColor = 0xffffffff, | |||
| const char* const appClassName = nullptr) | |||
| : ui(nullptr), | |||
| uiData(new UI::PrivateData()) | |||
| uiData(new UI::PrivateData(appClassName)) | |||
| { | |||
| uiData->sampleRate = sampleRate; | |||
| uiData->bundlePath = bundlePath != nullptr ? strdup(bundlePath) : nullptr; | |||
| @@ -78,41 +70,23 @@ public: | |||
| uiData->setSizeCallbackFunc = setSizeCall; | |||
| uiData->fileRequestCallbackFunc = fileRequestCall; | |||
| g_nextBundlePath = bundlePath; | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| g_nextWindowId = winId; | |||
| g_nextScaleFactor = scaleFactor; | |||
| #endif | |||
| UI::PrivateData::s_nextPrivateData = uiData; | |||
| UI* const uiPtr = createUI(); | |||
| g_nextBundlePath = nullptr; | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| g_nextWindowId = 0; | |||
| g_nextScaleFactor = 0.0; | |||
| #else | |||
| // enter context called in the PluginWindow constructor, see DistrhoUIPrivateData.hpp | |||
| uiData->window->leaveContext(); | |||
| #endif | |||
| UI::PrivateData::s_nextPrivateData = nullptr; | |||
| DISTRHO_SAFE_ASSERT_RETURN(uiPtr != nullptr,); | |||
| ui = uiPtr; | |||
| uiData->initializing = false; | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| // unused | |||
| (void)bundlePath; | |||
| #endif | |||
| } | |||
| ~UIExporter() | |||
| { | |||
| quit(); | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| uiData->window->enterContextForDeletion(); | |||
| #endif | |||
| delete ui; | |||
| delete uiData; | |||
| } | |||
| @@ -136,13 +110,9 @@ public: | |||
| bool getGeometryConstraints(uint& minimumWidth, uint& minimumHeight, bool& keepAspectRatio) const noexcept | |||
| { | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| uiData->window->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio); | |||
| #else | |||
| const DGL_NAMESPACE::Size<uint> size(uiData->window->getGeometryConstraints(keepAspectRatio)); | |||
| minimumWidth = size.getWidth(); | |||
| minimumHeight = size.getHeight(); | |||
| #endif | |||
| return true; | |||
| } | |||
| @@ -193,16 +163,16 @@ public: | |||
| ui->parameterChanged(index, value); | |||
| } | |||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
| #if DISTRHO_PLUGIN_WANT_PROGRAMS | |||
| void programLoaded(const uint32_t index) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| ui->programLoaded(index); | |||
| } | |||
| #endif | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| void stateChanged(const char* const key, const char* const value) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| @@ -211,11 +181,11 @@ public: | |||
| ui->stateChanged(key, value); | |||
| } | |||
| #endif | |||
| #endif | |||
| // ------------------------------------------------------------------- | |||
| #if DISTRHO_UI_IS_STANDALONE | |||
| #if DISTRHO_UI_IS_STANDALONE | |||
| void exec(DGL_NAMESPACE::IdleCallback* const cb) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,); | |||
| @@ -224,13 +194,20 @@ public: | |||
| uiData->window->focus(); | |||
| uiData->app.addIdleCallback(cb); | |||
| uiData->app.exec(); | |||
| uiData->app.removeIdleCallback(cb); | |||
| } | |||
| void exec_idle() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, ); | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| if (uiData->webview != nullptr) | |||
| webViewIdle(uiData->webview); | |||
| #endif | |||
| ui->uiIdle(); | |||
| uiData->app.repaintIfNeeeded(); | |||
| } | |||
| void showAndFocus() | |||
| @@ -238,14 +215,21 @@ public: | |||
| uiData->window->show(); | |||
| uiData->window->focus(); | |||
| } | |||
| #endif | |||
| #endif | |||
| bool plugin_idle() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false); | |||
| uiData->app.idle(); | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| if (uiData->webview != nullptr) | |||
| webViewIdle(uiData->webview); | |||
| #endif | |||
| ui->uiIdle(); | |||
| uiData->app.repaintIfNeeeded(); | |||
| return ! uiData->app.isQuitting(); | |||
| } | |||
| @@ -260,52 +244,53 @@ public: | |||
| uiData->app.quit(); | |||
| } | |||
| void repaint() | |||
| { | |||
| uiData->window->repaint(); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| #if defined(DISTRHO_PLUGIN_TARGET_VST3) && (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) | |||
| void idleForVST3() | |||
| #if defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS) | |||
| void idleFromNativeIdle() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| uiData->app.triggerIdleCallbacks(); | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| if (uiData->webview != nullptr) | |||
| webViewIdle(uiData->webview); | |||
| #endif | |||
| ui->uiIdle(); | |||
| uiData->app.repaintIfNeeeded(); | |||
| } | |||
| # if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| void addIdleCallbackForVST3(IdleCallback* const cb, const uint timerFrequencyInMs) | |||
| void addIdleCallbackForNativeIdle(DGL_NAMESPACE::IdleCallback* const cb, const uint timerFrequencyInMs) | |||
| { | |||
| uiData->window->addIdleCallback(cb, timerFrequencyInMs); | |||
| } | |||
| void removeIdleCallbackForVST3(IdleCallback* const cb) | |||
| void removeIdleCallbackForNativeIdle(DGL_NAMESPACE::IdleCallback* const cb) | |||
| { | |||
| uiData->window->removeIdleCallback(cb); | |||
| } | |||
| # endif | |||
| #endif | |||
| #endif | |||
| // ------------------------------------------------------------------- | |||
| void setWindowOffset(const int x, const int y) | |||
| { | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| // TODO | |||
| (void)x; (void)y; | |||
| #else | |||
| uiData->window->setOffset(x, y); | |||
| #endif | |||
| } | |||
| #ifdef DISTRHO_PLUGIN_TARGET_VST3 | |||
| void setWindowSizeForVST3(const uint width, const uint height) | |||
| #if DISTRHO_UI_USES_SIZE_REQUEST | |||
| void setWindowSizeFromHost(const uint width, const uint height) | |||
| { | |||
| # if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| ui->setSize(width, height); | |||
| # else | |||
| uiData->window->setSizeForVST3(width, height); | |||
| # endif | |||
| uiData->window->setSizeFromHost(width, height); | |||
| } | |||
| #endif | |||
| #endif | |||
| void setWindowTitle(const char* const uiTitle) | |||
| { | |||
| @@ -314,11 +299,7 @@ public: | |||
| void setWindowTransientWinId(const uintptr_t transientParentWindowHandle) | |||
| { | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| ui->setTransientWindowId(transientParentWindowHandle); | |||
| #else | |||
| uiData->window->setTransientParent(transientParentWindowHandle); | |||
| #endif | |||
| } | |||
| bool setWindowVisible(const bool yesNo) | |||
| @@ -328,7 +309,6 @@ public: | |||
| return ! uiData->app.isQuitting(); | |||
| } | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| bool handlePluginKeyboardVST(const bool press, const bool special, const uint keychar, const uint keycode, const uint16_t mods) | |||
| { | |||
| using namespace DGL_NAMESPACE; | |||
| @@ -361,7 +341,6 @@ public: | |||
| return ret; | |||
| } | |||
| #endif | |||
| // ------------------------------------------------------------------- | |||
| @@ -372,14 +351,12 @@ public: | |||
| ui->uiScaleFactorChanged(scaleFactor); | |||
| } | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| void notifyFocusChanged(const bool focus) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| ui->uiFocus(focus, DGL_NAMESPACE::kCrossingNormal); | |||
| } | |||
| #endif | |||
| void setSampleRate(const double sampleRate, const bool doCallback = false) | |||
| { | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -23,13 +23,17 @@ | |||
| # include "DistrhoPluginVST.hpp" | |||
| #endif | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| # include "../extra/Sleep.hpp" | |||
| // TODO import and use file browser here | |||
| #else | |||
| # include "../../dgl/src/ApplicationPrivateData.hpp" | |||
| # include "../../dgl/src/WindowPrivateData.hpp" | |||
| # include "../../dgl/src/pugl.hpp" | |||
| #include "../../dgl/src/ApplicationPrivateData.hpp" | |||
| #include "../../dgl/src/WindowPrivateData.hpp" | |||
| #include "../../dgl/src/pugl.hpp" | |||
| #if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER | |||
| # include <map> | |||
| # include <string> | |||
| #endif | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| # include "extra/WebView.hpp" | |||
| #endif | |||
| #if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_DSSI) | |||
| @@ -38,87 +42,57 @@ | |||
| # define DISTRHO_UI_IS_STANDALONE 0 | |||
| #endif | |||
| #ifdef DISTRHO_PLUGIN_TARGET_VST3 | |||
| # define DISTRHO_UI_IS_VST3 1 | |||
| #if defined(DISTRHO_PLUGIN_TARGET_AU) | |||
| # define DISTRHO_UI_USES_SCHEDULED_REPAINTS 1 | |||
| #else | |||
| # define DISTRHO_UI_IS_VST3 0 | |||
| # define DISTRHO_UI_USES_SCHEDULED_REPAINTS 0 | |||
| #endif | |||
| #ifdef DISTRHO_PLUGIN_TARGET_VST2 | |||
| #if defined(DISTRHO_PLUGIN_TARGET_CLAP) || defined(DISTRHO_PLUGIN_TARGET_VST3) | |||
| # define DISTRHO_UI_USES_SIZE_REQUEST 1 | |||
| #else | |||
| # define DISTRHO_UI_USES_SIZE_REQUEST 0 | |||
| #endif | |||
| #if defined(DISTRHO_PLUGIN_TARGET_AU) || defined(DISTRHO_PLUGIN_TARGET_VST2) | |||
| # undef DISTRHO_UI_USER_RESIZABLE | |||
| # define DISTRHO_UI_USER_RESIZABLE 0 | |||
| #endif | |||
| START_NAMESPACE_DISTRHO | |||
| /* define webview start */ | |||
| #if defined(HAVE_X11) && defined(DISTRHO_OS_LINUX) && DISTRHO_UI_WEB_VIEW | |||
| # define DISTRHO_UI_LINUX_WEBVIEW_START | |||
| int dpf_webview_start(int argc, char* argv[]); | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // Plugin Application, will set class name based on plugin details | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| struct PluginApplication | |||
| { | |||
| DGL_NAMESPACE::IdleCallback* idleCallback; | |||
| UI* ui; | |||
| explicit PluginApplication() | |||
| : idleCallback(nullptr), | |||
| ui(nullptr) {} | |||
| void addIdleCallback(DGL_NAMESPACE::IdleCallback* const cb) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,); | |||
| DISTRHO_SAFE_ASSERT_RETURN(idleCallback == nullptr,); | |||
| idleCallback = cb; | |||
| } | |||
| bool isQuitting() const noexcept | |||
| { | |||
| return ui->isQuitting(); | |||
| } | |||
| bool isStandalone() const noexcept | |||
| { | |||
| return DISTRHO_UI_IS_STANDALONE; | |||
| } | |||
| void exec() | |||
| { | |||
| while (ui->isRunning()) | |||
| { | |||
| d_msleep(30); | |||
| idleCallback->idleCallback(); | |||
| } | |||
| if (! ui->isQuitting()) | |||
| ui->close(); | |||
| } | |||
| // these are not needed | |||
| void idle() {} | |||
| void quit() {} | |||
| void triggerIdleCallbacks() {} | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) | |||
| }; | |||
| #else | |||
| class PluginApplication : public DGL_NAMESPACE::Application | |||
| { | |||
| public: | |||
| explicit PluginApplication() | |||
| explicit PluginApplication(const char* className) | |||
| : DGL_NAMESPACE::Application(DISTRHO_UI_IS_STANDALONE) | |||
| { | |||
| #ifndef DISTRHO_OS_WASM | |||
| const char* const className = ( | |||
| #ifdef DISTRHO_PLUGIN_BRAND | |||
| DISTRHO_PLUGIN_BRAND | |||
| #else | |||
| DISTRHO_MACRO_AS_STRING(DISTRHO_NAMESPACE) | |||
| #endif | |||
| "-" DISTRHO_PLUGIN_NAME | |||
| ); | |||
| #if defined(__MOD_DEVICES__) || !defined(__EMSCRIPTEN__) | |||
| if (className == nullptr) | |||
| { | |||
| className = ( | |||
| #ifdef DISTRHO_PLUGIN_BRAND | |||
| DISTRHO_PLUGIN_BRAND | |||
| #else | |||
| DISTRHO_MACRO_AS_STRING(DISTRHO_NAMESPACE) | |||
| #endif | |||
| "-" DISTRHO_PLUGIN_NAME | |||
| ); | |||
| } | |||
| setClassName(className); | |||
| #endif | |||
| #else | |||
| // unused | |||
| (void)className; | |||
| #endif | |||
| } | |||
| void triggerIdleCallbacks() | |||
| @@ -126,49 +100,17 @@ public: | |||
| pData->triggerIdleCallbacks(); | |||
| } | |||
| void repaintIfNeeeded() | |||
| { | |||
| pData->repaintIfNeeeded(); | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) | |||
| }; | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // Plugin Window, will pass some Window events to UI | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| class PluginWindow | |||
| { | |||
| UI* const ui; | |||
| public: | |||
| explicit PluginWindow(UI* const uiPtr, PluginApplication& app) | |||
| : ui(uiPtr) | |||
| { | |||
| app.ui = ui; | |||
| } | |||
| // fetch cached data | |||
| uint getWidth() const noexcept { return ui->pData.width; } | |||
| uint getHeight() const noexcept { return ui->pData.height; } | |||
| double getScaleFactor() const noexcept { return ui->pData.scaleFactor; } | |||
| // direct mappings | |||
| void close() { ui->close(); } | |||
| void focus() { ui->focus(); } | |||
| void show() { ui->show(); } | |||
| bool isResizable() const noexcept { return ui->isResizable(); } | |||
| bool isVisible() const noexcept { return ui->isVisible(); } | |||
| void setTitle(const char* const title) { ui->setTitle(title); } | |||
| void setVisible(const bool visible) { ui->setVisible(visible); } | |||
| uintptr_t getNativeWindowHandle() const noexcept { return ui->getNativeWindowHandle(); } | |||
| void getGeometryConstraints(uint& minimumWidth, uint& minimumHeight, bool& keepAspectRatio) const noexcept | |||
| { | |||
| minimumWidth = ui->pData.minWidth; | |||
| minimumHeight = ui->pData.minHeight; | |||
| keepAspectRatio = ui->pData.keepAspectRatio; | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) | |||
| }; | |||
| #else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| class PluginWindow : public DGL_NAMESPACE::Window | |||
| { | |||
| UI* const ui; | |||
| @@ -183,7 +125,10 @@ public: | |||
| const uint height, | |||
| const double scaleFactor) | |||
| : Window(app, parentWindowHandle, width, height, scaleFactor, | |||
| DISTRHO_UI_USER_RESIZABLE, DISTRHO_UI_IS_VST3, false), | |||
| DISTRHO_UI_USER_RESIZABLE, | |||
| DISTRHO_UI_USES_SCHEDULED_REPAINTS, | |||
| DISTRHO_UI_USES_SIZE_REQUEST, | |||
| false), | |||
| ui(uiPtr), | |||
| initializing(true), | |||
| receivedReshapeDuringInit(false) | |||
| @@ -208,14 +153,18 @@ public: | |||
| if (pData->view == nullptr) | |||
| return; | |||
| if (receivedReshapeDuringInit) | |||
| ui->uiReshape(getWidth(), getHeight()); | |||
| initializing = false; | |||
| puglBackendLeave(pData->view); | |||
| if (receivedReshapeDuringInit) | |||
| { | |||
| puglBackendEnter(pData->view); | |||
| ui->uiReshape(getWidth(), getHeight()); | |||
| puglBackendLeave(pData->view); | |||
| } | |||
| } | |||
| // used for temporary windows (VST2/3 get size without active/visible view) | |||
| // used for temporary windows (VST/CLAP get size without active/visible view) | |||
| void setIgnoreIdleCallbacks(const bool ignore = true) | |||
| { | |||
| pData->ignoreIdleCallbacks = ignore; | |||
| @@ -228,8 +177,8 @@ public: | |||
| puglBackendEnter(pData->view); | |||
| } | |||
| #ifdef DISTRHO_PLUGIN_TARGET_VST3 | |||
| void setSizeForVST3(const uint width, const uint height) | |||
| #if DISTRHO_UI_USES_SIZE_REQUEST | |||
| void setSizeFromHost(const uint width, const uint height) | |||
| { | |||
| puglSetSizeAndDefault(pData->view, width, height); | |||
| } | |||
| @@ -284,13 +233,12 @@ protected: | |||
| ui->uiScaleFactorChanged(scaleFactor); | |||
| } | |||
| # if DISTRHO_UI_FILE_BROWSER | |||
| #if DISTRHO_UI_FILE_BROWSER | |||
| void onFileSelected(const char* filename) override; | |||
| # endif | |||
| #endif | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) | |||
| }; | |||
| #endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| // ----------------------------------------------------------------------- | |||
| // UI callbacks | |||
| @@ -309,6 +257,9 @@ struct UI::PrivateData { | |||
| // DGL | |||
| PluginApplication app; | |||
| ScopedPointer<PluginWindow> window; | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| WebViewHandle webview; | |||
| #endif | |||
| // DSP | |||
| double sampleRate; | |||
| @@ -320,9 +271,10 @@ struct UI::PrivateData { | |||
| uint fgColor; | |||
| double scaleFactor; | |||
| uintptr_t winId; | |||
| #if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| #if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER | |||
| char* uiStateFileKeyRequest; | |||
| #endif | |||
| std::map<std::string,std::string> lastUsedDirnames; | |||
| #endif | |||
| char* bundlePath; | |||
| // Ignore initial resize events while initializing | |||
| @@ -337,9 +289,12 @@ struct UI::PrivateData { | |||
| setSizeFunc setSizeCallbackFunc; | |||
| fileRequestFunc fileRequestCallbackFunc; | |||
| PrivateData() noexcept | |||
| : app(), | |||
| PrivateData(const char* const appClassName) noexcept | |||
| : app(appClassName), | |||
| window(nullptr), | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| webview(nullptr), | |||
| #endif | |||
| sampleRate(0), | |||
| parameterOffset(0), | |||
| dspPtr(nullptr), | |||
| @@ -347,9 +302,9 @@ struct UI::PrivateData { | |||
| fgColor(0xffffffff), | |||
| scaleFactor(1.0), | |||
| winId(0), | |||
| #if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| #if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER | |||
| uiStateFileKeyRequest(nullptr), | |||
| #endif | |||
| #endif | |||
| bundlePath(nullptr), | |||
| initializing(true), | |||
| callbacksPtr(nullptr), | |||
| @@ -360,32 +315,32 @@ struct UI::PrivateData { | |||
| setSizeCallbackFunc(nullptr), | |||
| fileRequestCallbackFunc(nullptr) | |||
| { | |||
| #if defined(DISTRHO_PLUGIN_TARGET_DSSI) || defined(DISTRHO_PLUGIN_TARGET_LV2) | |||
| #if defined(DISTRHO_PLUGIN_TARGET_DSSI) || defined(DISTRHO_PLUGIN_TARGET_LV2) | |||
| parameterOffset += DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS; | |||
| # if DISTRHO_PLUGIN_WANT_LATENCY | |||
| #if DISTRHO_PLUGIN_WANT_LATENCY | |||
| parameterOffset += 1; | |||
| # endif | |||
| #endif | |||
| #endif | |||
| #endif | |||
| #ifdef DISTRHO_PLUGIN_TARGET_LV2 | |||
| # if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) | |||
| #ifdef DISTRHO_PLUGIN_TARGET_LV2 | |||
| #if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) | |||
| parameterOffset += 1; | |||
| # endif | |||
| # if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) | |||
| #endif | |||
| #if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) | |||
| parameterOffset += 1; | |||
| # endif | |||
| #endif | |||
| #endif | |||
| #endif | |||
| #ifdef DISTRHO_PLUGIN_TARGET_VST3 | |||
| #ifdef DISTRHO_PLUGIN_TARGET_VST3 | |||
| parameterOffset += kVst3InternalParameterCount; | |||
| #endif | |||
| #endif | |||
| } | |||
| ~PrivateData() noexcept | |||
| { | |||
| #if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| #if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER | |||
| std::free(uiStateFileKeyRequest); | |||
| #endif | |||
| #endif | |||
| std::free(bundlePath); | |||
| } | |||
| @@ -403,6 +358,9 @@ struct UI::PrivateData { | |||
| void setStateCallback(const char* const key, const char* const value) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',); | |||
| DISTRHO_SAFE_ASSERT_RETURN(value != nullptr,); | |||
| if (setStateCallbackFunc != nullptr) | |||
| setStateCallbackFunc(callbacksPtr, key, value); | |||
| } | |||
| @@ -415,19 +373,20 @@ struct UI::PrivateData { | |||
| void setSizeCallback(const uint width, const uint height) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(width != 0 && height != 0,); | |||
| if (setSizeCallbackFunc != nullptr) | |||
| setSizeCallbackFunc(callbacksPtr, width, height); | |||
| } | |||
| // implemented below, after PluginWindow | |||
| bool fileRequestCallback(const char* const key); | |||
| bool fileRequestCallback(const char* key); | |||
| static UI::PrivateData* s_nextPrivateData; | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| static ExternalWindow::PrivateData createNextWindow(UI* ui, uint width, uint height); | |||
| #else | |||
| static PluginWindow& createNextWindow(UI* ui, uint width, uint height); | |||
| #endif | |||
| #if DISTRHO_UI_USE_WEB_VIEW | |||
| static void webViewMessageCallback(void* arg, char* msg); | |||
| #endif | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| @@ -438,7 +397,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key) | |||
| if (fileRequestCallbackFunc != nullptr) | |||
| return fileRequestCallbackFunc(callbacksPtr, key); | |||
| #if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| #if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER | |||
| std::free(uiStateFileKeyRequest); | |||
| uiStateFileKeyRequest = strdup(key); | |||
| DISTRHO_SAFE_ASSERT_RETURN(uiStateFileKeyRequest != nullptr, false); | |||
| @@ -449,8 +408,10 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key) | |||
| DGL_NAMESPACE::FileBrowserOptions opts; | |||
| opts.title = title; | |||
| if (lastUsedDirnames.count(key)) | |||
| opts.startDir = lastUsedDirnames[key].c_str(); | |||
| return window->openFileBrowser(opts); | |||
| #endif | |||
| #endif | |||
| return false; | |||
| } | |||
| @@ -458,7 +419,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key) | |||
| // ----------------------------------------------------------------------- | |||
| // PluginWindow onFileSelected that require UI::PrivateData definitions | |||
| #if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| #if DISTRHO_UI_FILE_BROWSER | |||
| inline void PluginWindow::onFileSelected(const char* const filename) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| @@ -474,8 +435,13 @@ inline void PluginWindow::onFileSelected(const char* const filename) | |||
| { | |||
| // notify DSP | |||
| ui->setState(key, filename); | |||
| // notify UI | |||
| ui->stateChanged(key, filename); | |||
| // save dirname for next time | |||
| if (const char* const lastsep = std::strrchr(filename, DISTRHO_OS_SEP)) | |||
| ui->uiData->lastUsedDirnames[key] = std::string(filename, lastsep-filename); | |||
| } | |||
| std::free(key); | |||
| return; | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2012-2025 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| @@ -19,19 +19,29 @@ | |||
| #endif | |||
| #include "../extra/String.hpp" | |||
| #include "../DistrhoPluginUtils.hpp" | |||
| #include "../DistrhoStandaloneUtils.hpp" | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| # include <direct.h> | |||
| # include <shlobj.h> | |||
| # include <windows.h> | |||
| #else | |||
| # ifndef STATIC_BUILD | |||
| # include <dlfcn.h> | |||
| # endif | |||
| # include <fcntl.h> | |||
| # include <limits.h> | |||
| # include <pwd.h> | |||
| # include <stdlib.h> | |||
| # include <sys/stat.h> | |||
| # include <unistd.h> | |||
| #endif | |||
| #if defined(DISTRHO_OS_WINDOWS) && !defined(STATIC_BUILD) && !DISTRHO_IS_STANDALONE | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| # if DISTRHO_IS_STANDALONE || defined(STATIC_BUILD) | |||
| static constexpr const HINSTANCE hInstance = nullptr; | |||
| # else | |||
| static HINSTANCE hInstance = nullptr; | |||
| DISTRHO_PLUGIN_EXPORT | |||
| @@ -41,49 +51,222 @@ BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID) | |||
| hInstance = hInst; | |||
| return 1; | |||
| } | |||
| # endif | |||
| #endif | |||
| START_NAMESPACE_DISTRHO | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| const char* getBinaryFilename() | |||
| { | |||
| static String filename; | |||
| #ifndef STATIC_BUILD | |||
| #ifndef STATIC_BUILD | |||
| if (filename.isNotEmpty()) | |||
| return filename; | |||
| # ifdef DISTRHO_OS_WINDOWS | |||
| # if DISTRHO_IS_STANDALONE | |||
| constexpr const HINSTANCE hInstance = nullptr; | |||
| # endif | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| CHAR filenameBuf[MAX_PATH]; | |||
| filenameBuf[0] = '\0'; | |||
| GetModuleFileNameA(hInstance, filenameBuf, sizeof(filenameBuf)); | |||
| filename = filenameBuf; | |||
| # else | |||
| #else | |||
| Dl_info info; | |||
| dladdr((void*)getBinaryFilename, &info); | |||
| char filenameBuf[PATH_MAX]; | |||
| filename = realpath(info.dli_fname, filenameBuf); | |||
| # endif | |||
| #endif | |||
| #endif | |||
| #endif | |||
| return filename; | |||
| } | |||
| const char* getConfigDir() | |||
| { | |||
| #if defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WASM) || defined(DISTRHO_OS_WINDOWS) | |||
| return getDocumentsDir(); | |||
| #else | |||
| static String dir; | |||
| if (dir.isEmpty()) | |||
| { | |||
| if (const char* const xdgEnv = getenv("XDG_CONFIG_HOME")) | |||
| dir = xdgEnv; | |||
| if (dir.isEmpty()) | |||
| { | |||
| dir = getHomeDir(); | |||
| dir += "/.config"; | |||
| } | |||
| // ensure main config dir exists | |||
| if (access(dir, F_OK) != 0) | |||
| mkdir(dir, 0755); | |||
| // and also our custom subdir | |||
| dir += "/" DISTRHO_PLUGIN_NAME "/"; | |||
| if (access(dir, F_OK) != 0) | |||
| mkdir(dir, 0755); | |||
| } | |||
| return dir; | |||
| #endif | |||
| } | |||
| const char* getDocumentsDir() | |||
| { | |||
| static String dir; | |||
| if (dir.isEmpty()) | |||
| { | |||
| #if defined(DISTRHO_OS_MAC) | |||
| dir = getHomeDir(); | |||
| dir += "/Documents/" DISTRHO_PLUGIN_NAME "/"; | |||
| #elif defined(DISTRHO_OS_WASM) | |||
| dir = getHomeDir(); | |||
| dir += "/"; | |||
| #elif defined(DISTRHO_OS_WINDOWS) | |||
| WCHAR wpath[MAX_PATH]; | |||
| if (SHGetFolderPathW(nullptr, CSIDL_MYDOCUMENTS, nullptr, SHGFP_TYPE_CURRENT, wpath) == S_OK) | |||
| { | |||
| CHAR apath[MAX_PATH]; | |||
| if (WideCharToMultiByte(CP_UTF8, 0, wpath, -1, apath, MAX_PATH, nullptr, nullptr) != 0) | |||
| { | |||
| dir = apath; | |||
| dir += "\\" DISTRHO_PLUGIN_NAME "\\"; | |||
| wcscat(wpath, L"\\" DISTRHO_PLUGIN_NAME "\\"); | |||
| } | |||
| } | |||
| #else | |||
| String xdgDirsConfigPath(getConfigDir()); | |||
| xdgDirsConfigPath += "/user-dirs.dirs"; | |||
| if (FILE* const f = std::fopen(xdgDirsConfigPath, "r")) | |||
| { | |||
| std::fseek(f, 0, SEEK_END); | |||
| const long size = std::ftell(f); | |||
| std::fseek(f, 0, SEEK_SET); | |||
| // something is wrong if config dirs file is longer than 1MiB! | |||
| if (size > 0 && size < 1024 * 1024) | |||
| { | |||
| if (char* filedata = static_cast<char*>(std::malloc(size))) | |||
| { | |||
| for (long r = 0, total = 0; total < size;) | |||
| { | |||
| r = std::fread(filedata + total, 1, size - total, f); | |||
| if (r == 0) | |||
| { | |||
| std::free(filedata); | |||
| filedata = nullptr; | |||
| break; | |||
| } | |||
| total += r; | |||
| } | |||
| if (filedata != nullptr) | |||
| { | |||
| if (char* const xdgDocsDir = std::strstr(filedata, "XDG_DOCUMENTS_DIR=\"")) | |||
| { | |||
| if (char* const xdgDocsDirNL = std::strstr(xdgDocsDir, "\"\n")) | |||
| { | |||
| *xdgDocsDirNL = '\0'; | |||
| String sdir(xdgDocsDir + 19); | |||
| if (sdir.startsWith("$HOME")) | |||
| { | |||
| dir = getHomeDir(); | |||
| dir += sdir.buffer() + 5; | |||
| } | |||
| else | |||
| { | |||
| dir = sdir; | |||
| } | |||
| // ensure main config dir exists | |||
| if (access(dir, F_OK) != 0) | |||
| mkdir(dir, 0755); | |||
| } | |||
| } | |||
| std::free(filedata); | |||
| } | |||
| } | |||
| } | |||
| std::fclose(f); | |||
| } | |||
| // ${XDG_CONFIG_HOME}/user-dirs.dirs does not exist or has bad data | |||
| if (dir.isEmpty()) | |||
| { | |||
| dir = getDocumentsDir(); | |||
| dir += DISTRHO_PLUGIN_NAME "/"; | |||
| } | |||
| #endif | |||
| // ensure our custom subdir exists | |||
| if (dir.isNotEmpty()) | |||
| { | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| _wmkdir(wpath); | |||
| #else | |||
| if (access(dir, F_OK) != 0) | |||
| mkdir(dir, 0755); | |||
| #endif | |||
| } | |||
| } | |||
| return dir; | |||
| } | |||
| const char* getHomeDir() | |||
| { | |||
| static String dir; | |||
| if (dir.isEmpty()) | |||
| { | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| WCHAR wpath[MAX_PATH]; | |||
| if (SHGetFolderPathW(nullptr, CSIDL_PROFILE, nullptr, SHGFP_TYPE_CURRENT, wpath) == S_OK) | |||
| { | |||
| CHAR apath[MAX_PATH]; | |||
| if (WideCharToMultiByte(CP_UTF8, 0, wpath, -1, apath, MAX_PATH, nullptr, nullptr) != 0) | |||
| dir = apath; | |||
| } | |||
| #else | |||
| if (const char* const homeEnv = getenv("HOME")) | |||
| dir = homeEnv; | |||
| if (dir.isEmpty()) | |||
| if (struct passwd* const pwd = getpwuid(getuid())) | |||
| dir = pwd->pw_dir; | |||
| if (dir.isNotEmpty() && ! dir.endsWith('/')) | |||
| dir += "/"; | |||
| #endif | |||
| } | |||
| return dir; | |||
| } | |||
| const char* getPluginFormatName() noexcept | |||
| { | |||
| #if defined(DISTRHO_PLUGIN_TARGET_CARLA) | |||
| #if defined(DISTRHO_PLUGIN_TARGET_AU) | |||
| return "AudioUnit"; | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_CARLA) | |||
| return "Carla"; | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_JACK) | |||
| # ifdef DISTRHO_OS_WASM | |||
| #if defined(DISTRHO_OS_WASM) | |||
| return "Wasm/Standalone"; | |||
| # else | |||
| #elif defined(HAVE_JACK) | |||
| return "JACK/Standalone"; | |||
| # endif | |||
| #else | |||
| return "Standalone"; | |||
| #endif | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_LADSPA) | |||
| return "LADSPA"; | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_DSSI) | |||
| @@ -94,6 +277,10 @@ const char* getPluginFormatName() noexcept | |||
| return "VST2"; | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_VST3) | |||
| return "VST3"; | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_CLAP) | |||
| return "CLAP"; | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_STATIC) && defined(DISTRHO_PLUGIN_TARGET_STATIC_NAME) | |||
| return DISTRHO_PLUGIN_TARGET_STATIC_NAME; | |||
| #else | |||
| return "Unknown"; | |||
| #endif | |||
| @@ -103,21 +290,21 @@ const char* getResourcePath(const char* const bundlePath) noexcept | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(bundlePath != nullptr, nullptr); | |||
| #if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_VST2) | |||
| #if defined(DISTRHO_PLUGIN_TARGET_AU) || defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_VST2) || defined(DISTRHO_PLUGIN_TARGET_CLAP) | |||
| static String resourcePath; | |||
| if (resourcePath.isEmpty()) | |||
| { | |||
| resourcePath = bundlePath; | |||
| # ifdef DISTRHO_OS_MAC | |||
| #ifdef DISTRHO_OS_MAC | |||
| resourcePath += "/Contents/Resources"; | |||
| # else | |||
| #else | |||
| resourcePath += DISTRHO_OS_SEP_STR "resources"; | |||
| # endif | |||
| #endif | |||
| } | |||
| return resourcePath.buffer(); | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_LV2) | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_LV2) | |||
| static String resourcePath; | |||
| if (resourcePath.isEmpty()) | |||
| @@ -127,7 +314,7 @@ const char* getResourcePath(const char* const bundlePath) noexcept | |||
| } | |||
| return resourcePath.buffer(); | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_VST3) | |||
| #elif defined(DISTRHO_PLUGIN_TARGET_VST3) | |||
| static String resourcePath; | |||
| if (resourcePath.isEmpty()) | |||
| @@ -137,7 +324,7 @@ const char* getResourcePath(const char* const bundlePath) noexcept | |||
| } | |||
| return resourcePath.buffer(); | |||
| #endif | |||
| #endif | |||
| return nullptr; | |||
| } | |||
| @@ -156,6 +343,6 @@ bool requestBufferSizeChange(uint) { return false; } | |||
| bool requestMIDI() { return false; } | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| END_NAMESPACE_DISTRHO | |||
| @@ -33,6 +33,7 @@ endif | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| BUILD_CXX_FLAGS += -I$(CWD)/backend -I$(CWD)/includes -I$(CWD)/modules -I$(CWD)/utils | |||
| BUILD_CXX_FLAGS += -I$(CWD)/modules/dgl/src/pugl-upstream/include | |||
| BUILD_CXX_FLAGS += $(NATIVE_PLUGINS_FLAGS) | |||
| BUILD_CXX_FLAGS += $(FLUIDSYNTH_FLAGS) | |||
| @@ -41,7 +41,15 @@ class PluginWindow : public DGL_NAMESPACE::Window | |||
| { | |||
| public: | |||
| explicit PluginWindow(PluginApplication& app, const uintptr_t winId) | |||
| : Window(app, winId, ui_launcher_res::carla_uiWidth, ui_launcher_res::carla_uiHeight, 0.0, false, false, false) | |||
| : Window(app, | |||
| winId, | |||
| ui_launcher_res::carla_uiWidth, | |||
| ui_launcher_res::carla_uiHeight, | |||
| 0.0, | |||
| false, | |||
| false, | |||
| false, | |||
| false) | |||
| { | |||
| // this is called just before creating UI, ensuring proper context to it | |||
| if (pData->view != nullptr && pData->initPost()) | |||