Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## [1.2.x]

### Fixed
- Blocks embedded in the graph return to their embedded state when the plugin window is closed.

## [1.2.0]

### Added
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Copilot Instructions for Element

## Role

You are an expert in JUCE and in desktop audio application UI. Element is a JUCE application: prefer JUCE's own idioms and classes over hand-rolled equivalents, respect the message-thread / audio-thread split, and consult the JUCE API docs and source in `build/_deps/juce-src/modules/` rather than assuming an API's behaviour. Apply the same expert eye to UI work — component lifetime, layout in `resized()`, `LookAndFeel` usage, and keeping the graph editor's visual state in sync with the underlying `ValueTree` model.

## General Conventions

- **Always check documentation**: Before making assumptions about APIs, libraries, or tools, consult the official documentation first.
Expand Down
6 changes: 3 additions & 3 deletions include/element/element.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ extern "C" {
#ifdef _WIN32
// windows exports
#if defined(EL_SHARED_BUILD)
#define EL_API __declspec(dllexport)
#define EL_API __declspec (dllexport)
#pragma warning(disable : 4251)
#elif defined(EL_SHARED)
#define EL_API __declspec(dllimport)
#define EL_API __declspec (dllimport)
#pragma warning(disable : 4251)
#endif
#define EL_PLUGIN_EXPORT EL_EXTERN __declspec(dllexport)
#define EL_PLUGIN_EXPORT EL_EXTERN __declspec (dllexport)
#else
#if defined(EL_SHARED) || defined(EL_SHARED_BUILD)
#define EL_API __attribute__ ((visibility ("default")))
Expand Down
2 changes: 1 addition & 1 deletion include/element/porttype.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class PortType {
inline bool operator!= (const ID& id) const { return (type != id); }
inline bool operator== (const PortType& t) const { return (type == t.type); }
inline bool operator!= (const PortType& t) const { return (type != t.type); }
inline bool operator<(const PortType& t) const { return (type < t.type); }
inline bool operator< (const PortType& t) const { return (type < t.type); }

inline operator int() const { return (int) this->type; }

Expand Down
1 change: 1 addition & 0 deletions include/element/tags.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ static const juce::Identifier controllers = "controllers";
static const juce::Identifier collapsed = "collapsed";
static const juce::Identifier delayCompensation = "delayCompensation";
static const juce::Identifier displayMode = "displayMode";
static const juce::Identifier lastDisplayMode = "lastDisplayMode";
static const juce::Identifier enabled = "enabled";
static const juce::Identifier gain = "gain";
static const juce::Identifier graphs = "graphs";
Expand Down
2 changes: 1 addition & 1 deletion include/element/version.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class Version {
inline bool operator== (const Version& o) const noexcept { return _hex == o._hex; }
inline bool operator!= (const Version& o) const noexcept { return _hex != o._hex; }
inline bool operator> (const Version& o) const noexcept { return _hex > o._hex; }
inline bool operator<(const Version& o) const noexcept { return _hex < o._hex; }
inline bool operator< (const Version& o) const noexcept { return _hex < o._hex; }
inline bool operator>= (const Version& o) const noexcept { return _hex >= o._hex; }
inline bool operator<= (const Version& o) const noexcept { return _hex <= o._hex; }

Expand Down
4 changes: 1 addition & 3 deletions src/engine/clapprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,7 @@ class CLAPHost final : public CLAPBaseHost
pfds.reserve (fds.size());
for (const auto& f : fds)
{
struct pollfd p
{
};
struct pollfd p {};
p.fd = f.first;
if (f.second & CLAP_POSIX_FD_READ)
p.events |= POLLIN;
Expand Down
4 changes: 1 addition & 3 deletions src/filesystemwatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,7 @@ class FileSystemWatcher::Impl
CFArrayRef paths { nullptr };
dispatch_queue_t queue { nullptr };
FSEventStreamRef stream { nullptr };
struct FSEventStreamContext context
{
};
struct FSEventStreamContext context {};
};
#endif

Expand Down
12 changes: 11 additions & 1 deletion src/services/guiservice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ void GuiService::showPluginWindowsFor (const Node& node, const bool recursive, c
{
if (! node.isGraph())
{
if (force || (bool) node.getProperty ("windowVisible", false))
if (force || (bool) node.getProperty (tags::windowVisible, false))
presentPluginWindow (node, force);
return;
}
Expand All @@ -613,7 +613,17 @@ void GuiService::presentPluginWindow (const Node& node, const bool focus)

auto* window = windowManager->getPluginWindowFor (node);
if (! window)
{
// Flag the window visible up front. A graph block embedding this node's editor
// watches this property and releases the editor synchronously, freeing the node's
// single editor slot so the window can create the node's real UI instead of
// falling back to a generic one.
Node target (node);
target.setProperty (tags::windowVisible, true);
window = windowManager->createPluginWindowFor (node);
if (window == nullptr)
target.setProperty (tags::windowVisible, false);
}

if (window != nullptr)
{
Expand Down
47 changes: 42 additions & 5 deletions src/ui/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,14 @@ BlockComponent::BlockComponent (const Node& graph_, const Node& node_, const boo
auto blockData = node.getBlockValueTree();
displayModeValue = blockData.getPropertyAsValue (tags::displayMode, nullptr);
displayModeValue.addListener (this);

// Updates synchronously: GuiService flags the window visible before building it, and
// this block has to give up its embedded editor before the window creates its own.
windowVisibleValue = node.getPropertyAsValue (tags::windowVisible, true);
windowVisibleValue.addListener (this);
if (! (bool) node.getProperty (tags::windowVisible, false))
blockData.removeProperty (tags::lastDisplayMode, nullptr);

const auto idm = getDisplayModeFromString (displayModeValue.getValue());
setDisplayModeInternal (idm, false);
if (idm == Embed)
Expand Down Expand Up @@ -263,6 +271,7 @@ BlockComponent::~BlockComponent() noexcept
nodeName.removeListener (this);
hiddenPorts.removeListener (this);
displayModeValue.removeListener (this);
windowVisibleValue.removeListener (this);
deleteAllPins();
}

Expand Down Expand Up @@ -384,6 +393,27 @@ void BlockComponent::setDisplayMode (DisplayMode mode)
setDisplayModeInternal (mode, false);
}

void BlockComponent::stashDisplayModeForWindow()
{
if (displayMode != Embed)
return;

node.getBlockValueTree().setProperty (
tags::lastDisplayMode, getDisplayModeKey (displayMode), nullptr);
setDisplayMode (Small);
}

void BlockComponent::restoreDisplayModeAfterWindow()
{
auto blockData = node.getBlockValueTree();
const auto stashed = blockData.getProperty (tags::lastDisplayMode).toString();
if (stashed.isEmpty())
return;

blockData.removeProperty (tags::lastDisplayMode, nullptr);
setDisplayMode (getDisplayModeFromString (stashed));
}

void BlockComponent::setPortAlignment (PortAlignment newAlign)
{
node.getBlockValueTree().setProperty (
Expand Down Expand Up @@ -427,7 +457,14 @@ void BlockComponent::valueChanged (Value& value)
{
update (false, false);
}
else if (nodeObject.refersToSameSourceAs (nodeObject))
else if (windowVisibleValue.refersToSameSourceAs (value))
{
if ((bool) value.getValue())
stashDisplayModeForWindow();
else
restoreDisplayModeAfterWindow();
}
else if (nodeObject.refersToSameSourceAs (value))
{
willRemoveConn.disconnect();
clearEmbedded();
Expand Down Expand Up @@ -752,10 +789,7 @@ void BlockComponent::makeEditorActive()
}
else if (node.isValid())
{
if (displayMode == Embed)
{
setDisplayMode (Small);
}
stashDisplayModeForWindow();
ViewHelpers::presentPluginWindow (this, node);
}
}
Expand Down Expand Up @@ -1361,12 +1395,15 @@ void BlockComponent::addDisplaySubmenu (PopupMenu& menuToAddTo)
const auto m = static_cast<BlockComponent::DisplayMode> (i);
const bool enabled = m == BlockComponent::Embed ? detail::supportsEmbed (node) : true;
dMenu.addItem (BlockComponent::getDisplayModeName (m), enabled, mode == m, [this, block, m]() {
// Choosing a mode explicitly abandons any mode stashed for a plugin window.
auto b = block;
b.removeProperty (tags::lastDisplayMode, nullptr);
b.setProperty (tags::displayMode, BlockComponent::getDisplayModeKey (m), nullptr);
forEachSibling ([m] (BlockComponent& sibling) {
if (! sibling.isSelected())
return;
auto sb = sibling.node.getBlockValueTree();
sb.removeProperty (tags::lastDisplayMode, nullptr);
sb.setProperty (tags::displayMode, BlockComponent::getDisplayModeKey (m), nullptr);
});

Expand Down
26 changes: 21 additions & 5 deletions src/ui/block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ class BlockComponent : public Component,
inline static DisplayMode getDisplayModeFromString (const String& str)
{
auto s = str.toLowerCase().trim();
if (str == "normal")
if (s == "normal")
return Normal;
if (str == "compact")
if (s == "compact")
return Compact;
if (str == "small")
if (s == "small")
return Small;
if (str == "embed")
if (s == "embed")
return Embed;
return Normal;
}
Expand Down Expand Up @@ -274,7 +274,8 @@ class BlockComponent : public Component,
Value nodeEnabled,
nodeName,
hiddenPorts,
displayModeValue;
displayModeValue,
windowVisibleValue;

int numIns = 0, numOuts = 0;

Expand Down Expand Up @@ -407,6 +408,21 @@ class BlockComponent : public Component,
void setDisplayModeInternal (DisplayMode, bool);
void clearEmbedded();

/** Steps out of Embed mode so this node's editor can be shown in a plugin window.

A node's editor can only live in one place at a time, so an embedded block has to
give up its editor before a plugin window can create one. The mode being left is
remembered on the node so it can be returned to when the window closes.
*/
void stashDisplayModeForWindow();

/** Returns to the display mode stashed by stashDisplayModeForWindow().

Does nothing if no mode was stashed, which is the case for blocks that were never
embedded when their plugin window was opened.
*/
void restoreDisplayModeAfterWindow();

void componentMovedOrResized (Component& component,
bool wasMoved,
bool wasResized) override;
Expand Down
Loading