Document Information

Last modified:
2008/02/20 13:15 by ecker

Theme Support

Features

Theme handling has been improved considerably for qooxdoo 0.7. Just to mention a few of the new features:

  • easier-to-read configuration compared to qooxdoo 0.6.x, mostly simple key-value pairs like in CSS
  • internal caching to improve runtime performance of complex applications
  • no cross-browser code needed
  • include statement for appearance entries (e.g. a toolbarbutton can include the definitions of a button, ...)
  • deriving from existing themes, possibility to only override some selected entries
  • named fonts, borders and colors can easily be defined in a central place and can be used all over the appearance theme later by name
  • common syntax for all different types of themes
  • themed values for the general look are stored independent of the so-called user values, which are applied per instance. This allows to simply reset a value and to fallback to the appearance value afterwards.

Theme types

The theme system supports the following independent types of themes, each in a separate file:

  • colors
  • fonts
  • borders
  • icons
  • widgets
  • appearances

and also meta themes to combine references to all of them in one definition file.

It is possible to switch between alternate themes of each category at runtime. All dependent objects are automatically notified and updated.

Each theme supports only one type of theme, i.e. a color theme only defines colors. You can not directly combine multiple themes in one declaration. However, the single themes (colors, borders, etc.) can be easily combined into meta themes. A meta theme is meant to represent a feature-complete interface design, that includes all the information for a consistent look.

Defining a new theme

The most simple version looks like the following:

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme"
});

The title is a required parameter, also to support for the convenient list functionality found in qx.util.ThemeList. Now you can add some content to the theme:

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme",
 
  colors : {
     "header" : [ 200, 100, 20 ]
  }
});

As you may have guessed this defines a new color theme. This examplary color theme only defines a single color called header, with RGB 200, 100, 20. It is possible to refer to this color everywhere else in the theme (and outside) using the user-defined identifier header.

It is possible to define as many entries (here, colors) as wanted. The entry keys (like header) should however always adhere to the following scheme: normal characters and numbers, no spaces, no special characters ([a-zA-Z0-9]+). These keys should always been listed in quotes by convention. This makes the theme more readable and differentiates the key colors from the entry keys (values).

The other themes (border, fonts, icons, widgets, appearances) are defined in a similar way.

Defining a border or font theme

Font and border themes also have an entry key, as seen in the color theme, and a value. However, in these themes the value is not an array or string, but a map of properties with their respective values. An example:

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme",
 
  borders :
  {
    "black" : 
    {
      width : 1,
      style : "solid",
      color : "black"
    }
  }
});

This code creates a new border theme with a black border. Both the border and font configuration is internally stored inside a Border respectively Font object. The map can control any available properties of these objects.

Available border properties

A border has the following properties:

  • widthTop, widthRight, widthBottom, widthLeft
  • styleTop, styleRight, styleBottom, styleLeft
  • colorTop, colorRight, colorBottom, colorLeft
  • innerColorTop, innerColorRight, innerColorBottom, innerColorLeft

To allow the syntax of the code example above there are also some property groups:

  • top: configures widthTop, styleTop and colorTop
  • right:, configures widthRight, styleRight and colorRight
  • bottom: configures widthBottom, styleBottom and colorBottom
  • left: configures widthBottom, styleBottom and colorBottom

... and also ...

  • width: configures all width properties
  • style: configures all styleproperties
  • color: configures all color properties

For further information regarding property groups, please take a look at the corresponding documentation.

For all currently available properties, please also take a look at the API documentation for class Border.

Just one thing to note: Property groups support arrays. This way it is also possible to use the shorthand modes from inside the appearance theme e.g.

"black" : 
{
  width : 1,
  style : [ "solid", "dotted" ],
  color : "black"
}

creates a solid border at the top and bottom and a dotted at the left and right edges.

Defining complex borders

