Appearance
qooxdoo comes with an advanced system that allows you to control the appearance of widgets. Learn here how to use this feature.
Short overview
- qooxdoo includes support for so-called appearance themes. Theses themes allow you to completely select the appearance of “all” widgets. It even supports switching the entire look of your application at runtime.
- Each widget in qooxdoo has its own property “appearance”. This property corresponds to the appearance “id” of identical name in the appearance theme class. The values of this appearance property could for example be “checkbox”, “button”, “button-special”, ...
- If you want to style a whole group of identical widgets (like some special buttons that look different than the regular buttons) the best would be to define your own widget class (e.g. “SpecialButton”) by deriving from the already existing class (e.g. “Button”) and change the appearance (e.g. to “button-special”) through a changeProperty call (which is preferred over using setApperance inside the constructor of the new class). Your appearance theme needs to have an entry for your custom widgets (e.g. “button-special”).
Included themes
qooxdoo currently includes one default appearance theme (default). This theme is responsible for the Microsoft Windows look of the qooxdoo examples. It is important to note that qooxdoo widgets do not have to look like Windows controls at all. The styling of the widgets is independent from the class that defines a widget. Other appearance themes could provice a MacOS X user interface or not look like any OS user interface at all. It could easily resemble your corporate design or the individual CDs of your clients.
To demonstrate the power and independence of the appearance themes, it would be great to include another theme into qooxdoo. It would be great if the community could contribute such a theme (like the already mentioned MacOS X theme).
You can find the appearance theme in the source distribution under the path: \source\themes\appearance\default\theme.js. Through the usage of whole directories there, it is also possible to put additional theme files like background images and so on there.
Adding new appearances
If you want to add new apperances to the default (or any other) theme please follow these steps:
Get the reference of the currently selected theme:
theme = qx.manager.object.AppearanceManager.getInstance().getAppearanceTheme();
After this you can define new appearances in the same style as inside the theme file itself.
A typical theme entry looks like this (in this example for QxLabel):
theme.registerAppearance("label", { setup : function() { this.color_disabled = new qx.renderer.color.ColorObject("graytext"); this.font = new qx.renderer.font.Font(11, '"Segoe UI", Corbel, Calibri, Tahoma, "Lucida Sans Unicode", sans-serif'); }, initial : function(vTheme) { return { font: this.font, wrap : false }; }, state : function(vTheme, vStates) { return { color : vStates.disabled ? this.color_disabled : null }; } });
Basically you add a new javascript hash (the base javascript data object which consists of simple key-value pairs) named “label” in this case:
theme.registerAppearance(”label”, {});
This hash allows three keys:
- setup: Setup of environment, create multiple-times used objects
- initial: Initial appearance of the widget
- state: State appearance of the widget
where each value is a function.
Setup
setup is completely different from the others. It should not execute any real code. It has no reference to the widget or even the theme. The setup method for each appearance will be executed on the first use of this appearance and not for each widget which uses it. This allows you to define complex objects only once and use them in multiple instances of widgets which use this appearance. For example you can create custom border styles or fonts there.
setup: function() { return { this.font = new qx.renderer.font.Font(11, "Verdana"); this.border = new qx.renderer.border.Border(2, "outset"); this.border.setBottomWidth(0); }; }
Initial and State
In the case of initial and state the functions should return a new hash, which defines the properties to setup with the corresponding values. In these two functions you have also references to the widget, to which the appearance will be applied, and the theme. The widget is sometimes useful to react on some property settings inside the widget.
The initial function should only apply the appearance settings which are completely independent of any state changes. I will give you a simple example:
initial: function(vTheme) { return { color : "red", width : 100, height : 100 }; }
The state function instead should only define properties which change on some state change (for example over, selected, focused, ...). You get an additional (third) argument here. This is again a hash which has keys for each currently applied state. The value is always true. For example you can apply a mouseover effect for a widget using the following code:
state: function(vTheme, vStates) { return { backgroundColor : vStates.over ? "red" : null }; }
This makes the background of the widgets which use this appearance red if you move the mouse cursor over them.
Changing an existing appearance
Here is an example of changing the default appearance, so that the text field that has the focus shows a different background color:
function updateTheme() { var theme = qx.manager.object.AppearanceManager.getInstance().getAppearanceTheme(); var apar = theme._appearances['text-field']; if (!apar) { return; } var backColorFocused = new qx.renderer.color.ColorObject("#EEF"); var backColor = new qx.renderer.color.ColorObject("#FFF"); var oldState = apar.state; apar.state = function(vTheme, vStates) { var res = oldState ? oldState.apply(this, arguments):{}; if (vStates.focused) { res.backgroundColor = this.backColorFocused; } else if (!res.backgroundColor) { res.backgroundColor = this.backColor; } return res; } }
Observe that we don’t need a setup function, because we use closures. The backColor object is created just once.
Possibilities
As you are inside javascript and not simple CSS in this case, you can do all the fancy things you can do with javascript. You can combine different states, for example focused and mouseover together looks different from mouseover only.
Inheritance
We have also built in a way to inherit between different appearances. This inheritence is completely separated from the widget inheritance. For example you can inherit from the button appearance even for a completely different widget.
To load styles from another appearance you have the following methods inside your theme reference (given as vTheme in the above examples):
initialFrom: For getting the initial hash of another appearancestateFrom: For getting the state hash of another appearance
These methods returns the return value of the inherited appearance. This is a simple hash as already explained before. You can merge this with local settings or simply return it.
- Simple return
initial: function(vTheme) { return vTheme.initialFrom("label"); }
- Return with local changes:
initial: function(vTheme) { var orig = vTheme.initialFrom("label"); orig.backgroundColor = "red"; return orig; }
- More compact (same result as above):
initial: function(vTheme) { return qx.lang.Object.mergeObjectWith(vTheme.initialFrom("label"), { backgroundColor : "red" }); }
