DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

commit d7382324edbe5e24db5a5ad327e292e999d8c16c
parent 99781c4541a1bb608044e63725f9efc1fd869feb
Author: Filipe Coelho <falktx@falktx.com>
Date:   Sat, 29 May 2021 00:26:24 +0100

Merge pull request #281 from DISTRHO/rework-core-resize

Reworked resize handling
Diffstat:
MMakefile | 18+++++++++---------
Mdgl/TopLevelWidget.hpp | 21+++++++++++++++++++++
Mdgl/Window.hpp | 13++++++++++++-
Mdgl/src/SubWidget.cpp | 3+--
Mdgl/src/TopLevelWidget.cpp | 20++++++++++++++++++++
Mdgl/src/TopLevelWidgetPrivateData.cpp | 5++---
Mdgl/src/Widget.cpp | 2++
Mdgl/src/Window.cpp | 40++++++++++++++++++++++++++++++++++------
Mdgl/src/WindowPrivateData.cpp | 118++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mdgl/src/WindowPrivateData.hpp | 9++++++---
Mdgl/src/pugl.cpp | 40+++++++++++++++++++++++++++++++++++++---
Mdgl/src/pugl.hpp | 6+++++-
Mdistrho/DistrhoUI.hpp | 60+++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mdistrho/extra/ScopedPointer.hpp | 3+++
Mdistrho/src/DistrhoPluginJack.cpp | 21+++++----------------
Mdistrho/src/DistrhoPluginVST.cpp | 15++++-----------
Mdistrho/src/DistrhoUI.cpp | 106+++++++++++++++++++++++++++++--------------------------------------------------
Mdistrho/src/DistrhoUIDSSI.cpp | 57++++++++++++++++++++++++---------------------------------
Mdistrho/src/DistrhoUIInternal.hpp | 367++++++++++++++++++++++++-------------------------------------------------------
Mdistrho/src/DistrhoUILV2.cpp | 44+++++++++++++++-----------------------------
Mdistrho/src/DistrhoUIPrivateData.hpp | 170++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mexamples/Info/InfoExampleUI.cpp | 26+++++++++++++++++++++-----
Aexamples/Info/ResizeHandle.hpp | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/Demo.cpp | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
24 files changed, 976 insertions(+), 515 deletions(-)

