Re: UI subsystem interface design



Responding to Guild...

My fundamental design principle is that the interface should define classes of primitive drawing objects. The application constructs its UI by composing the primitive drawing objects and then giving those objects as data to the subsystem, which will be responsible for doing the drawing based upon the data it is given.

The most primitive class of drawing objects is the Window. Each window is a rectangle and contains coordinates for its position represented as floating-point numbers between 0 and 1. Windows contain other windows recursively, and the actual position of each window is relative to the containing window. So a window between coordinates (0,0) and (1,1) always exactly covers the containing window.

So far, so good. For a GUI the dominant abstractions will be Window/Control. Typically a Window instance in the subsystem will map to an object in the application and Control instances will map to attributes of the object. The logical cohesion is achieved through relationship instantiation.


A fundamental helper class for windows is the Mapping class. A mapping object in the context of this interface is a endomorphism on objects within some class. Each window contains a mapping for Colors and a mapping for Fonts.

This is commonly known as a Specification object. It is dumb data holder whose attributes are parametric values that control the rendering. These would usually be created at startup from a configuration file (e.g., Windows Resource file). When a Window or Control instance is created, it would be related to a Specification object that defined its characteristics in data.

Depending on the complexity of the GUI the Specification objects would probably be subclassed since the attributes relevant to a text box are likely to be different than the attributes of a radio button. The Window/Control instance would be related to a single Specification object of the appropriate subclass.


A color object represents a color idea so that drawing can take place with colors, and the mapping for a window defines the meaning of each color used in terms of the colors of the containing window. Adjusting the mappings of a window affects the way the UI looks for that window and all contained windows.

I am not sure one needs distinct objects for characteristics like color. The value for rendering just needs to be stored in a Specification object. The subsystem will be talking to the OS Window Manager for rendering and that usually just needs appropriate values.


The application can create its own color objects and color mappings to control the way each window appears, but the color mapping of the outermost window must convert all color objects into color objects that are supplied by the subsystem so that the subsystem is able to interpret the colors. To make this possible, the subsystem is required to supply a ColorScheme object which is an intricate container for color objects that allows the application to get colors from the subsystem at varying levels of abstraction.

Unless you are doing complex graphics in a graphics pane, this seems like overkill. A vanilla GUI usually has fixed colors for various classes of instances (or elements of the instances). Thus a standard Window instance has different elements (title bar, frame, foreground, etc.) and each may have its own color. But all one needs to specify that are attributes like titleBackgroundColor in the Specification object.

Unless the GUI is really complicated with multiple themes for windows or custom windows/controls, I would be hesitant the break out the individual elements like title bars and scroll bars. The notion of a GUI "theme" can be expressed in the configuration data by simply using the same values for the same element contexts.


Font objects are exactly like color objects, except that font objects represent type-setting information, including more than what one would normally call 'the font' of some displayed text.

Same argument here because typically all one has to specify to the OS Window Manger is font name, size, and a couple of other characteristic values for italics, bold, etc. Those can be expressed in data values.

However, unlike color, one can make a case for fonts having their own Specification objects simply because of the number of parameters. Something like

* specifies R2 *
[Control] ----------------------- [FontSpec]
| *
| specifies
|
| R1
|
| 1
[ControlSpec]
A
|
+--------------+--------- ...
| |
[TextBox] [CombBox]

One would need some sort of explicit identity in [FontSpec] to pass the information properly to the OS Window Manager.


I use the term ID-objects for Font and Color objects because they are atomic identifiers for the idea that they represent. There is no way for the application to examine an id-object supplied by the subsystem just as there is no way for the subsystem to examine an id-object supplied by the application. However, both the subsystem and the application will likely be able to examine its own id-objects. Color objects created by the subsystem to give to the application likely contain red-green-blue information that only the subsystem has access to.

You are going to have to put some words around how these objects differ from ordinary object identity. There has to be a mapping of the message data packets in the subsystem interface and you will need some way to ensure that mapping, but I don't see that being dedicated identity objects.

For example, in the application one might have an Account object with a 'balance' attribute. In the Application the specific Account has an account number that represents explicit identity. In the GUI there is a corresponding Window object with a unique window ID used by the OS Window Manager. That Window object will have an attribute for accountNumber and a related Control object for the "balance" text box.

Window.accountNumber will have the same value as Account.accountNumber but, unlike Account, it will not be the window identifier, which will be Window.windowID. Similarly, Account.balance will be the same value as Control.value but you will need an identity for the Control object that doesn't exist on the application side.

The identity of the Control is likely to be implicit (a pointer address) but one still needs to have a consistent mapping. Typically that will be handled positionally in the message data packet (e.g., the third parameter of the displayAccount message will be Account.balance). The subsystem interface handles the mapping to a factory object that will create the Window and related Control instances. The subsystem interface selects the right factory object based on the message ID and then passes the message data packet arguments to that factory in the proper order.

