0081: Notebook V – Customized Tabs Part 3
Okay, let’s have another look at what we’re aiming for:
A Second Look at the TabDrawingArea Preamble
This time around, we’ll concentrate on just the stuff we’re using to draw:
cairo_text_extents_t extents;
GtkAllocation size; // the area assigned to the DrawingArea by its parent
double[] northWestArc, northEastArc;
double xRight, xLeft, textBaseline = 22, yUpper;
double width, height;
double[] tabOutlineRGBA = [0.5, 0.5, 0.5, 1.0];
double[] tabFillRGBA = [0.949, 0.949, 0.949, 1.0];
double cornerRadius = 10, radians;
double lineWidth = 2;
The first two variables we looked at last time, but I’m repeating them here because they factor into the drawing. They are:
cairo_text_extents_t extents
: which gives us the amount of space (width and height) that our string will take up in theDrawingArea
, andGtkAllocation size
: which will tell us the size of the entireDrawingArea
.
The extents
we need so we can make the DrawingArea
large enough to fit the drawing. We need the size
so we can center the text within the tab.
Now, the rest of this is for the actual drawing of the shape we’ll use as the tab’s background. Here’s a diagram I used as a guide:
About the diagram:
- where the red and blue lines meet is the center point for
northWestArc
, - where the violet and gray lines meet is the center point for
northEastArc
,
Relating the preamble variables with the diagram:
- the
northWestArc
andnorthEastArc
arrays will store values, in radians, that denote compass points for starting and stopped the drawing of the arc, xRight
andxLeft
are the center points of the two green circles (where the blue/red lines intersect on the left and the gray/violet lines meet on the right),- skipping down a bit…
cornerRadius
is the measurement from the center of either of the circles to the outer edge, and lineWidth
is how thick the line will be.
Now, let’s skip back up and go over the rest of these preamble variables:
textBaseline
: this isn’t shown on the diagram, but it’s the y offset for where we’ll be drawing the text in relation to the top of theDrawingArea
… the top being ‘0’ (see Figure 2 below),yUpper
is marked by the horizontal blue and violet lines on the diagram, andwidth
andheight
will be the dimensions of our rendered text.
That’s it for the variables controlling the mechanics of the drawing, but we have two more arrays to look at (both are colors given as components of red, green, blue, and alpha):
tabOutlineRGBA
: this is the line color, andtabFillRGBA
: this is an optional fill color if we want the tab to be a color other than gray.
Okay, moving right along…
A Second Look at the Constructor
this(string labelText, int tabNumber, Notebook notebook)
{
_tabNumber = tabNumber;
_notebook = notebook;
_labelText = labelText ~ " " ~ _tabNumber.to!string();
radians = PI / 180.0;
// map out the shape ofa tab with rounded corners
northWestArc = [180 * radians, 270 * radians]; // upper-left
northEastArc = [-90 * radians, 0 * radians]; // upper-right
cornerRadius = 10;
addOnDraw(&onDraw);
addOnButtonPress(&onButtonPress);
} // this()
Just ignore the first two lines (which were covered in Part 2 of this series).
We did look at the third line before, but let me refresh your memory… This converts _tabNumber
to a string so it can be concatenated onto the end of _labelText
. This gives us a finalized string each time a TabDrawingArea
is instantiated with both text and a tab number.
From there we have:
radians
: we could have grabbed this from the standard math library, but it’s just as simple to do it this way; it comes out to a little less than two one-hundredths which is very close to the true measurement of one radian… when compared to one degree of arc around a circle (6.28 radians = 360 degrees),- then we set up the start/end point coordinates (
northWestArc
andnorthEastArc
) for drawing the rounded shoulders (keep in mind, as we talked about in the Cairo series, drawing in Cairo always goes clockwise), and cornerRadius
: as stated above, this is the distance starting from where the blue and red lines intersect and going to the outer edge of the arc.
The last two lines, where the signals are hooked up, we covered last time.
Finally, the onDraw() Callback
The first two lines:
bool onDraw(Scoped!Context context, Widget w)
{
// set the font size to something resembling the default UI font
context.setFontSize(13);
context.selectFontFace("Sans 10", CairoFontSlant.NORMAL, CairoFontWeight.BOLD);
These set up the font. I picked Sans 10 because it either is the standard Windows 10 UI font or looks enough like it for our purposes. And, yes, it’s got to be bold or it just doesn’t look heavy enough compared to other UI text.
In Linux and other UNIX-like OSs, the UI font will vary depending on your theme.
Next, we have:
getAllocation(size);
This gives us the size of the DrawingArea
which we’ll use in a moment to figure out where to place the text.
context.textExtents(_labelText, &extents);
width = extents.width + 20;
height = extents.height + 10;
A call to textExtents()
gives us the area that will be taken up by the rendered text, in pixels. We tack on a bit more in each direction so the text won’t look cramped.
setSizeRequest(cast(int)width, cast(int)height);
xLeft = 12;
xRight = width - 11;
yUpper = 15;
context.setLineWidth(lineWidth);
Here, we:
- resize the
DrawingArea
so we have enough room to do the job at hand, xLeft
: set the x coordinate of the point where we’ll start drawing. This is 12 pixels in from the left edge of theDrawingArea
,xRight
: set the x coordinate where we finish the drawing on the right hand side (offset 11 pixels in from the right edge,yUpper
: set the y location of the center of the circle (as we talked about in the preamble section above), andsetLineWidth()
: self explanitory.
Note: You might think both xLeft
and xRight
should have the same value, but because of a Cairo anomaly related to line width, they don’t.
Final Setup Before Drawing the Tab
context.newSubPath();
context.moveTo(2, 32);
context.lineTo(2, 12);
context.arc(xLeft, yUpper, cornerRadius, northWestArc[0], northWestArc[1]); // upper left corner
context.arc(xRight, yUpper, cornerRadius, northEastArc[0], northEastArc[1]); // upper right corner
context.lineTo(width - 2, 32);
// context.closePath(); // closing the path puts a line across the bottom which doesn't look very nice
These statements prep the Cairo drawing routines, outlining the round-shouldered tab starting in the bottom left corner, moving up into the left shoulder, across to the right, then back down to the bottom.
Note: If you want a line across the bottom of the tab, uncomment the closePath()
statement.
Note 2: Keep in mind that the above statements don’t actual do the drawing. It’s like handing someone a map with a route marked on it. The map will be used in the next step.
Drawing the Tab
First we fill it:
context.setSourceRgba(tabFillRGBA[0], tabFillRGBA[1], tabFillRGBA[2], tabFillRGBA[3]);
context.fillPreserve();
Then we do the outline:
context.setSourceRgba(tabOutlineRGBA[0], tabOutlineRGBA[1], tabOutlineRGBA[2], tabOutlineRGBA[3]);
context.stroke();
And lastly, we overlay the text:
context.moveTo(size.width / 2 - extents.width / 2, textBaseline); context.showText(_labelText);
And that is that.
Conclusion
Next time, we’ll start by looking at adding and removing tabs from a Notebook
.
Until then, have fun.
Comments? Questions? Observations?
Did we miss a tidbit of information that would make this post even more informative? Let's talk about it in the comments.
- come on over to the D Language Forum and look for one of the gtkDcoding announcement posts,
- drop by the GtkD Forum,
- follow the link below to email me, or
- go to the gtkDcoding Facebook page.
You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.
© Copyright 2024 Ron Tarrant