GUI Coding Guidelines
Contents
This document describes mandatory rules for UI coding.
These are obligatory for new code.
In order to provide a consistent look & feel to ST/X users,
it is mandatory that UI components (widgets, windows, tools)
follow common guidelines. Especially for new users, it is
very inconvenient, if the layout and/or position of common
controls or the naming of operations is non consistent.
In the past, a lot of code (especially from third parties)
has appeared, in which the personal preferences of the programmer
affected the graphical appearance, and it takes a lot of work
to fix such issues later.
This document shall prevent this as much as possible in the future.
There may be old code in the system which does not in part or fully follow
those rules. This is not an excuse for new code to not follow those rules.
If you come along old code which does not follow this guideline,
it should be changed, if it can be done with reasonable effort and you have nothing else to do.
Please care for proper english typing both in UI widget texts (labels, buttons, etc.)
AND in the program. Typing errors in the program (especially in message names)
make it *very* hard to find senders/implementors
unless the searching programmer knows the typo.
For example: method names like "removeContaines"
(instead of "removeContainers") makes it almost impossible for someone to find all manipulations
of "containers" by searching for "*container*".
Awoid the following:
- Do not perform expensive initializations in one of the following methods of a
subclass of ApplicationModel or
View:
- initialize
- postBuildWith:
- postOpenWith:
These lead to slow startup times. For example, the smallLint package instantiates
its parseRules in the codeView's initialize method. This instantiation involves
a lot of parsing (of all of the rules' parse trees) and may easily take a few seconds.
First, this parsing should not be done over and over.
Second, it should be done when needed, not when the view is created.
Third, it should be done by a background process, so that the view comes up quickly.
- Do not perform expensive operations in the redraw methods
Do not invoke file operations (like getting modification time, creation time, owner names etc. of files)
in a list- or menu item's redraw. If the underlying drive is a local one, this may seem to
be fast, und unnoticable. But if it is a network drive (especially a windows-shared drive),
these operations may take a very long time, and even block (freeze) the UI for a while.
It is very bad for the user if he has to wait seconds (or even minutes) only
for a list of filenames to be redrawn.
- Do not perform expensive operations in a menu constructor
Do not perform expensive disk operations in a menu's setup.
If there are items to be disabled/enabled, do so asynchronously before,
and set a valueHolder. Probe again, when the menu function is finally invoked,
not when the menu item is created/drawn.
It is very bad for the user if he has to wait seconds for a popup menu to appear.
Ensure that your algorithms scale
Not only test your app with a small number of data elements;
also test it with bigger amounts of data.
For example, a list of MP3 files may be shown fast if it contains 100 music files,
but how does it behave if there are 100000?
This error seems to be very common - even in many TV sets and Satelite receivers with
network support, users have to wait minutes to get their lists shown
As a concrete real-world example of a very bad UI:
I have to wait minutes on my brand new Samsung TV's builtin
mp3 player, which gets its playlists from a networked storage device
because I do have 100000 mp3's there.
The builtin software is just plain stupidly programmed: when ever I navigate back from
a subfolder to its parent, it scrolls back to the beginning, closes all open subfolders
and starts to read the list from the network drive again.
Whenever I stop playing a song, to navigate back to the playlist, it rereads the whole list (from the nework),
closes the folders, ...
The whole Samsung's builtin MP3 player is therefore completely useless
(and was obviously written by a total beginner)
Cache the data!
If the user has to switch between lists, make sure that you cache the previous one,
so he does not have to wait again, when returning back to a previous view/list/directory.
A bad example is again my Samsung TV, which reloads the complete 10000 title list from the cloud server,
after I look at the details of a song. This can easily take up to 15seconds!
Remember where the User came from
Wheb navigating in a tree, and the user returns back from a sub-application,
dialog, play program or editor, scroll to the previous position in a long list,
and reopen folders to the state they were before. Do not return to the top of a list,
if the list can get longer than 20 items.
Again, the Samsung TV shows how not to program a UI.
Ensure that your UI scales
Again the TV set: clicking on a remote control through a list of 50 items in a
list of music titles may be ok, but who wants to scroll through 10000 items?
And because it does not remember the previous scroll position,
I have to start over at the top of the list, after a title's details have been shown!
Think of quick navigation, search functions, remember previous entered search strings etc.
Looking at how bad some UIs are, makes me believe that the programmers
(or the product managers) actually never ever tried their product before deploying.
Give feedback
Always show at least a busy cursor, if the app is busy. If possible and useful,
show a progress bar. Never ever remember mouse- or keyboard clicks, during a
long running operation: the user might think that the previous click was not
working and may try again instead of waiting. If the new click is already recorded,
it may later be associated to a function (button) which was not anticipated.
Again, one of my TV apps is a good example: the maxdome UI does not give any feedback,
and some clicks take tens of seconds for the UI to react. If during that time I click on
the remote again, that click may (after 20 seconds) be associated to a back buctton,
which appears after that long pause. And I am back, and have to start again.
The layout of widgets within an application or dialog should be logical and
estetically pleasant. Elements must be grouped logically and consistent with the
way the rest of the system is organized.
All dialogs must have at least a cancel button.
Buttons must be in a horizontal panel at the bottom.
All such panels MUST have the following attributes:
Button Order
(in the UI spec): OK button(s) at right, Cancel at left.
Set the "reverse order if OK is at left" flag.
If there are multiple OK-like or Cancel-like buttons (like "OK for all", "Cancel for all"),
the "most-positive" OK-Button is at the far right, the most negative "Cancel" at the far left".
I.e. a "CancelAll" is leftmost, then a "Cancel", then "OK", then "OK for All".
Reason: This, together with the "reverse order if OK is at left" flag
ensures that the buttons will appear in the windowing system's natural order.
(i.e. it cares for differences between windows, linux and OSX, by reversing the order
if and only if the underlying window system requires this).
The "reverse order if OK is at left" flag is interpreted by the panel widget,
and it will do exactly what it says.
Without this setup, you application will look wrong on at least one of the above mentioned
operating systems.
Height & Position
30 pixels at the bottom of the dialog
Reason: have a common look and enough height for larger fonts (Xft).
Layout
Use the new #okCancel layout.
This will dynamically be mapped to either #fitSpace or #rightMax, depending on the
native window systems natural look.
Reason: have a common look which looks "natural" on each window system platform.
If future or other systems use any special layout, we can easily change the
horizontal panel class to whatever is needed. (for example, to maxCenter or similar).
Flags
OSX-resizeH (leaves space for any window manager resize handle)
Reason: some window systems (OSX) place a resize handle over the lower right edge area of
EVERY top window. The OSX-resizeH flag changes the layout so that this area is unused by the widget,
by giving the widget (in this case: panel) a -16 right offset.
The flag is ignored if running on windows or linux.
Thus, we get a good looking box on all systems.
No other solution is allowed here, as it will always look bad on either system.
You MAY NOT
- use explicit background or foreground colors; neither for panel nor for the buttons.
Reason: see below
- use explicit fonts
Reason: see below
- place buttons outside a panel (i.e. use explicit positioning)
Reason: see above
You MAY NOT:
- add extra bars, panels, progress indicators etc. with extra highlighting colors.
Reason: the app/dialog looks ugly and unprofessional, if different colors are
used at different places. Also, what you think is a neat look may seem ugly to others.
If you really think, that you cannot live without such extra colors,
read a styleSheet entry (but please with a reasonable, hierarchical name, to avoid conflicts),
and use that. Then, you can define your personal color style in your own personal
stylesheet file.
- use alert colors (red, orange) for non drastic operations / indicators / messages.
Reason: overuse of alert colors for non severe information distracts the user.
You MAY NOT:
- use explicit fonts
Reason: the app/dialog looks ugly and unprofessional, if different fonts are
used at different places. Also, the overall look cannot be changed by styleSheets then,
if the user has different preferences. If you think that the default font setting is
insufficient, add a stylesheet entry ("app.widget.font") and allow the user to
customize it.
You SHALL AVOID:
- set the 3D level or borderwidth of widgets
Reason: the app/dialog may look very ugly if the view style is changed
(and there will certainly a windows9 sooner or later, which looks different from what
you think is pleasant now).
The trend in general is going away from the 90's motif influenced overuse
of 3D levels.
You MUST:
- Define tooltips for all components. Boxes with label and actor-widgets may use a common tooltip.
Reason: It is *very* expensive to add tooltips afterwards - especially if someone else
has to first figure out what a widget/field's function actually is.
Tooltips should be keyed and indirected via a helpSpec method (i.e. define an activehelpKey
in the widget, and an english translation in a helpspec). Then add reanslations
for other languages to the resource files.
For long texts, you may omit the entry in the help spec, and instead use somethinh
like "APPNAME_HELP" as activeHelpKey and provide a translation for that
in the resource file. Do this only for long texts,
because this will not be self contained (work without resource file).
In general, an application should present a useful english UI, if no resource translation is present.
You MAY NOT:
- Define tooltip strings with embedded newlines. You must use "\" if the strings gets too long.
Reason: Otherwise no translation is possible via resource files,
and other languages may need more or less text lines (i.e. have a different number of
embedded "\"-characters for line breaks.
As already mentioned above, OSX needs a 16x16 pisel area at the lower right of
every top window. It will unconditionally draw its resize handle over that area.
So we better make sure, that nothing useful is located there.
This affects mostly three situations:
- Dialog boxes which have a button panel there
- Application windows which have an info panel there
- Text or Graphic applications without info panel, which have a scrollable window there.
The first two of the above MUST set the "OSX-resizeH" flag (in the UI-painter's layout).
So the panel will get a right inset of 16 pixels.
The other should set the "OSX-resizeV" flag of its scrollable view,
which passes this flag down to its vertical scrollbar.
So the scrollbar will get a vertical inset of 16 pixels.
This scheme currently only works if the vertical scrollbar is at least 16 pixels wide.
Notice: do NOT hardcode an inset at the lower right - if you do so, you UI will look
ugly on non-OSX systems. The above mentioned flag will dynamically use an inset of
either 16 pixels (on OSX) or zero, if on non-OSX.
Be prepared for longer labels in other languages
Always add some 25-30% in size to the english text.
Other languages (German and more so French) usually are a bit more redundant
and therefore require more space than english.
Be prepared for font size differences
The windows fonts are a bit denser than the corresponding Linux fonts.
Add another 20% when designing the UI in windows.
Let the layout be computed dynamically
If possible, place buttons in a large enough Panel,
let the buttons compute their size and the panel arrange them.
In many cases, a *Max* layout looks better. This lets the panel
compute the size of the widest button and resize the others to that size.
Put multiple, vertical arranged elements into a VPanel
This allows for each element to become larger, in case the user chooses a bigger font.
Test it!
There is no automatic unit test for "Has a pleasing look".
So try your UI under windows and linux.
Also, change the font settings to a (say 20pt) font and see if it looks ok.
Avoid Fixed Extents or Small MaxSize Limits
Many real world dialogs have a fixed size (or way too small max size).
Especially the native Windows UI (from Microsoft) often presents boxes - even list selection
boxes - with a too small extent. Small listboxes are especially inconvenient - why would
any end-user like a 4-line list, on a 30 inch display?
Reflect your own use of the UI
Watch your own behavior - if a dialogs regularily comes up
too small and the first thing you do is to resize it,
make it a bit larger right from the start.
This applies especially to selection-in-list views inside a dialog.
There is no reason to make the list only (say 4) lines high, if the
overall dialog is only taking up 1/5 of the screen height.
Unless the list is actually only showing a very small number of items.
Remember the last size
Many apps and dialogs remember their size-on-close in a class variable.
And come up with this default size the next time.
That is a very convenient behavior and you should also implement it.
(it is now done on the AppModel-level automatically for you,
if the user has checked so in his preferences).
But for some hand-crafted ad-hoq dialogs, you have to do it manually.
Variable Panels
If there are multiple lists and/or a list and text-editor in a dialog/app,
always put them into variable panel. Think what a good initial fraction
might be. The UI painter saves the fraction at construction time as
default to be used when the app is opened - so always go into the
test-geometry mode, adjust for a nice initial fraction and save it.
Copyright © 2014 Claus Gittinger, all rights reserved
<cg at exept.de>
Doc $Revision: 1.16 $ $Date: 2021/03/13 18:24:51 $