qooxdoo supports complex borders, i.e. two pixel thick colored outset borders. These borders need an inner and an outer color. qooxdoo supports these borders cross-browser and pixel perfect in a stable manner. Compared to previous qooxdoo versions, the support for these border styles was dramatically improved with qooxdoo 0.7. It is possible to define each color separately – independent from the browser habits. It is also possible to use any of the themed colors defined previously.

Complex borders are only supported for two pixel borders. Thicker multi-colored borders are currently not supported. Complex borders are also always solid. Other styles are normalized accordingly.

Available font properties

A font declaration is comparable to a border definition. A typical font theme could look like the following:

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme",
 
  fonts:
  {
    "default" :
    {
      size : 11,
      family : [ "Lucida Grande", "Tahoma", "sans-serif" ]
    },
 
    "bold" :
    {
      size : 11,
      family : [ "Lucida Grande", "Tahoma", "sans-serif" ],
      bold : true
    }
  }
});

It is as easy as to define new borders. One interesting property is family. This property accepts only arrays of font names because this improves the usability (no escaping, no handling with different quotes as in the single-string CSS version ‘“Lucida Grande”, Tahoma, sans-serif’).

The font object supports the following properties: size, family, bold, italic - really powerful. Please take a look at the corresponding API documentation for more details.

Please note that the color of the text is not available here. Often this color is needed independent from the font definition so this property is separated, too. It is available on each Widget under the name textColor.

Defining icon and widget themes

Both theme types consist of images. Widget themes contains all these small images used to display textures, arrows, tree lines, etc. Icon themes contain images in the typical icon dimensions: 16×16, 22×22, 32×32, 48×48, etc. Icon themes are often used by the applications and sometimes by the appearance theme, too, whereas widget themes normally should only used in the appearance theme to define the look and feel of the widgets.

The question is why at all one need to define a Theme, just to use some images which are usable without themes, too. The answer is, because only then they are known in a standardized way to the framework. This means that they can be listed or switched at runtime.

Both themes just define a URI. This is a URL where the images can be found. In combination with a setting this path can even be reconfigured between different builds e.g. internal and public.

An example:

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme",
 
  icons : {
    uri : "http://my.server/img/icons"
  }
});

It is also possible to place the icons inside the application resource folder. In this case an icon theme may look like:

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme",
 
  icons : {
    uri : qx.core.Setting.get("custom.resourceUri") + "/icons"
  }
});

In skeleton based application this should automatically work quite nicely.

Defining appearance themes

An appearance theme comes with the most complex declaration. But the declaration was improved with the last releases. Nowadays it is quite simple. As always it is a good idea to first try to customize using the other themes, especially color, border and font themes. Often the results with these changes are a good first step to fulfill the Photoshop draft created by the interface designers.

Each widget can have an appearance ID. Some of them embed other (hidden) widgets and control their appearance IDs. Each entry in an appearance theme represents such an ID. It is possible define as many as needed. All entries should be in quotes per convention. This improves readability and allows hyphens to structure the IDs a bit. Normally the embedded/internal widgets extend the appearance key of the parent widget e.g. window, window-captionbar, window-captionbar-button. As a convention all IDs should be completely lowercase. The different parts should be divided using hyphens. The IDs need not to be identical to the class name of the widget behind.

The value of each entry is a map which contains an (optional) style function and an optional include block. The function is needed to support the interactive state changes which may affect each widget. The first parameter of all these functions is a map of all currently active states. They can simply be queried using something like states.hover or states.focused. For available states and appearance keys please take a look at the API documentation.

An example:

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme",
 
  appearances : 
 {
    "window" : 
    {
      style : function(states)
      {
        return {
          backgroundColor : "gray",
          color : "black"
        };
      }
    }
  }
});

OK, this is definitely more complex than the previously mentioned themes, but it also comes with some really great features. Just another small example to show how to use the state system.

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme",
 
  appearances : 
 {
    "button" : 
    {
      style : function(states)
      {
        return {
          backgroundColor : states.hover ? "blue" : "grey",
          padding : 2
        };
      }
    }
  }
});

