It’s common for Smalltalk implementations to run on multiple platforms, and that leads to the problem of designing a framework to allow an application UI to display on all those platforms. The existing solutions (not only in Smalltalk) suffer from various shortcomings. In order to solve this problem better, Brazil from the start was based on a number of principles different from the industry examples. Here is an overview of those principles.

It’s not about widgets. A UI framework is not (just) a widget library. In that sense, Morphic is on the right track (and in the mainstream, so is WPF). Interfaces visualize information, and a good interactive visualization is typically more than a window with a few labels and list boxes plopped onto it. A UI framework needs to do much of what was traditionally expected from a structured graphics (“diagramming”) framework. Of course, buttons, list boxes and other primitive interactive elements are still necessary, but the value of a framework is primarily in composing rather than in implementing them. Which, in fact, it shouldn’t do at all.

When it’s about widgets, it’s about native widgets. Any OS provides about the same core set of primitive interactive elements. They are what the users expect to see in a “real program”, and the OS by definition does a better job implementing them than anyone could ever hope to. Emulated widgets are bound to be a poor replica of the original and are thus an exercise in futility. On the way from Smalltalk-80, one attraction of the emulated approach might have been the ability to customize, extend and roll your own, for the sake of creating more expressive UIs. But when a UI framework is also a graphics framework, expressive UIs can be built by composition rather than by widget hacking—so there really is no good reason not to use what’s already available, even if its implementation is sealed inside the OS.

Cross-platform shouldn’t mean platform-agnostic. So what about the lowest common denominator problem? It’s often assumed that using native widgets in a cross-platform environment means that only the subset of their features common across all platforms can be accessible. The mistake of this view is the assumption that it’s not cross-platform unless the user is shielded from platform differences. It doesn’t have to be that way, and shouldn’t be. Platforms are different, and a properly designed cross-platform framework should embrace and model those differences rather than try and inevitably fail creating an illusion that they do not exist. In practice, such modeling means that a framework object that represents a widget can provide access to the lowest common denominator features, while platform-specific features can be available as a “capability”—a separate object one can fetch from the widget when the capability is available (i.e. when the current platform is the one whose capabilities you ask for).

Qt is not your friend. It’s a common and understandable question—why not use an existing cross-platform framework like Qt or wxWidgets. Everything that has been said above is reason enough, and another important consideration is that a large complex hard-to-debug third party layer shielding you from the OS facilities you want to use sounds like bad news (something I dubbed as “high Space Shuttle factor” in the original implementation study). Which is also related to the last point.

If it’s not written in Smalltalk, it’s broken. There is a tendency (at least in Smalltalk-80 descendants) to do OS interaction through primitives. That is so perhaps because FFI arrived relatively late to ObjectWorks, and never was available for Squeak. Primitives are an arcane enough feature, so they are not discussed in Smalltalk style guides. If they were, the guides might have said something like this: many primitives you see in VisualWorks and Squeak should not have existed. A primitive is supposed to do what cannot be expressed in Smalltalk. If a call out to the OS or a call back from the OS cannot be expressed in Smalltalk, you need an FFI, not a VM plugin. OS interaction is not something that belongs in a primitive—and if fact, a primitive is about the worst place to do that kind of work. It means writing a large body of complex code in a low-level language with a higher probability of making a mistake, and then hiding it away in the VM where it can’t be easily debugged and fixed. Hardly a winning combination.