cat /dev/maxilys

A glance in the mind of a KDE/Linux developer to see how ideas turn into code.

2006-11-24

KDE: Invisible Man

Two days after my nightmarish change of harddisk... I can go back to my usual business: Serenity. The version 1.5 has been out since November, the 6th. It's even a version 1.5.0 because I'm no professional surgeon. The major surgery didn't go as well as planned.

In fact, I was a bit over-zealous in my search and replace job and the colorschemes landed in a wrong directory. It was a very discreet bug on my system because without checking the timestamp, I couldn't see that the colorschemes came from a previous install.

Another also discreet bug that had been reported to me: the focus rectangles leaved traces when you moved icons in Konqueror's window. I just couldn't see it because those (ugly) focus rectangles had been disabled for months. I found them totally useless. My huge mouse cursor and the mouseover effect are more than enough to tell me which widget I'm using. However, those who prefer or need to use the keyboard can't disable the focus rectangles.

I looked very closely (with KMag) and I found that the corners of the rectangles were responsible for the traces. I just removed them and the traces were gone, without needing to resort to sharp corners. A question remains: Why did "they" need to add focus rectangles around the dragged and dropped icons?

While I was at it, I tried to do something about the rubber band of which the code part is just below the one of the focus rectangles. No much success so far, but I still have ideas. The currently plain rectangle is so boring!

These were the two last things I did. Before that, I did another major surgery. It's pretty much invisible, hence the title of the blog page.

First, let me explain something. To draw a button the serene way represents a lot of calculations, a lot of conditional code and if you don't optimize everything, you can generate a lot of temporary variables. Serenity wasn't actually slow but the drawing took some time crawling across my code. I already optimized the gradient routines, it was time to take care of the rest.

Here came the C++ and its classes. That's what I did. I created a new class: SereneShape(QRect, uint); Everytime I need to draw a button, a slider, a frame, whatever, I initiate a new instance of my class with the containing rectangle (QRect) and a variable (uint) which is a bitfield indicating where the round corners are, if any. Qt provides the rectangle --most of the time-- and Serenity contains pre-defined constants like Button_Alike, Square_Box, or else TopTab_Alike. No calculations here.

And I minimized the ones I had to do. The first step of the initialization asks Qt the coordinates of the rectangle, from which I get all the coordinates to draw a Square_Box. And that makes already 12 points. 2 points to draw each of the four border lines plus 1 point for each corner for the antialiasing.

The second and last step checks the uint variable to find the round corners and stores QPoint's, re-using already known coordinates and moving only one coordinate of the two affected border lines when needed. Along the process, it takes note of where it put things and count the number of available coordinates. A picture being worth a thousand words, here it is:

[Explicative image]


What you must understand from this picture is that when I store the values left, left+1, top and top+1, to go from a square corner to a "round" one, the only new values I need are left+2 and top+2. I do this for the four corners and the drawing routine can go on.

For the moment, all it did was:
SereneShape shape(rect, flags);


Now, it can use the accessors to retrieve all the needed values. To make things even faster, I unfolded my code because, most of the time, the four borders are needed (only the tabs have three borders only), the four corners are either round or square all together (only the tabs or the pressed menubar entries are half-rounded), and making a test at each corner to know if the aliased corners must be drawn alphablended is a massive waste of time.

For example, to draw a solid frame with or without rounded corners with alphablend corners (like around an edit field or a tab page), all the code that is actually executed is:

p->setPen(contour);
if ((flags&Square_Box) == Square_Box)
{
p->drawLine( shape.topLeft(), shape.topRight() );
p->drawLine( shape.leftTop(), shape.leftBottom() );
p->drawLine( shape.rightTop(), shape.rightBottom() );
p->drawLine( shape.bottomLeft(), shape.bottomRight() );
}
//
// The else branch is here.
//
for (int i = 1 ; i <= shape.cornerPixels() ; i++)
{
p->drawPoint( shape.cornerPoint(i) );
}
if (flags & Draw_AlphaBlend)
{
for (int i = 1 ; i <= shape.aliasedPixels() ; i++)
{
renderPixel(p, shape.aliasedPoint(i), contour);
}
}
//
// The else branch is here.
//


That's all. To limit even more the accesses to the class, I used QPoint's everywhere.

I'm very partial --Hey! That's my code.-- but to my eyes, the gain in speed is very visible. The reason behind this result despite the class overhead is: less temporary variables and very less conditional code to eat your CPU cycles like a hungry shark.

And if you think that I'm bragging about my code... you're right! I'm very proud of my code. That's the first class that I ever write from scratch in C++ without taking inspiration from anywhere... despite the fact that I still haven't really learned C++ and it worked right from the beginning. I amazed myself. :-D

Well, I'd like to keep on bragging but I have other things to do. ;-) I'll talk about the other visible changes in Serenity style and windec next time.

Labels: , ,

0 Comments:

Post a Comment

<< Home