commit e59b5a5c155bef245006b562ad465d83d065a6b5
parent afcc3a98ac7a7a181007106e6a5b4b74904e0a33
Author: falkTX <falktx@falktx.com>
Date: Thu, 26 Aug 2021 11:40:43 +0100
Reorganize ExternalWindow methods and add documentation
Signed-off-by: falkTX <falktx@falktx.com>
Diffstat:
2 files changed, 189 insertions(+), 101 deletions(-)
diff --git 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
@@ -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