Modular UI questions

So my team and I have decided to use Juce in our application, I gotta say it’s really awesome and it’s so easy to work with compared to other options we looked at.

We are looking to build a Photoshop / Visual Studio type interface with toolbox docking and undocking elements and have attempted to recreate this however we are stuck. What we want is to have a child window that acts like a normal window (like toolboxes essentially, that can be draggable outside the parent window limits into its own window instance) but want the parent window to be able to receive the draggable event in order to dock a window into a panel.

Now the part that we are stuck on is that we can’t seem to get any Juce window to trigger a drag and drop event in another Juce window. An actual child window works however it cannot be dragged outside the parent’s window.

Any ideas?

Glad you’re enjoying it!

If you have a look at the window tab dragging/docking in Tracktion, we do that kind of thing, so it’s certainly possible. But you might not want to use the DragAndDropContainer for this, it’s not quite the same use-case… In tracktion IIRC we wrote our own custom handler that creates the windows and manages the mouse-drag events. Maybe explain in more detail what you’re struggling with and I might be able to give you some hints?

This would be a very regularly used feature if you happened to build it into the framework Jules…

1 Like

It’s quite tricky to know how to make a generic system for this kind of thing… For example, the implementation of the Tracktion tab dragging would have almost nothing in common with a traditional photoshop-style docking sysrtem. I’m not really sure what the common denominator would look like…

It feels conceptually simple. Presumably the longer I think about it the more complicated it will become though.

The simplest implementation includes:

  • Windows that can be dragged over a ‘dock’ component When they to the dock responds by showing the window can be dropped there and the heavyweight window removed if the user releases the mouse.
  • Windows in the dock can be resized in some cute fashion scaling the other windows around them like there’s a resizer bar between them.
  • Windows in the dock have title bars.
  • Dragging the title bar allows the window to be removed from the dock.
  • Multiple dock locations are supported, e.g. left of screen, right of screen.
  • Default positions for new windows and saving of positions

That seems pretty generic?

Then from there the possibilities are:

  • Minimizing windows.
  • Tabbed windows
  • Floating docks…

We could use this here in an application actually, we’ve got a concertina panel which would be much better in a VisualStudio / Photoshop style dockable windows setup…

Actually, just looking at Traktion. Isn’t traktion just implementing a subset of the features Visual Studio has for dragging windows around?

Well, it’s very specific to tabs. It’s more like the behaviour you get in a browser.

The thing is there’s a lot of different ways you can layout all these objects and then save/load the state. Are you docking to child Components, buttons, tabs etc. What does docking to a tab actually mean? (In Tracktion we have a custom TabButtonBar than controls the Component below it i.e. there’s no generic TabbedComponent) There’s also a lot of things to represent (more below). Believe me, it’s harder than it looks.

The general tab dragging/dropping isn’t too hard, it does use the juce::DragAndDropContainer but there’s a hell of a lot of stuff in between. You need to ensure the original Component stays alive to send the drag events (and this could be represented by no change, going invisible or disappearing completely), then you need a new window to represent the thing you’re dragging (in our case the tab button), then any attached Components (e.g. the Edit window that floats around with it).

Once you’ve got the Components semi-off the original one you need to detect this drag and act accordingly. For simplicity we show a red area when a tabbed would be docked on to a new TabButtonBar but in Tracktion you can also drag an Edit tab on to a Track to create an Edit Clip.

Once a source has been dropped you not only need to react to that but also clean up the original source e.g. by removing the corresponding tab. There’s also the case where no docking happens and you have to put all this stuff back where it came from.

You also need appropriately structured code to be able to do this at all. For example, you can’t have back pointers to windows in your tabs or Components you’ve moving. You also have to make sure any non-UI objects aren’t part of the window or these will dangle too.

I’m not saying it isn’t possible to do this in a generic way, it almost certainly is, just that there’s tons of gotchas and custom behaviour to build in to it. You may well end up with something that’s so confusing to customise it would be better to write a more concise, custom solution. I’m sure lambda’s would help with this nowadays though, I had to do it without them a few years ago.

If you do manage to get something up and running I’d be very interested in it though…

2 Likes

Didn’t someone post some code for a version of this a year or two ago … I’m going searching before I start typing ;0)

Ok, so I’ve sketched something out and it really didn’t seem that complicated so far. Maybe I’m missing some aspect of it…?? I’ll post a project up on github, it’s rough and I’ve not dealt with a bunch of drag position offset things … but what’s missing?

Saving and restoring positions I can see two ways of tackling and haven’t touched yet.

Don’t be too harsh on the style, the keyboard is still smouldering from the typing…I’ll tidy it up shortly!

So I’ve done it using a generic concept of a Dock which could be applied differently to tabs, windows, or any other arrangement. I think there are some obvious types of docks. I need to look at detecting the frontmost dock so I can do nested docks I think as well, tabs within docked windows or whatever …

I don’t really understand the ‘ensuring the component stays alive’ part. Do you mean not having to delete and recreate the component?

To be fair in my little example, I’ve left the component where it is until my representation of it has been dragged to a new home, but I think doing what you’ve said above isn’t too many steps away. The only trick is going to be caching the original location of the component which I don’t have a hook for yet. That could be done by each individual dock, because my DockableWindowManager has no knowledge of how the dock is actually laid out. But that is going to add some complexity to each dock implementation …

Nice job!

Yes, as I said, the actual drag and dropping part of the code isn’t too bad, it’s all the niceties and edge cases that add bloat (as always). If you’ve ever added animations to things you’ll know how much extra code is needed for that!

I mean that you can’t delete the Component that the drag started on (e.g. the tab) because it needs to send drag events. In your example, you’ve just left it where it is so isn’t a problem but in more complex examples (and in Tracktion) you would need to make it look like the tab has been detached but isn’t (we use a trick of making the tab 1 pixel wide and don’t paint anything to make it look invisible.

We also have a pre-detached state where you can drag to re-order tabs. The detachment is only triggered when the tab is dragged off the bar either to the sides or vertically.

Like I said, none of this is too tricky, it’s just when you add support for all the niceties the code starts to bloat. Especially in a generic form like this as you need virtual methods for every action someone might want to customise.

Looking forward to seeing it progress though!

Ah - now I see what you are getting at. That’s pretty filthy. Let me have a bit more of a play…

If I go for setVisible(false) on the component being dragged I still get the mouse drag events. The JUCE tabbedcomponent stuff would need to be set up as a dock tho, I suspect i cannot apply this easily by just inheriting from it…

And hence you’re back to a completely custom solution…

In all seriousness though this was one of the problems we had. All the tab stuff was already written and there was a lot of custom code in there to rename them, re-order them etc. I had to write all the detachable drag and drop stuff on top of that which is probably why it’s not quite as clean as it could be.

You have to be quite careful when dealing with existing complex juce Components like this as it’s quite easy to break them if you override the wrong thing…

Ok, checked in a bunch of improvements. Handles removing the windows from the dock and placing them much more nicely now, you can reorder them using the same code too which is nice.

Anyway, this has got totally out of hand, my point was I think there is a really useful generic way of handling a lot of this … and it’d be really handy if it was integrated into JUCE at the source … so that we didn’t need to build custom tabbed component stuff for it :slight_smile:

And it rather cutely sorts out reordering of components … maybe replacing existing code.

Ha. As does everything that starts with “I think I’ve got an idea…”

Why don’t you package it as a module (or even just a single header) so users can use it and provide feedback?
It’s more likely to get in to JUCE with some real world testing and a bit of fleshing out.

Yeah, I’ll try and get it into a single header…with some generous use of inline it’ll work…