Similarly, the setBalance interface message from the application will include values for Account.accountNumber and Account.balance. The subsystem interface needs to know how to interpret the data packet for setBalance to identify the right Window instance and navigate to the right Control instance to set Control.value.


The Window class is abstract. The simplest subclass of Window is called Drawing which represents something visible to the user. A Drawing is restricted to only contain other Drawings, and contained drawings hide part of the containing drawing by being painted over the containing drawing.

There are three subclasses of Drawing: Text, Bitmap, Vector. They correspond to the three major drawing modes that it would be nice to support. 3D drawing would also be nice, but let's not get greedy. These subclasses are concrete and each object contains what one would expect from the name. A Text object contains a string to be shown in the window and type-setting information. Bitmap contains an array of colors. Vector is a graph of lines and points with color and thickness, as well as perhaps other geometric objects such as circles.

OK, this is more complicated than I thought (I assumed a typical GUI). What goes on within a graphics pane of a window is much more complex. The abstractions one will use for rendering graphics in a graphics pane will depend strongly on what the rendering engine expects. The last time I did any graphics was so long ago that anything specific I suggest would likely be hopelessly out of date.

However, the basic idea here seems reasonable. The same sort of approach would apply. One identifies what sorts of things one needs to render, figures out what rendering engine abstractions are appropriate, and then provides an infrastructure of Specification objects and factories to create them. The same notions of identity mapping through the interface would apply.


The other subclass of Window is called UIWindow and it represents an area of the screen that reacts to the mouse. In addition to those things which all windows have, a UIWindow has an Event object and an Event mapping. When the user clicks on a UIWindow, the associated event is given to the event mapping and the resulting even is given to the event mapping of the containing UIWindow and so on until to outermost UIWindow, then the final resulting Event is given to the application as the notification that the user has clicked on something.

Event objects are the third sort of id-objects and the primary job of the application is to create event objects to associate with each place that the user might click and then to interpret the events when the objects are given back to the application. Event objects can also be associated with keyboard events through a completely separate part of the interface. Naturally, the application is also given the coordinates of the mouse-click relative to the UIWindow that was clicked.

Since user actions are inherently asynchronous, it is quite reasonable to employ event-based communications between the UI subsystem and the application. The events, though, are simply the messages exchanged between them (i.e., between subsystem and OS Window Manger and between subsystem and application). There will be associated objects because of the data packets.

I am not sure you need a notion of 'event' /within/ the subsystem, though. It will probably be easier to construct the subsystem to be synchronous and do any necessary serialization in the interfaces. The OS already provides a queue to serialize its events (keyboard & mouse) so one just needs to pop them when ready for processing. On the application side things will probably already be serialized (i.e., the application responds to UI events in knee-jerk fashion). Even if application messages are asynchronous, one would probably serialize them by having a queue in the subsystem interface implementation (e.g., a Facade pattern class implementation).


The primary job of the subsystem it to take a single Window, render it and report any events that the user generates upon seeing the resulting display. When the rendering of the UI is to change, the application must modify the Window object and then re-send it to the subsystem.

This sounds suspicious to me. Even simple applications usually have multiple windows, even if they are not readily apparent to the user; things like dialog boxes for options and whatnot. It is also easier to use borderless windows to construct things like lists.

OTOH, if your main goal is a single graphics display and few other controls are needed, then that could be a pane in a single window.


That completes my brief description of what I have designed up until now. I am concerned that when even a small part of the UI must change the entire data for all of the UI must be sent to the subsystem. I feel like I should have an interface for modifying the UI from the application even after it has been sent to the subsystem, so that small changes can be made efficiently with the cooperation of the subsystem, however that would surely greatly complicate the interface and I am not entirely certain how it should be done.

When one builds using abstractions from the UI paradigm itself, that should benefit maintainability greatly. That's because UIs are actually pretty narrowly defined in terms of very basic building blocks (e.g., Window/Control). A graphics rendering engine will be more complex, but still composes from basic widgets.

Better yet, the differences in those abstractions are described in data. So a lot of changes can probably be made in the configuration data without touching the code at all. Take a look at a Windows Resource file. An entire GUI can be defined in terms of external data. All one then need to "touch" is the identity mapping in the interface that links the application view to the configuration data.


Are there any examples of a good data-passing UI subsystem interface freely available?

Your OS Window Manager is the most obvious example. Your subsystem interface is equivalent to the typical message loop in a Windows program. It accepts a message from the OS Window Manager and dispatches it to the appropriate receiver based on message ID. There would be an equivalent subsystem interface one the application side to accept messages from the application. And the application would have its own interface to accept messages from the UI subsystem. [IOW, subsystem interfaces are two-way. The Application Partitioning category of my blog has a post on this.] All those interfaces do basically the same thing.

--

*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@xxxxxxxxxxxxxxxxx for your copy.
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.