Not to necro an old thread, but here I am in 2025 facing the same problem and I wanted to first thank everyone for the discussion it was very helpful and I wanted to add some details that might be useful to others. I spent around a week in my free time trying to get this working before finding this thread, it was indeed "a slog."
@kgamble's use case in this scenario specifically desired two separate Workspace's, but as @pturmel more or less pointed out - the Vision & Perspective modules only use one workspace. You can test this by just clicking on every ResourceType (Like Styles, Views) inside the Perspective folder and you'll notice nothing changes with regards to your Perspective Workspace. Each tab in the TabbedResourceWorkspace can be a different ResourceType and this is because of overriding newResourceEditor like @paul-griffith mentioned. On top of this, notice that the root "Perspective" node has no right click actions and shares the same Workspace as Views/Styles when you click it.
As far as I can tell, multi-node modules like Perspective are structured similar to the following:
- Define a custom
MutableNavTreeNode to act as the parent node, make sure to @Override getWorkspaceName() otherwise clicking on the parent node won't switch the Workspace you might be clicking from.
public class CustomNavTreeNode extends MutableNavTreeNode {
Icon ICON = DesignerHook.RESOURCE_ICON;
String NAME = "Custom Multi Node Resource";
String TEXT = "Custom Multi Node Resource";
protected CustomNavTreeNode() {
super(true);
}
@Override
public Icon getIcon() {
return ICON;
}
@Override
public String getName() {
return NAME;
}
@Override
public String getText() {
return TEXT;
}
@Override
public String getWorkspaceName() {
return DesignerHook.WORKSPACE_NAME;
}
@Override
public void showPopupMenu(Component source, int x, int y, int modifiers, TreePath[] paths, List<AbstractNavTreeNode> selection) {
// Intentionally do nothing to suppress the right-click popup menu
}
}
- Register your custom
MutableNavTreeNode in the DesignerHook
public void startup(DesignerContext context, LicenseState activationState) throws Exception {
this.context = context;
CustomNavTreeNode rootFolder = new CustomNavTreeNode();
context.getProjectBrowserRoot().addChild(rootFolder);
...
}
- In your custom Workspace, add a constructor argument passing in your custom
MutableNavTreeNode as an argument. @kgamble's method of mapping to this.rootNode did not work for me. I received a ResourceFolderNode class casting error on Designer startup. Mapping to a custom private variable fixed it.
public class CustomResourceWorkspace extends TabbedResourceWorkspace {
public static final ResourceDescriptor DESCRIPTOR = ResourceDescriptor.builder()
.resourceType(CustomResource.RESOURCE_TYPE)
.nounKey("custom.noun")
.rootFolderText("Custom Nested Resource 1")
.rootIcon(DesignerHook.NESTED_RESOUCE_1_ICON)
.build();
private final CustomNavTreeNode navTreeParentNode;
public CustomResourceWorkspace(DesignerContext context, CustomNavTreeNode navTreeParentNode) {
super(context, DESCRIPTOR);
this.navTreeParentNode = navTreeParentNode;
}
...
- Create your
Workspace in the DesignerHook and register it with the context
public void startup(DesignerContext context, LicenseState activationState) throws Exception {
this.context = context;
CustomNavTreeNode rootFolder = new CustomNavTreeNode();
context.getProjectBrowserRoot().addChild(rootFolder);
CustomResourceWorkspace workspace = new CustomResourceWorkspace(context, rootFolder);
context.registerResourceWorkspace(workspace);
...
}
There ya go! You now have a parent node in the NavTree and a nested resource similar to Perspective's Views. To add additional nodes, you have to make custom ResourceFolderNode's and pass your CustomResourceWorkspace into the ResourceFolderNode's constructor (At least I think that's how you do it.)
The main reason this whole process is "a slog" is because (in my opinion) context.registerResourceWorkspace(workspace) creates a ResourceFolderNode for you without the javadocs really telling you this. Adding a note about this to the javadocs would make it more clear to future newbie module developers (like myself) what actually happens when you call registerResourceWorkspace.