diff --git a/Makefile b/Makefile @@ -32,15 +32,15 @@ examples: dgl ifeq ($(HAVE_CAIRO),true) $(MAKE) all -C examples/CairoUI endif -ifneq ($(MACOS_OR_WINDOWS),true) - # ExternalUI is WIP - $(MAKE) all -C examples/ExternalUI - install -d bin/d_extui-dssi - install -d bin/d_extui.lv2 - install -m 755 examples/ExternalUI/ExternalLauncher.sh bin/d_extui.sh - install -m 755 examples/ExternalUI/ExternalLauncher.sh bin/d_extui-dssi/d_extui.sh - install -m 755 examples/ExternalUI/ExternalLauncher.sh bin/d_extui.lv2/d_extui.sh -endif +# ifneq ($(MACOS_OR_WINDOWS),true) +# # ExternalUI is WIP +# $(MAKE) all -C examples/ExternalUI +# install -d bin/d_extui-dssi +# install -d bin/d_extui.lv2 +# install -m 755 examples/ExternalUI/ExternalLauncher.sh bin/d_extui.sh +# install -m 755 examples/ExternalUI/ExternalLauncher.sh bin/d_extui-dssi/d_extui.sh +# install -m 755 examples/ExternalUI/ExternalLauncher.sh bin/d_extui.lv2/d_extui.sh +# endif ifeq ($(CAN_GENERATE_TTL),true) gen: examples utils/lv2_ttl_generator diff --git a/dgl/TopLevelWidget.hpp b/dgl/TopLevelWidget.hpp @@ -66,6 +66,27 @@ public: */ Window& getWindow() const noexcept; + /** + Set width of this widget's window. + @note This will not change the widget's size right away, but be pending on OS resizing the window + */ + void setWidth(uint width); + + /** + Set height of this widget's window. + */ + void setHeight(uint height); + + /** + Set size of this widget's window, using @a width and @a height values. + */ + void setSize(uint width, uint height); + + /** + Set size of this widget's window. + */ + void setSize(const Size<uint>& size); + // TODO group stuff after here, convenience functions present in Window class bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs = 0); bool removeIdleCallback(IdleCallback* callback); diff --git a/dgl/Window.hpp b/dgl/Window.hpp @@ -185,7 +185,7 @@ public: void close(); /** - Check if this window is resizable. + Check if this window is resizable (by the user or window manager). @see setResizable */ bool isResizable() const noexcept; @@ -367,6 +367,10 @@ protected: A function called when the window is attempted to be closed. Returning true closes the window, which is the default behaviour. Override this method and return false to prevent the window from being closed by the user. + + This method is not used for embed windows, and not even made available in DISTRHO_NAMESPACE::UI. + For embed windows, closing is handled by the host/parent process and we have no control over it. + As such, a close action on embed windows will always succeed and cannot be cancelled. */ virtual bool onClose(); @@ -383,6 +387,13 @@ protected: */ virtual void onReshape(uint width, uint height); + /** + A function called when scale factor requested for this window changes. + The default implementation does nothing. + WARNING function needs a proper name + */ + virtual void onScaleFactorChanged(double scaleFactor); + #ifndef DGL_FILE_BROWSER_DISABLED /** A function called when a path is selected by the user, as triggered by openFileBrowser(). diff --git a/dgl/src/SubWidget.cpp b/dgl/src/SubWidget.cpp @@ -96,8 +96,7 @@ void SubWidget::setAbsolutePos(const Point<int>& pos) noexcept pData->absolutePos = pos; onPositionChanged(ev); - // repaint the bounds of parent - pData->parentWidget->repaint(); + repaint(); } Widget* SubWidget::getParentWidget() const noexcept diff --git a/dgl/src/TopLevelWidget.cpp b/dgl/src/TopLevelWidget.cpp @@ -40,6 +40,26 @@ Window& TopLevelWidget::getWindow() const noexcept return pData->window; } +void TopLevelWidget::setWidth(const uint width) +{ + pData->window.setWidth(width); +} + +void TopLevelWidget::setHeight(const uint height) +{ + pData->window.setHeight(height); +} + +void TopLevelWidget::setSize(const uint width, const uint height) +{ + pData->window.setSize(width, height); +} + +void TopLevelWidget::setSize(const Size<uint>& size) +{ + pData->window.setSize(size); +} + bool TopLevelWidget::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs) { return pData->window.addIdleCallback(callback, timerFrequencyInMs); diff --git a/dgl/src/TopLevelWidgetPrivateData.cpp b/dgl/src/TopLevelWidgetPrivateData.cpp @@ -28,13 +28,12 @@ TopLevelWidget::PrivateData::PrivateData(TopLevelWidget* const s, Window& w) selfw(s), window(w) { - window.pData->topLevelWidget = self; + window.pData->topLevelWidgets.push_back(self); } TopLevelWidget::PrivateData::~PrivateData() { - // FIXME? - window.pData->topLevelWidget = nullptr; + window.pData->topLevelWidgets.remove(self); } bool TopLevelWidget::PrivateData::keyboardEvent(const KeyboardEvent& ev) diff --git a/dgl/src/Widget.cpp b/dgl/src/Widget.cpp @@ -46,6 +46,8 @@ void Widget::setVisible(bool visible) pData->visible = visible; repaint(); + + // FIXME check case of hiding a previously visible widget, does it trigger a repaint? } void Widget::show() diff --git a/dgl/src/Window.cpp b/dgl/src/Window.cpp @@ -136,13 +136,37 @@ void Window::setHeight(const uint height) setSize(getWidth(), height); } -void Window::setSize(const uint width, const uint height) +void Window::setSize(uint width, uint height) { DISTRHO_SAFE_ASSERT_UINT2_RETURN(width > 1 && height > 1, width, height,); - // FIXME add default and min props for this - if (pData->minWidth == 0 && pData->minHeight == 0) - puglSetDefaultSize(pData->view, static_cast<int>(width), static_cast<int>(height)); + if (pData->isEmbed) + { + // handle geometry constraints here + if (width < pData->minWidth) + width = pData->minWidth; + + if (height < pData->minHeight) + height = pData->minHeight; + + if (pData->keepAspectRatio) + { + const double ratio = static_cast<double>(pData->minWidth) + / static_cast<double>(pData->minHeight); + const double reqRatio = static_cast<double>(width) + / static_cast<double>(height); + + if (d_isNotEqual(ratio, reqRatio)) + { + // fix width + if (reqRatio > ratio) + width = height * ratio; + // fix height + else + height = width / ratio; + } + } + } puglSetWindowSize(pData->view, width, height); } @@ -250,8 +274,7 @@ void Window::setGeometryConstraints(const uint minimumWidth, DISTRHO_SAFE_ASSERT_RETURN(minimumHeight > 0,); if (pData->isEmbed) { - // Did you forget to set DISTRHO_UI_USER_RESIZABLE ? - DISTRHO_SAFE_ASSERT_RETURN(isResizable(),); + // nothing to do here } else if (! isResizable()) { setResizable(true); } @@ -259,6 +282,7 @@ void Window::setGeometryConstraints(const uint minimumWidth, pData->minWidth = minimumWidth; pData->minHeight = minimumHeight; pData->autoScaling = automaticallyScale; + pData->keepAspectRatio = keepAspectRatio; const double scaleFactor = pData->scaleFactor; @@ -290,6 +314,10 @@ void Window::onReshape(uint, uint) puglFallbackOnResize(pData->view); } +void Window::onScaleFactorChanged(double) +{ +} + #ifndef DGL_FILE_BROWSER_DISABLED void Window::onFileSelected(const char*) { diff --git a/dgl/src/WindowPrivateData.cpp b/dgl/src/WindowPrivateData.cpp @@ -51,6 +51,12 @@ START_NAMESPACE_DGL #define DEFAULT_WIDTH 640 #define DEFAULT_HEIGHT 480 +#define FOR_EACH_TOP_LEVEL_WIDGET(it) \ + for (std::list<TopLevelWidget*>::iterator it = topLevelWidgets.begin(); it != topLevelWidgets.end(); ++it) + +#define FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) \ + for (std::list<TopLevelWidget*>::reverse_iterator rit = topLevelWidgets.rbegin(); rit != topLevelWidgets.rend(); ++rit) + // ----------------------------------------------------------------------- #ifdef DISTRHO_OS_WINDOWS @@ -73,7 +79,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s) appData(a.pData), self(s), view(puglNewView(appData->world)), - topLevelWidget(nullptr), + topLevelWidgets(), isClosed(true), isVisible(false), isEmbed(false), @@ -82,6 +88,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s) autoScaleFactor(1.0), minWidth(0), minHeight(0), + keepAspectRatio(false), #ifdef DISTRHO_OS_WINDOWS win32SelectedFile(nullptr), #endif @@ -95,7 +102,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c appData(a.pData), self(s), view(puglNewView(appData->world)), - topLevelWidget(nullptr), + topLevelWidgets(), isClosed(true), isVisible(false), isEmbed(false), @@ -104,6 +111,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c autoScaleFactor(1.0), minWidth(0), minHeight(0), + keepAspectRatio(false), #ifdef DISTRHO_OS_WINDOWS win32SelectedFile(nullptr), #endif @@ -121,7 +129,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, appData(a.pData), self(s), view(puglNewView(appData->world)), - topLevelWidget(nullptr), + topLevelWidgets(), isClosed(parentWindowHandle == 0), isVisible(parentWindowHandle != 0), isEmbed(parentWindowHandle != 0), @@ -130,16 +138,14 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, autoScaleFactor(1.0), minWidth(0), minHeight(0), + keepAspectRatio(false), #ifdef DISTRHO_OS_WINDOWS win32SelectedFile(nullptr), #endif modal() { if (isEmbed) - { - // puglSetDefaultSize(DEFAULT_WIDTH, DEFAULT_HEIGHT, height); puglSetParentWindow(view, parentWindowHandle); - } initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, resizable); } @@ -152,7 +158,7 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, appData(a.pData), self(s), view(puglNewView(appData->world)), - topLevelWidget(nullptr), + topLevelWidgets(), isClosed(parentWindowHandle == 0), isVisible(parentWindowHandle != 0), isEmbed(parentWindowHandle != 0), @@ -161,16 +167,14 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, autoScaleFactor(1.0), minWidth(0), minHeight(0), + keepAspectRatio(false), #ifdef DISTRHO_OS_WINDOWS win32SelectedFile(nullptr), #endif modal() { if (isEmbed) - { - puglSetDefaultSize(view, static_cast<int>(width), static_cast<int>(height)); puglSetParentWindow(view, parentWindowHandle); - } initPre(width, height, resizable); } @@ -216,6 +220,9 @@ void Window::PrivateData::initPre(const uint width, const uint height, const boo puglSetMatchingBackendForCurrentBuild(view); + puglClearMinSize(view); + puglSetWindowSize(view, width, height); + puglSetHandle(view, this); puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE); puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_FALSE); @@ -223,11 +230,6 @@ void Window::PrivateData::initPre(const uint width, const uint height, const boo puglSetViewHint(view, PUGL_STENCIL_BITS, 8); // PUGL_SAMPLES ?? puglSetEventFunc(view, puglEventCallback); - - PuglRect rect = puglGetFrame(view); - rect.width = width; - rect.height = height; - puglSetFrame(view, rect); } void Window::PrivateData::initPost() @@ -293,7 +295,6 @@ void Window::PrivateData::show() // FIXME PuglRect rect = puglGetFrame(view); - puglSetDefaultSize(view, static_cast<int>(rect.width), static_cast<int>(rect.height)); puglSetWindowSize(view, static_cast<uint>(rect.width), static_cast<uint>(rect.height)); #ifdef DISTRHO_OS_WINDOWS @@ -552,10 +553,6 @@ void Window::PrivateData::startModal() // make parent give focus to us modal.parent->modal.child = this; - // FIXME? - PuglRect rect = puglGetFrame(view); - puglSetDefaultSize(view, static_cast<int>(rect.width), static_cast<int>(rect.height)); - // make sure both parent and ourselves are visible modal.parent->show(); show(); @@ -642,8 +639,20 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh self->onReshape(uwidth, uheight); #ifndef DPF_TEST_WINDOW_CPP - if (topLevelWidget != nullptr) - topLevelWidget->setSize(uwidth, uheight); + FOR_EACH_TOP_LEVEL_WIDGET(it) + { + TopLevelWidget* const widget(*it); + + /* Some special care here, we call Widget::setSize instead of the TopLevelWidget one. + * This is because we want TopLevelWidget::setSize to handle both window and widget size, + * but we dont want to change window size here, because we are the window.. + * So we just call the Widget specific method manually. + * + * Alternatively, we could expose a resize function on the pData, like done with the display function. + * But there is nothing extra we need to do in there, so this works fine. + */ + ((Widget*)widget)->setSize(uwidth, uheight); + } #endif // always repaint after a resize @@ -652,13 +661,18 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh void Window::PrivateData::onPuglExpose() { - DGL_DBGp("PUGL: onPuglExpose : %p\n", topLevelWidget); + DGL_DBGp("PUGL: onPuglExpose\n"); puglOnDisplayPrepare(view); #ifndef DPF_TEST_WINDOW_CPP - if (topLevelWidget != nullptr) - topLevelWidget->pData->display(); + FOR_EACH_TOP_LEVEL_WIDGET(it) + { + TopLevelWidget* const widget(*it); + + if (widget->isVisible()) + widget->pData->display(); + } #endif } @@ -711,8 +725,13 @@ void Window::PrivateData::onPuglKey(const Widget::KeyboardEvent& ev) return modal.child->focus(); #ifndef DPF_TEST_WINDOW_CPP - if (topLevelWidget != nullptr) - topLevelWidget->pData->keyboardEvent(ev); + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->pData->keyboardEvent(ev)) + break; + } #endif } @@ -724,8 +743,13 @@ void Window::PrivateData::onPuglSpecial(const Widget::SpecialEvent& ev) return modal.child->focus(); #ifndef DPF_TEST_WINDOW_CPP - if (topLevelWidget != nullptr) - topLevelWidget->pData->specialEvent(ev); + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->pData->specialEvent(ev)) + break; + } #endif } @@ -737,8 +761,13 @@ void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev) return modal.child->focus(); #ifndef DPF_TEST_WINDOW_CPP - if (topLevelWidget != nullptr) - topLevelWidget->pData->characterInputEvent(ev); + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->pData->characterInputEvent(ev)) + break; + } #endif } @@ -750,8 +779,13 @@ void Window::PrivateData::onPuglMouse(const Widget::MouseEvent& ev) return modal.child->focus(); #ifndef DPF_TEST_WINDOW_CPP - if (topLevelWidget != nullptr) - topLevelWidget->pData->mouseEvent(ev); + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->pData->mouseEvent(ev)) + break; + } #endif } @@ -763,8 +797,13 @@ void Window::PrivateData::onPuglMotion(const Widget::MotionEvent& ev) return modal.child->focus(); #ifndef DPF_TEST_WINDOW_CPP - if (topLevelWidget != nullptr) - topLevelWidget->pData->motionEvent(ev); + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->pData->motionEvent(ev)) + break; + } #endif } @@ -776,8 +815,13 @@ void Window::PrivateData::onPuglScroll(const Widget::ScrollEvent& ev) return modal.child->focus(); #ifndef DPF_TEST_WINDOW_CPP - if (topLevelWidget != nullptr) - topLevelWidget->pData->scrollEvent(ev); + FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + { + TopLevelWidget* const widget(*rit); + + if (widget->isVisible() && widget->pData->scrollEvent(ev)) + break; + } #endif } diff --git a/dgl/src/WindowPrivateData.hpp b/dgl/src/WindowPrivateData.hpp @@ -23,6 +23,8 @@ #include "pugl.hpp" +#include <list> + START_NAMESPACE_DGL class TopLevelWidget; @@ -45,8 +47,8 @@ struct Window::PrivateData : IdleCallback { /** Reserved space for graphics context. */ mutable uint8_t graphicsContext[sizeof(void*)]; - /** The top-level widget associated with this Window. */ - TopLevelWidget* topLevelWidget; + /** The top-level widgets associated with this Window. */ + std::list<TopLevelWidget*> topLevelWidgets; /** Whether this Window is closed (not visible or counted in the Application it is tied to). Defaults to true unless embed (embed windows are never closed). */ @@ -65,8 +67,9 @@ struct Window::PrivateData : IdleCallback { bool autoScaling; double autoScaleFactor; - /** Pugl minWidth, minHeight access. */ + /** Pugl geometry constraints access. */ uint minWidth, minHeight; + bool keepAspectRatio; #ifdef DISTRHO_OS_WINDOWS /** Selected file for openFileBrowser on windows, stored for fake async operation. */ diff --git a/dgl/src/pugl.cpp b/dgl/src/pugl.cpp @@ -152,12 +152,21 @@ START_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- // expose backend enter -void puglBackendEnter(PuglView* view) +void puglBackendEnter(PuglView* const view) { view->backend->enter(view, NULL); } // -------------------------------------------------------------------------------------------------------------------- +// clear minimum size to 0 + +void puglClearMinSize(PuglView* const view) +{ + view->minWidth = 0; + view->minHeight = 0; +} + +// -------------------------------------------------------------------------------------------------------------------- // missing in pugl, directly returns title char* pointer const char* puglGetWindowTitle(const PuglView* view) @@ -237,10 +246,13 @@ PuglStatus puglSetGeometryConstraints(PuglView* const view, const uint width, co } // -------------------------------------------------------------------------------------------------------------------- -// set window size without changing frame x/y position +// set window size with default size and without changing frame x/y position PuglStatus puglSetWindowSize(PuglView* const view, const uint width, const uint height) { + view->defaultWidth = width; + view->defaultHeight = height; + #if defined(DISTRHO_OS_HAIKU) || defined(DISTRHO_OS_MAC) // keep upstream behaviour const PuglRect frame = { view->frame.x, view->frame.y, (double)width, (double)height }; @@ -271,8 +283,30 @@ PuglStatus puglSetWindowSize(PuglView* const view, const uint width, const uint // matches upstream pugl, except we use XResizeWindow instead of XMoveResizeWindow if (view->impl->win) { - if (! XResizeWindow(view->world->impl->display, view->impl->win, width, height)) + Display* const display = view->world->impl->display; + + if (! XResizeWindow(display, view->impl->win, width, height)) return PUGL_UNKNOWN_ERROR; + +#if 0 + // custom handling for embed non-resizable windows + if (view->parent != 0 && ! view->hints[PUGL_RESIZABLE]) + { + XSizeHints sizeHints = {}; + sizeHints.flags = PSize | PBaseSize | PMinSize | PMaxSize; + sizeHints.width = static_cast<int>(width); + sizeHints.height = static_cast<int>(height); + sizeHints.base_width = width; + sizeHints.base_height = height; + sizeHints.min_width = width; + sizeHints.min_height = height; + sizeHints.max_width = width; + sizeHints.max_height = height; + XSetNormalHints(display, view->impl->win, &sizeHints); + } +#endif + + updateSizeHints(view); } #endif diff --git a/dgl/src/pugl.hpp b/dgl/src/pugl.hpp @@ -46,6 +46,10 @@ PUGL_BEGIN_DECLS PUGL_API void puglBackendEnter(PuglView* view); +// clear minimum size to 0 +PUGL_API void +puglClearMinSize(PuglView* view); + // missing in pugl, directly returns title char* pointer PUGL_API const char* puglGetWindowTitle(const PuglView* view); @@ -62,7 +66,7 @@ puglSetMatchingBackendForCurrentBuild(PuglView* view); PUGL_API PuglStatus puglSetGeometryConstraints(PuglView* view, unsigned int width, unsigned int height, bool aspect); -// set window size without changing frame x/y position +// set window size with default size and without changing frame x/y position PUGL_API PuglStatus puglSetWindowSize(PuglView* view, unsigned int width, unsigned int height); diff --git a/distrho/DistrhoUI.hpp b/distrho/DistrhoUI.hpp @@ -82,6 +82,15 @@ public: * Host state */ /** + Check if this UI window is resizable (by the user or window manager). + There are situations where an UI supports resizing but the plugin host does not, so this could return false. + + You might want to add a resize handle for such cases, so the user is still allowed to resize the window. + (programatically resizing a window is always possible, but the same is not true for the window manager) + */ + bool isResizable() const noexcept; + + /** Get the color used for UI background (i.e. window color) in RGBA format. Returns 0 by default, in case of error or lack of host support. @@ -237,26 +246,55 @@ protected: * UI Callbacks (optional) */ /** - uiIdle. - @TODO Document this. + UI idle function, called to give idle time to the plugin UI directly from the host. + This is called right after OS event handling and Window idle events (within the same cycle). + There are no guarantees in terms of timing. + @see addIdleCallback(IdleCallback*, uint). */ virtual void uiIdle() {} -# ifndef DGL_FILE_BROWSER_DISABLED /** - File browser selected function. - @see Window::onFileSelected(const char*) + Windows focus function, called when the window gains or loses the keyboard focus. + This function is for plugin UIs to be able to override Window::onFocus(bool, CrossingMode). + + The default implementation does nothing. */ - virtual void uiFileBrowserSelected(const char* filename); -# endif + virtual void uiFocus(bool focus, CrossingMode mode); /** - OpenGL window reshape function, called when parent window is resized. - You can reimplement this function for a custom OpenGL state. - @see Window::onReshape(uint,uint) + Window reshape function, called when the window is resized. + This function is for plugin UIs to be able to override Window::onReshape(uint, uint). + + The plugin UI size will be set right after this function. + The default implementation sets up drawing context where necessary. + + You should almost never need to override this function. + The most common exception is custom OpenGL setup, but only really needed for custom OpenGL drawing code. */ virtual void uiReshape(uint width, uint height); + /** + Window scale factor function, called when the scale factor changes. + This function is for plugin UIs to be able to override Window::onScaleFactorChanged(double). + + The default implementation does nothing. + WARNING function needs a proper name + */ + virtual void uiScaleFactorChanged(double scaleFactor); + +# ifndef DGL_FILE_BROWSER_DISABLED + /** + Window file selected function, called when a path is selected by the user, as triggered by openFileBrowser(). + This function is for plugin UIs to be able to override Window::onFileSelected(const char*). + + This action happens after the user confirms the action, so the file browser dialog will be closed at this point. + The default implementation does nothing. + + If you need to use files as plugin state, please setup and use DISTRHO_PLUGIN_WANT_STATEFILES instead. + */ + virtual void uiFileBrowserSelected(const char* filename); +# endif + /* -------------------------------------------------------------------------------------------------------- * UI Resize Handling, internal */ @@ -273,8 +311,8 @@ protected: private: struct PrivateData; PrivateData* const uiData; + friend class PluginWindow; friend class UIExporter; - friend class UIExporterWindow; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UI) }; diff --git a/distrho/extra/ScopedPointer.hpp b/distrho/extra/ScopedPointer.hpp @@ -146,6 +146,9 @@ public: ObjectType* get() const noexcept { return object; } /** Returns the object that this ScopedPointer refers to. */ + ObjectType& getObject() const noexcept { return *object; } + + /** Returns the object that this ScopedPointer refers to. */ ObjectType& operator*() const noexcept { return *object; } /** Lets you access methods and properties of the object that this ScopedPointer refers to. */ diff --git a/distrho/src/DistrhoPluginJack.cpp b/distrho/src/DistrhoPluginJack.cpp @@ -17,7 +17,6 @@ #include "DistrhoPluginInternal.hpp" #if DISTRHO_PLUGIN_HAS_UI -# define DISTRHO_UI_IS_STANDALONE true # include "DistrhoUIInternal.hpp" # include "../extra/RingBuffer.hpp" #else @@ -110,12 +109,14 @@ public: PluginJack(jack_client_t* const client) : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback), #if DISTRHO_PLUGIN_HAS_UI - fUI(this, 0, + fUI(this, + 0, // winId + d_lastSampleRate, nullptr, // edit param setParameterValueCallback, setStateCallback, sendNoteCallback, - setSizeCallback, + nullptr, // window size nullptr, // file request nullptr, // bundle fPlugin.getInstancePointer(), @@ -494,11 +495,6 @@ protected: fPlugin.setParameterValue(index, value); } - void setSize(const uint width, const uint height) - { - fUI.setWindowSize(width, height); - } - # if DISTRHO_PLUGIN_WANT_MIDI_INPUT void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) { @@ -680,11 +676,6 @@ private: thisPtr->setParameterValue(index, value); } - static void setSizeCallback(void* ptr, uint width, uint height) - { - thisPtr->setSize(width, height); - } - # if DISTRHO_PLUGIN_WANT_MIDI_INPUT static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) { @@ -797,9 +788,7 @@ int main() d_lastBufferSize = jack_get_buffer_size(client); d_lastSampleRate = jack_get_sample_rate(client); -#if DISTRHO_PLUGIN_HAS_UI - d_lastUiSampleRate = d_lastSampleRate; -#endif + d_lastCanRequestParameterValueChanges = true; const PluginJack p(client); diff --git a/distrho/src/DistrhoPluginVST.cpp b/distrho/src/DistrhoPluginVST.cpp @@ -23,9 +23,6 @@ #endif #if DISTRHO_PLUGIN_HAS_UI -# undef DISTRHO_UI_USER_RESIZABLE -# define DISTRHO_UI_USER_RESIZABLE 0 -# define DISTRHO_UI_IS_STANDALONE false # include "DistrhoUIInternal.hpp" # include "../extra/RingBuffer.hpp" #endif @@ -178,7 +175,7 @@ public: fEffect(effect), fUiHelper(uiHelper), fPlugin(plugin), - fUI(this, winId, + fUI(this, winId, plugin->getSampleRate(), editParameterCallback, setParameterCallback, setStateCallback, @@ -213,7 +210,7 @@ public: } } - fUI.idle(); + fUI.plugin_idle(); } int16_t getWidth() const @@ -387,7 +384,7 @@ protected: void setSize(const uint width, const uint height) { - fUI.setWindowSize(width, height); + // fUI.setWindowSize(width, height); hostCallback(audioMasterSizeWindow, width, height); } @@ -671,9 +668,7 @@ public: } else { - d_lastUiSampleRate = fPlugin.getSampleRate(); - - UIExporter tmpUI(nullptr, 0, + UIExporter tmpUI(nullptr, 0, fPlugin.getSampleRate(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, fPlugin.getInstancePointer(), fLastScaleFactor); fVstRect.right = tmpUI.getWidth(); @@ -694,8 +689,6 @@ public: return 0; } # endif - d_lastUiSampleRate = fPlugin.getSampleRate(); - fVstUI = new UIVst(fAudioMaster, fEffect, this, &fPlugin, (intptr_t)ptr, fLastScaleFactor); # if DISTRHO_PLUGIN_WANT_FULL_STATE diff --git a/distrho/src/DistrhoUI.cpp b/distrho/src/DistrhoUI.cpp @@ -20,76 +20,52 @@ # include "src/TopLevelWidgetPrivateData.hpp" #endif -#include "NanoVG.hpp" - START_NAMESPACE_DISTRHO /* ------------------------------------------------------------------------------------------------------------ - * Static data, see DistrhoUIInternal.hpp and DistrhoUIPrivateData.hpp */ + * Static data, see DistrhoUIInternal.hpp */ -double d_lastUiSampleRate = 0.0; -void* d_lastUiDspPtr = nullptr; #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI -const char* g_nextBundlePath = nullptr; -double g_nextScaleFactor = 1.0; -uintptr_t g_nextWindowId = 0; -#else -Window* d_lastUiWindow = nullptr; +uintptr_t g_nextWindowId = 0; +double g_nextScaleFactor = 1.0; +const char* g_nextBundlePath = nullptr; #endif -// ----------------------------------------------------------------------------------------------------------- +/* ------------------------------------------------------------------------------------------------------------ + * UI::PrivateData special handling */ -#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI -UI* createUiWrapper(void* const dspPtr, const uintptr_t winId, const double scaleFactor, const char* const bundlePath) -{ - d_lastUiDspPtr = dspPtr; - g_nextWindowId = winId; - g_nextScaleFactor = scaleFactor; - g_nextBundlePath = bundlePath; - UI* const ret = createUI(); - d_lastUiDspPtr = nullptr; - g_nextWindowId = 0; - g_nextScaleFactor = 1.0; - g_nextBundlePath = nullptr; - return ret; -} -#else -UI* createUiWrapper(void* const dspPtr, Window* const window) +UI::PrivateData* UI::PrivateData::s_nextPrivateData = nullptr; + +PluginWindow& UI::PrivateData::createNextWindow(UI* const ui, const uint width, const uint height) { - d_lastUiDspPtr = dspPtr; - d_lastUiWindow = window; - UI* const ret = createUI(); - d_lastUiDspPtr = nullptr; - d_lastUiWindow = nullptr; - return ret; + UI::PrivateData* const pData = s_nextPrivateData; + pData->window = new PluginWindow(ui, pData, width, height); + return pData->window.getObject(); } -#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* ------------------------------------------------------------------------------------------------------------ * UI */ -#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI -UI::UI(uint width, uint height) - : UIWidget(width, height), - uiData(new PrivateData()) {} -#else -UI::UI(uint width, uint height) - : UIWidget(*d_lastUiWindow), - uiData(new PrivateData()) -{ - if (width > 0 && height > 0) - setSize(width, height); -} -#endif +UI::UI(const uint width, const uint height) + : UIWidget(UI::PrivateData::createNextWindow(this, width, height)), + uiData(UI::PrivateData::s_nextPrivateData) {} UI::~UI() { - delete uiData; } /* ------------------------------------------------------------------------------------------------------------ * Host state */ +bool UI::isResizable() const noexcept +{ +#if DISTRHO_UI_USER_RESIZABLE + return uiData->window->isResizable(); +#else + return false; +#endif +} + uint UI::getBackgroundColor() const noexcept { return uiData->bgColor; @@ -166,22 +142,22 @@ uintptr_t UI::getNextWindowId() noexcept return g_nextWindowId; } # endif -#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI +#endif /* ------------------------------------------------------------------------------------------------------------ * DSP/Plugin Callbacks (optional) */ -void UI::sampleRateChanged(double) {} +void UI::sampleRateChanged(double) +{ +} #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* ------------------------------------------------------------------------------------------------------------ * UI Callbacks (optional) */ -# ifndef DGL_FILE_BROWSER_DISABLED -void UI::uiFileBrowserSelected(const char*) +void UI::uiFocus(bool, CrossingMode) { } -# endif void UI::uiReshape(uint, uint) { @@ -189,22 +165,25 @@ void UI::uiReshape(uint, uint) pData->fallbackOnResize(); } +void UI::uiScaleFactorChanged(double) +{ +} + +# ifndef DGL_FILE_BROWSER_DISABLED +void UI::uiFileBrowserSelected(const char*) +{ +} +# endif + /* ------------------------------------------------------------------------------------------------------------ * UI Resize Handling, internal */ void UI::onResize(const ResizeEvent& ev) { - if (uiData->resizeInProgress) - return; - UIWidget::onResize(ev); const uint width = ev.size.getWidth(); const uint height = ev.size.getHeight(); - - /* - pData->window.setSize(width, height); - */ uiData->setSizeCallback(width, height); } #endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI @@ -212,10 +191,3 @@ void UI::onResize(const ResizeEvent& ev) // ----------------------------------------------------------------------------------------------------------- END_NAMESPACE_DISTRHO - -// ----------------------------------------------------------------------- -// Possible template data types - -// template class NanoBaseWidget<SubWidget>; -// template class NanoBaseWidget<TopLevelWidget>; -// template class NanoBaseWidget<StandaloneWindow>; diff --git a/distrho/src/DistrhoUIDSSI.cpp b/distrho/src/DistrhoUIDSSI.cpp @@ -14,7 +14,6 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#define DISTRHO_UI_IS_STANDALONE true #include "DistrhoUIInternal.hpp" #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS @@ -94,11 +93,12 @@ struct OscData { // ----------------------------------------------------------------------- -class UIDssi +class UIDssi : public IdleCallback { public: - UIDssi(const OscData& oscData, const char* const uiTitle) - : fUI(this, 0, nullptr, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, nullptr), + UIDssi(const OscData& oscData, const char* const uiTitle, const double sampleRate) + : fUI(this, 0, sampleRate, nullptr, + setParameterCallback, setStateCallback, sendNoteCallback, nullptr, nullptr), fHostClosed(false), fOscData(oscData) { @@ -111,17 +111,19 @@ public: fOscData.send_exiting(); } - void exec() + void exec_start() { - for (;;) - { - fOscData.idle(); + fUI.exec(this); + } + + void idleCallback() override + { + fOscData.idle(); - if (fHostClosed || ! fUI.idle()) - break; + if (fHostClosed) + return; - d_msleep(30); - } + fUI.exec_idle(); } // ------------------------------------------------------------------- @@ -206,11 +208,6 @@ protected: } #endif - void setSize(const uint width, const uint height) - { - fUI.setWindowSize(width, height); - } - private: UIExporter fUI; bool fHostClosed; @@ -239,11 +236,6 @@ private: } #endif - static void setSizeCallback(void* ptr, uint width, uint height) - { - uiPtr->setSize(width, height); - } - #undef uiPtr }; @@ -252,16 +244,17 @@ private: static OscData gOscData; static const char* gUiTitle = nullptr; static UIDssi* globalUI = nullptr; +static double sampleRate = 0.0; static void initUiIfNeeded() { if (globalUI != nullptr) return; - if (d_lastUiSampleRate == 0.0) - d_lastUiSampleRate = 44100.0; + if (sampleRate == 0.0) + sampleRate = 44100.0; - globalUI = new UIDssi(gOscData, gUiTitle); + globalUI = new UIDssi(gOscData, gUiTitle, sampleRate); } // ----------------------------------------------------------------------- @@ -337,10 +330,8 @@ int osc_program_handler(const char*, const char*, lo_arg** argv, int, lo_message int osc_sample_rate_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*) { - const int32_t sampleRate = argv[0]->i; - d_debug("osc_sample_rate_handler(%i)", sampleRate); - - d_lastUiSampleRate = sampleRate; + sampleRate = argv[0]->i; + d_debug("osc_sample_rate_handler(%f)", sampleRate); if (globalUI != nullptr) globalUI->dssiui_samplerate(sampleRate); @@ -394,7 +385,7 @@ int main(int argc, char* argv[]) initUiIfNeeded(); globalUI->dssiui_show(true); - globalUI->exec(); + globalUI->exec_start(); delete globalUI; globalUI = nullptr; @@ -481,7 +472,7 @@ int main(int argc, char* argv[]) { lo_server_recv(oscServer); - if (d_lastUiSampleRate != 0.0 || globalUI != nullptr) + if (sampleRate != 0.0 || globalUI != nullptr) break; d_msleep(50); @@ -489,11 +480,11 @@ int main(int argc, char* argv[]) int ret = 1; - if (d_lastUiSampleRate != 0.0 || globalUI != nullptr) + if (sampleRate != 0.0 || globalUI != nullptr) { initUiIfNeeded(); - globalUI->exec(); + globalUI->exec_start(); delete globalUI; globalUI = nullptr; diff --git a/distrho/src/DistrhoUIInternal.hpp b/distrho/src/DistrhoUIInternal.hpp @@ -21,13 +21,6 @@ #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI # include "../extra/Sleep.hpp" -using DGL_NAMESPACE::IdleCallback; -#else -# include "../../dgl/Application.hpp" -# include "../../dgl/Window.hpp" -using DGL_NAMESPACE::Application; -using DGL_NAMESPACE::IdleCallback; -using DGL_NAMESPACE::Window; #endif START_NAMESPACE_DISTRHO @@ -35,121 +28,32 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // Static data, see DistrhoUI.cpp -extern double d_lastUiSampleRate; -extern void* d_lastUiDspPtr; #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI -extern const char* g_nextBundlePath; -extern double g_nextScaleFactor; extern uintptr_t g_nextWindowId; -#else -extern Window* d_lastUiWindow; -#endif - -// ----------------------------------------------------------------------- - -#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI -UI* createUiWrapper(void* const dspPtr, const uintptr_t winId, const double scaleFactor, const char* const bundlePath); -#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI -UI* createUiWrapper(void* const dspPtr, Window* const window); -#endif - -#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI -// ----------------------------------------------------------------------- -// Plugin Application, will set class name based on plugin details - -class PluginApplication : public Application -{ -public: - PluginApplication() - : Application(DISTRHO_UI_IS_STANDALONE) - { - const char* const className = ( -#ifdef DISTRHO_PLUGIN_BRAND - DISTRHO_PLUGIN_BRAND -#else - DISTRHO_MACRO_AS_STRING(DISTRHO_NAMESPACE) +extern double g_nextScaleFactor; +extern const char* g_nextBundlePath; #endif - "-" DISTRHO_PLUGIN_NAME - ); - setClassName(className); - } -}; // ----------------------------------------------------------------------- -// Plugin Window, needed to take care of resize properly +// UI exporter class -class UIExporterWindow : public Window +class UIExporter { -public: - UIExporterWindow(PluginApplication& app, const intptr_t winId, const double scaleFactor, void* const dspPtr) - : Window(app, winId, scaleFactor, DISTRHO_UI_USER_RESIZABLE), - fUI(createUiWrapper(dspPtr, this)), - fIsReady(false) - { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(fUI->uiData != nullptr,); - - setSize(fUI->getWidth(), fUI->getHeight()); - } - - ~UIExporterWindow() - { - delete fUI; - } - - UI* getUI() const noexcept - { - return fUI; - } - - bool isReady() const noexcept - { - return fIsReady; - } - -protected: - // custom window reshape - void onReshape(uint width, uint height) override - { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - - UI::PrivateData* const uiData = fUI->uiData; - DISTRHO_SAFE_ASSERT_RETURN(uiData != nullptr,); - - /* - uiData->resizeInProgress = true; - fUI->setSize(width, height); - uiData->resizeInProgress = false; - */ - - fUI->uiReshape(width, height); - fIsReady = true; - } - -# ifndef DGL_FILE_BROWSER_DISABLED - // custom file-browser selected - void onFileSelected(const char* const filename) override - { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + // ------------------------------------------------------------------- + // UI Widget and its private data - fUI->uiFileBrowserSelected(filename); - } -# endif + UI* ui; + UI::PrivateData* uiData; -private: - UI* const fUI; - bool fIsReady; -}; -#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI + // prevent resize recursion + bool changingSizeRecursionCheck; -// ----------------------------------------------------------------------- -// UI exporter class + // ------------------------------------------------------------------- -class UIExporter -{ public: UIExporter(void* const callbacksPtr, - const intptr_t winId, + const uintptr_t winId, + const double sampleRate, const editParamFunc editParamCall, const setParamFunc setParamCall, const setStateFunc setStateCall, @@ -161,139 +65,130 @@ public: const double scaleFactor = 1.0, const uint32_t bgColor = 0, const uint32_t fgColor = 0xffffffff) -#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI - : fUI(createUiWrapper(dspPtr, winId, scaleFactor, bundlePath)), -#else - : glApp(), - glWindow(glApp, winId, scaleFactor, dspPtr), - fChangingSize(false), - fUI(glWindow.getUI()), -#endif - fData((fUI != nullptr) ? fUI->uiData : nullptr) + : ui(nullptr), + uiData(new UI::PrivateData()), + changingSizeRecursionCheck(false) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); - - fData->bgColor = bgColor; - fData->fgColor = fgColor; - - fData->callbacksPtr = callbacksPtr; - fData->editParamCallbackFunc = editParamCall; - fData->setParamCallbackFunc = setParamCall; - fData->setStateCallbackFunc = setStateCall; - fData->sendNoteCallbackFunc = sendNoteCall; - fData->setSizeCallbackFunc = setSizeCall; - fData->fileRequestCallbackFunc = fileRequestCall; + uiData->sampleRate = sampleRate; + uiData->dspPtr = dspPtr; + uiData->bgColor = bgColor; + uiData->fgColor = fgColor; #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI - // unused - return; (void)bundlePath; + uiData->scaleFactor = scaleFactor; + uiData->winId = winId; #endif - } + + uiData->callbacksPtr = callbacksPtr; + uiData->editParamCallbackFunc = editParamCall; + uiData->setParamCallbackFunc = setParamCall; + uiData->setStateCallbackFunc = setStateCall; + uiData->sendNoteCallbackFunc = sendNoteCall; + uiData->setSizeCallbackFunc = setSizeCall; + uiData->fileRequestCallbackFunc = fileRequestCall; #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI - ~UIExporter() - { - delete fUI; - } + g_nextWindowId = winId; + g_nextScaleFactor = scaleFactor; + g_nextBundlePath = bundlePath; #endif + UI::PrivateData::s_nextPrivateData = uiData; - // ------------------------------------------------------------------- + UI* const uiPtr = createUI(); #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI - uint getWidth() const noexcept - { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, 1); - return fUI->getWidth(); - } + g_nextWindowId = 0; + g_nextScaleFactor = 0.0; + g_nextBundlePath = nullptr; +#endif + UI::PrivateData::s_nextPrivateData = nullptr; - uint getHeight() const noexcept - { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, 1); - return fUI->getHeight(); - } + DISTRHO_SAFE_ASSERT_RETURN(uiPtr != nullptr,); + ui = uiPtr; - bool isVisible() const noexcept - { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, false); - return fUI->isRunning(); +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + // unused + (void)bundlePath; +#endif } - uintptr_t getNativeWindowHandle() const noexcept + ~UIExporter() { - return 0; + delete ui; + delete uiData; } -#else + + // ------------------------------------------------------------------- + uint getWidth() const noexcept { - return glWindow.getWidth(); + return uiData->window->getWidth(); } uint getHeight() const noexcept { - return glWindow.getHeight(); + return uiData->window->getHeight(); } bool isVisible() const noexcept { - return glWindow.isVisible(); + return uiData->window->isVisible(); } uintptr_t getNativeWindowHandle() const noexcept { - return glWindow.getNativeWindowHandle(); + return uiData->window->getNativeWindowHandle(); } -#endif uint getBackgroundColor() const noexcept { - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0); + DISTRHO_SAFE_ASSERT_RETURN(uiData != nullptr, 0); - return fData->bgColor; + return uiData->bgColor; } uint getForegroundColor() const noexcept { - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0xffffffff); + DISTRHO_SAFE_ASSERT_RETURN(uiData != nullptr, 0xffffffff); - return fData->fgColor; + return uiData->fgColor; } // ------------------------------------------------------------------- uint32_t getParameterOffset() const noexcept { - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0); + DISTRHO_SAFE_ASSERT_RETURN(uiData != nullptr, 0); - return fData->parameterOffset; + return uiData->parameterOffset; } // ------------------------------------------------------------------- void parameterChanged(const uint32_t index, const float value) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - fUI->parameterChanged(index, value); + ui->parameterChanged(index, value); } #if DISTRHO_PLUGIN_WANT_PROGRAMS void programLoaded(const uint32_t index) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - fUI->programLoaded(index); + ui->programLoaded(index); } #endif #if DISTRHO_PLUGIN_WANT_STATE void stateChanged(const char* const key, const char* const value) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0',); DISTRHO_SAFE_ASSERT_RETURN(value != nullptr,); - fUI->stateChanged(key, value); + ui->stateChanged(key, value); } #endif @@ -303,22 +198,18 @@ public: void exec(IdleCallback* const cb) { DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - fUI->setVisible(true); + ui->setVisible(true); cb->idleCallback(); - while (fUI->isRunning()) + while (ui->isRunning()) { d_msleep(10); cb->idleCallback(); } } - void exec_idle() - { - } - bool idle() { return true; @@ -330,103 +221,87 @@ public: void quit() { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - fUI->setVisible(false); - fUI->terminateAndWaitForProcess(); + ui->setVisible(false); + ui->terminateAndWaitForProcess(); } #else +# if DISTRHO_UI_IS_STANDALONE void exec(IdleCallback* const cb) { DISTRHO_SAFE_ASSERT_RETURN(cb != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - glWindow.setVisible(true); - glApp.addIdleCallback(cb); - glApp.exec(); + uiData->window->show(); + uiData->app.addIdleCallback(cb); + uiData->app.exec(); } void exec_idle() { - if (glWindow.isReady()) - fUI->uiIdle(); - } + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, ); - void focus() - { - glWindow.focus(); + ui->uiIdle(); } - - bool idle() +# else + bool plugin_idle() { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, false); - - glApp.idle(); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false); - if (glWindow.isReady()) - fUI->uiIdle(); + uiData->app.idle(); + ui->uiIdle(); + return ! uiData->app.isQuiting(); + } +# endif - return ! glApp.isQuiting(); + void focus() + { + uiData->window->focus(); } void quit() { - glWindow.close(); - glApp.quit(); + uiData->window->close(); + uiData->app.quit(); } - #endif + // ------------------------------------------------------------------- #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI void setWindowTitle(const char* const uiTitle) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - fUI->setTitle(uiTitle); + ui->setTitle(uiTitle); } - void setWindowSize(const uint width, const uint height, const bool = false) + void setWindowSize(const uint width, const uint height) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - fUI->setSize(width, height); + ui->setSize(width, height); } void setWindowTransientWinId(const uintptr_t winId) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); - fUI->setTransientWinId(winId); + ui->setTransientWinId(winId); } bool setWindowVisible(const bool yesNo) { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false); - fUI->setVisible(yesNo); + ui->setVisible(yesNo); - return fUI->isRunning(); + return ui->isRunning(); } #else void setWindowTitle(const char* const uiTitle) { - glWindow.setTitle(uiTitle); - } - - void setWindowSize(const uint width, const uint height, const bool updateUI = false) - { - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(! fChangingSize,); - - fChangingSize = true; - - if (updateUI) - fUI->setSize(width, height); - - glWindow.setSize(width, height); - - fChangingSize = false; + uiData->window->setTitle(uiTitle); } void setWindowTransientWinId(const uintptr_t /*winId*/) @@ -438,9 +313,9 @@ public: bool setWindowVisible(const bool yesNo) { - glWindow.setVisible(yesNo); + uiData->window->setVisible(yesNo); - return ! glApp.isQuiting(); + return ! uiData->app.isQuiting(); } bool handlePluginKeyboard(const bool /*press*/, const uint /*key*/, const uint16_t /*mods*/) @@ -464,37 +339,19 @@ public: void setSampleRate(const double sampleRate, const bool doCallback = false) { - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(uiData != nullptr,); DISTRHO_SAFE_ASSERT(sampleRate > 0.0); - if (d_isEqual(fData->sampleRate, sampleRate)) + if (d_isEqual(uiData->sampleRate, sampleRate)) return; - fData->sampleRate = sampleRate; + uiData->sampleRate = sampleRate; if (doCallback) - fUI->sampleRateChanged(sampleRate); + ui->sampleRateChanged(sampleRate); } -private: -#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI - // ------------------------------------------------------------------- - // DGL Application and Window for this widget - - PluginApplication glApp; - UIExporterWindow glWindow; - - // prevent recursion - bool fChangingSize; -#endif - - // ------------------------------------------------------------------- - // Widget and DistrhoUI data - - UI* const fUI; - UI::PrivateData* const fData; - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UIExporter) }; diff --git a/distrho/src/DistrhoUILV2.cpp b/distrho/src/DistrhoUILV2.cpp @@ -14,7 +14,6 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#define DISTRHO_UI_IS_STANDALONE false #include "DistrhoUIInternal.hpp" #include "../extra/String.hpp" @@ -73,10 +72,11 @@ public: const LV2UI_Write_Function writeFunc, LV2UI_Widget* const widget, void* const dspPtr, + const float sampleRate, const float scaleFactor, const uint32_t bgColor, const uint32_t fgColor) - : fUI(this, winId, + : fUI(this, winId, sampleRate, editParameterCallback, setParameterCallback, setStateCallback, @@ -97,8 +97,12 @@ public: fURIDs(uridMap), fWinIdWasNull(winId == 0) { +#if ! DISTRHO_UI_USER_RESIZABLE + // this is not needed, hosts can query child window size + // it is best for them to do so anyway, since properties other than current-size are important (like ratio) if (fUiResize != nullptr && winId != 0) fUiResize->ui_resize(fUiResize->handle, fUI.getWidth(), fUI.getHeight()); +#endif if (widget != nullptr) *widget = (LV2UI_Widget)fUI.getNativeWindowHandle(); @@ -191,9 +195,9 @@ public: int lv2ui_idle() { if (fWinIdWasNull) - return (fUI.idle() && fUI.isVisible()) ? 0 : 1; + return (fUI.plugin_idle() && fUI.isVisible()) ? 0 : 1; - return fUI.idle() ? 0 : 1; + return fUI.plugin_idle() ? 0 : 1; } int lv2ui_show() @@ -206,12 +210,6 @@ public: return fUI.setWindowVisible(false) ? 0 : 1; } - int lv2ui_resize(uint width, uint height) - { - fUI.setWindowSize(width, height, true); - return 0; - } - // ------------------------------------------------------------------- uint32_t lv2_get_options(LV2_Options_Option* const /*options*/) @@ -333,8 +331,8 @@ protected: void setSize(const uint width, const uint height) { - fUI.setWindowSize(width, height); - + // report window size change to host. + // at the moment no lv2 hosts automatically adapt to child window size changes, so this is still needed if (fUiResize != nullptr && ! fWinIdWasNull) fUiResize->ui_resize(fUiResize->handle, width, height); } @@ -526,6 +524,7 @@ static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, #endif const intptr_t winId = (intptr_t)parentId; + float sampleRate = 0.0f; float scaleFactor = 1.0f; uint32_t bgColor = 0; uint32_t fgColor = 0xffffffff; @@ -544,7 +543,7 @@ static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, /**/ if (options[i].key == uridSampleRate) { if (options[i].type == uridAtomFloat) - d_lastUiSampleRate = *(const float*)options[i].value; + sampleRate = *(const float*)options[i].value; else d_stderr("Host provides UI sample-rate but has wrong value type"); } @@ -572,15 +571,15 @@ static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, } } - if (d_lastUiSampleRate < 1.0) + if (sampleRate < 1.0) { d_stdout("WARNING: this host does not send sample-rate information for LV2 UIs, using 44100 as fallback (this could be wrong)"); - d_lastUiSampleRate = 44100.0; + sampleRate = 44100.0; } return new UiLv2(bundlePath, winId, options, uridMap, features, controller, writeFunction, widget, instance, - scaleFactor, bgColor, fgColor); + sampleRate, scaleFactor, bgColor, fgColor); } #define uiPtr ((UiLv2*)ui) @@ -612,16 +611,6 @@ static int lv2ui_hide(LV2UI_Handle ui) return uiPtr->lv2ui_hide(); } -static int lv2ui_resize(LV2UI_Handle ui, int width, int height) -{ - DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, 1); - DISTRHO_SAFE_ASSERT_RETURN(width > 0, 1); - DISTRHO_SAFE_ASSERT_RETURN(height > 0, 1); - - return 1; // This needs more testing - //return uiPtr->lv2ui_resize(width, height); -} - // ----------------------------------------------------------------------- static uint32_t lv2_get_options(LV2UI_Handle ui, LV2_Options_Option* options) @@ -650,7 +639,6 @@ static const void* lv2ui_extension_data(const char* uri) static const LV2_Options_Interface options = { lv2_get_options, lv2_set_options }; static const LV2UI_Idle_Interface uiIdle = { lv2ui_idle }; static const LV2UI_Show_Interface uiShow = { lv2ui_show, lv2ui_hide }; - static const LV2UI_Resize uiResz = { nullptr, lv2ui_resize }; if (std::strcmp(uri, LV2_OPTIONS__interface) == 0) return &options; @@ -658,8 +646,6 @@ static const void* lv2ui_extension_data(const char* uri) return &uiIdle; if (std::strcmp(uri, LV2_UI__showInterface) == 0) return &uiShow; - if (std::strcmp(uri, LV2_UI__resize) == 0) - return &uiResz; #if DISTRHO_PLUGIN_WANT_PROGRAMS static const LV2_Programs_UI_Interface uiPrograms = { lv2ui_select_program }; diff --git a/distrho/src/DistrhoUIPrivateData.hpp b/distrho/src/DistrhoUIPrivateData.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2020 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 @@ -18,14 +18,27 @@ #define DISTRHO_UI_PRIVATE_DATA_HPP_INCLUDED #include "../DistrhoUI.hpp" +#include "../../dgl/Application.hpp" -START_NAMESPACE_DISTRHO +#ifndef DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# include "../../dgl/Window.hpp" +#endif -// ----------------------------------------------------------------------- -// Static data, see DistrhoUI.cpp +#if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_DSSI) +# define DISTRHO_UI_IS_STANDALONE 1 +#else +# define DISTRHO_UI_IS_STANDALONE 0 +#endif + +#if defined(DISTRHO_PLUGIN_TARGET_VST) +# undef DISTRHO_UI_USER_RESIZABLE +# define DISTRHO_UI_USER_RESIZABLE 0 +#endif + +START_NAMESPACE_DISTRHO -extern double d_lastUiSampleRate; -extern void* d_lastUiDspPtr; +using DGL_NAMESPACE::Application; +using DGL_NAMESPACE::Window; // ----------------------------------------------------------------------- // UI callbacks @@ -38,23 +51,50 @@ typedef void (*setSizeFunc) (void* ptr, uint width, uint height); typedef bool (*fileRequestFunc) (void* ptr, const char* key); // ----------------------------------------------------------------------- +// Plugin Application, will set class name based on plugin details + +class PluginApplication : public Application +{ +public: + explicit PluginApplication() + : Application(DISTRHO_UI_IS_STANDALONE) + { + const char* const className = ( +#ifdef DISTRHO_PLUGIN_BRAND + DISTRHO_PLUGIN_BRAND +#else + DISTRHO_MACRO_AS_STRING(DISTRHO_NAMESPACE) +#endif + "-" DISTRHO_PLUGIN_NAME + ); + setClassName(className); + } + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) +}; + +class PluginWindow; + +// ----------------------------------------------------------------------- // UI private data struct UI::PrivateData { + // DGL + PluginApplication app; + ScopedPointer<PluginWindow> window; + // DSP double sampleRate; uint32_t parameterOffset; -#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS void* dspPtr; -#endif // UI - bool automaticallyScale; - bool resizeInProgress; - uint minWidth; - uint minHeight; uint bgColor; uint fgColor; +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + double scaleFactor; + uintptr_t winId; +#endif // Callbacks void* callbacksPtr; @@ -66,17 +106,19 @@ struct UI::PrivateData { fileRequestFunc fileRequestCallbackFunc; PrivateData() noexcept - : sampleRate(d_lastUiSampleRate), - parameterOffset(0), -#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS - dspPtr(d_lastUiDspPtr), + : app(), +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + window(nullptr), #endif - automaticallyScale(false), - resizeInProgress(false), - minWidth(0), - minHeight(0), + sampleRate(0), + parameterOffset(0), + dspPtr(nullptr), bgColor(0), - fgColor(0), + fgColor(0xffffffff), +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + scaleFactor(1.0), + winId(0), +#endif callbacksPtr(nullptr), editParamCallbackFunc(nullptr), setParamCallbackFunc(nullptr), @@ -85,8 +127,6 @@ struct UI::PrivateData { setSizeCallbackFunc(nullptr), fileRequestCallbackFunc(nullptr) { - DISTRHO_SAFE_ASSERT(d_isNotZero(sampleRate)); - #if defined(DISTRHO_PLUGIN_TARGET_DSSI) || defined(DISTRHO_PLUGIN_TARGET_LV2) parameterOffset += DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS; # if DISTRHO_PLUGIN_WANT_LATENCY @@ -143,7 +183,91 @@ struct UI::PrivateData { return false; } + + static UI::PrivateData* s_nextPrivateData; + 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 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,); + + ui->uiFileBrowserSelected(filename); + } +# endif + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) }; +#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI // ----------------------------------------------------------------------- diff --git a/examples/Info/InfoExampleUI.cpp b/examples/Info/InfoExampleUI.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2019 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 @@ -14,11 +14,9 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "DistrhoPluginInfo.h" - #include "DistrhoUI.hpp" -#include "Window.hpp" +#include "ResizeHandle.hpp" START_NAMESPACE_DISTRHO @@ -33,7 +31,9 @@ public: InfoExampleUI() : UI(kInitialWidth, kInitialHeight), fSampleRate(getSampleRate()), - fScale(1.0f) + fResizable(isResizable()), + fScale(1.0f), + fResizeHandle(this) { std::memset(fParameters, 0, sizeof(float)*kParameterCount); std::memset(fStrBuf, 0, sizeof(char)*(0xff+1)); @@ -45,6 +45,10 @@ public: #endif setGeometryConstraints(kInitialWidth, kInitialHeight, true); + + // no need to show resize handle if window is user-resizable + if (fResizable) + fResizeHandle.hide(); } protected: @@ -57,6 +61,11 @@ protected: */ void parameterChanged(uint32_t index, float value) override { + // some hosts send parameter change events for output parameters even when nothing changed + // we catch that here in order to prevent excessive repaints + if (d_isEqual(fParameters[index], value)) + return; + fParameters[index] = value; repaint(); } @@ -123,6 +132,11 @@ protected: drawRight(x, y, (fParameters[kParameterCanRequestParameterValueChanges] > 0.5f) ? "Yes" : "No", 40); y+=lineHeight; + // resizable + drawLeft(x, y, "Host resizable:", 20); + drawRight(x, y, fResizable ? "Yes" : "No", 40); + y+=lineHeight; + // BBT x = 200.0f * fScale; y = 15.0f * fScale; @@ -184,7 +198,9 @@ private: double fSampleRate; // UI stuff + bool fResizable; float fScale; + ResizeHandle fResizeHandle; // temp buf for text char fStrBuf[0xff+1]; diff --git a/examples/Info/ResizeHandle.hpp b/examples/Info/ResizeHandle.hpp @@ -0,0 +1,176 @@ +/* + * Resize handle 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. + */ + +#pragma once + +#include "TopLevelWidget.hpp" +#include "Color.hpp" + +START_NAMESPACE_DGL + +/** Resize handle for DPF windows, will sit on bottom-right. */ +class ResizeHandle : public TopLevelWidget +{ +public: + /** Constructor for placing this handle on top of a window. */ + explicit ResizeHandle(Window& window) + : TopLevelWidget(window), + handleSize(16), + resizing(false) + { + resetArea(); + } + + /** Overloaded constructor, will fetch the window from an existing top-level widget. */ + explicit ResizeHandle(TopLevelWidget* const tlw) + : TopLevelWidget(tlw->getWindow()), + handleSize(16), + resizing(false) + { + resetArea(); + } + + /** Set the handle size, minimum 16. */ + void setHandleSize(const uint size) + { + handleSize = std::max(16u, size); + resetArea(); + } + +protected: + void onDisplay() override + { + const GraphicsContext& context(getGraphicsContext()); + const double lineWidth = 1.0 * getScaleFactor(); + + // draw white lines, 1px wide + Color(1.0f, 1.0f, 1.0f).setFor(context); + l1.draw(context, lineWidth); + l2.draw(context, lineWidth); + l3.draw(context, lineWidth); + + // draw black lines, offset by 1px and 1px wide + Color(0.0f, 0.0f, 0.0f).setFor(context); + Line<double> l1b(l1), l2b(l2), l3b(l3); + l1b.moveBy(lineWidth, lineWidth); + l2b.moveBy(lineWidth, lineWidth); + l3b.moveBy(lineWidth, lineWidth); + l1b.draw(context, lineWidth); + l2b.draw(context, lineWidth); + l3b.draw(context, lineWidth); + } + + bool onMouse(const MouseEvent& ev) override + { + if (ev.button != 1) + return false; + + if (ev.press && area.contains(ev.pos)) + { + resizing = true; + resizingSize = Size<double>(getWidth(), getHeight()); + lastResizePoint = ev.pos; + return true; + } + + if (resizing && ! ev.press) + { + resizing = false; + return true; + } + + return false; + } + + bool onMotion(const MotionEvent& ev) override + { + if (! resizing) + return false; + + const Size<double> offset(ev.pos.getX() - lastResizePoint.getX(), + ev.pos.getY() - lastResizePoint.getY()); + + resizingSize += offset; + lastResizePoint = ev.pos; + + // TODO min width, min height + const uint minWidth = 16; + const uint minHeight = 16; + + if (resizingSize.getWidth() < minWidth) + resizingSize.setWidth(minWidth); + if (resizingSize.getHeight() < minHeight) + resizingSize.setHeight(minHeight); + + setSize(resizingSize.getWidth(), resizingSize.getHeight()); + return true; + } + + void onResize(const ResizeEvent& ev) override + { + TopLevelWidget::onResize(ev); + resetArea(); + } + +private: + Rectangle<uint> area; + Line<double> l1, l2, l3; + uint handleSize; + + // event handling state + bool resizing; + Point<double> lastResizePoint; + Size<double> resizingSize; + + void resetArea() + { + const double scaleFactor = getScaleFactor(); + const uint margin = 0.0 * scaleFactor; + const uint size = handleSize * scaleFactor; + + area = Rectangle<uint>(getWidth() - size - margin, + getHeight() - size - margin, + size, size); + + recreateLines(area.getX(), area.getY(), size); + } + + void recreateLines(const uint x, const uint y, const uint size) + { + uint linesize = size; + uint offset = 0; + + // 1st line, full diagonal size + l1.setStartPos(x + size, y); + l1.setEndPos(x, y + size); + + // 2nd line, bit more to the right and down, cropped + offset += size / 3; + linesize -= size / 3; + l2.setStartPos(x + linesize + offset, y + offset); + l2.setEndPos(x + offset, y + linesize + offset); + + // 3rd line, even more right and down + offset += size / 3; + linesize -= size / 3; + l3.setStartPos(x + linesize + offset, y + offset); + l3.setEndPos(x + offset, y + linesize + offset); + } + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ResizeHandle) +}; + +END_NAMESPACE_DGL diff --git a/tests/Demo.cpp b/tests/Demo.cpp @@ -235,6 +235,155 @@ private: }; // -------------------------------------------------------------------------------------------------------------------- +// Resize handle widget + +class ResizeHandle : public TopLevelWidget +{ + Rectangle<uint> area; + Line<double> l1, l2, l3; + uint baseSize; + + // event handling state + bool resizing; + Point<double> lastResizePoint; + Size<double> resizingSize; + +public: + explicit ResizeHandle(TopLevelWidget* const tlw) + : TopLevelWidget(tlw->getWindow()), + baseSize(16), + resizing(false) + { + resetArea(); + } + + explicit ResizeHandle(Window& window) + : TopLevelWidget(window), + baseSize(16), + resizing(false) + { + resetArea(); + } + + void setBaseSize(const uint size) + { + baseSize = std::max(16u, size); + resetArea(); + } + +protected: + void onDisplay() override + { + const GraphicsContext& context(getGraphicsContext()); + const double offset = getScaleFactor(); + + // draw white lines, 1px wide + Color(1.0f, 1.0f, 1.0f).setFor(context); + l1.draw(context, 1); + l2.draw(context, 1); + l3.draw(context, 1); + + // draw black lines, offset by 1px and 2px wide + Color(0.0f, 0.0f, 0.0f).setFor(context); + Line<double> l1b(l1), l2b(l2), l3b(l3); + l1b.moveBy(offset, offset); + l2b.moveBy(offset, offset); + l3b.moveBy(offset, offset); + l1b.draw(context, 1); + l2b.draw(context, 1); + l3b.draw(context, 1); + } + + bool onMouse(const MouseEvent& ev) override + { + if (ev.button != 1) + return false; + + if (ev.press && area.contains(ev.pos)) + { + resizing = true; + resizingSize = Size<double>(getWidth(), getHeight()); + lastResizePoint = ev.pos; + return true; + } + + if (resizing && ! ev.press) + { + resizing = false; + return true; + } + + return false; + } + + bool onMotion(const MotionEvent& ev) override + { + if (! resizing) + return false; + + const Size<double> offset(ev.pos.getX() - lastResizePoint.getX(), + ev.pos.getY() - lastResizePoint.getY()); + + resizingSize += offset; + lastResizePoint = ev.pos; + + // TODO min width, min height + const uint minWidth = 16; + const uint minHeight = 16; + + if (resizingSize.getWidth() < minWidth) + resizingSize.setWidth(minWidth); + if (resizingSize.getHeight() < minHeight) + resizingSize.setHeight(minHeight); + + setSize(resizingSize.getWidth(), resizingSize.getHeight()); + return true; + } + + void onResize(const ResizeEvent& ev) override + { + TopLevelWidget::onResize(ev); + resetArea(); + } + +private: + void resetArea() + { + const double scaleFactor = getScaleFactor(); + const uint margin = 0.0 * scaleFactor; + const uint size = baseSize * scaleFactor; + + area = Rectangle<uint>(getWidth() - size - margin, + getHeight() - size - margin, + size, size); + + recreateLines(area.getX(), area.getY(), size); + } + + void recreateLines(const uint x, const uint y, const uint size) + { + uint linesize = size; + uint offset = 0; + + // 1st line, full diagonal size + l1.setStartPos(x + size, y); + l1.setEndPos(x, y + size); + + // 2nd line, bit more to the right and down, cropped + offset += size / 3; + linesize -= size / 3; + l2.setStartPos(x + linesize + offset, y + offset); + l2.setEndPos(x + offset, y + linesize + offset); + + // 3rd line, even more right and down + offset += size / 3; + linesize -= size / 3; + l3.setStartPos(x + linesize + offset, y + offset); + l3.setEndPos(x + offset, y + linesize + offset); + } +}; + +// -------------------------------------------------------------------------------------------------------------------- // Main Demo Window, having a left-side tab-like widget and main area for current widget class DemoWindow : public StandaloneWindow, @@ -263,6 +412,7 @@ public: wText(this), #endif wLeft(this, this), + resizer(this), curWidget(nullptr) { wColor.hide(); @@ -350,6 +500,7 @@ private: ExampleTextSubWidget wText; #endif LeftSideWidget wLeft; + ResizeHandle resizer; Widget* curWidget; };