Correct way to change the background color and label font size of a GroupComponent?


#1

I am working on a GroupComponent system for labeling and organizing my GUI:

It looks like this by default:

I want to change the background colors of these elements without losing the nice rounded outline rectangles and text labels.

I tried doing it like this:
color%20-%20labeledGroup%20graphic%20override

But when I do that, it wipes out the outline and the labeled text:

How do I set a new background color without losing these elements? How do I also change the text size of the labels?

Thanks


#2

Have a look at the documentation here: https://docs.juce.com/master/structGroupComponent_1_1LookAndFeelMethods.html

and read the tutorials/forum about writing your own “Look And Feel” classes.

also, have a look at the paint routine for the Group Component. it’s not filling anything, so your window’s background color is what you’re seeing.

if you’re going to inherit from GroupComponent, be sure to call the base class’s paint function inside your own paint function so the GroupComponent outline is drawn.

LabeledGroup::paint(Graphics& g)
{
    GroupComponent::paint(g); //call base class paint function
    //then do your own painting on top of the base class's painting.
    g.setColour( Colours::red );
    g.fillRect( getLocalBounds() );
}

#3

Hey Matkat,

Yeah I understand GroupComponent does not have a basic feature for built in background painting. I am guessing I might have to draw a new rectangle inside each group component with rounded corners to “match” the expected shape of the natural GroupComponent border to do this.

This method:

void LabeledGroup::paint(Graphics& g)
{
	//background stuff
	GroupComponent::paint(g);
	g.setFont(4);
	g.setColour(Colours::black);
	g.fillRect(getLocalBounds());
	
}

Still provides the same blacked out square result with no text headers for the groups.

Any further thoughts? Also on setting text size for the headers? g.setFont(4) seems to do nothing even when g.setColour and g.fillRect are removed.


#4

Call GroupComponent::paint after your fill, instead of before.


#5

It sounds like you don’t understand how painting works in JUCE, Mike. Have a look at the documentation for Graphics::setFont


#6

Perfect! Thanks. That helped the outlines still be drawn overtop of the fill. But the fill was still applying just a full black rectangle which was spilling outside the GroupComponent margin.

So I applied the idea I said before which was to draw a rounded rectangle and shift it a bit to compensate which has worked nicely:

void LabeledGroup::paint(Graphics& g)
{
	//background stuff
	g.setColour(Colours::black);
	g.fillRoundedRectangle(3, 9, getWidth()-5, getHeight()-12, 6);
	GroupComponent::paint(g);
	g.setFont(4);
	
}

Result:

Do you have any suggestion on how I can set the default font being used for my group labels? I have read and tried a few things but can’t figure out where/how this is specified.

Thanks again.


#7

as I said earlier, learn about LookAndFeel classes and writing your own implementation of the function that the GroupComponent::paint() function calls to paint the outline. See the original implementation for a code example to follow, specifically where it sets the font size, font color, and font that gets drawn on the screen:

Don’t be afraid to right-click -> Jump To Definition or right-click -> show Call Hierarchy to navigate to the implementation of these functions in order to understand how to use them.


#8

Thanks Matkat! That was very helpful. I was able to make a LookAndFeel with some modifications. I will need to develop a second object of this class so I can have different parameters for the big groups and the individual label groups, but that should be easy enough now that I know how to do this.

Here’s what I got:

I found a cool slider design in the LookAndFeel V2 that I prefer to the V4 version so I’m using that now instead.

This is how I did the changes:

/*
  ==============================================================================

    LookAndFeel.h
    Created: 25 Oct 2018 9:39:10pm
    Author:  Mike

  ==============================================================================
*/

#pragma once
#include "JuceHeader.h"


class OtherLookAndFeel : public LookAndFeel_V4
{
public:
	OtherLookAndFeel()
	{
		//setColour(Slider::thumbColourId, Colours::red);
	}

	
	void drawRotarySlider(Graphics& g, int x, int y, int width, int height, float sliderPos,
		const float rotaryStartAngle, const float rotaryEndAngle, Slider& slider) override
	{
		auto bounds = Rectangle<int>(x, y, width, height).toFloat().reduced(6); //Margin reduction for slider.

		//auto radius = jmin(bounds.getWidth(), bounds.getHeight()) / 2.0f;
		const float radius = jmin(bounds.getWidth() / 2, bounds.getHeight() / 2) - 2.0f;
		const float centreX = x + width * 0.5f;
		const float centreY = y + height * 0.5f + (radius *0.2); //controls vertical shift of center of knobs
		const float rx = centreX - radius;
		const float ry = centreY - radius;
		const float rw = radius * 2.0f;
		const float angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
		const bool isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled();

		if (radius > 12.0f) //DESCRIBES BIGGER KNOB WITH PERCENTAGE ROTARY INDICATOR
		{
			if (slider.isEnabled())
				g.setColour(slider.findColour(Slider::rotarySliderFillColourId).withAlpha(isMouseOver ? 1.0f : 0.7f));
			else
				g.setColour(Colour(0x80808080));

			const float thickness = 0.7f;

			{
				Path filledArc;
				filledArc.addPieSegment(rx, ry, rw, rw, rotaryStartAngle, angle, thickness);
				g.fillPath(filledArc);
			}

			{
				const float innerRadius = radius * 0.2f;
				Path p;
				p.addTriangle(-innerRadius, 0.0f,
					0.0f, -radius * thickness * 1.1f,
					innerRadius, 0.0f);

				p.addEllipse(-innerRadius, -innerRadius, innerRadius * 2.0f, innerRadius * 2.0f);

				g.fillPath(p, AffineTransform::rotation(angle).translated(centreX, centreY));
			}

			if (slider.isEnabled())
				g.setColour(slider.findColour(Slider::rotarySliderOutlineColourId));
			else
				g.setColour(Colour(0x80808080));

			Path outlineArc;
			outlineArc.addPieSegment(rx, ry, rw, rw, rotaryStartAngle, rotaryEndAngle, thickness);
			outlineArc.closeSubPath();

			g.strokePath(outlineArc, PathStrokeType(slider.isEnabled() ? (isMouseOver ? 2.0f : 1.2f) : 0.3f));
		}
		else //DESCRIBES SMALLER KNOB WITH SIMPLER DESIGN
		{
			if (slider.isEnabled())
				g.setColour(Colour(0x14145495).withAlpha(isMouseOver ? 1.0f : 0.7f));
			else
				g.setColour(Colour(0x80808080));

			Path p;
			p.addEllipse(-0.4f * rw, -0.4f * rw, rw * 0.8f, rw * 0.8f);
			PathStrokeType(rw * 0.12f).createStrokedPath(p, p); //muliplier describes thickness of outer circle stroke

			p.addLineSegment(Line<float>(0.0, -radius * 0.15f, 0.0f, -radius *0.8f), rw * 0.18f);  
			//2nd param describes how far from center to start
			//4th param describes length
			//last param describes thickness of line

			g.fillPath(p, AffineTransform::rotation(angle).translated(centreX, centreY));
		}
	}

