Browse Source

Reorganize ExternalWindow methods and add documentation

Signed-off-by: falkTX <falktx@falktx.com>
pull/314/head
falkTX 3 years ago
parent
commit
e59b5a5c15
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
2 changed files with 189 additions and 101 deletions
  1. +183
    -95
      distrho/extra/ExternalWindow.hpp
  2. +6
    -6
      examples/EmbedExternalUI/EmbedExternalExampleUI.cpp

+ 183
- 95
distrho/extra/ExternalWindow.hpp View File

@@ -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;



+ 6
- 6
examples/EmbedExternalUI/EmbedExternalExampleUI.cpp View File

@@ -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


Loading…
Cancel
Save