The example above declares a new appearance button with a backgroundColor which reacts on mouse hover / mouse out. Please remember that, as this is a function, you have full access to all advanced JavaScript features. But as the themes are often edited by less experienced JavaScript developers it is a good manner to keep it as simple as possible. The typical map structure with simple checks is the preferred declaration.

If you are a widget developer, you can easily add and remove own states to your widgets using the methods addState and removeState on each instance. Each appearance is independent of the widgets. This is needed because of an advanced internal caching system and also a good idea to prohibit access to the instances and doing more custom stuff. The only way to control which style to choose should be the given states map. A widget developer must add the states needed by the appearance developers as needed to allow them to fulfill their requirements.

Including from other appearance IDs

Each entry can include another entry from the same theme. For example a toolbar button can include the appearance of the button and change only a few things, if needed.

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme",
 
  appearances : 
 {
    "button" : 
    {
      style : function(states)
      {
        return {
          backgroundColor : states.hover ? "blue" : "grey",
          padding : 2
        };
      }
    },
 
    "toolbar-button" :
    {
      include : "button",
 
      style : function(states)
      {
        return {
          padding : 4
        };
      }
    }
  }
});

The toolbar-button will include all the styles from button and update the padding from 2 to 4.

The include mechanism only works for appearances, not for fonts or any other type of theme.

Extending appearance IDs

Each entry can extend an appearance ID from the parent theme by using a base call if this appearance ID is available in the super appearance theme. For example one can create an own appearance theme and define an appearance button just like in the super appearance theme and then extending this appearance using the base call. Without this the appearance of the super theme would be overwritten by the local changes.

qx.Theme.define("name.space.SuperTheme",
{
  title : "My super theme",
 
  appearances : 
 {
    "button" : 
    {
      style : function(states)
      {
        return {
          backgroundColor : states.hover ? "blue" : "grey",
          padding : 4,
          margin : 2
        };
      }
    },
  }
});
qx.Theme.define("name.space.SubTheme",
{
  title : "My sub theme",
 
  appearances : 
 {
    "base-button" : 
    {
       style : function(states)
       {
           return { padding : 2 }
       }
    }    
 
    "button" : 
    {
      base    : true,
      include : "base-button",
 
      style : function(states)
      {
        return {
          backgroundColor : states.hover ? "red" : "grey"
        };
      }
    },
  }
});

The button will extend the existing button appearance of the super theme and have all the styles from button which are not defined locally or merged into via an include. The merge of the properties is done with the following priorities:

  • local
  • include
  • base

In the shown example only the property margin of the appearance in the super theme will be applied. The property backgroundColor is overwritten locally and the property padding is overwritten via the include.

The base mechanism only works for appearances, not for fonts or any other type of theme.

Resetting values

To delete a property defined through the include you have to explicitly have to make it “undefined”. This is especially interesting for this include system, but may also be useful for other properties, especially inheritable ones, using normal state processing.

Take a look at the following example which has declares a modified hover effect. In this case the

qx.Theme.define("name.space.ThemeName",
{
  title : "My theme",
 
  appearances : 
 {
    "button" : 
    {
      style : function(states)
      {
        return {
          backgroundColor : states.hover ? "blue" : "undefined",
          padding : 2
        };
      }
    }
  }
});

Please pay attention to the string “undefined”. This is a special value supported by the property system which is only available in the appearance theme (as a replacement of the reset() method which is not available here).

Extending existing themes

The theme system supports inheritance from existing themes. The idea behind is comparable to the features known from qx.Class.define. All entries inside the theme definition block gets inherited e.g. all members of the appearance, borders etc. keys are also available in the derived theme.

The inherited theme and the current theme needs to be of identical type. It is not possible to mix themes in one using inheritance. To do such a combined theme follow this article to the meta theme section.

If you define an appearance button inside themeA and extend themeA in a themeB, you automatically get the value of themeA, if you do not overwrite it. A difference, compared to classes, is that it is not possible to make a super theme (super()) theme call. Overwritten entries get completely overwritten. Access to the derived class is impossible.

