| @@ -0,0 +1,189 @@ | |||
| name: build | |||
| on: | |||
| push: | |||
| branches: | |||
| - '*' | |||
| pull_request: | |||
| branches: | |||
| - '*' | |||
| env: | |||
| DEBIAN_FRONTEND: noninteractive | |||
| jobs: | |||
| linux-arm64: | |||
| runs-on: ubuntu-18.04 | |||
| steps: | |||
| - uses: actions/checkout@v2 | |||
| with: | |||
| submodules: recursive | |||
| - name: Set up dependencies | |||
| run: | | |||
| sudo dpkg --add-architecture arm64 && \ | |||
| sudo sed -i "s/deb http/deb [arch=amd64] http/" /etc/apt/sources.list && \ | |||
| echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic main restricted universe multiverse" | sudo tee /etc/apt/sources.list.d/ports-arm64.list && \ | |||
| echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list && \ | |||
| echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list && \ | |||
| sudo apt-get update -qq && \ | |||
| sudo apt-get install -yq g++-aarch64-linux-gnu libasound2-dev:arm64 libcairo2-dev:arm64 libgl1-mesa-dev:arm64 liblo-dev:arm64 libpulse-dev:arm64 qemu-user-static | |||
| - name: Build linux arm64 cross-compiled | |||
| env: | |||
| CC: aarch64-linux-gnu-gcc | |||
| CXX: aarch64-linux-gnu-g++ | |||
| LDFLAGS: -static-libgcc -static-libstdc++ | |||
| run: | | |||
| make -j $(nproc) | |||
| - name: Set sha8 | |||
| id: slug | |||
| run: echo "::set-output name=sha8::$(echo ${{ github.sha }} | cut -c1-8)" | |||
| - uses: actions/upload-artifact@v2 | |||
| with: | |||
| name: ${{ github.event.repository.name }}-linux-arm64-${{ github.event.pull_request.number || steps.slug.outputs.sha8 }} | |||
| path: | | |||
| bin/* | |||
| linux-armhf: | |||
| runs-on: ubuntu-18.04 | |||
| steps: | |||
| - uses: actions/checkout@v2 | |||
| with: | |||
| submodules: recursive | |||
| - name: Set up dependencies | |||
| run: | | |||
| sudo dpkg --add-architecture armhf && \ | |||
| sudo sed -i "s/deb http/deb [arch=amd64] http/" /etc/apt/sources.list && \ | |||
| echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic main restricted universe multiverse" | sudo tee /etc/apt/sources.list.d/ports-armhf.list && \ | |||
| echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list && \ | |||
| echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list && \ | |||
| sudo apt-get update -qq && \ | |||
| sudo apt-get install -yq g++-arm-linux-gnueabihf libasound2-dev:armhf libcairo2-dev:armhf libgl1-mesa-dev:armhf liblo-dev:armhf libpulse-dev:armhf qemu-user-static | |||
| - name: Build linux armhf cross-compiled | |||
| env: | |||
| CC: arm-linux-gnueabihf-gcc | |||
| CXX: arm-linux-gnueabihf-g++ | |||
| LDFLAGS: -static-libgcc -static-libstdc++ | |||
| run: | | |||
| make -j $(nproc) | |||
| - name: Set sha8 | |||
| id: slug | |||
| run: echo "::set-output name=sha8::$(echo ${{ github.sha }} | cut -c1-8)" | |||
| - uses: actions/upload-artifact@v2 | |||
| with: | |||
| name: ${{ github.event.repository.name }}-linux-armhf-${{ github.event.pull_request.number || steps.slug.outputs.sha8 }} | |||
| path: | | |||
| bin/* | |||
| linux-x64: | |||
| runs-on: ubuntu-18.04 | |||
| steps: | |||
| - uses: actions/checkout@v2 | |||
| with: | |||
| submodules: recursive | |||
| - name: Set up dependencies | |||
| run: | | |||
| sudo apt-get install -yq libasound2-dev libgl1-mesa-dev liblo-dev libpulse-dev | |||
| - name: Build linux x64 | |||
| env: | |||
| LDFLAGS: -static-libgcc -static-libstdc++ | |||
| run: | | |||
| make -j $(nproc) | |||
| - name: Set sha8 | |||
| id: slug | |||
| run: echo "::set-output name=sha8::$(echo ${{ github.sha }} | cut -c1-8)" | |||
| - uses: actions/upload-artifact@v2 | |||
| with: | |||
| name: ${{ github.event.repository.name }}-linux-x64-${{ github.event.pull_request.number || steps.slug.outputs.sha8 }} | |||
| path: | | |||
| bin/* | |||
| macos-universal: | |||
| runs-on: macos-10.15 | |||
| steps: | |||
| - uses: actions/checkout@v2 | |||
| with: | |||
| submodules: recursive | |||
| - name: Fix up Xcode | |||
| run: | | |||
| sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/* | |||
| sudo xcode-select -s "/Applications/Xcode_12.3.app" | |||
| - name: Build macOS universal | |||
| env: | |||
| CFLAGS: -arch x86_64 -arch arm64 -DMAC_OS_X_VERSION_MAX_ALLOWED=MAC_OS_X_VERSION_10_12 -mmacosx-version-min=10.12 -mtune=generic -msse -msse2 | |||
| CXXFLAGS: -arch x86_64 -arch arm64 -DMAC_OS_X_VERSION_MAX_ALLOWED=MAC_OS_X_VERSION_10_12 -mmacosx-version-min=10.12 -mtune=generic -msse -msse2 | |||
| LDFLAGS: -arch x86_64 -arch arm64 -mmacosx-version-min=10.12 | |||
| run: | | |||
| make NOOPT=true -j $(sysctl -n hw.logicalcpu) && \ | |||
| ./dpf/utils/package-osx-bundles.sh | |||
| - name: Set sha8 | |||
| id: slug | |||
| run: echo "::set-output name=sha8::$(echo ${{ github.sha }} | cut -c1-8)" | |||
| - uses: actions/upload-artifact@v2 | |||
| with: | |||
| name: ${{ github.event.repository.name }}-macOS-universal-${{ github.event.pull_request.number || steps.slug.outputs.sha8 }} | |||
| path: | | |||
| *-macOS.pkg | |||
| bin/* | |||
| !bin/*-ladspa.dylib | |||
| !bin/*-dssi.dylib | |||
| !bin/lv2 | |||
| !bin/vst2 | |||
| win32: | |||
| runs-on: ubuntu-20.04 | |||
| steps: | |||
| - uses: actions/checkout@v2 | |||
| with: | |||
| submodules: recursive | |||
| - name: Set up dependencies | |||
| run: | | |||
| sudo dpkg --add-architecture i386 && \ | |||
| sudo apt-get update -qq && \ | |||
| sudo apt-get install -yq binutils-mingw-w64-i686 g++-mingw-w64-i686 mingw-w64 wine-stable:i386 | |||
| - name: Build win32 cross-compiled | |||
| env: | |||
| CC: i686-w64-mingw32-gcc | |||
| CXX: i686-w64-mingw32-g++ | |||
| EXE_WRAPPER: wine | |||
| PKG_CONFIG: "false" | |||
| WINEDEBUG: "-all" | |||
| run: | | |||
| make -j $(nproc) | |||
| - name: Set sha8 | |||
| id: slug | |||
| run: echo "::set-output name=sha8::$(echo ${{ github.sha }} | cut -c1-8)" | |||
| - uses: actions/upload-artifact@v2 | |||
| with: | |||
| name: ${{ github.event.repository.name }}-win32-${{ github.event.pull_request.number || steps.slug.outputs.sha8 }} | |||
| path: | | |||
| bin/* | |||
| !bin/*-ladspa.dll | |||
| !bin/*-dssi.dll | |||
| win64: | |||
| runs-on: ubuntu-20.04 | |||
| steps: | |||
| - uses: actions/checkout@v2 | |||
| with: | |||
| submodules: recursive | |||
| - name: Set up dependencies | |||
| run: | | |||
| sudo apt-get install -yq binutils-mingw-w64-x86-64 g++-mingw-w64-x86-64 mingw-w64 wine-stable | |||
| - name: Build win64 cross-compiled | |||
| env: | |||
| CC: x86_64-w64-mingw32-gcc | |||
| CXX: x86_64-w64-mingw32-g++ | |||
| EXE_WRAPPER: wine | |||
| PKG_CONFIG: "false" | |||
| WINEDEBUG: "-all" | |||
| run: | | |||
| make -j $(nproc) | |||
| - name: Set sha8 | |||
| id: slug | |||
| run: echo "::set-output name=sha8::$(echo ${{ github.sha }} | cut -c1-8)" | |||
| - uses: actions/upload-artifact@v2 | |||
| with: | |||
| name: ${{ github.event.repository.name }}-win64-${{ github.event.pull_request.number || steps.slug.outputs.sha8 }} | |||
| path: | | |||
| bin/* | |||
| !bin/*-ladspa.dll | |||
| !bin/*-dssi.dll | |||
| @@ -1,4 +1,3 @@ | |||
| DISTRHO Plugin Framework (DPF) | |||
| Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
| Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| @@ -198,7 +198,7 @@ endif | |||
| ifeq ($(WINDOWS),true) | |||
| # Always build statically on windows | |||
| LINK_FLAGS += -static | |||
| LINK_FLAGS += -static -static-libgcc -static-libstdc++ | |||
| endif | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| @@ -247,6 +247,20 @@ endif | |||
| HAVE_LIBLO = $(shell $(PKG_CONFIG) --exists liblo && echo true) | |||
| ifeq ($(MACOS),true) | |||
| HAVE_RTAUDIO = true | |||
| else ifeq ($(WINDOWS),true) | |||
| HAVE_RTAUDIO = true | |||
| else ifneq ($(HAIKU),true) | |||
| HAVE_ALSA = $(shell $(PKG_CONFIG) --exists alsa && echo true) | |||
| HAVE_PULSEAUDIO = $(shell $(PKG_CONFIG) --exists libpulse-simple && echo true) | |||
| ifeq ($(HAVE_ALSA),true) | |||
| HAVE_RTAUDIO = true | |||
| else ifeq ($(HAVE_PULSEAUDIO),true) | |||
| HAVE_RTAUDIO = true | |||
| endif | |||
| endif | |||
| # backwards compat | |||
| HAVE_JACK = true | |||
| @@ -357,9 +371,19 @@ endif | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| # Set optional libraries specific stuff | |||
| ifeq ($(HAVE_ALSA),true) | |||
| ALSA_FLAGS = $(shell $(PKG_CONFIG) --cflags alsa) | |||
| ALSA_LIBS = $(shell $(PKG_CONFIG) --libs alsa) | |||
| endif | |||
| ifeq ($(HAVE_LIBLO),true) | |||
| LIBLO_FLAGS = $(shell $(PKG_CONFIG) --cflags liblo) | |||
| LIBLO_LIBS = $(shell $(PKG_CONFIG) --libs liblo) | |||
| LIBLO_FLAGS = $(shell $(PKG_CONFIG) --cflags liblo) | |||
| LIBLO_LIBS = $(shell $(PKG_CONFIG) --libs liblo) | |||
| endif | |||
| ifeq ($(HAVE_PULSEAUDIO),true) | |||
| PULSEAUDIO_FLAGS = $(shell $(PKG_CONFIG) --cflags libpulse-simple) | |||
| PULSEAUDIO_LIBS = $(shell $(PKG_CONFIG) --libs libpulse-simple) | |||
| endif | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| @@ -7,14 +7,12 @@ | |||
| # NOTE: NAME, FILES_DSP and FILES_UI must have been defined before including this file! | |||
| ifeq ($(DPF_CUSTOM_PATH),) | |||
| ifeq ($(DPF_PATH),) | |||
| ifeq (,$(wildcard ../../Makefile.base.mk)) | |||
| DPF_PATH=../../dpf | |||
| else | |||
| DPF_PATH=../.. | |||
| endif | |||
| else | |||
| DPF_PATH = $(DPF_CUSTOM_PATH) | |||
| endif | |||
| include $(DPF_PATH)/Makefile.base.mk | |||
| @@ -22,31 +20,49 @@ include $(DPF_PATH)/Makefile.base.mk | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| # Basic setup | |||
| ifeq ($(DPF_CUSTOM_PATH),) | |||
| ifeq ($(DPF_TARGET_DIR),) | |||
| TARGET_DIR = ../../bin | |||
| BUILD_DIR = ../../build/$(NAME) | |||
| else | |||
| ifeq ($(DPF_CUSTOM_TARGET_DIR),) | |||
| $(error DPF_CUSTOM_TARGET_DIR is not set) | |||
| else | |||
| TARGET_DIR = $(DPF_CUSTOM_TARGET_DIR) | |||
| TARGET_DIR = $(DPF_TARGET_DIR) | |||
| endif | |||
| ifeq ($(DPF_CUSTOM_BUILD_DIR),) | |||
| $(error DPF_CUSTOM_BUILD_DIR is not set) | |||
| ifeq ($(DPF_BUILD_DIR),) | |||
| BUILD_DIR = ../../build/$(NAME) | |||
| else | |||
| BUILD_DIR = $(DPF_CUSTOM_BUILD_DIR) | |||
| endif | |||
| BUILD_DIR = $(DPF_BUILD_DIR) | |||
| endif | |||
| BUILD_C_FLAGS += -I. | |||
| BUILD_CXX_FLAGS += -I. -I$(DPF_PATH)/distrho -I$(DPF_PATH)/dgl | |||
| ifeq ($(HAVE_ALSA),true) | |||
| BASE_FLAGS += -DHAVE_ALSA | |||
| endif | |||
| ifeq ($(HAVE_LIBLO),true) | |||
| BASE_FLAGS += -DHAVE_LIBLO | |||
| endif | |||
| ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true) | |||
| JACK_LIBS = -ldl | |||
| ifeq ($(HAVE_PULSEAUDIO),true) | |||
| BASE_FLAGS += -DHAVE_PULSEAUDIO | |||
| endif | |||
| ifeq ($(MACOS),true) | |||
| JACK_LIBS += -framework CoreAudio -framework CoreFoundation | |||
| else ifeq ($(WINDOWS),true) | |||
| JACK_LIBS += -lksuser -lmfplat -lmfuuid -lole32 -lwinmm -lwmcodecdspuuid | |||
| else ifneq ($(HAIKU),true) | |||
| JACK_LIBS = -ldl | |||
| ifeq ($(HAVE_ALSA),true) | |||
| JACK_FLAGS += $(ALSA_FLAGS) | |||
| JACK_LIBS += $(ALSA_LIBS) | |||
| endif | |||
| ifeq ($(HAVE_PULSEAUDIO),true) | |||
| JACK_FLAGS += $(PULSEAUDIO_FLAGS) | |||
| JACK_LIBS += $(PULSEAUDIO_LIBS) | |||
| endif | |||
| ifeq ($(HAVE_RTAUDIO),true) | |||
| JACK_LIBS += -lpthread | |||
| endif # !HAIKU | |||
| endif | |||
| # backwards compat | |||
| @@ -257,7 +273,7 @@ $(BUILD_DIR)/DistrhoUI_macOS_%.mm.o: $(DPF_PATH)/distrho/DistrhoUI_macOS.mm | |||
| $(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o: $(DPF_PATH)/distrho/DistrhoPluginMain.cpp | |||
| -@mkdir -p $(BUILD_DIR) | |||
| @echo "Compiling DistrhoPluginMain.cpp (JACK)" | |||
| $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_JACK -c -o $@ | |||
| $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_JACK $(JACK_FLAGS) -c -o $@ | |||
| $(BUILD_DIR)/DistrhoUIMain_DSSI.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp | |||
| -@mkdir -p $(BUILD_DIR) | |||
| @@ -187,6 +187,13 @@ function(dpf__build_jack NAME DGL_LIBRARY) | |||
| if((NOT WIN32) AND (NOT APPLE) AND (NOT HAIKU)) | |||
| target_link_libraries("${NAME}-jack" PRIVATE "dl") | |||
| endif() | |||
| # for RtAudio native fallback | |||
| if(APPLE) | |||
| find_library(APPLE_COREAUDIO_FRAMEWORK "CoreAudio") | |||
| find_library(APPLE_COREFOUNDATION_FRAMEWORK "CoreFoundation") | |||
| target_link_libraries("${NAME}-jack" PRIVATE "${APPLE_COREAUDIO_FRAMEWORK}" "${APPLE_COREFOUNDATION_FRAMEWORK}") | |||
| endif() | |||
| endfunction() | |||
| # dpf__build_ladspa | |||
| @@ -326,6 +333,7 @@ function(dpf__add_dgl_cairo) | |||
| "${DPF_ROOT_DIR}/dgl/src/Application.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/ApplicationPrivateData.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/Color.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/EventHandlers.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/Geometry.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/ImageBase.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/ImageBaseWidgets.cpp" | |||
| @@ -386,6 +394,7 @@ function(dpf__add_dgl_opengl) | |||
| "${DPF_ROOT_DIR}/dgl/src/Application.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/ApplicationPrivateData.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/Color.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/EventHandlers.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/Geometry.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/ImageBase.cpp" | |||
| "${DPF_ROOT_DIR}/dgl/src/ImageBaseWidgets.cpp" | |||
| @@ -30,6 +30,8 @@ START_NAMESPACE_DGL | |||
| There's no single/global application instance in DGL, and multiple windows can share the same app instance. | |||
| In standalone mode an application will automatically quit its event-loop when all its windows are closed. | |||
| Unless stated otherwise, functions within this class are not thread-safe. | |||
| */ | |||
| class Application | |||
| { | |||
| @@ -61,6 +63,7 @@ public: | |||
| /** | |||
| Quit the application. | |||
| This stops the event-loop and closes all Windows. | |||
| This function is thread-safe. | |||
| @note This function is meant for standalones only, *never* call this from plugins. | |||
| */ | |||
| void quit(); | |||
| @@ -68,14 +71,21 @@ public: | |||
| /** | |||
| Check if the application is about to quit. | |||
| Returning true means there's no event-loop running at the moment (or it's just about to stop). | |||
| This function is thread-safe. | |||
| */ | |||
| bool isQuiting() const noexcept; | |||
| /** | |||
| Check if the application is standalone, otherwise running as a module or plugin. | |||
| This function is thread-safe. | |||
| */ | |||
| bool isStandalone() const noexcept; | |||
| /** | |||
| Add a callback function to be triggered on every idle cycle. | |||
| You can add more than one, and remove them at anytime with removeIdleCallback(). | |||
| Idle callbacks trigger right after OS event handling and Window idle events (within the same cycle). | |||
| There are no guarantees in terms of timing. | |||
| There are no guarantees in terms of timing, use Window::addIdleCallback for time-relative callbacks. | |||
| */ | |||
| void addIdleCallback(IdleCallback* callback); | |||
| @@ -65,6 +65,11 @@ struct Color { | |||
| */ | |||
| Color(const Color& color1, const Color& color2, float u) noexcept; | |||
| /** | |||
| Create a new color based on this one but with a different alpha value. | |||
| */ | |||
| Color withAlpha(float alpha) noexcept; | |||
| /** | |||
| Create a color specified by hue, saturation and lightness. | |||
| Values must in [0..1] range. | |||
| @@ -0,0 +1,154 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 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 | |||
| * permission notice appear in all copies. | |||
| * | |||
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #ifndef DGL_EVENT_HANDLERS_HPP_INCLUDED | |||
| #define DGL_EVENT_HANDLERS_HPP_INCLUDED | |||
| #include "Widget.hpp" | |||
| START_NAMESPACE_DGL | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| class ButtonEventHandler | |||
| { | |||
| public: | |||
| enum State { | |||
| kButtonStateDefault = 0x0, | |||
| kButtonStateHover = 0x1, | |||
| kButtonStateActive = 0x2, | |||
| kButtonStateActiveHover = kButtonStateActive|kButtonStateHover | |||
| }; | |||
| class Callback | |||
| { | |||
| public: | |||
| virtual ~Callback() {} | |||
| virtual void buttonClicked(SubWidget* widget, int button) = 0; | |||
| }; | |||
| explicit ButtonEventHandler(SubWidget* self); | |||
| ~ButtonEventHandler(); | |||
| bool isActive() noexcept; | |||
| void setActive(bool active, bool sendCallback) noexcept; | |||
| bool isChecked() const noexcept; | |||
| void setChecked(bool checked, bool sendCallback) noexcept; | |||
| bool isCheckable() const noexcept; | |||
| void setCheckable(bool checkable) noexcept; | |||
| Point<double> getLastClickPosition() const noexcept; | |||
| Point<double> getLastMotionPosition() const noexcept; | |||
| void setCallback(Callback* callback) noexcept; | |||
| bool mouseEvent(const Widget::MouseEvent& ev); | |||
| bool motionEvent(const Widget::MotionEvent& ev); | |||
| protected: | |||
| State getState() const noexcept; | |||
| void clearState() noexcept; | |||
| virtual void stateChanged(State state, State oldState); | |||
| void setInternalCallback(Callback* callback) noexcept; | |||
| void triggerUserCallback(SubWidget* widget, int button); | |||
| private: | |||
| struct PrivateData; | |||
| PrivateData* const pData; | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ButtonEventHandler) | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| class KnobEventHandler | |||
| { | |||
| public: | |||
| enum Orientation { | |||
| Horizontal, | |||
| Vertical | |||
| }; | |||
| // NOTE hover not implemented yet | |||
| enum State { | |||
| kKnobStateDefault = 0x0, | |||
| kKnobStateHover = 0x1, | |||
| kKnobStateDragging = 0x2, | |||
| kKnobStateDraggingHover = kKnobStateDragging|kKnobStateHover | |||
| }; | |||
| class Callback | |||
| { | |||
| public: | |||
| virtual ~Callback() {} | |||
| virtual void knobDragStarted(SubWidget* widget) = 0; | |||
| virtual void knobDragFinished(SubWidget* widget) = 0; | |||
| virtual void knobValueChanged(SubWidget* widget, float value) = 0; | |||
| }; | |||
| explicit KnobEventHandler(SubWidget* self); | |||
| explicit KnobEventHandler(SubWidget* self, const KnobEventHandler& other); | |||
| KnobEventHandler& operator=(const KnobEventHandler& other); | |||
| ~KnobEventHandler(); | |||
| // returns raw value, is assumed to be scaled if using log | |||
| float getValue() const noexcept; | |||
| // NOTE: value is assumed to be scaled if using log | |||
| virtual bool setValue(float value, bool sendCallback = false) noexcept; | |||
| // returns 0-1 ranged value, already with log scale as needed | |||
| float getNormalizedValue() const noexcept; | |||
| // NOTE: value is assumed to be scaled if using log | |||
| void setDefault(float def) noexcept; | |||
| // NOTE: value is assumed to be scaled if using log | |||
| void setRange(float min, float max) noexcept; | |||
| void setStep(float step) noexcept; | |||
| void setUsingLogScale(bool yesNo) noexcept; | |||
| Orientation getOrientation() const noexcept; | |||
| void setOrientation(const Orientation orientation) noexcept; | |||
| void setCallback(Callback* callback) noexcept; | |||
| bool mouseEvent(const Widget::MouseEvent& ev); | |||
| bool motionEvent(const Widget::MotionEvent& ev); | |||
| bool scrollEvent(const Widget::ScrollEvent& ev); | |||
| protected: | |||
| State getState() const noexcept; | |||
| private: | |||
| struct PrivateData; | |||
| PrivateData* const pData; | |||
| DISTRHO_LEAK_DETECTOR(KnobEventHandler) | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| END_NAMESPACE_DGL | |||
| #endif // DGL_EVENT_HANDLERS_HPP_INCLUDED | |||
| @@ -17,6 +17,7 @@ | |||
| #ifndef DGL_IMAGE_BASE_WIDGETS_HPP_INCLUDED | |||
| #define DGL_IMAGE_BASE_WIDGETS_HPP_INCLUDED | |||
| #include "EventHandlers.hpp" | |||
| #include "StandaloneWindow.hpp" | |||
| #include "SubWidget.hpp" | |||
| @@ -47,7 +48,8 @@ private: | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| class ImageBaseButton : public SubWidget | |||
| class ImageBaseButton : public SubWidget, | |||
| public ButtonEventHandler | |||
| { | |||
| public: | |||
| class Callback | |||
| @@ -80,14 +82,10 @@ private: | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| class ImageBaseKnob : public SubWidget | |||
| class ImageBaseKnob : public SubWidget, | |||
| public KnobEventHandler | |||
| { | |||
| public: | |||
| enum Orientation { | |||
| Horizontal, | |||
| Vertical | |||
| }; | |||
| class Callback | |||
| { | |||
| public: | |||
| @@ -102,25 +100,16 @@ public: | |||
| ImageBaseKnob& operator=(const ImageBaseKnob& imageKnob); | |||
| ~ImageBaseKnob() override; | |||
| float getValue() const noexcept; | |||
| void setDefault(float def) noexcept; | |||
| void setRange(float min, float max) noexcept; | |||
| void setStep(float step) noexcept; | |||
| void setValue(float value, bool sendCallback = false) noexcept; | |||
| void setUsingLogScale(bool yesNo) noexcept; | |||
| void setCallback(Callback* callback) noexcept; | |||
| void setOrientation(Orientation orientation) noexcept; | |||
| void setRotationAngle(int angle); | |||
| void setImageLayerCount(uint count) noexcept; | |||
| void setRotationAngle(int angle); | |||
| bool setValue(float value, bool sendCallback = false) noexcept override; | |||
| protected: | |||
| void onDisplay() override; | |||
| bool onMouse(const MouseEvent&) override; | |||
| bool onMotion(const MotionEvent&) override; | |||
| bool onScroll(const ScrollEvent&) override; | |||
| void onDisplay() override; | |||
| bool onMouse(const MouseEvent&) override; | |||
| bool onMotion(const MotionEvent&) override; | |||
| bool onScroll(const ScrollEvent&) override; | |||
| private: | |||
| struct PrivateData; | |||
| @@ -27,6 +27,7 @@ OBJS_common = \ | |||
| ../build/dgl/Application.cpp.o \ | |||
| ../build/dgl/ApplicationPrivateData.cpp.o \ | |||
| ../build/dgl/Color.cpp.o \ | |||
| ../build/dgl/EventHandlers.cpp.o \ | |||
| ../build/dgl/Geometry.cpp.o \ | |||
| ../build/dgl/ImageBase.cpp.o \ | |||
| ../build/dgl/ImageBaseWidgets.cpp.o \ | |||
| @@ -39,7 +40,6 @@ OBJS_common = \ | |||
| ../build/dgl/WidgetPrivateData.cpp.o \ | |||
| ../build/dgl/Window.cpp.o \ | |||
| ../build/dgl/WindowPrivateData.cpp.o | |||
| # ../build/dgl/WindowFileBrowser.cpp.o | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| @@ -111,6 +111,22 @@ public: | |||
| */ | |||
| void setAbsolutePos(const Point<int>& pos) noexcept; | |||
| /** | |||
| Get the margin currently in use for widget coordinates. | |||
| By default this value is (0,0). | |||
| */ | |||
| Point<int> getMargin() const noexcept; | |||
| /** | |||
| Set a margin to be used for widget coordinates using @a x and @a y values. | |||
| */ | |||
| void setMargin(int x, int y) noexcept; | |||
| /** | |||
| Set a margin to be used for widget coordinates. | |||
| */ | |||
| void setMargin(const Point<int>& offset) noexcept; | |||
| /** | |||
| Get parent Widget, as passed in the constructor. | |||
| */ | |||
| @@ -66,7 +66,7 @@ public: | |||
| uint flags; | |||
| uint time; | |||
| /** Constuctor */ | |||
| /** Constructor */ | |||
| BaseEvent() noexcept : mod(0x0), flags(0x0), time(0) {} | |||
| /** Destuctor */ | |||
| virtual ~BaseEvent() noexcept {} | |||
| @@ -96,7 +96,7 @@ public: | |||
| uint key; | |||
| uint keycode; | |||
| /** Constuctor */ | |||
| /** Constructor */ | |||
| KeyboardEvent() noexcept | |||
| : BaseEvent(), | |||
| press(false), | |||
| @@ -118,7 +118,7 @@ public: | |||
| bool press; | |||
| Key key; | |||
| /** Constuctor */ | |||
| /** Constructor */ | |||
| SpecialEvent() noexcept | |||
| : BaseEvent(), | |||
| press(false), | |||
| @@ -145,7 +145,7 @@ public: | |||
| uint character; | |||
| char string[8]; | |||
| /** Constuctor */ | |||
| /** Constructor */ | |||
| CharacterInputEvent() noexcept | |||
| : BaseEvent(), | |||
| keycode(0), | |||
| @@ -168,7 +168,7 @@ public: | |||
| Point<double> pos; | |||
| Point<double> absolutePos; | |||
| /** Constuctor */ | |||
| /** Constructor */ | |||
| MouseEvent() noexcept | |||
| : BaseEvent(), | |||
| button(0), | |||
| @@ -188,7 +188,7 @@ public: | |||
| Point<double> pos; | |||
| Point<double> absolutePos; | |||
| /** Constuctor */ | |||
| /** Constructor */ | |||
| MotionEvent() noexcept | |||
| : BaseEvent(), | |||
| pos(0.0, 0.0), | |||
| @@ -216,7 +216,7 @@ public: | |||
| Point<double> delta; | |||
| ScrollDirection direction; | |||
| /** Constuctor */ | |||
| /** Constructor */ | |||
| ScrollEvent() noexcept | |||
| : BaseEvent(), | |||
| pos(0.0, 0.0), | |||
| @@ -235,7 +235,7 @@ public: | |||
| Size<uint> size; | |||
| Size<uint> oldSize; | |||
| /** Constuctor */ | |||
| /** Constructor */ | |||
| ResizeEvent() noexcept | |||
| : size(0, 0), | |||
| oldSize(0, 0) {} | |||
| @@ -251,7 +251,7 @@ public: | |||
| Point<int> pos; | |||
| Point<int> oldPos; | |||
| /** Constuctor */ | |||
| /** Constructor */ | |||
| PositionChangedEvent() noexcept | |||
| : pos(0, 0), | |||
| oldPos(0, 0) {} | |||
| @@ -87,14 +87,14 @@ public: | |||
| /** Whether to show list of places (bookmarks) */ | |||
| ButtonState showPlaces; | |||
| /** Constuctor for default values */ | |||
| /** Constructor for default values */ | |||
| Buttons() | |||
| : listAllFiles(kButtonVisibleChecked), | |||
| showHidden(kButtonVisibleUnchecked), | |||
| showPlaces(kButtonVisibleUnchecked) {} | |||
| } buttons; | |||
| /** Constuctor for default values */ | |||
| /** Constructor for default values */ | |||
| FileBrowserOptions() | |||
| : startDir(nullptr), | |||
| title(nullptr), | |||
| @@ -104,6 +104,43 @@ public: | |||
| }; | |||
| #endif // DGL_FILE_BROWSER_DISABLED | |||
| /** | |||
| Window graphics context as a scoped struct. | |||
| This class gives graphics context drawing time to a window's widgets. | |||
| Typically used for allowing OpenGL drawing operations during a window + widget constructor. | |||
| Unless you are subclassing the Window or StandaloneWindow classes, you do not need to care. | |||
| In such cases you will need to use this struct as a way to get a valid OpenGL context. | |||
| For example in a standalone application: | |||
| ``` | |||
| int main() | |||
| { | |||
| Application app; | |||
| Window win(app); | |||
| ScopedPointer<MyCustomTopLevelWidget> widget; | |||
| { | |||
| const ScopedGraphicsContext sgc(win); | |||
| widget = new MyCustomTopLevelWidget(win); | |||
| } | |||
| app.exec(); | |||
| return 0; | |||
| } | |||
| ``` | |||
| This struct is necessary because we cannot automatically make the window leave the OpenGL context in custom code. | |||
| We must always cleanly enter and leave the OpenGL context. | |||
| In order to avoid messing up the global host context, this class is used around widget creation. | |||
| */ | |||
| class ScopedGraphicsContext | |||
| { | |||
| Window& window; | |||
| public: | |||
| explicit ScopedGraphicsContext(Window& window); | |||
| ~ScopedGraphicsContext(); | |||
| DISTRHO_DECLARE_NON_COPYABLE(ScopedGraphicsContext) | |||
| DISTRHO_PREVENT_HEAP_ALLOCATION | |||
| }; | |||
| /** | |||
| Constructor for a regular, standalone window. | |||
| */ | |||
| @@ -362,9 +399,6 @@ public: | |||
| DISTRHO_DEPRECATED_BY("runAsModal(bool)") | |||
| inline void exec(bool blockWait = false) { runAsModal(blockWait); } | |||
| // TESTING, DO NOT USE | |||
| void leaveContext(); | |||
| protected: | |||
| /** | |||
| A function called when the window is attempted to be closed. | |||
| @@ -414,6 +448,7 @@ private: | |||
| struct PrivateData; | |||
| PrivateData* const pData; | |||
| friend class Application; | |||
| friend class PluginWindow; | |||
| friend class TopLevelWidget; | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Window); | |||
| @@ -423,15 +458,4 @@ private: | |||
| END_NAMESPACE_DGL | |||
| /* TODO | |||
| * add eventcrossing/enter-leave event | |||
| */ | |||
| #if 0 | |||
| protected: | |||
| bool handlePluginKeyboard(const bool press, const uint key); | |||
| bool handlePluginSpecial(const bool press, const Key key); | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| #endif // DGL_WINDOW_HPP_INCLUDED | |||
| @@ -53,6 +53,11 @@ bool Application::isQuiting() const noexcept | |||
| return pData->isQuitting || pData->isQuittingInNextCycle; | |||
| } | |||
| bool Application::isStandalone() const noexcept | |||
| { | |||
| return pData->isStandalone; | |||
| } | |||
| void Application::addIdleCallback(IdleCallback* const callback) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,) | |||
| @@ -25,7 +25,7 @@ START_NAMESPACE_DGL | |||
| typedef std::list<DGL_NAMESPACE::Window*>::reverse_iterator WindowListReverseIterator; | |||
| static ThreadHandle getCurrentThreadHandle() noexcept | |||
| static d_ThreadHandle getCurrentThreadHandle() noexcept | |||
| { | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| return GetCurrentThread(); | |||
| @@ -34,7 +34,7 @@ static ThreadHandle getCurrentThreadHandle() noexcept | |||
| #endif | |||
| } | |||
| static bool isThisMainThread(const ThreadHandle mainThreadHandle) noexcept | |||
| static bool isThisTheMainThread(const d_ThreadHandle mainThreadHandle) noexcept | |||
| { | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| return GetCurrentThread() == mainThreadHandle; // IsGUIThread ? | |||
| @@ -127,7 +127,7 @@ void Application::PrivateData::quit() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(isStandalone,); | |||
| if (! isThisMainThread(mainThreadHandle)) | |||
| if (! isThisTheMainThread(mainThreadHandle)) | |||
| { | |||
| if (! isQuittingInNextCycle) | |||
| { | |||
| @@ -24,10 +24,10 @@ | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| # include <winsock2.h> | |||
| # include <windows.h> | |||
| typedef HANDLE ThreadHandle; | |||
| typedef HANDLE d_ThreadHandle; | |||
| #else | |||
| # include <pthread.h> | |||
| typedef pthread_t ThreadHandle; | |||
| typedef pthread_t d_ThreadHandle; | |||
| #endif | |||
| typedef struct PuglWorldImpl PuglWorld; | |||
| @@ -59,7 +59,7 @@ struct Application::PrivateData { | |||
| uint visibleWindows; | |||
| /** Handle that identifies the main thread. Used to check if calls belong to current thread or not. */ | |||
| ThreadHandle mainThreadHandle; | |||
| d_ThreadHandle mainThreadHandle; | |||
| /** List of windows for this application. Only used during `close`. */ | |||
| std::list<DGL_NAMESPACE::Window*> windows; | |||
| @@ -24,7 +24,6 @@ | |||
| #include "../Color.hpp" | |||
| #include "../ImageBaseWidgets.hpp" | |||
| #include "Common.hpp" | |||
| #include "SubWidgetPrivateData.hpp" | |||
| #include "TopLevelWidgetPrivateData.hpp" | |||
| #include "WidgetPrivateData.hpp" | |||
| @@ -657,8 +656,7 @@ void ImageBaseKnob<CairoImage>::onDisplay() | |||
| { | |||
| const GraphicsContext& context(getGraphicsContext()); | |||
| cairo_t* const handle = ((const CairoGraphicsContext&)context).handle; | |||
| const double normValue = ((pData->usingLog ? pData->invlogscale(pData->value) : pData->value) - pData->minimum) | |||
| / (pData->maximum - pData->minimum); | |||
| const double normValue = getNormalizedValue(); | |||
| cairo_surface_t* surface = (cairo_surface_t*)pData->cairoSurface; | |||
| @@ -114,6 +114,13 @@ Color::Color(const Color& color1, const Color& color2, const float u) noexcept | |||
| interpolate(color2, u); | |||
| } | |||
| Color Color::withAlpha(const float alpha) noexcept | |||
| { | |||
| Color color(*this); | |||
| color.alpha = alpha; | |||
| return color; | |||
| } | |||
| Color Color::fromHSL(float hue, float saturation, float lightness, float alpha) | |||
| { | |||
| float m1, m2; | |||
| @@ -1,188 +0,0 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 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 | |||
| * permission notice appear in all copies. | |||
| * | |||
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #ifndef DGL_COMMON_HPP_INCLUDED | |||
| #define DGL_COMMON_HPP_INCLUDED | |||
| #include "../ImageBaseWidgets.hpp" | |||
| START_NAMESPACE_DGL | |||
| // ----------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| struct ButtonImpl { | |||
| enum State { | |||
| kStateNormal = 0, | |||
| kStateHover, | |||
| kStateDown | |||
| }; | |||
| int button; | |||
| int state; | |||
| ImageBaseButton<ImageType>* const self; | |||
| typename ImageBaseButton<ImageType>::Callback* callback_img; | |||
| explicit ButtonImpl(ImageBaseButton<ImageType>* const s) noexcept | |||
| : button(-1), | |||
| state(kStateNormal), | |||
| self(s), | |||
| callback_img(nullptr) {} | |||
| bool onMouse(const Widget::MouseEvent& ev) | |||
| { | |||
| // button was released, handle it now | |||
| if (button != -1 && ! ev.press) | |||
| { | |||
| DISTRHO_SAFE_ASSERT(state == kStateDown); | |||
| // release button | |||
| const int button2 = button; | |||
| button = -1; | |||
| // cursor was moved outside the button bounds, ignore click | |||
| if (! self->contains(ev.pos)) | |||
| { | |||
| state = kStateNormal; | |||
| self->repaint(); | |||
| return true; | |||
| } | |||
| // still on bounds, register click | |||
| state = kStateHover; | |||
| self->repaint(); | |||
| if (callback_img != nullptr) | |||
| callback_img->imageButtonClicked(self, button2); | |||
| return true; | |||
| } | |||
| // button was pressed, wait for release | |||
| if (ev.press && self->contains(ev.pos)) | |||
| { | |||
| button = static_cast<int>(ev.button); | |||
| state = kStateDown; | |||
| self->repaint(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool onMotion(const Widget::MotionEvent& ev) | |||
| { | |||
| // keep pressed | |||
| if (button != -1) | |||
| return true; | |||
| if (self->contains(ev.pos)) | |||
| { | |||
| // check if entering hover | |||
| if (state == kStateNormal) | |||
| { | |||
| state = kStateHover; | |||
| self->repaint(); | |||
| return true; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // check if exiting hover | |||
| if (state == kStateHover) | |||
| { | |||
| state = kStateNormal; | |||
| self->repaint(); | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| DISTRHO_PREVENT_HEAP_ALLOCATION | |||
| DISTRHO_DECLARE_NON_COPYABLE(ButtonImpl) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| struct ImageBaseKnob<ImageType>::PrivateData { | |||
| ImageType image; | |||
| float minimum; | |||
| float maximum; | |||
| float step; | |||
| float value; | |||
| float valueDef; | |||
| float valueTmp; | |||
| bool usingDefault; | |||
| bool usingLog; | |||
| Orientation orientation; | |||
| int rotationAngle; | |||
| bool dragging; | |||
| double lastX; | |||
| double lastY; | |||
| Callback* callback; | |||
| bool alwaysRepaint; | |||
| bool isImgVertical; | |||
| uint imgLayerWidth; | |||
| uint imgLayerHeight; | |||
| uint imgLayerCount; | |||
| bool isReady; | |||
| union { | |||
| uint glTextureId; | |||
| void* cairoSurface; | |||
| }; | |||
| explicit PrivateData(const ImageType& img, const Orientation o); | |||
| explicit PrivateData(PrivateData* const other); | |||
| void assignFrom(PrivateData* const other); | |||
| ~PrivateData() | |||
| { | |||
| cleanup(); | |||
| } | |||
| void init(); | |||
| void cleanup(); | |||
| inline float logscale(const float v) const | |||
| { | |||
| const float b = std::log(maximum/minimum)/(maximum-minimum); | |||
| const float a = maximum/std::exp(maximum*b); | |||
| return a * std::exp(b*v); | |||
| } | |||
| inline float invlogscale(const float v) const | |||
| { | |||
| const float b = std::log(maximum/minimum)/(maximum-minimum); | |||
| const float a = maximum/std::exp(maximum*b); | |||
| return std::log(v/a)/b; | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE(PrivateData) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| END_NAMESPACE_DGL | |||
| #endif // DGL_APP_PRIVATE_DATA_HPP_INCLUDED | |||
| @@ -0,0 +1,632 @@ | |||
| /* | |||
| * DISTRHO Plugin Framework (DPF) | |||
| * Copyright (C) 2012-2021 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 | |||
| * permission notice appear in all copies. | |||
| * | |||
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #include "../EventHandlers.hpp" | |||
| #include "../SubWidget.hpp" | |||
| START_NAMESPACE_DGL | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| struct ButtonEventHandler::PrivateData { | |||
| ButtonEventHandler* const self; | |||
| SubWidget* const widget; | |||
| ButtonEventHandler::Callback* internalCallback; | |||
| ButtonEventHandler::Callback* userCallback; | |||
| int button; | |||
| int state; | |||
| bool checkable; | |||
| bool checked; | |||
| Point<double> lastClickPos; | |||
| Point<double> lastMotionPos; | |||
| PrivateData(ButtonEventHandler* const s, SubWidget* const w) | |||
| : self(s), | |||
| widget(w), | |||
| internalCallback(nullptr), | |||
| userCallback(nullptr), | |||
| button(-1), | |||
| state(kButtonStateDefault), | |||
| checkable(false), | |||
| checked(false), | |||
| lastClickPos(0, 0), | |||
| lastMotionPos(0, 0) {} | |||
| bool mouseEvent(const Widget::MouseEvent& ev) | |||
| { | |||
| lastClickPos = ev.pos; | |||
| // button was released, handle it now | |||
| if (button != -1 && ! ev.press) | |||
| { | |||
| DISTRHO_SAFE_ASSERT(state & kButtonStateActive); | |||
| // release button | |||
| const int button2 = button; | |||
| button = -1; | |||
| const int state2 = state; | |||
| state &= ~kButtonStateActive; | |||
| self->stateChanged(static_cast<State>(state), static_cast<State>(state2)); | |||
| widget->repaint(); | |||
| // cursor was moved outside the button bounds, ignore click | |||
| if (! widget->contains(ev.pos)) | |||
| return true; | |||
| // still on bounds, register click | |||
| if (checkable) | |||
| checked = !checked; | |||
| if (internalCallback != nullptr) | |||
| internalCallback->buttonClicked(widget, button2); | |||
| else if (userCallback != nullptr) | |||
| userCallback->buttonClicked(widget, button2); | |||
| return true; | |||
| } | |||
| // button was pressed, wait for release | |||
| if (ev.press && widget->contains(ev.pos)) | |||
| { | |||
| const int state2 = state; | |||
| button = static_cast<int>(ev.button); | |||
| state |= kButtonStateActive; | |||
| self->stateChanged(static_cast<State>(state), static_cast<State>(state2)); | |||
| widget->repaint(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool motionEvent(const Widget::MotionEvent& ev) | |||
| { | |||
| // keep pressed | |||
| if (button != -1) | |||
| { | |||
| lastMotionPos = ev.pos; | |||
| return true; | |||
| } | |||
| bool ret = false; | |||
| if (widget->contains(ev.pos)) | |||
| { | |||
| // check if entering hover | |||
| if ((state & kButtonStateHover) == 0x0) | |||
| { | |||
| const int state2 = state; | |||
| state |= kButtonStateHover; | |||
| ret = widget->contains(lastMotionPos); | |||
| self->stateChanged(static_cast<State>(state), static_cast<State>(state2)); | |||
| widget->repaint(); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| // check if exiting hover | |||
| if (state & kButtonStateHover) | |||
| { | |||
| const int state2 = state; | |||
| state &= ~kButtonStateHover; | |||
| ret = widget->contains(lastMotionPos); | |||
| self->stateChanged(static_cast<State>(state), static_cast<State>(state2)); | |||
| widget->repaint(); | |||
| } | |||
| } | |||
| lastMotionPos = ev.pos; | |||
| return ret; | |||
| } | |||
| void setActive(const bool active2, const bool sendCallback) noexcept | |||
| { | |||
| const bool active = state & kButtonStateActive; | |||
| if (active == active2) | |||
| return; | |||
| state |= kButtonStateActive; | |||
| widget->repaint(); | |||
| if (sendCallback) | |||
| { | |||
| if (internalCallback != nullptr) | |||
| internalCallback->buttonClicked(widget, -1); | |||
| else if (userCallback != nullptr) | |||
| userCallback->buttonClicked(widget, -1); | |||
| } | |||
| } | |||
| void setChecked(const bool checked2, const bool sendCallback) noexcept | |||
| { | |||
| if (checked == checked2) | |||
| return; | |||
| checked = checked2; | |||
| widget->repaint(); | |||
| if (sendCallback) | |||
| { | |||
| if (internalCallback != nullptr) | |||
| internalCallback->buttonClicked(widget, -1); | |||
| else if (userCallback != nullptr) | |||
| userCallback->buttonClicked(widget, -1); | |||
| } | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE(PrivateData) | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| ButtonEventHandler::ButtonEventHandler(SubWidget* const self) | |||
| : pData(new PrivateData(this, self)) {} | |||
| ButtonEventHandler::~ButtonEventHandler() | |||
| { | |||
| delete pData; | |||
| } | |||
| bool ButtonEventHandler::isActive() noexcept | |||
| { | |||
| return pData->state & kButtonStateActive; | |||
| } | |||
| void ButtonEventHandler::setActive(const bool active, const bool sendCallback) noexcept | |||
| { | |||
| pData->setActive(active, sendCallback); | |||
| } | |||
| bool ButtonEventHandler::isChecked() const noexcept | |||
| { | |||
| return pData->checked; | |||
| } | |||
| void ButtonEventHandler::setChecked(const bool checked, const bool sendCallback) noexcept | |||
| { | |||
| pData->setChecked(checked, sendCallback); | |||
| } | |||
| bool ButtonEventHandler::isCheckable() const noexcept | |||
| { | |||
| return pData->checkable; | |||
| } | |||
| void ButtonEventHandler::setCheckable(const bool checkable) noexcept | |||
| { | |||
| if (pData->checkable == checkable) | |||
| return; | |||
| pData->checkable = checkable; | |||
| } | |||
| Point<double> ButtonEventHandler::getLastClickPosition() const noexcept | |||
| { | |||
| return pData->lastClickPos; | |||
| } | |||
| Point<double> ButtonEventHandler::getLastMotionPosition() const noexcept | |||
| { | |||
| return pData->lastMotionPos; | |||
| } | |||
| void ButtonEventHandler::setCallback(Callback* const callback) noexcept | |||
| { | |||
| pData->userCallback = callback; | |||
| } | |||
| bool ButtonEventHandler::mouseEvent(const Widget::MouseEvent& ev) | |||
| { | |||
| return pData->mouseEvent(ev); | |||
| } | |||
| bool ButtonEventHandler::motionEvent(const Widget::MotionEvent& ev) | |||
| { | |||
| return pData->motionEvent(ev); | |||
| } | |||
| ButtonEventHandler::State ButtonEventHandler::getState() const noexcept | |||
| { | |||
| return static_cast<State>(pData->state); | |||
| } | |||
| void ButtonEventHandler::clearState() noexcept | |||
| { | |||
| pData->state = kButtonStateDefault; | |||
| } | |||
| void ButtonEventHandler::stateChanged(State, State) | |||
| { | |||
| } | |||
| void ButtonEventHandler::setInternalCallback(Callback* const callback) noexcept | |||
| { | |||
| pData->internalCallback = callback; | |||
| } | |||
| void ButtonEventHandler::triggerUserCallback(SubWidget* const widget, const int button) | |||
| { | |||
| if (pData->userCallback != nullptr) | |||
| pData->userCallback->buttonClicked(widget, button); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| struct KnobEventHandler::PrivateData { | |||
| KnobEventHandler* const self; | |||
| SubWidget* const widget; | |||
| KnobEventHandler::Callback* callback; | |||
| float minimum; | |||
| float maximum; | |||
| float step; | |||
| float value; | |||
| float valueDef; | |||
| float valueTmp; | |||
| bool usingDefault; | |||
| bool usingLog; | |||
| Orientation orientation; | |||
| int state; | |||
| double lastX; | |||
| double lastY; | |||
| PrivateData(KnobEventHandler* const s, SubWidget* const w) | |||
| : self(s), | |||
| widget(w), | |||
| callback(nullptr), | |||
| minimum(0.0f), | |||
| maximum(1.0f), | |||
| step(0.0f), | |||
| value(0.5f), | |||
| valueDef(value), | |||
| valueTmp(value), | |||
| usingDefault(false), | |||
| usingLog(false), | |||
| orientation(Vertical), | |||
| state(kKnobStateDefault), | |||
| lastX(0.0), | |||
| lastY(0.0) {} | |||
| PrivateData(KnobEventHandler* const s, SubWidget* const w, PrivateData* const other) | |||
| : self(s), | |||
| widget(w), | |||
| callback(other->callback), | |||
| minimum(other->minimum), | |||
| maximum(other->maximum), | |||
| step(other->step), | |||
| value(other->value), | |||
| valueDef(other->valueDef), | |||
| valueTmp(value), | |||
| usingDefault(other->usingDefault), | |||
| usingLog(other->usingLog), | |||
| orientation(other->orientation), | |||
| state(kKnobStateDefault), | |||
| lastX(0.0), | |||
| lastY(0.0) {} | |||
| void assignFrom(PrivateData* const other) | |||
| { | |||
| callback = other->callback; | |||
| minimum = other->minimum; | |||
| maximum = other->maximum; | |||
| step = other->step; | |||
| value = other->value; | |||
| valueDef = other->valueDef; | |||
| valueTmp = value; | |||
| usingDefault = other->usingDefault; | |||
| usingLog = other->usingLog; | |||
| orientation = other->orientation; | |||
| state = kKnobStateDefault; | |||
| lastX = 0.0; | |||
| lastY = 0.0; | |||
| } | |||
| inline float logscale(const float v) const | |||
| { | |||
| const float b = std::log(maximum/minimum)/(maximum-minimum); | |||
| const float a = maximum/std::exp(maximum*b); | |||
| return a * std::exp(b*v); | |||
| } | |||
| inline float invlogscale(const float v) const | |||
| { | |||
| const float b = std::log(maximum/minimum)/(maximum-minimum); | |||
| const float a = maximum/std::exp(maximum*b); | |||
| return std::log(v/a)/b; | |||
| } | |||
| bool mouseEvent(const Widget::MouseEvent& ev) | |||
| { | |||
| if (ev.button != 1) | |||
| return false; | |||
| if (ev.press) | |||
| { | |||
| if (! widget->contains(ev.pos)) | |||
| return false; | |||
| if ((ev.mod & kModifierShift) != 0 && usingDefault) | |||
| { | |||
| setValue(valueDef, true); | |||
| valueTmp = value; | |||
| return true; | |||
| } | |||
| state |= kKnobStateDragging; | |||
| lastX = ev.pos.getX(); | |||
| lastY = ev.pos.getY(); | |||
| widget->repaint(); | |||
| if (callback != nullptr) | |||
| callback->knobDragStarted(widget); | |||
| return true; | |||
| } | |||
| else if (state & kKnobStateDragging) | |||
| { | |||
| state &= ~kKnobStateDragging; | |||
| widget->repaint(); | |||
| if (callback != nullptr) | |||
| callback->knobDragFinished(widget); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool motionEvent(const Widget::MotionEvent& ev) | |||
| { | |||
| if ((state & kKnobStateDragging) == 0x0) | |||
| return false; | |||
| bool doVal = false; | |||
| float d, value2 = 0.0f; | |||
| if (orientation == Horizontal) | |||
| { | |||
| if (const double movX = ev.pos.getX() - lastX) | |||
| { | |||
| 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; | |||
| } | |||
| } | |||
| if (! doVal) | |||
| return false; | |||
| if (usingLog) | |||
| value2 = logscale(value2); | |||
| if (value2 < minimum) | |||
| { | |||
| valueTmp = value2 = minimum; | |||
| } | |||
| else if (value2 > maximum) | |||
| { | |||
| valueTmp = value2 = maximum; | |||
| } | |||
| else | |||
| { | |||
| valueTmp = value2; | |||
| if (d_isNotZero(step)) | |||
| { | |||
| const float rest = std::fmod(value2, step); | |||
| value2 -= rest + (rest > step/2.0f ? step : 0.0f); | |||
| } | |||
| } | |||
| setValue(value2, true); | |||
| lastX = ev.pos.getX(); | |||
| lastY = ev.pos.getY(); | |||
| return true; | |||
| } | |||
| bool scrollEvent(const Widget::ScrollEvent& ev) | |||
| { | |||
| 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; | |||
| float value2 = (usingLog ? invlogscale(valueTmp) : valueTmp) | |||
| + ((maximum - minimum) / d * 10.f * dir); | |||
| if (usingLog) | |||
| value2 = logscale(value2); | |||
| if (value2 < minimum) | |||
| { | |||
| valueTmp = value2 = minimum; | |||
| } | |||
| else if (value2 > maximum) | |||
| { | |||
| valueTmp = value2 = maximum; | |||
| } | |||
| else | |||
| { | |||
| valueTmp = value2; | |||
| if (d_isNotZero(step)) | |||
| { | |||
| const float rest = std::fmod(value2, step); | |||
| value2 = value2 - rest + (rest > step/2.0f ? step : 0.0f); | |||
| } | |||
| } | |||
| setValue(value2, true); | |||
| return true; | |||
| } | |||
| float getNormalizedValue() const noexcept | |||
| { | |||
| const float diff = maximum - minimum; | |||
| return ((usingLog ? invlogscale(value) : value) - minimum) / diff; | |||
| } | |||
| void setRange(const float min, const float max) noexcept | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(max > min,); | |||
| if (value < min) | |||
| { | |||
| valueTmp = value = min; | |||
| widget->repaint(); | |||
| } | |||
| else if (value > max) | |||
| { | |||
| valueTmp = value = max; | |||
| widget->repaint(); | |||
| } | |||
| minimum = min; | |||
| maximum = max; | |||
| } | |||
| bool setValue(const float value2, const bool sendCallback) | |||
| { | |||
| if (d_isEqual(value, value2)) | |||
| return false; | |||
| valueTmp = value = value2; | |||
| widget->repaint(); | |||
| if (sendCallback && callback != nullptr) | |||
| { | |||
| try { | |||
| callback->knobValueChanged(widget, value); | |||
| } DISTRHO_SAFE_EXCEPTION("KnobEventHandler::setValue"); | |||
| } | |||
| return true; | |||
| } | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| KnobEventHandler::KnobEventHandler(SubWidget* const self) | |||
| : pData(new PrivateData(this, self)) {} | |||
| KnobEventHandler::KnobEventHandler(SubWidget* const self, const KnobEventHandler& other) | |||
| : pData(new PrivateData(this, self, other.pData)) {} | |||
| KnobEventHandler& KnobEventHandler::operator=(const KnobEventHandler& other) | |||
| { | |||
| pData->assignFrom(other.pData); | |||
| return *this; | |||
| } | |||
| KnobEventHandler::~KnobEventHandler() | |||
| { | |||
| delete pData; | |||
| } | |||
| float KnobEventHandler::getValue() const noexcept | |||
| { | |||
| return pData->value; | |||
| } | |||
| bool KnobEventHandler::setValue(const float value, const bool sendCallback) noexcept | |||
| { | |||
| return pData->setValue(value, sendCallback); | |||
| } | |||
| float KnobEventHandler::getNormalizedValue() const noexcept | |||
| { | |||
| return pData->getNormalizedValue(); | |||
| } | |||
| void KnobEventHandler::setDefault(const float def) noexcept | |||
| { | |||
| pData->valueDef = def; | |||
| pData->usingDefault = true; | |||
| } | |||
| void KnobEventHandler::setRange(const float min, const float max) noexcept | |||
| { | |||
| pData->setRange(min, max); | |||
| } | |||
| void KnobEventHandler::setStep(const float step) noexcept | |||
| { | |||
| pData->step = step; | |||
| } | |||
| void KnobEventHandler::setUsingLogScale(const bool yesNo) noexcept | |||
| { | |||
| pData->usingLog = yesNo; | |||
| } | |||
| KnobEventHandler::Orientation KnobEventHandler::getOrientation() const noexcept | |||
| { | |||
| return pData->orientation; | |||
| } | |||
| void KnobEventHandler::setOrientation(const Orientation orientation) noexcept | |||
| { | |||
| if (pData->orientation == orientation) | |||
| return; | |||
| pData->orientation = orientation; | |||
| } | |||
| void KnobEventHandler::setCallback(Callback* const callback) noexcept | |||
| { | |||
| pData->callback = callback; | |||
| } | |||
| bool KnobEventHandler::mouseEvent(const Widget::MouseEvent& ev) | |||
| { | |||
| return pData->mouseEvent(ev); | |||
| } | |||
| bool KnobEventHandler::motionEvent(const Widget::MotionEvent& ev) | |||
| { | |||
| return pData->motionEvent(ev); | |||
| } | |||
| bool KnobEventHandler::scrollEvent(const Widget::ScrollEvent& ev) | |||
| { | |||
| return pData->scrollEvent(ev); | |||
| } | |||
| KnobEventHandler::State KnobEventHandler::getState() const noexcept | |||
| { | |||
| return static_cast<State>(pData->state); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| END_NAMESPACE_DGL | |||
| @@ -16,7 +16,6 @@ | |||
| #include "../ImageBaseWidgets.hpp" | |||
| #include "../Color.hpp" | |||
| #include "Common.hpp" | |||
| START_NAMESPACE_DGL | |||
| @@ -89,18 +88,25 @@ bool ImageBaseAboutWindow<ImageType>::onMouse(const MouseEvent& ev) | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| struct ImageBaseButton<ImageType>::PrivateData { | |||
| ButtonImpl<ImageType> impl; | |||
| struct ImageBaseButton<ImageType>::PrivateData : public ButtonEventHandler::Callback { | |||
| ImageBaseButton<ImageType>::Callback* callback; | |||
| ImageType imageNormal; | |||
| ImageType imageHover; | |||
| ImageType imageDown; | |||
| PrivateData(ImageBaseButton<ImageType>* const s, const ImageType& normal, const ImageType& hover, const ImageType& down) | |||
| : impl(s), | |||
| PrivateData(const ImageType& normal, const ImageType& hover, const ImageType& down) | |||
| : callback(nullptr), | |||
| imageNormal(normal), | |||
| imageHover(hover), | |||
| imageDown(down) {} | |||
| void buttonClicked(SubWidget* widget, int button) override | |||
| { | |||
| if (callback != nullptr) | |||
| if (ImageBaseButton* const imageButton = dynamic_cast<ImageBaseButton*>(widget)) | |||
| callback->imageButtonClicked(imageButton, button); | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE(PrivateData) | |||
| }; | |||
| @@ -109,28 +115,34 @@ struct ImageBaseButton<ImageType>::PrivateData { | |||
| template <class ImageType> | |||
| ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& image) | |||
| : SubWidget(parentWidget), | |||
| pData(new PrivateData(this, image, image, image)) | |||
| ButtonEventHandler(this), | |||
| pData(new PrivateData(image, image, image)) | |||
| { | |||
| ButtonEventHandler::setCallback(pData); | |||
| setSize(image.getSize()); | |||
| } | |||
| template <class ImageType> | |||
| ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageDown) | |||
| : SubWidget(parentWidget), | |||
| pData(new PrivateData(this, imageNormal, imageNormal, imageDown)) | |||
| ButtonEventHandler(this), | |||
| pData(new PrivateData(imageNormal, imageNormal, imageDown)) | |||
| { | |||
| DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageDown.getSize()); | |||
| ButtonEventHandler::setCallback(pData); | |||
| setSize(imageNormal.getSize()); | |||
| } | |||
| template <class ImageType> | |||
| ImageBaseButton<ImageType>::ImageBaseButton(Widget* const parentWidget, const ImageType& imageNormal, const ImageType& imageHover, const ImageType& imageDown) | |||
| : SubWidget(parentWidget), | |||
| pData(new PrivateData(this, imageNormal, imageHover, imageDown)) | |||
| ButtonEventHandler(this), | |||
| pData(new PrivateData(imageNormal, imageHover, imageDown)) | |||
| { | |||
| DISTRHO_SAFE_ASSERT(imageNormal.getSize() == imageHover.getSize() && imageHover.getSize() == imageDown.getSize()); | |||
| ButtonEventHandler::setCallback(pData); | |||
| setSize(imageNormal.getSize()); | |||
| } | |||
| @@ -143,7 +155,7 @@ ImageBaseButton<ImageType>::~ImageBaseButton() | |||
| template <class ImageType> | |||
| void ImageBaseButton<ImageType>::setCallback(Callback* callback) noexcept | |||
| { | |||
| pData->impl.callback_img = callback; | |||
| pData->callback = callback; | |||
| } | |||
| template <class ImageType> | |||
| @@ -151,137 +163,162 @@ void ImageBaseButton<ImageType>::onDisplay() | |||
| { | |||
| const GraphicsContext& context(getGraphicsContext()); | |||
| switch (pData->impl.state) | |||
| { | |||
| case ButtonImpl<ImageType>::kStateDown: | |||
| const State state = ButtonEventHandler::getState(); | |||
| if (state & kButtonStateActive) | |||
| pData->imageDown.draw(context); | |||
| break; | |||
| case ButtonImpl<ImageType>::kStateHover: | |||
| else if (state & kButtonStateHover) | |||
| pData->imageHover.draw(context); | |||
| break; | |||
| default: | |||
| else | |||
| pData->imageNormal.draw(context); | |||
| break; | |||
| } | |||
| } | |||
| template <class ImageType> | |||
| bool ImageBaseButton<ImageType>::onMouse(const MouseEvent& ev) | |||
| { | |||
| return pData->impl.onMouse(ev); | |||
| if (SubWidget::onMouse(ev)) | |||
| return true; | |||
| return ButtonEventHandler::mouseEvent(ev); | |||
| } | |||
| template <class ImageType> | |||
| bool ImageBaseButton<ImageType>::onMotion(const MotionEvent& ev) | |||
| { | |||
| return pData->impl.onMotion(ev); | |||
| if (SubWidget::onMotion(ev)) | |||
| return true; | |||
| return ButtonEventHandler::motionEvent(ev); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>::PrivateData::PrivateData(const ImageType& img, const Orientation o) | |||
| : image(img), | |||
| minimum(0.0f), | |||
| maximum(1.0f), | |||
| step(0.0f), | |||
| value(0.5f), | |||
| valueDef(value), | |||
| valueTmp(value), | |||
| usingDefault(false), | |||
| usingLog(false), | |||
| orientation(o), | |||
| rotationAngle(0), | |||
| dragging(false), | |||
| lastX(0.0), | |||
| lastY(0.0), | |||
| callback(nullptr), | |||
| alwaysRepaint(false), | |||
| isImgVertical(img.getHeight() > img.getWidth()), | |||
| imgLayerWidth(isImgVertical ? img.getWidth() : img.getHeight()), | |||
| imgLayerHeight(imgLayerWidth), | |||
| imgLayerCount(isImgVertical ? img.getHeight()/imgLayerHeight : img.getWidth()/imgLayerWidth), | |||
| isReady(false) | |||
| { | |||
| init(); | |||
| } | |||
| struct ImageBaseKnob<ImageType>::PrivateData : public KnobEventHandler::Callback { | |||
| ImageBaseKnob<ImageType>::Callback* callback; | |||
| ImageType image; | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>::PrivateData::PrivateData(PrivateData* const other) | |||
| : image(other->image), | |||
| minimum(other->minimum), | |||
| maximum(other->maximum), | |||
| step(other->step), | |||
| value(other->value), | |||
| valueDef(other->valueDef), | |||
| valueTmp(value), | |||
| usingDefault(other->usingDefault), | |||
| usingLog(other->usingLog), | |||
| orientation(other->orientation), | |||
| rotationAngle(other->rotationAngle), | |||
| dragging(false), | |||
| lastX(0.0), | |||
| lastY(0.0), | |||
| callback(other->callback), | |||
| alwaysRepaint(other->alwaysRepaint), | |||
| isImgVertical(other->isImgVertical), | |||
| imgLayerWidth(other->imgLayerWidth), | |||
| imgLayerHeight(other->imgLayerHeight), | |||
| imgLayerCount(other->imgLayerCount), | |||
| isReady(false) | |||
| { | |||
| init(); | |||
| } | |||
| int rotationAngle; | |||
| bool alwaysRepaint; | |||
| bool isImgVertical; | |||
| uint imgLayerWidth; | |||
| uint imgLayerHeight; | |||
| uint imgLayerCount; | |||
| bool isReady; | |||
| union { | |||
| uint glTextureId; | |||
| void* cairoSurface; | |||
| }; | |||
| explicit PrivateData(const ImageType& img) | |||
| : callback(nullptr), | |||
| image(img), | |||
| rotationAngle(0), | |||
| alwaysRepaint(false), | |||
| isImgVertical(img.getHeight() > img.getWidth()), | |||
| imgLayerWidth(isImgVertical ? img.getWidth() : img.getHeight()), | |||
| imgLayerHeight(imgLayerWidth), | |||
| imgLayerCount(isImgVertical ? img.getHeight()/imgLayerHeight : img.getWidth()/imgLayerWidth), | |||
| isReady(false) | |||
| { | |||
| init(); | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::PrivateData::assignFrom(PrivateData* const other) | |||
| { | |||
| cleanup(); | |||
| image = other->image; | |||
| minimum = other->minimum; | |||
| maximum = other->maximum; | |||
| step = other->step; | |||
| value = other->value; | |||
| valueDef = other->valueDef; | |||
| valueTmp = value; | |||
| usingDefault = other->usingDefault; | |||
| usingLog = other->usingLog; | |||
| orientation = other->orientation; | |||
| rotationAngle = other->rotationAngle; | |||
| dragging = false; | |||
| lastX = 0.0; | |||
| lastY = 0.0; | |||
| callback = other->callback; | |||
| alwaysRepaint = other->alwaysRepaint; | |||
| isImgVertical = other->isImgVertical; | |||
| imgLayerWidth = other->imgLayerWidth; | |||
| imgLayerHeight = other->imgLayerHeight; | |||
| imgLayerCount = other->imgLayerCount; | |||
| isReady = false; | |||
| init(); | |||
| } | |||
| explicit PrivateData(PrivateData* const other) | |||
| : callback(other->callback), | |||
| image(other->image), | |||
| rotationAngle(other->rotationAngle), | |||
| alwaysRepaint(other->alwaysRepaint), | |||
| isImgVertical(other->isImgVertical), | |||
| imgLayerWidth(other->imgLayerWidth), | |||
| imgLayerHeight(other->imgLayerHeight), | |||
| imgLayerCount(other->imgLayerCount), | |||
| isReady(false) | |||
| { | |||
| init(); | |||
| } | |||
| void assignFrom(PrivateData* const other) | |||
| { | |||
| cleanup(); | |||
| image = other->image; | |||
| rotationAngle = other->rotationAngle; | |||
| callback = other->callback; | |||
| alwaysRepaint = other->alwaysRepaint; | |||
| isImgVertical = other->isImgVertical; | |||
| imgLayerWidth = other->imgLayerWidth; | |||
| imgLayerHeight = other->imgLayerHeight; | |||
| imgLayerCount = other->imgLayerCount; | |||
| isReady = false; | |||
| init(); | |||
| } | |||
| ~PrivateData() | |||
| { | |||
| cleanup(); | |||
| } | |||
| void knobDragStarted(SubWidget* const widget) override | |||
| { | |||
| if (callback != nullptr) | |||
| if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) | |||
| callback->imageKnobDragStarted(imageKnob); | |||
| } | |||
| void knobDragFinished(SubWidget* const widget) override | |||
| { | |||
| if (callback != nullptr) | |||
| if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) | |||
| callback->imageKnobDragFinished(imageKnob); | |||
| } | |||
| void knobValueChanged(SubWidget* const widget, const float value) override | |||
| { | |||
| if (rotationAngle == 0 || alwaysRepaint) | |||
| isReady = false; | |||
| if (callback != nullptr) | |||
| if (ImageBaseKnob* const imageKnob = dynamic_cast<ImageBaseKnob*>(widget)) | |||
| callback->imageKnobValueChanged(imageKnob, value); | |||
| } | |||
| // implemented independently per graphics backend | |||
| void init(); | |||
| void cleanup(); | |||
| DISTRHO_DECLARE_NON_COPYABLE(PrivateData) | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>::ImageBaseKnob(Widget* const parentWidget, const ImageType& image, const Orientation orientation) noexcept | |||
| ImageBaseKnob<ImageType>::ImageBaseKnob(Widget* const parentWidget, | |||
| const ImageType& image, | |||
| const Orientation orientation) noexcept | |||
| : SubWidget(parentWidget), | |||
| pData(new PrivateData(image, orientation)) | |||
| KnobEventHandler(this), | |||
| pData(new PrivateData(image)) | |||
| { | |||
| KnobEventHandler::setCallback(pData); | |||
| setOrientation(orientation); | |||
| setSize(pData->imgLayerWidth, pData->imgLayerHeight); | |||
| } | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>::ImageBaseKnob(const ImageBaseKnob<ImageType>& imageKnob) | |||
| : SubWidget(imageKnob.getParentWidget()), | |||
| KnobEventHandler(this, imageKnob), | |||
| pData(new PrivateData(imageKnob.pData)) | |||
| { | |||
| KnobEventHandler::setCallback(pData); | |||
| setOrientation(imageKnob.getOrientation()); | |||
| setSize(pData->imgLayerWidth, pData->imgLayerHeight); | |||
| } | |||
| template <class ImageType> | |||
| ImageBaseKnob<ImageType>& ImageBaseKnob<ImageType>::operator=(const ImageBaseKnob<ImageType>& imageKnob) | |||
| { | |||
| KnobEventHandler::operator=(imageKnob); | |||
| pData->assignFrom(imageKnob.pData); | |||
| setSize(pData->imgLayerWidth, pData->imgLayerHeight); | |||
| return *this; | |||
| @@ -293,116 +330,12 @@ ImageBaseKnob<ImageType>::~ImageBaseKnob() | |||
| delete pData; | |||
| } | |||
| template <class ImageType> | |||
| float ImageBaseKnob<ImageType>::getValue() const noexcept | |||
| { | |||
| return pData->value; | |||
| } | |||
| // NOTE: value is assumed to be scaled if using log | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setDefault(float value) noexcept | |||
| { | |||
| pData->valueDef = value; | |||
| pData->usingDefault = true; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setRange(float min, float max) noexcept | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(max > min,); | |||
| if (pData->value < min) | |||
| { | |||
| pData->value = min; | |||
| repaint(); | |||
| if (pData->callback != nullptr) | |||
| { | |||
| try { | |||
| pData->callback->imageKnobValueChanged(this, pData->value); | |||
| } DISTRHO_SAFE_EXCEPTION("ImageBaseKnob<ImageType>::setRange < min"); | |||
| } | |||
| } | |||
| else if (pData->value > max) | |||
| { | |||
| pData->value = max; | |||
| repaint(); | |||
| if (pData->callback != nullptr) | |||
| { | |||
| try { | |||
| pData->callback->imageKnobValueChanged(this, pData->value); | |||
| } DISTRHO_SAFE_EXCEPTION("ImageBaseKnob<ImageType>::setRange > max"); | |||
| } | |||
| } | |||
| pData->minimum = min; | |||
| pData->maximum = max; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setStep(float step) noexcept | |||
| { | |||
| pData->step = step; | |||
| } | |||
| // NOTE: value is assumed to be scaled if using log | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setValue(float value, bool sendCallback) noexcept | |||
| { | |||
| if (d_isEqual(pData->value, value)) | |||
| return; | |||
| pData->value = value; | |||
| if (d_isZero(pData->step)) | |||
| pData->valueTmp = value; | |||
| if (pData->rotationAngle == 0 || pData->alwaysRepaint) | |||
| pData->isReady = false; | |||
| repaint(); | |||
| if (sendCallback && pData->callback != nullptr) | |||
| { | |||
| try { | |||
| pData->callback->imageKnobValueChanged(this, pData->value); | |||
| } DISTRHO_SAFE_EXCEPTION("ImageBaseKnob<ImageType>::setValue"); | |||
| } | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setUsingLogScale(bool yesNo) noexcept | |||
| { | |||
| pData->usingLog = yesNo; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setCallback(Callback* callback) noexcept | |||
| { | |||
| pData->callback = callback; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setOrientation(Orientation orientation) noexcept | |||
| { | |||
| if (pData->orientation == orientation) | |||
| return; | |||
| pData->orientation = orientation; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setRotationAngle(int angle) | |||
| { | |||
| if (pData->rotationAngle == angle) | |||
| return; | |||
| pData->rotationAngle = angle; | |||
| pData->isReady = false; | |||
| } | |||
| template <class ImageType> | |||
| void ImageBaseKnob<ImageType>::setImageLayerCount(uint count) noexcept | |||
| { | |||
| @@ -419,38 +352,23 @@ void ImageBaseKnob<ImageType>::setImageLayerCount(uint count) noexcept | |||
| } | |||
| template <class ImageType> | |||
| bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev) | |||
| void ImageBaseKnob<ImageType>::setRotationAngle(int angle) | |||
| { | |||
| if (ev.button != 1) | |||
| return false; | |||
| if (ev.press) | |||
| { | |||
| if (! contains(ev.pos)) | |||
| return false; | |||
| if ((ev.mod & kModifierShift) != 0 && pData->usingDefault) | |||
| { | |||
| setValue(pData->valueDef, true); | |||
| pData->valueTmp = pData->value; | |||
| return true; | |||
| } | |||
| pData->dragging = true; | |||
| pData->lastX = ev.pos.getX(); | |||
| pData->lastY = ev.pos.getY(); | |||
| if (pData->rotationAngle == angle) | |||
| return; | |||
| if (pData->callback != nullptr) | |||
| pData->callback->imageKnobDragStarted(this); | |||
| pData->rotationAngle = angle; | |||
| pData->isReady = false; | |||
| } | |||
| return true; | |||
| } | |||
| else if (pData->dragging) | |||
| template <class ImageType> | |||
| bool ImageBaseKnob<ImageType>::setValue(float value, bool sendCallback) noexcept | |||
| { | |||
| if (KnobEventHandler::setValue(value, sendCallback)) | |||
| { | |||
| if (pData->callback != nullptr) | |||
| pData->callback->imageKnobDragFinished(this); | |||
| if (pData->rotationAngle == 0 || pData->alwaysRepaint) | |||
| pData->isReady = false; | |||
| pData->dragging = false; | |||
| return true; | |||
| } | |||
| @@ -458,93 +376,27 @@ bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev) | |||
| } | |||
| template <class ImageType> | |||
| bool ImageBaseKnob<ImageType>::onMotion(const MotionEvent& ev) | |||
| bool ImageBaseKnob<ImageType>::onMouse(const MouseEvent& ev) | |||
| { | |||
| if (! pData->dragging) | |||
| return false; | |||
| bool doVal = false; | |||
| float d, value = 0.0f; | |||
| if (pData->orientation == ImageBaseKnob<ImageType>::Horizontal) | |||
| { | |||
| if (const double movX = ev.pos.getX() - pData->lastX) | |||
| { | |||
| d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; | |||
| value = (pData->usingLog ? pData->invlogscale(pData->valueTmp) : pData->valueTmp) + (float(pData->maximum - pData->minimum) / d * float(movX)); | |||
| doVal = true; | |||
| } | |||
| } | |||
| else if (pData->orientation == ImageBaseKnob<ImageType>::Vertical) | |||
| { | |||
| if (const double movY = pData->lastY - ev.pos.getY()) | |||
| { | |||
| d = (ev.mod & kModifierControl) ? 2000.0f : 200.0f; | |||
| value = (pData->usingLog ? pData->invlogscale(pData->valueTmp) : pData->valueTmp) + (float(pData->maximum - pData->minimum) / d * float(movY)); | |||
| doVal = true; | |||
| } | |||
| } | |||
| if (! doVal) | |||
| return false; | |||
| if (pData->usingLog) | |||
| value = pData->logscale(value); | |||
| if (value < pData->minimum) | |||
| { | |||
| pData->valueTmp = value = pData->minimum; | |||
| } | |||
| else if (value > pData->maximum) | |||
| { | |||
| pData->valueTmp = value = pData->maximum; | |||
| } | |||
| else if (d_isNotZero(pData->step)) | |||
| { | |||
| pData->valueTmp = value; | |||
| const float rest = std::fmod(value, pData->step); | |||
| value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); | |||
| } | |||
| setValue(value, true); | |||
| pData->lastX = ev.pos.getX(); | |||
| pData->lastY = ev.pos.getY(); | |||
| if (SubWidget::onMouse(ev)) | |||
| return true; | |||
| return KnobEventHandler::mouseEvent(ev); | |||
| } | |||
| return true; | |||
| template <class ImageType> | |||
| bool ImageBaseKnob<ImageType>::onMotion(const MotionEvent& ev) | |||
| { | |||
| if (SubWidget::onMotion(ev)) | |||
| return true; | |||
| return KnobEventHandler::motionEvent(ev); | |||
| } | |||
| template <class ImageType> | |||
| bool ImageBaseKnob<ImageType>::onScroll(const ScrollEvent& ev) | |||
| { | |||
| if (! 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; | |||
| float value = (pData->usingLog ? pData->invlogscale(pData->valueTmp) : pData->valueTmp) | |||
| + ((pData->maximum - pData->minimum) / d * 10.f * dir); | |||
| if (pData->usingLog) | |||
| value = pData->logscale(value); | |||
| if (value < pData->minimum) | |||
| { | |||
| pData->valueTmp = value = pData->minimum; | |||
| } | |||
| else if (value > pData->maximum) | |||
| { | |||
| pData->valueTmp = value = pData->maximum; | |||
| } | |||
| else if (d_isNotZero(pData->step)) | |||
| { | |||
| pData->valueTmp = value; | |||
| const float rest = std::fmod(value, pData->step); | |||
| value = value - rest + (rest > pData->step/2.0f ? pData->step : 0.0f); | |||
| } | |||
| setValue(value, true); | |||
| return true; | |||
| if (SubWidget::onScroll(ev)) | |||
| return true; | |||
| return KnobEventHandler::scrollEvent(ev); | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -287,7 +287,10 @@ void NanoVG::beginFrame(Widget* const widget) | |||
| return; | |||
| if (TopLevelWidget* const tlw = widget->getTopLevelWidget()) | |||
| nvgBeginFrame(fContext, static_cast<int>(tlw->getWidth()), static_cast<int>(tlw->getHeight()), 1.0f); | |||
| nvgBeginFrame(fContext, | |||
| static_cast<int>(tlw->getWidth()), | |||
| static_cast<int>(tlw->getHeight()), | |||
| tlw->getScaleFactor()); | |||
| } | |||
| void NanoVG::cancelFrame() | |||
| @@ -23,7 +23,6 @@ | |||
| #include "../Color.hpp" | |||
| #include "../ImageWidgets.hpp" | |||
| #include "Common.hpp" | |||
| #include "SubWidgetPrivateData.hpp" | |||
| #include "TopLevelWidgetPrivateData.hpp" | |||
| #include "WidgetPrivateData.hpp" | |||
| @@ -317,6 +316,8 @@ static void drawOpenGLImage(const OpenGLImage& image, const Point<int>& pos, con | |||
| setupCalled = true; | |||
| } | |||
| glColor4f(1.0f, 1.0f, 1.0f, 1.0f); | |||
| glEnable(GL_TEXTURE_2D); | |||
| glBindTexture(GL_TEXTURE_2D, textureId); | |||
| @@ -486,8 +487,7 @@ template <> | |||
| void ImageBaseKnob<OpenGLImage>::onDisplay() | |||
| { | |||
| const GraphicsContext& context(getGraphicsContext()); | |||
| const float normValue = ((pData->usingLog ? pData->invlogscale(pData->value) : pData->value) - pData->minimum) | |||
| / (pData->maximum - pData->minimum); | |||
| const float normValue = getNormalizedValue(); | |||
| glEnable(GL_TEXTURE_2D); | |||
| glBindTexture(GL_TEXTURE_2D, pData->glTextureId); | |||
| @@ -32,9 +32,9 @@ SubWidget::~SubWidget() | |||
| } | |||
| template<typename T> | |||
| bool SubWidget::contains(T x, T y) const noexcept | |||
| bool SubWidget::contains(const T x, const T y) const noexcept | |||
| { | |||
| return Rectangle<double>(0, 0, getWidth(), getHeight()).contains(x, y); | |||
| return Rectangle<double>(0, 0, getWidth()-pData->margin.getX(), getHeight()-pData->margin.getY()).contains(x, y); | |||
| } | |||
| template<typename T> | |||
| @@ -70,17 +70,17 @@ Rectangle<uint> SubWidget::getConstrainedAbsoluteArea() const noexcept | |||
| getSize()); | |||
| } | |||
| void SubWidget::setAbsoluteX(int x) noexcept | |||
| void SubWidget::setAbsoluteX(const int x) noexcept | |||
| { | |||
| setAbsolutePos(Point<int>(x, getAbsoluteY())); | |||
| } | |||
| void SubWidget::setAbsoluteY(int y) noexcept | |||
| void SubWidget::setAbsoluteY(const int y) noexcept | |||
| { | |||
| setAbsolutePos(Point<int>(getAbsoluteX(), y)); | |||
| } | |||
| void SubWidget::setAbsolutePos(int x, int y) noexcept | |||
| void SubWidget::setAbsolutePos(const int x, const int y) noexcept | |||
| { | |||
| setAbsolutePos(Point<int>(x, y)); | |||
| } | |||
| @@ -100,6 +100,21 @@ void SubWidget::setAbsolutePos(const Point<int>& pos) noexcept | |||
| repaint(); | |||
| } | |||
| Point<int> SubWidget::getMargin() const noexcept | |||
| { | |||
| return pData->margin; | |||
| } | |||
| void SubWidget::setMargin(const int x, const int y) noexcept | |||
| { | |||
| pData->margin = Point<int>(x, y); | |||
| } | |||
| void SubWidget::setMargin(const Point<int>& offset) noexcept | |||
| { | |||
| pData->margin = offset; | |||
| } | |||
| Widget* SubWidget::getParentWidget() const noexcept | |||
| { | |||
| return pData->parentWidget; | |||
| @@ -26,6 +26,7 @@ SubWidget::PrivateData::PrivateData(SubWidget* const s, Widget* const pw) | |||
| selfw((Widget*)s), | |||
| parentWidget(pw), | |||
| absolutePos(), | |||
| margin(), | |||
| needsFullViewportForDrawing(false), | |||
| needsViewportScaling(false), | |||
| skipDrawing(false), | |||
| @@ -28,6 +28,7 @@ struct SubWidget::PrivateData { | |||
| Widget* const selfw; | |||
| Widget* const parentWidget; | |||
| Point<int> absolutePos; | |||
| Point<int> margin; | |||
| bool needsFullViewportForDrawing; // needed for widgets drawing out of bounds | |||
| bool needsViewportScaling; // needed for NanoVG | |||
| bool skipDrawing; // for context reuse in NanoVG based guis | |||
| @@ -152,8 +152,8 @@ bool Widget::PrivateData::giveMouseEventForSubWidgets(MouseEvent& ev) | |||
| if (! widget->isVisible()) | |||
| continue; | |||
| ev.pos = Point<double>(x - widget->getAbsoluteX(), | |||
| y - widget->getAbsoluteY()); | |||
| ev.pos = Point<double>(x - widget->getAbsoluteX() + widget->getMargin().getX(), | |||
| y - widget->getAbsoluteY() + widget->getMargin().getY()); | |||
| if (widget->onMouse(ev)) | |||
| return true; | |||
| @@ -191,8 +191,8 @@ bool Widget::PrivateData::giveMotionEventForSubWidgets(MotionEvent& ev) | |||
| if (! widget->isVisible()) | |||
| continue; | |||
| ev.pos = Point<double>(x - widget->getAbsoluteX(), | |||
| y - widget->getAbsoluteY()); | |||
| ev.pos = Point<double>(x - widget->getAbsoluteX() + widget->getMargin().getX(), | |||
| y - widget->getAbsoluteY() + widget->getMargin().getY()); | |||
| if (widget->onMotion(ev)) | |||
| return true; | |||
| @@ -230,8 +230,8 @@ bool Widget::PrivateData::giveScrollEventForSubWidgets(ScrollEvent& ev) | |||
| if (! widget->isVisible()) | |||
| continue; | |||
| ev.pos = Point<double>(x - widget->getAbsoluteX(), | |||
| y - widget->getAbsoluteY()); | |||
| ev.pos = Point<double>(x - widget->getAbsoluteX() + widget->getMargin().getX(), | |||
| y - widget->getAbsoluteY() + widget->getMargin().getY()); | |||
| if (widget->onScroll(ev)) | |||
| return true; | |||
| @@ -20,6 +20,20 @@ | |||
| START_NAMESPACE_DGL | |||
| // ----------------------------------------------------------------------- | |||
| // ScopedGraphicsContext | |||
| Window::ScopedGraphicsContext::ScopedGraphicsContext(Window& win) | |||
| : window(win) | |||
| { | |||
| puglBackendEnter(window.pData->view); | |||
| } | |||
| Window::ScopedGraphicsContext::~ScopedGraphicsContext() | |||
| { | |||
| puglBackendLeave(window.pData->view); | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| // Window | |||
| @@ -265,11 +279,6 @@ void Window::runAsModal(bool blockWait) | |||
| pData->runAsModal(blockWait); | |||
| } | |||
| void Window::leaveContext() | |||
| { | |||
| pData->leaveContext(); | |||
| } | |||
| void Window::setGeometryConstraints(const uint minimumWidth, | |||
| const uint minimumHeight, | |||
| const bool keepAspectRatio, | |||
| @@ -79,6 +79,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s) | |||
| appData(a.pData), | |||
| self(s), | |||
| view(puglNewView(appData->world)), | |||
| transientParentView(nullptr), | |||
| topLevelWidgets(), | |||
| isClosed(true), | |||
| isVisible(false), | |||
| @@ -102,6 +103,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c | |||
| appData(a.pData), | |||
| self(s), | |||
| view(puglNewView(appData->world)), | |||
| transientParentView(ppData->view), | |||
| topLevelWidgets(), | |||
| isClosed(true), | |||
| isVisible(false), | |||
| @@ -117,9 +119,10 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c | |||
| #endif | |||
| modal(ppData) | |||
| { | |||
| initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false); | |||
| puglBackendLeave(transientParentView); | |||
| puglSetTransientFor(view, puglGetNativeWindow(transientParentView)); | |||
| puglSetTransientFor(view, puglGetNativeWindow(ppData->view)); | |||
| initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false); | |||
| } | |||
| Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
| @@ -129,6 +132,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
| appData(a.pData), | |||
| self(s), | |||
| view(puglNewView(appData->world)), | |||
| transientParentView(nullptr), | |||
| topLevelWidgets(), | |||
| isClosed(parentWindowHandle == 0), | |||
| isVisible(parentWindowHandle != 0), | |||
| @@ -158,6 +162,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
| appData(a.pData), | |||
| self(s), | |||
| view(puglNewView(appData->world)), | |||
| transientParentView(nullptr), | |||
| topLevelWidgets(), | |||
| isClosed(parentWindowHandle == 0), | |||
| isVisible(parentWindowHandle != 0), | |||
| @@ -237,14 +242,15 @@ void Window::PrivateData::initPost() | |||
| // create view now, as a few methods we allow devs to use require it | |||
| puglRealize(view); | |||
| // FIXME this is bad, the enter/leave should be well scoped. try to find a better place for it.. | |||
| puglBackendEnter(view); | |||
| if (isEmbed) | |||
| { | |||
| appData->oneWindowShown(); | |||
| puglShow(view); | |||
| } | |||
| // give context back to transient parent window | |||
| if (transientParentView != nullptr) | |||
| puglBackendEnter(transientParentView); | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| @@ -630,14 +636,6 @@ void Window::PrivateData::runAsModal(const bool blockWait) | |||
| } | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| // TESTING | |||
| void Window::PrivateData::leaveContext() | |||
| { | |||
| puglBackendLeave(view); | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| // pugl events | |||
| @@ -44,6 +44,9 @@ struct Window::PrivateData : IdleCallback { | |||
| /** Pugl view instance. */ | |||
| PuglView* const view; | |||
| /** Pugl view instance of the transient parent window. */ | |||
| PuglView* const transientParentView; | |||
| /** Reserved space for graphics context. */ | |||
| mutable uint8_t graphicsContext[sizeof(void*)]; | |||
| @@ -161,9 +164,6 @@ struct Window::PrivateData : IdleCallback { | |||
| void stopModal(); | |||
| void runAsModal(bool blockWait); | |||
| // TESTING | |||
| void leaveContext(); | |||
| // pugl events | |||
| void onPuglConfigure(double width, double height); | |||
| void onPuglExpose(); | |||
| @@ -118,8 +118,7 @@ puglWinGetWindowExFlags(const PuglView* const view) | |||
| } | |||
| PuglWorldInternals* | |||
| puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type), | |||
| PuglWorldFlags PUGL_UNUSED(flags)) | |||
| puglInitWorldInternals(PuglWorldType type, PuglWorldFlags PUGL_UNUSED(flags)) | |||
| { | |||
| PuglWorldInternals* impl = | |||
| (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals)); | |||
| @@ -127,15 +126,17 @@ puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type), | |||
| return NULL; | |||
| } | |||
| HMODULE user32 = LoadLibrary("user32.dll"); | |||
| if (user32) { | |||
| PFN_SetProcessDPIAware SetProcessDPIAware = | |||
| (PFN_SetProcessDPIAware)GetProcAddress(user32, "SetProcessDPIAware"); | |||
| if (SetProcessDPIAware) { | |||
| SetProcessDPIAware(); | |||
| } | |||
| if (type == PUGL_PROGRAM) { | |||
| HMODULE user32 = LoadLibrary("user32.dll"); | |||
| if (user32) { | |||
| PFN_SetProcessDPIAware SetProcessDPIAware = | |||
| (PFN_SetProcessDPIAware)GetProcAddress(user32, "SetProcessDPIAware"); | |||
| if (SetProcessDPIAware) { | |||
| SetProcessDPIAware(); | |||
| } | |||
| FreeLibrary(user32); | |||
| FreeLibrary(user32); | |||
| } | |||
| } | |||
| LARGE_INTEGER frequency; | |||
| @@ -177,10 +177,18 @@ void puglClearMinSize(PuglView* const view) | |||
| view->minHeight = 0; | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // missing in pugl, directly returns transient parent | |||
| PuglNativeView puglGetTransientParent(const PuglView* const view) | |||
| { | |||
| return view->transientParent; | |||
| } | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // missing in pugl, directly returns title char* pointer | |||
| const char* puglGetWindowTitle(const PuglView* view) | |||
| const char* puglGetWindowTitle(const PuglView* const view) | |||
| { | |||
| return view->title; | |||
| } | |||
| @@ -54,6 +54,10 @@ puglBackendLeave(PuglView* view); | |||
| PUGL_API void | |||
| puglClearMinSize(PuglView* view); | |||
| // missing in pugl, directly returns transient parent | |||
| PUGL_API PuglNativeView | |||
| puglGetTransientParent(const PuglView* view); | |||
| // missing in pugl, directly returns title char* pointer | |||
| PUGL_API const char* | |||
| puglGetWindowTitle(const PuglView* view); | |||
| @@ -51,20 +51,20 @@ static const uint32_t kAudioPortIsSidechain = 0x2; | |||
| static const uint32_t kCVPortHasBipolarRange = 0x10; | |||
| /** | |||
| CV port has negative unipolar range (0 to +1, or 0 to +10 if scaled). | |||
| 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 (-1 to 0, or -10 to 0 if scaled). | |||
| 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 range flag is required if this flag is set. | |||
| 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. | |||
| */ | |||
| @@ -48,6 +48,10 @@ typedef DGL_NAMESPACE::NanoTopLevelWidget UIWidget; | |||
| typedef DGL_NAMESPACE::TopLevelWidget UIWidget; | |||
| #endif | |||
| START_NAMESPACE_DGL | |||
| class PluginWindow; | |||
| END_NAMESPACE_DGL | |||
| START_NAMESPACE_DISTRHO | |||
| /* ------------------------------------------------------------------------------------------------------------ | |||
| @@ -311,7 +315,7 @@ protected: | |||
| private: | |||
| struct PrivateData; | |||
| PrivateData* const uiData; | |||
| friend class PluginWindow; | |||
| friend class DGL_NAMESPACE::PluginWindow; | |||
| friend class UIExporter; | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UI) | |||
| @@ -20,6 +20,9 @@ | |||
| #include "../DistrhoUtils.hpp" | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| # ifndef NOMINMAX | |||
| # define NOMINMAX | |||
| # endif | |||
| # include <winsock2.h> | |||
| # include <windows.h> | |||
| typedef HMODULE lib_t; | |||
| @@ -20,6 +20,9 @@ | |||
| #include "../DistrhoUtils.hpp" | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| # ifndef NOMINMAX | |||
| # define NOMINMAX | |||
| # endif | |||
| # include <winsock2.h> | |||
| # include <windows.h> | |||
| #endif | |||
| @@ -20,6 +20,9 @@ | |||
| #include "../DistrhoUtils.hpp" | |||
| #ifdef DISTRHO_OS_WINDOWS | |||
| # ifndef NOMINMAX | |||
| # define NOMINMAX | |||
| # endif | |||
| # include <winsock2.h> | |||
| # include <windows.h> | |||
| #else | |||
| @@ -70,6 +70,7 @@ static void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& por | |||
| break; | |||
| } | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| // Plugin private data | |||
| @@ -157,9 +158,9 @@ struct Plugin::PrivateData { | |||
| #ifdef DISTRHO_PLUGIN_TARGET_LV2 | |||
| # if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) | |||
| parameterOffset += 1; | |||
| # if DISTRHO_PLUGIN_WANT_STATE | |||
| # endif | |||
| # if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) | |||
| parameterOffset += 1; | |||
| # endif | |||
| # endif | |||
| #endif | |||
| } | |||
| @@ -38,6 +38,10 @@ | |||
| # define JACK_METADATA_PRETTY_NAME "http://jackaudio.org/metadata/pretty-name" | |||
| #endif | |||
| #ifndef JACK_METADATA_PORT_GROUP | |||
| # define JACK_METADATA_PORT_GROUP "http://jackaudio.org/metadata/port-group" | |||
| #endif | |||
| #ifndef JACK_METADATA_SIGNAL_TYPE | |||
| # define JACK_METADATA_SIGNAL_TYPE "http://jackaudio.org/metadata/signal-type" | |||
| #endif | |||
| @@ -351,6 +355,12 @@ protected: | |||
| fTimePosition.bbt.bar = pos.bar; | |||
| fTimePosition.bbt.beat = pos.beat; | |||
| fTimePosition.bbt.tick = pos.tick; | |||
| #ifdef JACK_TICK_DOUBLE | |||
| if (pos.valid & JackTickDouble) | |||
| fTimePosition.bbt.tick = pos.tick_double; | |||
| else | |||
| #endif | |||
| fTimePosition.bbt.tick = pos.tick; | |||
| fTimePosition.bbt.barStartTick = pos.bar_start_tick; | |||
| fTimePosition.bbt.beatsPerBar = pos.beats_per_bar; | |||
| @@ -588,6 +598,12 @@ private: | |||
| jackbridge_set_property(fClient, uuid, JACK_METADATA_ORDER, strBuf, "http://www.w3.org/2001/XMLSchema#integer"); | |||
| } | |||
| if (port.groupId != kPortGroupNone) | |||
| { | |||
| const PortGroupWithId& portGroup(fPlugin.getPortGroupById(port.groupId)); | |||
| jackbridge_set_property(fClient, uuid, JACK_METADATA_PORT_GROUP, portGroup.name, "text/plain"); | |||
| } | |||
| if (port.hints & kAudioPortIsCV) | |||
| { | |||
| jackbridge_set_property(fClient, uuid, JACK_METADATA_SIGNAL_TYPE, "CV", "text/plain"); | |||
| @@ -236,7 +236,7 @@ public: | |||
| fTimePosition.bbt.barStartTick = 0; | |||
| fTimePosition.bbt.beatsPerBar = 4; | |||
| fTimePosition.bbt.beatType = 4; | |||
| fTimePosition.bbt.ticksPerBeat = 960.0; | |||
| fTimePosition.bbt.ticksPerBeat = 1920.0; | |||
| fTimePosition.bbt.beatsPerMinute = 120.0; | |||
| #endif | |||
| fPlugin.activate(); | |||
| @@ -359,7 +359,7 @@ public: | |||
| if (obj->body.otype != fURIDs.timePosition) | |||
| continue; | |||
| LV2_Atom* bar = nullptr; | |||
| LV2_Atom* bar = nullptr; | |||
| LV2_Atom* barBeat = nullptr; | |||
| LV2_Atom* beatUnit = nullptr; | |||
| LV2_Atom* beatsPerBar = nullptr; | |||
| @@ -430,9 +430,21 @@ void lv2_generate_ttl(const char* const basename) | |||
| if (port.hints & kAudioPortIsSidechain) | |||
| pluginString += " lv2:portProperty lv2:isSideChain;\n"; | |||
| if (port.groupId != kPortGroupNone) | |||
| switch (port.groupId) | |||
| { | |||
| case kPortGroupNone: | |||
| break; | |||
| case kPortGroupMono: | |||
| pluginString += " pg:group pg:MonoGroup ;\n"; | |||
| break; | |||
| case kPortGroupStereo: | |||
| pluginString += " pg:group pg:StereoGroup ;\n"; | |||
| break; | |||
| default: | |||
| pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_" | |||
| + plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n"; | |||
| break; | |||
| } | |||
| // set ranges | |||
| if (port.hints & kCVPortHasBipolarRange) | |||
| @@ -508,9 +520,21 @@ void lv2_generate_ttl(const char* const basename) | |||
| if (port.hints & kAudioPortIsSidechain) | |||
| pluginString += " lv2:portProperty lv2:isSideChain;\n"; | |||
| if (port.groupId != kPortGroupNone) | |||
| switch (port.groupId) | |||
| { | |||
| case kPortGroupNone: | |||
| break; | |||
| case kPortGroupMono: | |||
| pluginString += " pg:group pg:MonoGroup ;\n"; | |||
| break; | |||
| case kPortGroupStereo: | |||
| pluginString += " pg:group pg:StereoGroup ;\n"; | |||
| break; | |||
| default: | |||
| pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_" | |||
| + plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n"; | |||
| break; | |||
| } | |||
| // set ranges | |||
| if (port.hints & kCVPortHasBipolarRange) | |||
| @@ -731,7 +755,7 @@ void lv2_generate_ttl(const char* const basename) | |||
| // unit | |||
| const String& unit(plugin.getParameterUnit(i)); | |||
| if (unit.isNotEmpty() && ! unit.contains(" ")) | |||
| if (unit.isNotEmpty() && ! unit.contains(' ')) | |||
| { | |||
| String lunit(unit); | |||
| lunit.toLower(); | |||
| @@ -770,7 +794,10 @@ void lv2_generate_ttl(const char* const basename) | |||
| pluginString += " a unit:Unit ;\n"; | |||
| pluginString += " rdfs:label \"" + unit + "\" ;\n"; | |||
| pluginString += " unit:symbol \"" + unit + "\" ;\n"; | |||
| pluginString += " unit:render \"%f " + unit + "\" ;\n"; | |||
| if (plugin.getParameterHints(i) & kParameterIsInteger) | |||
| pluginString += " unit:render \"%d " + unit + "\" ;\n"; | |||
| else | |||
| pluginString += " unit:render \"%f " + unit + "\" ;\n"; | |||
| pluginString += " ] ;\n"; | |||
| } | |||
| } | |||
| @@ -779,7 +806,12 @@ void lv2_generate_ttl(const char* const basename) | |||
| const String& comment(plugin.getParameterDescription(i)); | |||
| if (comment.isNotEmpty()) | |||
| pluginString += " rdfs:comment \"\"\"" + comment + "\"\"\" ;\n"; | |||
| { | |||
| if (comment.contains('"') || comment.contains('\n')) | |||
| pluginString += " rdfs:comment \"\"\"" + comment + "\"\"\" ;\n"; | |||
| else | |||
| pluginString += " rdfs:comment \"" + comment + "\" ;\n"; | |||
| } | |||
| // hints | |||
| const uint32_t hints(plugin.getParameterHints(i)); | |||
| @@ -805,9 +837,22 @@ void lv2_generate_ttl(const char* const basename) | |||
| // group | |||
| const uint32_t groupId = plugin.getParameterGroupId(i); | |||
| if (groupId != kPortGroupNone) | |||
| switch (groupId) | |||
| { | |||
| case kPortGroupNone: | |||
| break; | |||
| case kPortGroupMono: | |||
| pluginString += " pg:group pg:MonoGroup ;\n"; | |||
| break; | |||
| case kPortGroupStereo: | |||
| pluginString += " pg:group pg:StereoGroup ;\n"; | |||
| break; | |||
| default: | |||
| pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_" | |||
| + plugin.getPortGroupSymbolForId(groupId) + "> ;\n"; | |||
| break; | |||
| } | |||
| } // ! designated | |||
| if (i+1 == count) | |||
| @@ -823,8 +868,8 @@ void lv2_generate_ttl(const char* const basename) | |||
| if (comment.isNotEmpty()) | |||
| { | |||
| if (comment.contains('"')) | |||
| pluginString += " rdfs:comment \"" + comment + "\" ;\n\n"; | |||
| if (comment.contains('"') || comment.contains('\n')) | |||
| pluginString += " rdfs:comment \"\"\"" + comment + "\"\"\" ;\n\n"; | |||
| else | |||
| pluginString += " rdfs:comment \"" + comment + "\" ;\n\n"; | |||
| } | |||
| @@ -903,6 +948,13 @@ void lv2_generate_ttl(const char* const basename) | |||
| DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.groupId != kPortGroupNone); | |||
| DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.symbol.isNotEmpty()); | |||
| switch (portGroup.groupId) | |||
| { | |||
| case kPortGroupMono: | |||
| case kPortGroupStereo: | |||
| continue; | |||
| } | |||
| pluginString += "\n<" DISTRHO_PLUGIN_URI "#portGroup_" + portGroup.symbol + ">\n"; | |||
| isInput = isOutput = false; | |||
| @@ -1063,7 +1063,7 @@ public: | |||
| fTimePosition.bbt.valid = ((vstTimeInfo->flags & kVstTempoValid) != 0 || (vstTimeInfo->flags & kVstTimeSigValid) != 0); | |||
| // ticksPerBeat is not possible with VST | |||
| fTimePosition.bbt.ticksPerBeat = 960.0; | |||
| fTimePosition.bbt.ticksPerBeat = 1920.0; | |||
| if (vstTimeInfo->flags & kVstTempoValid) | |||
| fTimePosition.bbt.beatsPerMinute = vstTimeInfo->tempo; | |||
| @@ -1099,7 +1099,9 @@ public: | |||
| fTimePosition.bbt.beatType = 4.0f; | |||
| } | |||
| fTimePosition.bbt.barStartTick = fTimePosition.bbt.ticksPerBeat*fTimePosition.bbt.beatsPerBar*(fTimePosition.bbt.bar-1); | |||
| fTimePosition.bbt.barStartTick = fTimePosition.bbt.ticksPerBeat* | |||
| fTimePosition.bbt.beatsPerBar* | |||
| (fTimePosition.bbt.bar-1); | |||
| fPlugin.setTimePosition(fTimePosition); | |||
| } | |||
| @@ -1448,7 +1450,9 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t | |||
| memset(properties, 0, sizeof(VstParameterProperties)); | |||
| // full name | |||
| DISTRHO_NAMESPACE::strncpy(properties->label, plugin.getParameterName(index), VestigeMaxLabelLen); | |||
| DISTRHO_NAMESPACE::strncpy(properties->label, | |||
| plugin.getParameterName(index), | |||
| sizeof(properties->label)); | |||
| // short name | |||
| const String& shortName(plugin.getParameterShortName(index)); | |||
| @@ -1456,7 +1460,7 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t | |||
| if (shortName.isNotEmpty()) | |||
| DISTRHO_NAMESPACE::strncpy(properties->shortLabel, | |||
| plugin.getParameterShortName(index), | |||
| VestigeMaxShortLabelLen); | |||
| sizeof(properties->shortLabel)); | |||
| // parameter hints | |||
| const uint32_t hints = plugin.getParameterHints(index); | |||
| @@ -1482,6 +1486,34 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t | |||
| properties->flags |= kVstParameterCanRamp; | |||
| } | |||
| // parameter group (category in vst) | |||
| const uint32_t groupId = plugin.getParameterGroupId(index); | |||
| if (groupId != kPortGroupNone) | |||
| { | |||
| // we can't use groupId directly, so use the index array where this group is stored in | |||
| for (uint32_t i=0, count=plugin.getPortGroupCount(); i < count; ++i) | |||
| { | |||
| const PortGroupWithId& portGroup(plugin.getPortGroupByIndex(i)); | |||
| if (portGroup.groupId == groupId) | |||
| { | |||
| properties->category = i + 1; | |||
| DISTRHO_NAMESPACE::strncpy(properties->categoryLabel, | |||
| portGroup.name.buffer(), | |||
| sizeof(properties->categoryLabel)); | |||
| break; | |||
| } | |||
| } | |||
| if (properties->category != 0) | |||
| { | |||
| for (uint32_t i=0, count=plugin.getParameterCount(); i < count; ++i) | |||
| if (plugin.getParameterGroupId(i) == groupId) | |||
| ++properties->numParametersInCategory; | |||
| } | |||
| } | |||
| return 1; | |||
| } | |||
| } | |||
| @@ -96,7 +96,7 @@ private: | |||
| static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value) | |||
| { | |||
| return ((PluginVst*)ptr)->requestParameterValueChange(index, value); | |||
| return ((PluginVst3*)ptr)->requestParameterValueChange(index, value); | |||
| } | |||
| #endif | |||
| @@ -109,7 +109,7 @@ private: | |||
| static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent) | |||
| { | |||
| return ((PluginVst*)ptr)->writeMidi(midiEvent); | |||
| return ((PluginVst3*)ptr)->writeMidi(midiEvent); | |||
| } | |||
| #endif | |||
| @@ -39,7 +39,7 @@ UI::PrivateData* UI::PrivateData::s_nextPrivateData = nullptr; | |||
| PluginWindow& UI::PrivateData::createNextWindow(UI* const ui, const uint width, const uint height) | |||
| { | |||
| UI::PrivateData* const pData = s_nextPrivateData; | |||
| pData->window = new PluginWindow(ui, pData, width, height); | |||
| pData->window = new PluginWindow(ui, pData->app, pData->winId, width, height, pData->scaleFactor); | |||
| return pData->window.getObject(); | |||
| } | |||
| @@ -260,7 +260,9 @@ public: | |||
| void quit() | |||
| { | |||
| uiData->window->close(); | |||
| uiData->app.quit(); | |||
| if (uiData->app.isStandalone()) | |||
| uiData->app.quit(); | |||
| } | |||
| #endif | |||
| @@ -316,20 +318,23 @@ public: | |||
| return ! uiData->app.isQuiting(); | |||
| } | |||
| bool handlePluginKeyboard(const bool /*press*/, const uint /*key*/, const uint16_t /*mods*/) | |||
| bool handlePluginKeyboard(const bool press, const uint key, const uint16_t mods) | |||
| { | |||
| #if 0 /* TODO */ | |||
| return glWindow.handlePluginKeyboard(press, key); | |||
| #endif | |||
| return false; | |||
| // TODO also trigger Character input event | |||
| Widget::KeyboardEvent ev; | |||
| ev.press = press; | |||
| ev.key = key; | |||
| ev.mod = mods; | |||
| return ui->onKeyboard(ev); | |||
| } | |||
| bool handlePluginSpecial(const bool /*press*/, const DGL_NAMESPACE::Key /*key*/, const uint16_t /*mods*/) | |||
| bool handlePluginSpecial(const bool press, const DGL_NAMESPACE::Key key, const uint16_t mods) | |||
| { | |||
| #if 0 /* TODO */ | |||
| return glWindow.handlePluginSpecial(press, key); | |||
| #endif | |||
| return false; | |||
| Widget::SpecialEvent ev; | |||
| ev.press = press; | |||
| ev.key = key; | |||
| ev.mod = mods; | |||
| return ui->onSpecial(ev); | |||
| } | |||
| #endif | |||
| @@ -89,11 +89,14 @@ public: | |||
| bgColor, | |||
| fgColor), | |||
| fUridMap(uridMap), | |||
| fUiPortMap(getLv2Feature<LV2UI_Port_Map>(features, LV2_UI__portMap)), | |||
| fUiRequestValue(getLv2Feature<LV2UI_Request_Value>(features, LV2_UI__requestValue)), | |||
| fUiTouch(getLv2Feature<LV2UI_Touch>(features, LV2_UI__touch)), | |||
| fController(controller), | |||
| fWriteFunction(writeFunc), | |||
| fURIDs(uridMap), | |||
| fBypassParameterIndex(fUiPortMap != nullptr ? fUiPortMap->port_index(fUiPortMap->handle, "lv2_enabled") | |||
| : LV2UI_INVALID_PORT_INDEX), | |||
| fWinIdWasNull(winId == 0) | |||
| { | |||
| if (widget != nullptr) | |||
| @@ -159,7 +162,11 @@ public: | |||
| DISTRHO_SAFE_ASSERT_RETURN(bufferSize == sizeof(float),) | |||
| const float value = *(const float*)buffer; | |||
| float value = *(const float*)buffer; | |||
| if (rindex == fBypassParameterIndex) | |||
| value = 1.0f - value; | |||
| fUI.parameterChanged(rindex-parameterOffset, value); | |||
| } | |||
| #if DISTRHO_PLUGIN_WANT_STATE | |||
| @@ -253,10 +260,13 @@ protected: | |||
| fUiTouch->touch(fUiTouch->handle, rindex, started); | |||
| } | |||
| void setParameterValue(const uint32_t rindex, const float value) | |||
| void setParameterValue(const uint32_t rindex, float value) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); | |||
| if (rindex == fBypassParameterIndex) | |||
| value = 1.0f - value; | |||
| fWriteFunction(fController, rindex, sizeof(float), 0, &value); | |||
| } | |||
| @@ -345,6 +355,7 @@ private: | |||
| // LV2 features | |||
| const LV2_URID_Map* const fUridMap; | |||
| const LV2UI_Port_Map* const fUiPortMap; | |||
| const LV2UI_Request_Value* const fUiRequestValue; | |||
| const LV2UI_Touch* const fUiTouch; | |||
| @@ -383,8 +394,11 @@ private: | |||
| } | |||
| } fURIDs; | |||
| // index of bypass parameter, if present | |||
| const uint32_t fBypassParameterIndex; | |||
| // using ui:showInterface if true | |||
| bool fWinIdWasNull; | |||
| const bool fWinIdWasNull; | |||
| // ------------------------------------------------------------------- | |||
| // Callbacks | |||
| @@ -20,8 +20,9 @@ | |||
| #include "../DistrhoUI.hpp" | |||
| #include "../../dgl/Application.hpp" | |||
| #ifndef DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| # include "../../dgl/Window.hpp" | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| # include "../../dgl/src/WindowPrivateData.hpp" | |||
| # include "../../dgl/src/pugl.hpp" | |||
| #endif | |||
| #if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_DSSI) | |||
| @@ -35,20 +36,9 @@ | |||
| # define DISTRHO_UI_USER_RESIZABLE 0 | |||
| #endif | |||
| START_NAMESPACE_DISTRHO | |||
| using DGL_NAMESPACE::Application; | |||
| using DGL_NAMESPACE::Window; | |||
| // ----------------------------------------------------------------------- | |||
| // UI callbacks | |||
| typedef void (*editParamFunc) (void* ptr, uint32_t rindex, bool started); | |||
| typedef void (*setParamFunc) (void* ptr, uint32_t rindex, float value); | |||
| typedef void (*setStateFunc) (void* ptr, const char* key, const char* value); | |||
| typedef void (*sendNoteFunc) (void* ptr, uint8_t channel, uint8_t note, uint8_t velo); | |||
| typedef void (*setSizeFunc) (void* ptr, uint width, uint height); | |||
| typedef bool (*fileRequestFunc) (void* ptr, const char* key); | |||
| START_NAMESPACE_DGL | |||
| // ----------------------------------------------------------------------- | |||
| // Plugin Application, will set class name based on plugin details | |||
| @@ -73,7 +63,118 @@ public: | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) | |||
| }; | |||
| class PluginWindow; | |||
| // ----------------------------------------------------------------------- | |||
| // Plugin Window, will pass some Window events to UI | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| // TODO external ui stuff | |||
| class PluginWindow | |||
| { | |||
| UI* const ui; | |||
| public: | |||
| explicit PluginWindow(UI* const uiPtr, | |||
| PluginApplication& app, | |||
| const uintptr_t parentWindowHandle, | |||
| const uint width, | |||
| const uint height, | |||
| const double scaleFactor) | |||
| : Window(app, parentWindowHandle, width, height, scaleFactor, DISTRHO_UI_USER_RESIZABLE), | |||
| ui(uiPtr) {} | |||
| uint getWidth() const noexcept | |||
| { | |||
| return ui->getWidth(); | |||
| } | |||
| uint getHeight() const noexcept | |||
| { | |||
| return ui->getHeight(); | |||
| } | |||
| bool isVisible() const noexcept | |||
| { | |||
| return ui->isRunning(); | |||
| } | |||
| uintptr_t getNativeWindowHandle() const noexcept | |||
| { | |||
| return 0; | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) | |||
| }; | |||
| #else | |||
| class PluginWindow : public Window | |||
| { | |||
| UI* const ui; | |||
| public: | |||
| explicit PluginWindow(UI* const uiPtr, | |||
| PluginApplication& app, | |||
| const uintptr_t parentWindowHandle, | |||
| const uint width, | |||
| const uint height, | |||
| const double scaleFactor) | |||
| : Window(app, parentWindowHandle, width, height, scaleFactor, DISTRHO_UI_USER_RESIZABLE), | |||
| ui(uiPtr) | |||
| { | |||
| puglBackendEnter(pData->view); | |||
| } | |||
| void leaveContext() | |||
| { | |||
| puglBackendLeave(pData->view); | |||
| } | |||
| protected: | |||
| void onFocus(const bool focus, const DGL_NAMESPACE::CrossingMode mode) override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| ui->uiFocus(focus, mode); | |||
| } | |||
| void onReshape(const uint width, const uint height) override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| ui->uiReshape(width, height); | |||
| } | |||
| void onScaleFactorChanged(const double scaleFactor) override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| ui->uiScaleFactorChanged(scaleFactor); | |||
| } | |||
| # ifndef DGL_FILE_BROWSER_DISABLED | |||
| void onFileSelected(const char* filename) override; | |||
| # endif | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) | |||
| }; | |||
| #endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| END_NAMESPACE_DGL | |||
| // ----------------------------------------------------------------------- | |||
| START_NAMESPACE_DISTRHO | |||
| using DGL_NAMESPACE::PluginApplication; | |||
| using DGL_NAMESPACE::PluginWindow; | |||
| // ----------------------------------------------------------------------- | |||
| // UI callbacks | |||
| typedef void (*editParamFunc) (void* ptr, uint32_t rindex, bool started); | |||
| typedef void (*setParamFunc) (void* ptr, uint32_t rindex, float value); | |||
| typedef void (*setStateFunc) (void* ptr, const char* key, const char* value); | |||
| typedef void (*sendNoteFunc) (void* ptr, uint8_t channel, uint8_t note, uint8_t velo); | |||
| typedef void (*setSizeFunc) (void* ptr, uint width, uint height); | |||
| typedef bool (*fileRequestFunc) (void* ptr, const char* key); | |||
| // ----------------------------------------------------------------------- | |||
| // UI private data | |||
| @@ -143,16 +244,18 @@ struct UI::PrivateData { | |||
| #ifdef DISTRHO_PLUGIN_TARGET_LV2 | |||
| # if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) | |||
| parameterOffset += 1; | |||
| # if DISTRHO_PLUGIN_WANT_STATE | |||
| # endif | |||
| # if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) | |||
| parameterOffset += 1; | |||
| # endif | |||
| # endif | |||
| #endif | |||
| } | |||
| ~PrivateData() noexcept | |||
| { | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) | |||
| std::free(uiStateFileKeyRequest); | |||
| #endif | |||
| } | |||
| void editParamCallback(const uint32_t rindex, const bool started) | |||
| @@ -192,100 +295,6 @@ struct UI::PrivateData { | |||
| static PluginWindow& createNextWindow(UI* ui, uint width, uint height); | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| // Plugin Window, will pass some Window events to UI | |||
| #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| // TODO external ui stuff | |||
| class PluginWindow | |||
| { | |||
| UI* const ui; | |||
| public: | |||
| explicit PluginWindow(UI* const uiPtr, UI::PrivateData* const pData, const uint width, const uint height) | |||
| : Window(pData->app, pData->winId, pData->scaleFactor, width, height, DISTRHO_UI_USER_RESIZABLE), | |||
| ui(uiPtr) {} | |||
| uint getWidth() const noexcept | |||
| { | |||
| return ui->getWidth(); | |||
| } | |||
| uint getHeight() const noexcept | |||
| { | |||
| return ui->getHeight(); | |||
| } | |||
| bool isVisible() const noexcept | |||
| { | |||
| return ui->isRunning(); | |||
| } | |||
| uintptr_t getNativeWindowHandle() const noexcept | |||
| { | |||
| return 0; | |||
| } | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) | |||
| }; | |||
| #else | |||
| class PluginWindow : public Window | |||
| { | |||
| UI* const ui; | |||
| public: | |||
| explicit PluginWindow(UI* const uiPtr, UI::PrivateData* const pData, const uint width, const uint height) | |||
| : Window(pData->app, pData->winId, width, height, pData->scaleFactor, DISTRHO_UI_USER_RESIZABLE), | |||
| ui(uiPtr) {} | |||
| protected: | |||
| void onFocus(const bool focus, const DGL_NAMESPACE::CrossingMode mode) override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| ui->uiFocus(focus, mode); | |||
| } | |||
| void onReshape(const uint width, const uint height) override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| ui->uiReshape(width, height); | |||
| } | |||
| void onScaleFactorChanged(const double scaleFactor) override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| ui->uiScaleFactorChanged(scaleFactor); | |||
| } | |||
| # ifndef DGL_FILE_BROWSER_DISABLED | |||
| void onFileSelected(const char* const filename) override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| # if DISTRHO_PLUGIN_WANT_STATEFILES | |||
| if (char* const key = ui->uiData->uiStateFileKeyRequest) | |||
| { | |||
| ui->uiData->uiStateFileKeyRequest = nullptr; | |||
| // notify DSP | |||
| ui->setState(key, filename); | |||
| // notify UI | |||
| ui->stateChanged(key, filename); | |||
| std::free(key); | |||
| return; | |||
| } | |||
| # endif | |||
| ui->uiFileBrowserSelected(filename); | |||
| } | |||
| # endif | |||
| DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) | |||
| }; | |||
| #endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
| // ----------------------------------------------------------------------- | |||
| // UI private data fileRequestCallback, which requires PluginWindow definitions | |||
| @@ -311,8 +320,37 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key) | |||
| return false; | |||
| } | |||
| END_NAMESPACE_DISTRHO | |||
| // ----------------------------------------------------------------------- | |||
| // PluginWindow onFileSelected that require UI::PrivateData definitions | |||
| END_NAMESPACE_DISTRHO | |||
| #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) | |||
| START_NAMESPACE_DGL | |||
| inline void PluginWindow::onFileSelected(const char* const filename) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); | |||
| # if DISTRHO_PLUGIN_WANT_STATEFILES | |||
| if (char* const key = ui->uiData->uiStateFileKeyRequest) | |||
| { | |||
| ui->uiData->uiStateFileKeyRequest = nullptr; | |||
| // notify DSP | |||
| ui->setState(key, filename); | |||
| // notify UI | |||
| ui->stateChanged(key, filename); | |||
| std::free(key); | |||
| return; | |||
| } | |||
| # endif | |||
| ui->uiFileBrowserSelected(filename); | |||
| } | |||
| END_NAMESPACE_DGL | |||
| #endif | |||
| // ----------------------------------------------------------------------- | |||
| #endif // DISTRHO_UI_PRIVATE_DATA_HPP_INCLUDED | |||
| @@ -99,6 +99,8 @@ | |||
| #define JACK_UUID_STRING_SIZE (JACK_UUID_SIZE+1) /* includes trailing null */ | |||
| #define JACK_UUID_EMPTY_INITIALIZER 0 | |||
| #define JACK_TICK_DOUBLE | |||
| extern "C" { | |||
| enum JackOptions { | |||
| @@ -153,7 +155,8 @@ enum JackPositionBits { | |||
| JackPositionTimecode = 0x020, | |||
| JackBBTFrameOffset = 0x040, | |||
| JackAudioVideoRatio = 0x080, | |||
| JackVideoFrameOffset = 0x100 | |||
| JackVideoFrameOffset = 0x100, | |||
| JackTickDouble = 0x200 | |||
| }; | |||
| enum JackSessionEventType { | |||
| @@ -223,7 +226,8 @@ struct _jack_position { | |||
| jack_nframes_t bbt_offset; | |||
| float audio_frames_per_video_frame; | |||
| jack_nframes_t video_offset; | |||
| int32_t padding[7]; | |||
| double tick_double; | |||
| int32_t padding[5]; | |||
| jack_unique_t unique_2; | |||
| } POST_PACKED_STRUCTURE; | |||
| @@ -0,0 +1,216 @@ | |||
| /* | |||
| * RtAudioBridge for DPF | |||
| * Copyright (C) 2021 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 | |||
| * permission notice appear in all copies. | |||
| * | |||
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #ifndef RTAUDIOBRIDGE_HPP_INCLUDED | |||
| #define RTAUDIOBRIDGE_HPP_INCLUDED | |||
| #include "JackBridge.hpp" | |||
| #if defined(DISTRHO_OS_MAC) | |||
| # define __MACOSX_CORE__ | |||
| # define RTAUDIO_API_TYPE MACOSX_CORE | |||
| #elif defined(DISTRHO_OS_WINDOWS) && !defined(_MSC_VER) | |||
| # define __WINDOWS_WASAPI__ | |||
| # define RTAUDIO_API_TYPE WINDOWS_WASAPI | |||
| #elif defined(HAVE_PULSEAUDIO) | |||
| # define __LINUX_PULSE__ | |||
| # define RTAUDIO_API_TYPE LINUX_PULSE | |||
| #elif defined(HAVE_ALSA) | |||
| # define __LINUX_ALSA__ | |||
| # define RTAUDIO_API_TYPE LINUX_ALSA | |||
| #endif | |||
| #ifdef RTAUDIO_API_TYPE | |||
| # define Point CorePoint /* fix conflict between DGL and macOS Point name */ | |||
| # include "rtaudio/RtAudio.h" | |||
| # undef Point | |||
| # include "../../extra/ScopedPointer.hpp" | |||
| struct RtAudioBridge { | |||
| // pointer to RtAudio instance | |||
| ScopedPointer<RtAudio> handle; | |||
| // RtAudio information | |||
| uint bufferSize = 0; | |||
| uint sampleRate = 0; | |||
| // Port caching information | |||
| uint numAudioIns = 0; | |||
| uint numAudioOuts = 0; | |||
| uint numMidiIns = 0; | |||
| uint numMidiOuts = 0; | |||
| // JACK callbacks | |||
| JackProcessCallback jackProcessCallback = nullptr; | |||
| void* jackProcessArg = nullptr; | |||
| // Runtime buffers | |||
| float* audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS]; | |||
| // TODO midi buffer, likely using ringbuffer class | |||
| bool open() | |||
| { | |||
| ScopedPointer<RtAudio> rtAudio; | |||
| try { | |||
| rtAudio = new RtAudio(RtAudio::RTAUDIO_API_TYPE); | |||
| } DISTRHO_SAFE_EXCEPTION_RETURN("new RtAudio()", false); | |||
| uint rtAudioBufferFrames = 512; | |||
| RtAudio::StreamParameters inParams; | |||
| inParams.deviceId = rtAudio->getDefaultInputDevice(); | |||
| inParams.nChannels = DISTRHO_PLUGIN_NUM_INPUTS; | |||
| RtAudio::StreamParameters outParams; | |||
| outParams.deviceId = rtAudio->getDefaultOutputDevice(); | |||
| outParams.nChannels = DISTRHO_PLUGIN_NUM_OUTPUTS; | |||
| RtAudio::StreamOptions opts; | |||
| opts.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_ALSA_USE_DEFAULT; | |||
| try { | |||
| rtAudio->openStream(&outParams, &inParams, RTAUDIO_FLOAT32, 48000, &rtAudioBufferFrames, RtAudioCallback, this, &opts, nullptr); | |||
| } DISTRHO_SAFE_EXCEPTION_RETURN("rtAudio->openStream()", false); | |||
| handle = rtAudio; | |||
| bufferSize = rtAudioBufferFrames; | |||
| sampleRate = handle->getStreamSampleRate(); | |||
| return true; | |||
| } | |||
| bool close() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false); | |||
| if (handle->isStreamRunning()) | |||
| { | |||
| try { | |||
| handle->abortStream(); | |||
| } DISTRHO_SAFE_EXCEPTION("handle->abortStream()"); | |||
| } | |||
| handle = nullptr; | |||
| return true; | |||
| } | |||
| bool activate() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false); | |||
| try { | |||
| handle->startStream(); | |||
| } DISTRHO_SAFE_EXCEPTION("handle->startStream()"); | |||
| return true; | |||
| } | |||
| bool deactivate() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false); | |||
| try { | |||
| handle->stopStream(); | |||
| } DISTRHO_SAFE_EXCEPTION("handle->stopStream()"); | |||
| return true; | |||
| } | |||
| jack_port_t* registerPort(const char* const type, const ulong flags) | |||
| { | |||
| bool isAudio, isInput; | |||
| if (std::strcmp(type, JACK_DEFAULT_AUDIO_TYPE) == 0) | |||
| isAudio = true; | |||
| else if (std::strcmp(type, JACK_DEFAULT_MIDI_TYPE) == 0) | |||
| isAudio = false; | |||
| else | |||
| return nullptr; | |||
| if (flags & JackPortIsInput) | |||
| isInput = true; | |||
| else if (flags & JackPortIsOutput) | |||
| isInput = false; | |||
| else | |||
| return nullptr; | |||
| const uintptr_t ret = (isAudio ? 0x1000 : 0x2000) | (isInput ? 0x4000 : 0x8000); | |||
| return (jack_port_t*)(ret + (isAudio ? (isInput ? numAudioIns++ : numAudioOuts++) | |||
| : (isInput ? numMidiIns++ : numMidiOuts++))); | |||
| } | |||
| void* getPortBuffer(jack_port_t* const port) | |||
| { | |||
| const uintptr_t portMask = (uintptr_t)port; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| if (portMask & 0x1000) | |||
| return audioBuffers[(portMask & 0x4000 ? 0 : DISTRHO_PLUGIN_NUM_INPUTS) + (portMask & 0x0fff)]; | |||
| #endif | |||
| return nullptr; | |||
| } | |||
| static int RtAudioCallback(void* const outputBuffer, | |||
| void* const inputBuffer, | |||
| const uint numFrames, | |||
| const double /* streamTime */, | |||
| const RtAudioStreamStatus /* status */, | |||
| void* const userData) | |||
| { | |||
| RtAudioBridge* const self = (RtAudioBridge*)userData; | |||
| if (self->jackProcessCallback == nullptr) | |||
| { | |||
| if (outputBuffer != nullptr) | |||
| std::memset((float*)outputBuffer, 0, sizeof(float)*numFrames*DISTRHO_PLUGIN_NUM_OUTPUTS); | |||
| return 0; | |||
| } | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| float** const selfAudioBuffers = self->audioBuffers; | |||
| uint i = 0; | |||
| # if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| if (float* const insPtr = (float*)inputBuffer) | |||
| { | |||
| for (uint j=0; j<DISTRHO_PLUGIN_NUM_INPUTS; ++j, ++i) | |||
| selfAudioBuffers[i] = insPtr + (j * numFrames); | |||
| } | |||
| # endif | |||
| # if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| if (float* const outsPtr = (float*)outputBuffer) | |||
| { | |||
| for (uint j=0; j<DISTRHO_PLUGIN_NUM_OUTPUTS; ++j, ++i) | |||
| selfAudioBuffers[i] = outsPtr + (j * numFrames); | |||
| } | |||
| # endif | |||
| #endif | |||
| self->jackProcessCallback(numFrames, self->jackProcessArg); | |||
| return 0; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS == 0 | |||
| // unused | |||
| (void)inputBuffer; | |||
| #endif | |||
| } | |||
| }; | |||
| #endif // RTAUDIO_API_TYPE | |||
| #endif // RTAUDIOBRIDGE_HPP_INCLUDED | |||
| @@ -0,0 +1,27 @@ | |||
| RtAudio: a set of realtime audio i/o C++ classes | |||
| Copyright (c) 2001-2019 Gary P. Scavone | |||
| Permission is hereby granted, free of charge, to any person | |||
| obtaining a copy of this software and associated documentation files | |||
| (the "Software"), to deal in the Software without restriction, | |||
| including without limitation the rights to use, copy, modify, merge, | |||
| publish, distribute, sublicense, and/or sell copies of the Software, | |||
| and to permit persons to whom the Software is furnished to do so, | |||
| subject to the following conditions: | |||
| The above copyright notice and this permission notice shall be | |||
| included in all copies or substantial portions of the Software. | |||
| Any person wishing to distribute modifications to the Software is | |||
| asked to send the modifications to the original developer so that | |||
| they can be incorporated into the canonical version. This is, | |||
| however, not a binding provision of this license. | |||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |||
| IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR | |||
| ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |||
| CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||
| @@ -404,33 +404,35 @@ public: | |||
| DemoWindow(Application& app) | |||
| : StandaloneWindow(app), | |||
| wColor(this), | |||
| wImages(this), | |||
| wRects(this), | |||
| wShapes(this), | |||
| #ifdef DGL_OPENGL | |||
| wText(this), | |||
| #endif | |||
| wLeft(this, this), | |||
| resizer(this), | |||
| curWidget(nullptr) | |||
| { | |||
| wColor.hide(); | |||
| wImages.hide(); | |||
| wRects.hide(); | |||
| wShapes.hide(); | |||
| #ifdef DGL_OPENGL | |||
| wText.hide(); | |||
| #endif | |||
| const ScopedGraphicsContext sgc(*this); | |||
| wColor = new ExampleColorSubWidget(this); | |||
| wColor->hide(); | |||
| wColor->setAbsoluteX(kSidebarWidth); | |||
| wImages = new ExampleImagesSubWidget(this); | |||
| wImages->hide(); | |||
| wImages->setAbsoluteX(kSidebarWidth); | |||
| wRects = new ExampleRectanglesSubWidget(this); | |||
| wRects->hide(); | |||
| wRects->setAbsoluteX(kSidebarWidth); | |||
| wShapes = new ExampleShapesSubWidget(this); | |||
| wShapes->hide(); | |||
| wShapes->setAbsoluteX(kSidebarWidth); | |||
| wColor.setAbsoluteX(kSidebarWidth); | |||
| wImages.setAbsoluteX(kSidebarWidth); | |||
| wRects.setAbsoluteX(kSidebarWidth); | |||
| wShapes.setAbsoluteX(kSidebarWidth); | |||
| #ifdef DGL_OPENGL | |||
| wText.setAbsoluteX(kSidebarWidth); | |||
| wText = new ExampleTextSubWidget(this), | |||
| wText->hide(); | |||
| wText->setAbsoluteX(kSidebarWidth); | |||
| #endif | |||
| wLeft.setAbsolutePos(2, 2); | |||
| wLeft = new LeftSideWidget(this, this), | |||
| wLeft->setAbsolutePos(2, 2); | |||
| resizer = new ResizeHandle(this); | |||
| curPageChanged(0); | |||
| } | |||
| @@ -444,20 +446,20 @@ protected: | |||
| switch (curPage) | |||
| { | |||
| case 0: | |||
| curWidget = &wColor; | |||
| curWidget = wColor; | |||
| break; | |||
| case 1: | |||
| curWidget = &wImages; | |||
| curWidget = wImages; | |||
| break; | |||
| case 2: | |||
| curWidget = &wRects; | |||
| curWidget = wRects; | |||
| break; | |||
| case 3: | |||
| curWidget = &wShapes; | |||
| curWidget = wShapes; | |||
| break; | |||
| #ifdef DGL_OPENGL | |||
| case 4: | |||
| curWidget = &wText; | |||
| curWidget = wText; | |||
| break; | |||
| #endif | |||
| default: | |||
| @@ -481,26 +483,26 @@ protected: | |||
| return; | |||
| Size<uint> size(width-kSidebarWidth, height); | |||
| wColor.setSize(size); | |||
| wImages.setSize(size); | |||
| wRects.setSize(size); | |||
| wShapes.setSize(size); | |||
| wColor->setSize(size); | |||
| wImages->setSize(size); | |||
| wRects->setSize(size); | |||
| wShapes->setSize(size); | |||
| #ifdef DGL_OPENGL | |||
| wText.setSize(size); | |||
| wText->setSize(size); | |||
| #endif | |||
| wLeft.setSize(kSidebarWidth-4, height-4); | |||
| wLeft->setSize(kSidebarWidth-4, height-4); | |||
| } | |||
| private: | |||
| ExampleColorSubWidget wColor; | |||
| ExampleImagesSubWidget wImages; | |||
| ExampleRectanglesSubWidget wRects; | |||
| ExampleShapesSubWidget wShapes; | |||
| ScopedPointer<ExampleColorSubWidget> wColor; | |||
| ScopedPointer<ExampleImagesSubWidget> wImages; | |||
| ScopedPointer<ExampleRectanglesSubWidget> wRects; | |||
| ScopedPointer<ExampleShapesSubWidget> wShapes; | |||
| #ifdef DGL_OPENGL | |||
| ExampleTextSubWidget wText; | |||
| ScopedPointer<ExampleTextSubWidget> wText; | |||
| #endif | |||
| LeftSideWidget wLeft; | |||
| ResizeHandle resizer; | |||
| ScopedPointer<LeftSideWidget> wLeft; | |||
| ScopedPointer<ResizeHandle> resizer; | |||
| Widget* curWidget; | |||
| }; | |||
| @@ -1,13 +0,0 @@ | |||
| ###################################### | |||
| ## Generic README for DISTRHO Plugins | |||
| This folder contains audio plugins downloaded from http://distrho.sourceforge.net/plugins | |||
| The included *.lv2 folder is an LV2 bundle and the *.dll file is a VST. | |||
| Check your host documentation to know where to place these files. | |||
| Note that most hosts support VST but not LV2. | |||
| If your host is not listing these plugins, make sure the binaries you downloaded match your host architecture. | |||
| 32bit plugins go into 32bit hosts, same for 64bit. | |||
| Some hosts are able to load plugins with different architecture, but that's the exception rather than the rule. | |||
| @@ -1,7 +1,7 @@ | |||
| #!/bin/bash | |||
| # function not available on some systems | |||
| if ! which realpath 2>/dev/null; then | |||
| if ! which realpath &>/dev/null; then | |||
| function realpath() { | |||
| [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" | |||
| } | |||
| @@ -1,54 +0,0 @@ | |||
| #!/bin/bash | |||
| set -e | |||
| # -------------------------------------------------------------------------------------------------------------------------------- | |||
| # extract debs and pack them | |||
| function compressFolderAsZip() { | |||
| rm -f "$1.zip" | |||
| zip -X -r "$1" "$1" | |||
| rm -r "$1" | |||
| } | |||
| # -------------------------------------------------------------------------------------------------------------------------------- | |||
| if [ "$1" == "" ]; then | |||
| echo Missing argument | |||
| exit | |||
| fi | |||
| # -------------------------------------------------------------------------------------------------------------------------------- | |||
| cd bin | |||
| mkdir -p tmp | |||
| rm -rf tmp/* | |||
| NAME="$1" | |||
| _mingw32-build make -C .. clean | |||
| _mingw32-build make -C .. | |||
| for i in `ls *-vst.dll`; do mv $i `echo $i | awk 'sub("-vst","")'`; done | |||
| rm -rf *ladspa* *dssi* *vst* | |||
| mkdir -p "$NAME-win32bit" | |||
| mv *.dll *.lv2/ "$NAME-win32bit" | |||
| cp "../dpf/utils/README-DPF-Windows.txt" "$NAME-win32bit/README.txt" | |||
| compressFolderAsZip "$NAME-win32bit" | |||
| rm -rf tmp/* | |||
| _mingw64-build make -C .. clean | |||
| _mingw64-build make -C .. | |||
| for i in `ls *-vst.dll`; do mv $i `echo $i | awk 'sub("-vst","")'`; done | |||
| rm -rf *ladspa* *dssi* *vst* | |||
| mkdir -p "$NAME-win64bit" | |||
| mv *.dll *.lv2/ "$NAME-win64bit" | |||
| cp "../dpf/utils/README-DPF-Windows.txt" "$NAME-win64bit/README.txt" | |||
| compressFolderAsZip "$NAME-win64bit" | |||
| rm -rf tmp/* | |||
| _mingw64-build make -C .. clean | |||
| cd .. | |||
| # -------------------------------------------------------------------------------------------------------------------------------- | |||
| @@ -0,0 +1,51 @@ | |||
| #!/bin/bash | |||
| set -e | |||
| if [ -d bin ]; then | |||
| cd bin | |||
| else | |||
| echo "Please run this script from the root folder" | |||
| exit | |||
| fi | |||
| NAME="$(basename $(git rev-parse --show-toplevel))" | |||
| SNAME="$(echo ${NAME} | tr -d ' ' | tr '/' '-')" | |||
| rm -rf lv2 | |||
| rm -rf vst2 | |||
| mkdir lv2 vst2 | |||
| mv *.lv2 lv2/ | |||
| mv *.vst vst2/ | |||
| pkgbuild \ | |||
| --identifier "studio.kx.distrho.plugins.${SNAME}.lv2bundles" \ | |||
| --install-location "/Library/Audio/Plug-Ins/LV2/" \ | |||
| --root "${PWD}/lv2/" \ | |||
| ../dpf-${SNAME}-lv2bundles.pkg | |||
| pkgbuild \ | |||
| --identifier "studio.kx.distrho.plugins.${SNAME}.vst2bundles" \ | |||
| --install-location "/Library/Audio/Plug-Ins/VST/" \ | |||
| --root "${PWD}/vst2/" \ | |||
| ../dpf-${SNAME}-vst2bundles.pkg | |||
| cd .. | |||
| DPF_UTILS_DIR=$(dirname ${0}) | |||
| sed -e "s|@name@|${NAME}|" ${DPF_UTILS_DIR}/plugin.pkg/welcome.txt.in > build/welcome.txt | |||
| sed -e "s|@builddir@|${PWD}/build|" \ | |||
| -e "s|@lv2bundleref@|dpf-${SNAME}-lv2bundles.pkg|" \ | |||
| -e "s|@vst2bundleref@|dpf-${SNAME}-vst2bundles.pkg|" \ | |||
| -e "s|@name@|${NAME}|g" \ | |||
| -e "s|@sname@|${SNAME}|g" \ | |||
| ${DPF_UTILS_DIR}/plugin.pkg/package.xml.in > build/package.xml | |||
| productbuild \ | |||
| --distribution build/package.xml \ | |||
| --identifier "studio.kx.distrho.${SNAME}" \ | |||
| --package-path "${PWD}" \ | |||
| --version 0 \ | |||
| ${SNAME}-macOS.pkg | |||
| @@ -0,0 +1,18 @@ | |||
| <?xml version="1.0" encoding="utf-8" standalone="no"?> | |||
| <installer-gui-script minSpecVersion="1"> | |||
| <title>@name@</title> | |||
| <domains enable_anywhere="false" enable_currentUserHome="false" enable_localSystem="true" /> | |||
| <options customize="always" hostArchitectures="x86_64" require-scripts="false" rootVolumeOnly="true" /> | |||
| <pkg-ref id="studio.kx.distrho.@sname@" /> | |||
| <welcome file="@builddir@/welcome.txt" mime-type="text/plain" /> | |||
| <choice id="studio.kx.distrho.@sname@-lv2" title="LV2" description="Install LV2 plugins" visible="true"> | |||
| <pkg-ref id="studio.kx.distrho.@sname@-lv2bundles" version="0">@lv2bundleref@</pkg-ref> | |||
| </choice> | |||
| <choice id="studio.kx.distrho.@sname@-vst2" title="VST2" description="Install VST2 plugins" visible="true"> | |||
| <pkg-ref id="studio.kx.distrho.@sname@-vst2bundles" version="0">@vst2bundleref@</pkg-ref> | |||
| </choice> | |||
| <choices-outline> | |||
| <line choice="studio.kx.distrho.@sname@-lv2"/> | |||
| <line choice="studio.kx.distrho.@sname@-vst2"/> | |||
| </choices-outline> | |||
| </installer-gui-script> | |||
| @@ -0,0 +1,3 @@ | |||
| @name@ | |||
| This is an audio plugin installer based on DPF. | |||
| @@ -9,7 +9,7 @@ | |||
| <key>CFBundleIconFile</key> | |||
| <string></string> | |||
| <key>CFBundleIdentifier</key> | |||
| <string>net.sf.distrho.X-PROJECTNAME-X</string> | |||
| <string>studio.kx.distrho.X-PROJECTNAME-X</string> | |||
| <key>CFBundleInfoDictionaryVersion</key> | |||
| <string>6.0</string> | |||
| <key>CFBundlePackageType</key> | |||
| @@ -1,5 +1,4 @@ | |||
| DISTRHO Plugin Framework (DPF) | |||
| Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com> | |||
| Copyright (C) 2012-2021 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,7 +29,7 @@ using DGL::Color; | |||
| DistrhoUIMVerb::DistrhoUIMVerb() | |||
| : UI(Art::backgroundWidth, Art::backgroundHeight), | |||
| fImgBackground(Art::backgroundData, Art::backgroundWidth, Art::backgroundHeight, GL_BGR) | |||
| fImgBackground(Art::backgroundData, Art::backgroundWidth, Art::backgroundHeight, kImageFormatBGR) | |||
| { | |||
| // text | |||
| fNanoText.loadSharedResources(); | |||
| @@ -122,6 +122,9 @@ DistrhoUIMVerb::DistrhoUIMVerb() | |||
| // set initial values | |||
| programLoaded(0); | |||
| // TODO auto-scale but non-resizable | |||
| // setGeometryConstraints(Art::backgroundWidth, Art::backgroundHeight, true, true); | |||
| } | |||
| DistrhoUIMVerb::~DistrhoUIMVerb() | |||
| @@ -225,7 +228,9 @@ void DistrhoUIMVerb::imageKnobValueChanged(ImageKnob* knob, float value) | |||
| void DistrhoUIMVerb::onDisplay() | |||
| { | |||
| fImgBackground.draw(); | |||
| const GraphicsContext& context(getGraphicsContext()); | |||
| fImgBackground.draw(context); | |||
| // text display | |||
| fNanoText.beginFrame(this); | |||