I try to draw button in a ListBox. But it doesn’t show. Area prepared for it, when I use removeFromLeft, but no button shown. Other elements like AudioThumbnail or rectangles are drawn.
It shows if I do not use ListBox.
Model:
class TracksListModel : public ListBoxModel
{
public:
virtual int getNumRows() override;
void paintListBoxItem (int rowNumber, Graphics& g,
int width, int height, bool rowIsSelected) override
{
tracks[rowNumber]->setBounds(0, rowNumber * height, width, height);
tracks[rowNumber]->paint(g);
}
void add(TrackComponent *track);
private:
OwnedArray<TrackComponent> tracks;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TracksListModel)
};
Component:
class TrackComponent : public Component
{
public:
void paint(Graphics &g) override
{
auto area = getLocalBounds();
auto top = area.removeFromTop(20);
g.setColour (Colours::green);
g.fillRect (top);
playButton.setBounds(area.removeFromLeft(100));
addAndMakeVisible(&playButton);
paintIfFileLoaded(g, area);
}
private:
AudioThumbnail* thumbnail;
TextButton playButton;
void paintIfFileLoaded(Graphics& g, Rectangle<int> area)
{
g.setColour (Colours::red);
thumbnail->drawChannels (g,
area.removeFromTop(80),
0.0,
thumbnail->getTotalLength(),
1.0f);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrackComponent)
};
If you want to react to mouse events, you capture the event in its callback, change whatever you need to change, and call repaint(). The painting mechanism is asynchronous. You call repaint() on a component to mark it as dirty, so that soon, but later, the message loop will call paint() on it. You never call paint() directly, and you never call things from paint() that would call repaint(). Think about it -paint() calls something that calls repaint(), that makes paint() be called later, that calls something that calls repaint(), that makes paint() be called later… and so on. paint() should only draw whatever is in the component at any moment, without changing its content.
In addition to what @kamedin said, also note that Component, which is the base class of all JUCE widgets, is also derived from MouseListener which has all sorts of callbacks for mouse events.
If you want to react to mouse events on your own class derived from Component, you can simply override those callbacks coming from MouseListener.
If you want to react to mouse events coming also from other widgets, you can register yours as the listener for those events too via the other Component's addMouseListener() method, and inside the callbacks you can determine which Component received the event by looking at the eventComponent member of their MouseEvent argument
Add a mouse listener for your ListBox, catching also events for child components (it’s an argument given to addMouseListener() ) then every time you receive a mouse event you can find which row component it affects using ListBox::getRowContainingPosition() and ListBox::getComponentForRowNumber(), then act accordingly
Depending on the amount of mouse interaction that you want to implement, the approach above can become tricky, in which case I’d abandon the idea of having tracks as rows of a ListBox, and implement them as Components that you directly manage inside a parent panel that serves as their “container”, which you can embed in a Viewport to make it scrollable