Stacking Components one below another: setTopLeftPosition has no effect


#1

I need to create a vertical array of identical sets of UI controls. I have created a Component to host one set of controls, and then I create as many components as I need. Is that the right way?

Now, this is how I attempt to stack them vertically in the parent window:

    _editors.emplace_back();
    DelayEditor& newEditor = _editors.back();
    newEditor.setSize(getWidth(), newEditor.getHeight());

    auto rect = newEditor.getBoundsInParent();
    newEditor.setTopLeftPosition(0, previousEditorBottom);
    rect = newEditor.getBoundsInParent();

    addAndMakeVisible(newEditor);

This doesn't work. I only see one editor (I assume others are beneath it), although rect does reflect the change after setTopLeftPosition call.

_editors is std::deque<MyComponent>.


#2

Upon experimenting, I've realized that all the components but the first (i. e. all of them that I've tried to move) are entirely invisible, not just obscured. I've also tried

newEditor.setBounds(newEditor.getBounds().translated(0, previousEditorBottom))

with exactly the same effect as setTopLeftPosition.

The previousEditorBottom number is correct, specifically - 60 while the main window height is 400.

 

And then I've tried

newEditor.setBounds("parent.left, previousEditorId.bottom, parent.right, top + 60");

Also to no avail. And no, I didn't forget to setComponentID.

 


#3

If the Component you are adding your DelayEditor  has a parent, the top left corner is not at 0,0 any longer. To position in there you need to use getLocalBounds() of your Component. Maybe that is the problem.

BTW, no need to set componentID, that's just for your own reference afaik.

I created for these kind of jobs a module, which you can find here: https://github.com/audiokinematics/juceLayouts - maybe you find it helpful.

The code formatting is in the editor the rightmost combobox, here in german it's labeled "Stil", probably originally "style". Select "code" there.

 


#4

No, the parent is nullptr and getLocalBounds returns {0,0}.

 

The hierarchy is this: main component (window) -> my compound component (several of them in parallel) -> a couple widgets inside each compound.

 

Here's the link to the UI .cpp file in the repository: https://github.com/VioletGiraffe/sound-processor-plugin/blob/master/Source/PluginEditor.cpp#L95


#5

Ok, was a shot in the dark...

String expression = String("parent.left, ") + (previousEditorID.isEmpty() ? "parent.top" : (previousEditorID + ".bottom")) + ", parent.right, top + " + newEditor.getHeight(); 
newEditor.setBounds(expression);

I never tried the Component::setBounds with a string expression. Maybe try this with normal numbers instead?

Or did you want to use Rectangle::fromString:


String expression = String("parent.left, ") + (previousEditorID.isEmpty() ? "parent.top" : (previousEditorID + ".bottom")) + ", parent.right, top + " + newEditor.getHeight();
newEditor.setBounds (Rectangle<int>::fromString (expression));

But I don't think that your expression evaluates to something the setBounds can work with...

Good luck,
daniel


#6

Quick observation: I'd actually recommend not using the setBounds version that takes a string expression - it's something that may get deprecated one day. I've found it's rarely a good way to write your code, as it's hard to debug and maintain.


#7

Firstly, there's a bug. You're setting newEditor's height as newEditor.getHeight() - but that hasn't been set yet! Also, your code could be cleaner, what's rect for? Finally, to know why this doesn't work would require us knowing where/how previousEditorBottom is set. But if I were you I would simply do this in my parent component:

void resized ()
{
    auto lBounds = getLocalBounds();

    const int delayEdHeight = 100; // Whatever you wish

    for (auto it = _editors.begin(); it != _editors.end(); ++it)
        it->setBounds (lBounds.removeFromTop (delayEdHeight));
}

void addEditor ()
{
    _editors.emplace_back();
    addAndMakeVisible (_editors.back());
    resized();
}

Rectangle<T>::removeFromTop (along with removeFromBottom etc) is extremely useful for laying out components, used along with getLocalBounds. It chops a piece off the rectangle representing your component space, and returns the chopped off piece. Plugging that into a setBounds call means that you can repeatedly chop off pieces of a bigger rectangle, and use what's left for your remaining components. Not sure that explanation makes sense, but try it for yourself, it's revolutionized my layout code.


#8

Wait, isn't setBounds(RelativeRectangle) the way to implement layouts? I already use it inside my compound component and it works perfectly.

It's definitely not a good way to manually handle all the widgets in resizeEvent() every time you need to any kind of dynamic layout...


#9

Yes, it works, but no, it's not good. IMHO it's better just to use resized(), it's more flexible and easier to debug.

We have plans this year to come up with a better scheme, but it won't involve the RelativeRectangle stuff, I'd consider that to be an interesting but failed experiment.


#10

1. I actually call `setSize` with proper values inside the newEditor's constructor.

2. Rect is for debugging only.

3. previousEditorBottom is getBottom of the current bottom-most component (but as you can see, I should have no need for it if RelativeRect would work).

4. I was hoping to avoid this tedious manual labor in favor of automatic layout system. But I've tried your solution, and I still only see one "editor" component on screen, although there are 2 in the _editors container.

If I simply do

_editors.emplace_back(processor, channelId);
    _editors.back().setBounds(20, 100, 300, 60);
    addAndMakeVisible(_editors.back());

I will not see this component on the screen, even though the window (parent) is larger still. But if I do setBounds(0, 0, 300, 60) to eahc of the editors, I will finally see that they're right there one over another in the same coordinates.


#11

Fair enough. I only need it work this one time, and the UI isn't complicated. But I can't make it work no matter what I try. Please see my last reply below.


#12

4. I was hoping to avoid this tedious manual labor in favor of automatic layout system. But I've tried your solution, and I still only see one "editor" component on screen, although there are 2 in the _editors container.

That was exactly what I was thinking too. There exists the https://www.juce.com/doc/classStretchableLayoutManager which can lay out your editor components. However, a dynamic structure and referencing by array indices don't look maintainable to me.

That's why I added the layout class, which uses Component::SafePointers. The link again: https://github.com/audiokinematics/juceLayouts and since 5 minutes ago the api doc: http://audiokinematics.github.io/juceLayouts/ (just added)

HTH

 


#13

Managed to get manual resized() handling working, but it's ridiculously cvompliated for such an easy task. Looking forward to that solution you've mentioned.

 

P. S. I'm working on a small project (me and the project lead) with our OpenGL-based GUI. I've written a layout manager for it (vertical and horizontal layouts with support for margins and justification), it was much easier than I expected. 2 or 3 days worth of development, another 3 days of debugging, and it's fully working.


#14

You're pushing these things onto a vector? You realise that Components aren't by-value classes, right? You're probably resizing or adding a copy of the object you think you're dealing with.


#15

I couldn't push them into a vector because Components are not copy-contructible. I'm creating them in-place in std::deque. This definitely has nothing to do with the issue.

The code is a bit ugly, but I've managed to finalyl make it work. Thanks a lot for help!