Struggling with adding a viewport as a tab in the TabbedComponent

I’m having trouble with getting a viewport to show inside a tab in my application. I have tried reading forum posts, the juce source, and various examples of implemenation, but I’m not able to get my thoughts sorted on this particular issue.

What I have from before that has been working is a MainComponent that owns a TabbedComponent. The TabbedComponent contains three tabs where one of the tabs contains a child component which is the AudioDeviceSelectorComponent. This component will resize on its own depending on how many options are available for the selected sound driver. This causes an issue in some cases where the UI spills outside the application bounds, rendering them inaccessible. This is where I want to use a viewport.

What I don’t understand is how I do this. By adding the viewport as a tab and making the parent of the AudioDeviceSelectorComponent the viewport’s viewed component, and also giving both hardcoded values for bounds the AudioDeviceSelectorComponent will show. Only problem is that it isn’t possible to make it not overlay the tabs at the top, since Component::getHeight() is not set in the constructor and cannot be used to push the bounds lower. Also no scrollbar appears no matter how small the viewport is or how big the viewed component is. Another thing is that overriding TabbedComponent::resized() to access getHeight() will stop any of the tabs from drawing anything at all.

How do I implement a viewport in my code?

Here is my code before any of my attempts at implementing the viewport:

MainComponent:

MainComponent::MainComponent(juce::ValueTree v) :
    tree{ v },
    tabbedComponent{ v }
{ 
    addAndMakeVisible(tabbedComponent);
    setSize(400, 290);
}

void MainComponent::resized()
{
    tabbedComponent.setBounds(getLocalBounds());
}

TabbedComp:

TabbedComp::TabbedComp(juce::ValueTree v) :
    TabbedComponent(juce::TabbedButtonBar::TabsAtTop),
    tree{ v },
    audioSetupPage{ v },
    appSettingsPage{ v },
    debugPage{ v }
{
    auto color = getLookAndFeel().findColour(juce::TabbedComponent::backgroundColourId);

    addTab("App Settings", color, &appSettingsPage, true);
    addTab("Audio Settings", color, &audioSetupPage, true);
    addTab("Debug", color, &debugPage, true);
    
}

AudioSetupPage:

AudioSetupPage::AudioSetupPage(juce::ValueTree v) :
    tree{ v }        
{
    // Fetch audio device manager from the value tree.
    auto deviceManager = dynamic_cast<anyMidi::AudioDeviceManagerRCO*>
        (
            tree.getParent().getChildWithName(anyMidi::AUDIO_PROC_ID).getProperty(anyMidi::DEVICE_MANAGER_ID).getObject()
        );

    audioSetupComp = std::make_unique<juce::AudioDeviceSelectorComponent>
        (
            *deviceManager,
            0,      // min input ch
            256,    // max input ch
            0,      // min output ch
            0,      // max output ch
            false,  // can select midi inputs?
            true,   // can select midi output device?
            false,  // treat channels as stereo pairs
            false   // hide advanced options?
        );

    addAndMakeVisible(*audioSetupComp);
}

void AudioSetupPage::resized()
{
    audioSetupComp->setBounds(getLocalBounds().withWidth(getWidth()));
}

Are you setting the viewport Bounds to that of the tabbed window before you add the AudioSetupPage, which you then also set the bounds to that of the viewport?

1 Like

Okay, so by implementing the TabbedComp constructor like this, I get a viewport inside a tab that I can scroll in:

TabbedComp::TabbedComp(juce::ValueTree v) :
    TabbedComponent(juce::TabbedButtonBar::TabsAtTop),
    tree{ v },
    audioSetupPage{ v },
    appSettingsPage{ v },
    debugPage{ v }
{
    
    audioSetupViewport.setBounds(0, 0, 400, 100);
    audioSetupPage.setBounds(0, 0, 400, 500);
    audioSetupViewport.setViewedComponent(&audioSetupPage, false);

    auto color = getLookAndFeel().findColour(juce::TabbedComponent::backgroundColourId);

    addTab("App Settings", color, &appSettingsPage, true);
    addTab("Audio Settings", color, &audioSetupViewport, true);
    addTab("Debug", color, &debugPage, true); 
}

The problem here is that the AudioDeviceSelectorComponent will resize itself, but the size of the audioSetupViewport and the audioSetupPage cannot be resized to fit it later. Merely implementing an empty method like this, will make none of the tabs render:

void TabbedComp::resized()
{
}

Maybe I’m misunderstanding something here but it seems to me that you’ve got something not quite right. Any child component that is to be laid out ‘inside’ another component - whether its a view or some other UI object - has to respond to the “resized()” method by setting its bounds to that of the parent.

So the AudioDeviceSelectroComponent::resized() should have something like “setBounds(getLocalBounds())”, meaning that the resize method calls will propagate from parent->children, and everything needs to resize to the bounds of their parent.

Override the method, add the setBounds() call and see what happens.

1 Like