	void drawGroupComponentOutline(Graphics& g, int width, int height,
		const String& text, const Justification& position,
		GroupComponent& group)
	{
		const float textH = 11.0f; //controls header text
		const float indent = 3.0f;
		const float textEdgeGap = 4.0f;
		auto cs = 5.0f;

		Font f(textH);

		Path p;
		auto x = indent;
		auto y = f.getAscent() - 3.0f;
		auto w = jmax(0.0f, width - x * 2.0f);
		auto h = jmax(0.0f, height - y - indent);
		cs = jmin(cs, w * 0.5f, h * 0.5f);
		auto cs2 = 2.0f * cs;
				
		auto textW = text.isEmpty() ? 0 : jlimit(0.0f, jmax(0.0f, w - cs2 - textEdgeGap * 2), f.getStringWidth(text) + textEdgeGap * 2.0f);
		auto textX = cs + textEdgeGap;

		if (position.testFlags(Justification::horizontallyCentred))
			textX = cs + (w - cs2 - textW) * 0.5f;
		else if (position.testFlags(Justification::right))
			textX = w - cs - textW - textEdgeGap;

		p.startNewSubPath(x + textX + textW, y);
		p.lineTo(x + w - cs, y);

		p.addArc(x + w - cs2, y, cs2, cs2, 0, MathConstants<float>::halfPi);
		p.lineTo(x + w, y + h - cs);

		p.addArc(x + w - cs2, y + h - cs2, cs2, cs2, MathConstants<float>::halfPi, MathConstants<float>::pi);
		p.lineTo(x + cs, y + h);

		p.addArc(x, y + h - cs2, cs2, cs2, MathConstants<float>::pi, MathConstants<float>::pi * 1.5f);
		p.lineTo(x, y + cs);

		p.addArc(x, y, cs2, cs2, MathConstants<float>::pi * 1.5f, MathConstants<float>::twoPi);
		p.lineTo(x + textX, y);

		auto alpha = group.isEnabled() ? 1.0f : 0.5f;


		g.setColour(Colours::black);
		g.fillPath(p);

		g.setColour(group.findColour(GroupComponent::outlineColourId)
			.withMultipliedAlpha(alpha));

		g.strokePath(p, PathStrokeType(2.0f));

		g.setColour(group.findColour(GroupComponent::textColourId)
			.withMultipliedAlpha(alpha));
		g.setFont(f);
		g.drawText(text,
			roundToInt(x + textX), 0,
			roundToInt(textW),
			roundToInt(textH),
			Justification::centred, true);
	}

	Font getSliderPopupFont(Slider&)
	{
		return Font(10.0f, Font::bold);
	}




};

Essentially I just copied and pasted the definitions from the ROLI code on GitHub and then modified the code. Then I used:

setLookAndFeel(&otherLookAndFeel);

In MainContentComponent. Is this the right idea?

The last thing I can’t figure out is how to change the slider textBox font. I can’t see any clear reference to how to do it in the GitHub.

Any help? What code would I need to use in my existing file to make this font size change?

Thanks. I’m starting to get the hang of this I think. I learn more with each change I implement. Appreciate the help.


#9

Read the documentation for Graphics::setFont(). BOTH functions with that name…

As well as label::getFont/setFont

You’ll end up writing your own widget since the label in the slider class is not accessible and should just be hidden via the slider constructor.

struct Widget : public Component { 
    Slider slider;
    Label label;
};

Add resized(), LookAndFeel functions and stuff to place everything where you want it.

AKA the typical JUCE workflow when writing custom GUIs


#10

Okay MatKat. I figured that one must be more difficult because as you say the label in the slider class is not accessible.

I at least got everything else working. Ugly colors for now but working. :slight_smile: