diff --git a/distrho/extra/ExternalWindow.hpp b/distrho/extra/ExternalWindow.hpp index 48a05948..57f3d1b7 100644 --- a/distrho/extra/ExternalWindow.hpp +++ b/distrho/extra/ExternalWindow.hpp @@ -36,10 +36,40 @@ START_NAMESPACE_DISTRHO /** External Window class. - This is a standalone TopLevelWidget-compatible class, but without any real event handling. - Being compatible with TopLevelWidget, it allows to be used as DPF UI target. + This is a standalone TopLevelWidget/Window-compatible class, but without any real event handling. + Being compatible with TopLevelWidget/Window, it allows to be used as DPF UI target. It can be used to embed non-DPF things or to run a tool in a new process as the "UI". + The uiIdle() function will be called at regular intervals to keep UI running. + There are helper methods in place to launch external tools and keep track of its running state. + + External windows can be setup to run in 3 different modes: + * Embed: + Embed into the host UI, even-loop driven by the host. + This is basically working as a regular plugin UI, as you typically expect them to. + The plugin side does not get control over showing, hiding or closing the window (as usual for plugins). + No restrictions on supported plugin format, everything should work. + Requires DISTRHO_PLUGIN_HAS_EMBED_UI to be set to 1. + + * Semi-external: + The UI is not embed into the host, but the even-loop is still driven by it. + In this mode the host does not have control over the UI except for showing, hiding and setting transient parent. + It is possible to close the window from the plugin, the host will be notified of such case. + Host regularly calls isQuitting() to check if the UI got closed by the user or plugin side. + This mode is only possible in LV2 plugin formats, using lv2ui:showInterface extension. + + * Standalone: + The UI is not embed into the host or use its event-loop, basically running as standalone. + The host only has control over showing and hiding the window, nothing else. + The UI is still free to close itself at any point. + DPF will keep calling isRunning() to check if it should keep the event-loop running. + Only possible in JACK and DSSI targets, as the UIs are literally standalone applications there. + + Please note that for non-embed windows, you cannot show the window yourself. + The plugin window is only allowed to hide or close itself, a "show" action needs to come from the host. + + A few callbacks are provided so that implementations do not need to care about checking for state changes. + They are not called on construction, but will be everytime something changes either by the host or the window itself. */ class ExternalWindow { @@ -53,7 +83,7 @@ public: : pData() {} /** - Constructor. + Constructor for DPF internal use. */ explicit ExternalWindow(const PrivateData& data) : pData(data) {} @@ -67,8 +97,13 @@ public: } /* -------------------------------------------------------------------------------------------------------- - * ExternalWindow specific calls */ + * ExternalWindow specific calls - Host side calls that you can reimplement for fine-grained funtionality */ + /** + Check if main-loop is running. + This is used under standalone mode to check whether to keep things running. + Returning false from this function will stop the event-loop and close the window. + */ virtual bool isRunning() const { if (ext.inUse) @@ -77,124 +112,140 @@ public: return isVisible(); } + /** + Check if we are about to close. + This is used when the event-loop is provided by the host to check if it should close the window. + It is also used in standalone mode right after isRunning() returns false to verify if window needs to be closed. + */ virtual bool isQuitting() const { return ext.inUse ? ext.isQuitting : pData.isQuitting; } +#if DISTRHO_PLUGIN_HAS_EMBED_UI /** - Hide the UI and gracefully terminate. + Get the "native" window handle. + This can be reimplemented in order to pass the native window to hosts that can use such informaton. + + Returned value type depends on the platform: + - HaikuOS: This is a pointer to a `BView`. + - MacOS: This is a pointer to an `NSView*`. + - Windows: This is a `HWND`. + - Everything else: This is an [X11] `Window`. + + @note Only available to override if DISTRHO_PLUGIN_HAS_EMBED_UI is set to 1. */ - virtual void close() + virtual uintptr_t getNativeWindowHandle() const noexcept { - pData.isQuitting = true; - hide(); - - if (ext.inUse) - terminateAndWaitForExternalProcess(); + return 0; } +#endif /** Grab the keyboard input focus. + Typically you would setup OS-native methods to bring the window to front and give it focus. + Default implementation does nothing. */ virtual void focus() {} + /* -------------------------------------------------------------------------------------------------------- + * TopLevelWidget-like calls - Information, can be called by either host or plugin */ + +#if DISTRHO_PLUGIN_HAS_EMBED_UI /** - Get the transient window that we should attach ourselves to. - TODO what id? also NSView* on macOS, or NSWindow? + Whether this Window is embed into another (usually not DGL-controlled) Window. */ - uintptr_t getTransientWindowId() const noexcept + bool isEmbed() const noexcept { - return pData.transientWinId; + return pData.parentWindowHandle != 0; } +#endif /** - Called by the host to set the transient window that we should attach ourselves to. - TODO what id? also NSView* on macOS, or NSWindow? + Check if this window is visible. + @see setVisible(bool) */ - void setTransientWindowId(uintptr_t winId) + bool isVisible() const noexcept { - if (pData.transientWinId == winId) - return; - pData.transientWinId = winId; - transientWindowChanged(winId); + return pData.visible; } -#if DISTRHO_PLUGIN_HAS_EMBED_UI /** - Whether this Window is embed into another (usually not DGL-controlled) Window. + Whether this Window is running as standalone, that is, without being coupled to a host event-loop. + When in standalone mode, isRunning() is called to check if the event-loop should keep running. */ - bool isEmbed() const noexcept + bool isStandalone() const noexcept { - return pData.parentWindowHandle != 0; + return pData.isStandalone; } /** - Get the "native" window handle. - This can be reimplemented in order to pass the child window to hosts that can use such informaton. - - Returned value type depends on the platform: - - HaikuOS: This is a pointer to a `BView`. - - MacOS: This is a pointer to an `NSView*`. - - Windows: This is a `HWND`. - - Everything else: This is an [X11] `Window`. + Get width of this window. + Only relevant to hosts when the UI is embedded. */ - virtual uintptr_t getNativeWindowHandle() const noexcept + uint getWidth() const noexcept { - return 0; + return pData.width; } /** - Get the "native" window handle that this window should embed itself into. - Returned value type depends on the platform: - - HaikuOS: This is a pointer to a `BView`. - - MacOS: This is a pointer to an `NSView*`. - - Windows: This is a `HWND`. - - Everything else: This is an [X11] `Window`. + Get height of this window. + Only relevant to hosts when the UI is embedded. */ - uintptr_t getParentWindowHandle() const noexcept + uint getHeight() const noexcept { - return pData.parentWindowHandle; + return pData.height; } -#endif - /* -------------------------------------------------------------------------------------------------------- - * TopLevelWidget-like calls */ + /** + Get the scale factor requested for this window. + This is purely informational, and up to developers to choose what to do with it. + */ + double getScaleFactor() const noexcept + { + return pData.scaleFactor; + } /** - Check if this window is visible. - @see setVisible(bool) + Get the title of the window previously set with setTitle(). + This is typically displayed in the title bar or in window switchers. */ - bool isVisible() const noexcept + const char* getTitle() const noexcept { - return pData.visible; + return pData.title; } +#if DISTRHO_PLUGIN_HAS_EMBED_UI /** - Set window visible (or not) according to @a visible. - @see isVisible(), hide(), show() + Get the "native" window handle that this window should embed itself into. + Returned value type depends on the platform: + - HaikuOS: This is a pointer to a `BView`. + - MacOS: This is a pointer to an `NSView*`. + - Windows: This is a `HWND`. + - Everything else: This is an [X11] `Window`. */ - void setVisible(bool visible) + uintptr_t getParentWindowHandle() const noexcept { - if (pData.visible == visible) - return; - pData.visible = visible; - visibilityChanged(visible); + return pData.parentWindowHandle; } +#endif /** - Show window. - This is the same as calling setVisible(true). - @see isVisible(), setVisible(bool) + Get the transient window that we should attach ourselves to. + TODO what id? also NSView* on macOS, or NSWindow? */ - void show() + uintptr_t getTransientWindowId() const noexcept { - setVisible(true); + return pData.transientWinId; } + /* -------------------------------------------------------------------------------------------------------- + * TopLevelWidget-like calls - actions called by either host or plugin */ + /** Hide window. This is the same as calling setVisible(false). + Embed windows should never call this! @see isVisible(), setVisible(bool) */ void hide() @@ -203,23 +254,22 @@ public: } /** - Get width. + Hide the UI and gracefully terminate. + Embed windows should never call this! */ - uint getWidth() const noexcept + virtual void close() { - return pData.width; - } + pData.isQuitting = true; + hide(); - /** - Get height. - */ - uint getHeight() const noexcept - { - return pData.height; + if (ext.inUse) + terminateAndWaitForExternalProcess(); } /** - Set width. + Set width of this window. + Can trigger a sizeChanged callback. + Only relevant to hosts when the UI is embedded. */ void setWidth(uint width) { @@ -227,7 +277,9 @@ public: } /** - Set height. + Set height of this window. + Can trigger a sizeChanged callback. + Only relevant to hosts when the UI is embedded. */ void setHeight(uint height) { @@ -235,7 +287,9 @@ public: } /** - Set size using @a width and @a height values. + Set size of this window using @a width and @a height values. + Can trigger a sizeChanged callback. + Only relevant to hosts when the UI is embedded. */ void setSize(uint width, uint height) { @@ -246,19 +300,13 @@ public: pData.width = width; pData.height = height; - onResize(width, height); - } - - /** - Get the title of the window previously set with setTitle(). - */ - const char* getTitle() const noexcept - { - return pData.title; + sizeChanged(width, height); } /** Set the title of the window, typically displayed in the title bar or in window switchers. + Can trigger a titleChanged callback. + Only relevant to hosts when the UI is not embedded. */ void setTitle(const char* title) { @@ -268,13 +316,41 @@ public: titleChanged(title); } + /* -------------------------------------------------------------------------------------------------------- + * TopLevelWidget-like calls - actions called by the host */ + /** - Get the scale factor requested for this window. - This is purely informational, and up to developers to choose what to do with it. + Show window. + This is the same as calling setVisible(true). + @see isVisible(), setVisible(bool) */ - double getScaleFactor() const noexcept + void show() { - return pData.scaleFactor; + setVisible(true); + } + + /** + Set window visible (or not) according to @a visible. + @see isVisible(), hide(), show() + */ + void setVisible(bool visible) + { + if (pData.visible == visible) + return; + pData.visible = visible; + visibilityChanged(visible); + } + + /** + Called by the host to set the transient parent window that we should attach ourselves to. + TODO what id? also NSView* on macOS, or NSWindow? + */ + void setTransientWindowId(uintptr_t winId) + { + if (pData.transientWinId == winId) + return; + pData.transientWinId = winId; + transientParentWindowChanged(winId); } protected: @@ -298,29 +374,39 @@ protected: * ExternalWindow specific callbacks */ /** - A function called when the window is resized. + A callback for when the window size changes. + @note WIP this might need to get fed back into the host somehow. */ - virtual void onResize(uint width, uint height) + virtual void sizeChanged(uint width, uint height) { // unused, meant for custom implementations - return; - (void)width; - (void)height; + return; (void)width; (void)height; } + /** + A callback for when the window title changes. + @note WIP this might need to get fed back into the host somehow. + */ virtual void titleChanged(const char* title) { // unused, meant for custom implementations return; (void)title; } + /** + A callback for when the window visibility changes. + @note WIP this might need to get fed back into the host somehow. + */ virtual void visibilityChanged(bool visible) { // unused, meant for custom implementations return; (void)visible; } - virtual void transientWindowChanged(uintptr_t winId) + /** + A callback for when the transient parent window changes. + */ + virtual void transientParentWindowChanged(uintptr_t winId) { // unused, meant for custom implementations return; (void)winId; @@ -435,6 +521,7 @@ private: double scaleFactor; String title; bool isQuitting; + bool isStandalone; bool visible; PrivateData() @@ -445,6 +532,7 @@ private: scaleFactor(1.0), title(), isQuitting(false), + isStandalone(false), visible(false) {} } pData; diff --git a/examples/EmbedExternalUI/EmbedExternalExampleUI.cpp b/examples/EmbedExternalUI/EmbedExternalExampleUI.cpp index b08edf54..8787b384 100644 --- a/examples/EmbedExternalUI/EmbedExternalExampleUI.cpp +++ b/examples/EmbedExternalUI/EmbedExternalExampleUI.cpp @@ -60,12 +60,12 @@ public: { #if defined(DISTRHO_OS_MAC) NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc] init]; - [NSApplication sharedApplication]; + [NSApplication sharedApplication]; if (isEmbed()) { - // [fView retain]; - // [(NSView*)getParentWindowHandle() fView]; + // [fView retain]; + // [(NSView*)getParentWindowHandle() fView]; } else { @@ -78,7 +78,7 @@ public: initWithBytes:getTitle() length:strlen(getTitle()) encoding:NSUTF8StringEncoding]) - [fWindow setTitle:nsTitle]; + [fWindow setTitle:nsTitle]; // [fWindow setContentView:impl->view]; // [fWindow makeFirstResponder:impl->view]; @@ -205,9 +205,9 @@ protected: #endif } - void transientWindowChanged(const uintptr_t winId) override + void transientParentWindowChanged(const uintptr_t winId) override { - d_stdout("transientWindowChanged %lu", winId); + d_stdout("transientParentWindowChanged %lu", winId); #if defined(DISTRHO_OS_MAC) #elif defined(DISTRHO_OS_WINDOWS) #else