Just a short example with two color themes:

qx.Theme.define("name.space.ThemeNameA",
{
  title : "My theme",
 
  colors : 
  {
    "header" : [ 200, 100, 20 ],
    "footer" : [ 0, 0, 0 ],
    "logo" : [ 100, 33, 220 ]
  }
});
qx.Theme.define("name.space.ThemeNameB",
{
  title : "My theme",
  extend : name.space.ThemeNameA,
 
  colors : {
    "footer" : [ 20, 20, 20 ]
  }
});

The theme ThemeNameB gets all the entries of ThemeNameA but overwrites the footer definition from black to a dark gray. This works the same with all the other themes.

Defining meta themes

These are maybe the simplest themes. They just define the other themes to use and combine it into one, easy manageable, object.

qx.Theme.define("name.space.Blueish",
{
  title : "Blueish",
 
  meta :
  {
    color : name.space.color.Blue,
    border : name.space.Border,
    font : name.space.Font,
    widget : name.space.Widget,
    appearance : name.space.Appearance,
    icon : name.space.Icon
  }
});

And yes, they are inheritable, too. You can just define one entry from another meta theme e.g. create different color variations easily.

How to apply a theme

Typically, your application will have a certain, pre-defined appearance known at build-time. The best way to associate such a default appearance with your application, is to use the Makefile variable APPLICATION_THEME. Setting this variable to the fully-qualified theme class lets the build process handle the proper inclusion and linkage of the theme classes automatically. E.g.:

APPLICATION_THEME=my.theme.Cool

It is also possible to set a certain appearance at runtime:

qx.theme.manager.Appearance.getInstance().setAppearanceTheme(my.theme.Cool);

For a meta themes or color, border, icon and widget themes, you can use similar classes in the qx.theme.manager package. Look at the API viewer for further details.

The ability to switch meta themes or individual themes at runtime allows you to create applications that let the user apply different skins in the deployed application. See the feedreader application for a demostration of this runtime-switching of themes.

This is also a handy feature when developing an application that shares a common code base for different customers. As a developer or tester you are now able to switch to a different theme that reflects the customer’s individual corporate design at any time during development. Of course, in the final product you would deploy or deliver the customer-specific application only with the appropriate pre-configured theme.

How to add new appearances to current theme

There is even a way of adding more appearances to the currently selected theme (even without knowing its actual name). While it is recommended to configure complete, self-consistent theme files rather than manipulating them programmatically at runtime, you may find it useful to do so nonetheless:

function updateTheme() {
        qx.Theme.define("MyNewAppearance", {
                title: 'Menu',
                extend: qx.theme.manager.Appearance.getInstance().getAppearanceTheme(),
 
                appearances: {
                        'vertical-toolbar': {
                                include: 'toolbar',
                                style: function(states) {
                                        return {
                                                width           : 'auto',
                                                height          : '100%',
                                                backgroundColor : "button",
                                                backgroundImage : null
                                        }
                                }
                        }
 
                }
        });
        qx.theme.manager.Appearance.getInstance().setAppearanceTheme(MyNewAppearance);
}

The arbitrarily named function updateTheme() should then be called from within the main() method of your application class.

BTW, you can also inherit from theme classes not known until runtime!!!.

Information

Last modified:
2008/02/20 13:15 by ecker

Account

Not logged in

 
 

Job Offers

To further improve qooxdoo we are seeking javascript developers. Read more...

Rich Ajax Platform (RAP)

RAP uses qooxdoo, Java and the Eclipse development model to build rich web applications. Read more...

qooxdoo Web Toolkit (QWT)

Similar to GWT this framework allows to create impressive qooxdoo applications just using Java. Read more...

Pustefix

Pustefix is a MVC-based web application framework using Java and XML/XSLT. Read more...

 
SourceForge.net Logo

Bad Behavior has blocked 0 potential spam attempts in the last 7 days.