Reworked resize handlingpull/282/head
| @@ -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 | |||
| @@ -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); | |||
| @@ -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(). | |||
| @@ -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 | |||
| @@ -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); | |||
| @@ -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) | |||
| @@ -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() | |||
| @@ -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*) | |||
| { | |||
| @@ -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 | |||
| } | |||
| @@ -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. */ | |||
| @@ -152,11 +152,20 @@ 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 | |||
| @@ -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 | |||
| @@ -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); | |||
| @@ -81,6 +81,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) | |||
| }; | |||
| @@ -145,6 +145,9 @@ public: | |||
| /** Returns the object that this ScopedPointer refers to. */ | |||
| 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; } | |||
| @@ -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); | |||
| @@ -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 | |||
| @@ -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>; | |||
| @@ -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; | |||
| @@ -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) | |||
| }; | |||
| @@ -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 }; | |||
| @@ -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 | |||
| @@ -37,24 +50,51 @@ typedef void (*sendNoteFunc) (void* ptr, uint8_t channel, uint8_t note, uint8 | |||
| 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 | |||
| // ----------------------------------------------------------------------- | |||
| @@ -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]; | |||
| @@ -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 | |||
| @@ -234,6 +234,155 @@ private: | |||
| #endif | |||
| }; | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| // 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 | |||
| @@ -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; | |||
| }; | |||