2010-02-11

Simplifying the API Part 4

The basic Woopsi gadget includes the concept of a border. This is one pixel wide and surrounds the entire gadget. It can have one of a variety of appearances, such as bevelled in, bevelled out, bevelled depending on whether or not the gadget is clicked, and so on. Other gadgets, such as the AmigaWindow and TextBox, have wider borders. The AmigaWindow is particularly unusual because its top border has a different size to the other borders. It eschews the standard border appearance, opting for a border built out of other gadgets instead.

Up until now, the border system has not been implemented very well. Most of the gadget calculate the amount of space taken up by their border using something like this:

u8 width = (!_flags.borderless) << 1;

If the gadget has a border, this evaluates to “2”. If not, it evaluates to “0”. Efficient, yes. Maintainable, no.

Instead, the Gadget class now includes a “GadgetBorderSize” struct, that contains individual sizes for each of the four borders. All of the hacky bitshifting code has been removed and replaced with calculations based on the new struct. This has allowed me to remove all custom “getClientRect()” methods from all gadgets and make the method in the base class non-virtual.

Next, I have moved the “bonus” folder, which contains the skinned gadgets, bitmap I/O classes, hashmap and linked list classes, out of the Woopsi section of the SVN repository and into a new section called “extras”. I’ve decided not to include these in any future distributions, at least until Woopsi 1.0 is out. Too much scope creep will prevent me from ever finishing this library.

To trim some of the flab from the API, I’ve been cutting unused functionality throughout the codebase. The following methods have been removed:

  • Gadget::clear()
  • Gadget::clear(clipRect)
  • Gadget::newInternalGraphicsPort(isForeground)
  • AmigaWindow::getBorderSize()
  • Screen::getBorderSize()
  • TextBox::getBorderSize()
  • Screen::getTitleHeight()
  • Window::getTitleHeight()
  • Gadget::getBackgroundRegions()
  • Woopsi::closeChild()
  • Woopsi::shelveChild()
  • Woopsi::release()
  • Woopsi::drag()
  • Woopsi::click()
  • Woopsi::shiftClick()
  • Woopsi::shelve()

Many more of the Gadget methods that used to be virtual cannot be overridden any more. This prevents essential pieces of functionalty being replaced.

The GraphicsPort has seen a few changes. It no longer tries to delete a null pointer if its clipRect has not been allocated. More interestingly, the gadgets use the GraphicsPort in a different way. Previously, a gadget would define the code to be called when it was to be drawn like this:

void draw(Rect clipRect) {
    GraphicsPort* port = newGraphicsPort(clipRect);
    port->drawFilledRect(0, 0, _width, _height, woopsiRGB(31, 31, 31));
    delete port;
}

Behind the scenes, this was very inefficient. A GraphicsPort object was created and deleted every time this method was called. As this method is called for every visible region of the gadget (and there could be dozens of visible regions), GraphicsPort objects were being created and deleted far too often.

The draw() method has been replaced with two new methods: drawBorder(), which can draw across the entire visible surface of a gadget (including its border space, hence the name) and drawContents(), which can only draw inside the border. The code above would look like this in the new model:

void drawBorder(GraphicsPort* port) {
    port->drawFilledRect(0, 0, _width, _height, woopsiRGB(31, 31, 31));
}

In the new design, two GraphicsPorts are created every time the gadget is redrawn. One can draw to the border area, whilst the other can only draw within that area. They are re-used for every region of the gadget drawn during that redraw operation.

It is still possible to get the clipping region by calling the GraphicsPort’s new “getClipRect()’ method.

The Gadget class’ GadgetStyle object used to be allocated using “new” and referenced with a pointer; it’s now allocated as a standard object. There’s no real reason for this change other than it’s a little tidier.

There were a number of fiddly problems with the Gadget::closeChild() and shelveChild() methods, which are now fixed. They were causing the focus to jump between objects even if the closed/shelved gadget did not have focus.

Lastly, I hit a problem with the shift-click code from yesterday. The clicks were allowed to propagate back up the hierarchy, which caused nasty problems. If the front-most screen did not contain a context menu, the screen below it would be sent the shift-click. If this screen had a context menu then it would be displayed despite the screen itself being totally obscured by the screen above it. I’ve added a new “checkCollisionWithForegroundRects()” method to the Gadget class that fixes this problem. It checks to see if the click falls within one of the regions of the gadget that is not obscured by its siblings or ancestors. If not, the shift-click is ignored.