Contents

Theming Ubuntu Components

The theming is under heavy development, therefore changes may occur leaving the documentation out of date.

What is theming and why is needed?

Theming is one of the most important aspects of a component set that is aimed to be fully reusable and customizable. With proper theming support both the look-and-feel can be changed without altering the components API or implementation. Creating a division between the visual implementation and the stylistic settings will help enforce visual consistency throughout the application and perhaps ease visual experimentation by designers.

The following sample illustrates the abilities of the theming.

Theme file (sample.qmltheme):

@qml-mapping(.frame, , FrameDelegate);
@qml-import(Ubuntu.Components 0.1);

.frame {
   borderColor: "darkgray";
   radius: 10;
   fillColor: "lightgray";
   opacity: 0.8;
}
.pushbutton {
   color: item.hovered ? "tan" : "#757373";
}

Delegate files used by the theme providing the visuals:

// FrameDelegate
import QtQuick 2.0
import Ubuntu.Components 0.1

Rectangle {
   anchors.fill: parent
   color: itemStyle.fillColor
   radius: itemStyle.radius
   opacity: itemStyle.opacity
   border {
      color: itemStyle.borderColor
      border.width: 2
   }
}

The PushButton document:

// PushButton.qml
import QtQuick 2.0

Item {
   id: root
   property alias hovered: mouseArea.containsMouse
   property alias pressed: mouseArea.pressed

   signal clicked

   MouseArea {
      id: mouseArea
      anchors.fill: parent
      hoverEnabled: true

      onClicked: clicked()
   }
}

Finally the main document:

// Main doculent
import QtQuick 2.0
import Ubuntu.Components 0.1

Rectangle {
  ItemStyle.class: "frame"
  width: 100
  height: 100
  PushButton {
     width: 100
     height: 40
     anchors {
        left: parent.left
        top: parent.top
        leftMargin: 20
        topMargin: 20
     }
     ItemStyle.class: "pushbutton"
     ItemStyle.delegate: Rectangle {
        anchors.fill: parent
        color: (ItemStyle.style) ? ItemStyle.style.color : "transparent"
        radius: 25
        Text {
          anchors.centerIn: parent
          text: "Button"
        }
     }
  }
}

Theming Engine, Theming Components

The theming used in Ubuntu Components follows this idea of changing the look-and-feel. Our approach on selecting items to theme is inspired by CSS stylesheets, however does not implement every feature of CSS format. Therefore the theme file format is also called QmlTheme theme file format (.qmltheme). Theme files are loaded by the components library upon startup before any QML element of the application would be created. The library loads the default theme that is set for the user in $HOME/.config/UITK/theme.ini file, which contains the following setting keys:

Theme files are stored within a directory named qmltheme, within which the default theme files should be named default.qml or default.qmltheme.

For example the Ambiance theme is located under /usr/share/themes/Ambiance and the default theme file therefore will be at /usr/share/themes/Ambiance/qmltheme/default.qmltheme.

When themes are loaded, the engine builds up a map of rules to ease style lookup. These style rules are applied only on items which have styling defined using ItemStyle attached properties. The ItemStyle attached object will look after style rules based on the styling information set and based on the hierarchy they are nested in. Style rules can define configuration properties (style property), visuals (delegate property) or both.

Style rules are identified through selectors. Selectors follow the syntax defined by the W3C for CSS selectors however does not provide full support. The supported syntax is:

Examples:

1) a Rule having its selector identifying QML elements styled with class "PushButton"

// QmlTheme rule
.PushButton {
   ...
}

2) a Rule having its selector identifying QML elements styled with class "pushbutton" and named as "red"

//QmlTheme rule
.pushbutton#red {
   ...
}

3) a Rule having its selector identifying QML elements styled with class "PushButton" named as "red" and which are children of QML elements styled as class "frame"

//QmlTheme rule
.frame .PushButton#red {
   ...
}

4) a Rule having its selector identifying QML elements styled with class "PushButton" named as "red" and which are direct children of QML elements styled as class "frame"

//QmlTheme rule
.frame>.PushButton#red {
   ...
}

QmlTheme file format

The QmlTheme file format uses the ideas and format of CSS stylesheets, however does not implement the full specification. QmlTheme files are identified through their extension, which is .qmltheme. A theme file can contain styling rules and so-called @-rules.

@-rules

@-rules are special rules, usually defined in one line ending with a semicolon character, which define further actions or primitives. QmlTheme defines the following @-rules:

@import rule

Syntax:

@import url(<url>);

The rule instructs the theme engine to import an other QmlTheme file from a given location. The location can be relative to the current theme's folder, from an absolute path or from the resources.

Examples:

Note: see also The url() macro.

@qml-import() rule

Syntax:

@qml-import (<import-module>[,<import-path>]);

The rule instructs the theming engine to use the import-module when creating the styling components (styles and delegates). The import-path is an optional parameter, which when specified will be added to the QML engine's import path list. Therefore the parameter should be specified when the imported module specified in import-module is not included in the QML Engine's import path.

The engine defines three tags that can be used in import-path syntax. These are the following:

Examples:

@qml-mapping() rule

Syntax:

@qml-mapping(<selector>,[<Style-element>],[<Delegate-element>]);

The rule defines the style configuration and delegate elements for a given selector, which will be used when generating Rule elements. One of the Style-element and Delegate-element parameters must be specified. If one is left out, an empty value must be specified.

Example:

The url() macro

Resources may be stored in locations that are not resolvable using Qt.resolvedUrl() and those can be located relative to the current theme file. For example a theme may have all its resources stored in a subfolder near by the theme definition file. This URL cannot be resolved by the Qt.resolvedUrl() function as that one resolves paths that are relative to application's current path. In these cases themes can use the url() macro, which will resolve the given path relative to the current theme file parsed.

Modifying the previous example having resources under a subfolder of the theme named "resources". The declaration block of the pushbutton would look as follows:

  .pushbutton {
      color: "lightgray";
      frame: url("resources/button-frame.png");
      pressedColor: Qt.darker(color, 1.2);
  }

Style rules

Style rules are defined using "selectors {declaration-block}" syntax, where selectors define one or more selectors separated by commas, and the declaration block follow QML syntax to ease transfering of those to QML. Note: Unlike QML every line in the declaration block must be separated with semicolon character.

The following example defines styling for a PushButton element identified with ".pushbutton"

  .pushbutton {
      color: "lightgray";
      frame: Qt.resolvedUrl("resources/button-frame.png");
      pressedColor: Qt.darker(color, 1.2);
  }

Assuming the theme defines a mapping for the style called PushButtonStyle, the engine will map the property values listed in the rule using the type. In case the style type is not defined, the engine will create a QtObject using var property type for each declaration. Note that the engine does not perform any type checking nor check the property presence in the specified type.

The theming engine defines two context properties that are available for the style and delegate components. These context properties are:

For example, assume the PushButton widget is defined as follows:

  Item {
      property bool hovered
      property bool pressed
  }

where hovered specifies the hovered state and pressed the pressed state. The previous style could be modified to change the pushbutton color to lightgreen when hovered as follows:

  .pushbutton {
      color: item.hovered ? "lightgreen" : "lightgray";
      frame: Qt.resolvedUrl("resources/button-frame.png");
      pressedColor: Qt.darker(color, 1.2);
  }

Inheritance

One aspect that differs radically from CSS is the ability to derive selectors. Selectors can be built to use previous declared selectors. When so, the new selectors will "inherit" the properties of all derived ones. The syntax of such inheritance is derived-selector[.base-selector]*. The new selector can be referred without its derivates within the theme file, however widgets must specify all the classes the selector is derived from in the same order as defined in the theme (see example below). The following example defines two button styles, one deriving from the other, and derived one overriding one property of its base style.

theme.qmltheme:

  .pushbutton {
      color: item.hovered ? "lightgreen" : "lightgray";
      frame: Qt.resolvedUrl("resources/button-frame.png");
      pressedColor: Qt.darker(color, 1.2);
  }
  .red-button.pushbutton {
      pressedColor: "red";
  }
  // define one more property for red-button
  .red-button {
      moreProperty: "extra";
  }

Somewhere in QML...

  Item {
      PushButton {
          text: "Normal"
      }
      PushButton {
          text: "Normal"
          // style must specify the entire selector chain with space separators
          ItemStyle.class: "red-button pushbutton"
      }
  }

... so the second PushButton will have all the properties .red-button and .pushbutton has, with .pushbutton properties overriding .red-button properties of the same name.

The derived selector properties are built up in the following order:

The following example illustrates building the properties

  .baseA {
      baseAProperty: "baseA";
  }
  // extends .baseA
  .baseA {
      baseAProperty2: "baseA2";
  }
  //define few properties
  .derived {
      property: "some base stuff";
  }
  //then derive from bases
  .derived.baseA {
      baseAProperty: "override";
  }

So, derived would have properties with the following values:

Selectors can be built of as many base selectors as needed, and selectors used as base can also be derived ones.

  .colors {
      color: "lightgreen";
      hoveredColor: "green";
      pressedColor: "gray";
  }
  .pushbutton.colors {
      color: item.hovered ? hoveredColor : "lightgray";
      background: url(someimage.png);
  }
  // create red-button derived from pushbutton and colors
  .red-button.pushbutton.colors {
  }

Styling Widgets

Styling elements is realised using attached properties grouped in ItemStyle element. The component can be attached to any element that supports visuals (i.e. derived from Item or QQuickItem), in this way it is possible to style existing QML elements, as well as component library widgets.

The main properties that define styling are the class and name properties. class property declares the style class to be applied, and name defines the unique identifier of the widget. Theming engine uses these properties to build up the selector node that identifies the widget. The format used to build it is .<class>[#<name>]. If neither of these are specified, but the widget still uses styling, the engine will apply the default style class, by substituting class with the meta class name (i.e. PushButton.qml this will be PushButton).

Updating the previous example, the complete PushButton widget with style information would look like

  Item {
      property bool hovered
      property bool pressed
      ItemStyle.class: "pushbutton"
  }

When a widget style is located, the engine will build up the complete selector using the widget and its parents, considering only those parents that are styled for chosing the relation between them. The following example illustrates two situations for descendant and direct child compositors:

  Toolbar {
      ItemStyle.class: "toolbar"
      PushButton {
      }
  }

In this sample, the button will be styled using the rules defined for ".toolbar > .pushbutton" or if not defined, according to the rules defined by ".pushbutton" selector. However, the following example will create a selector like ".toolbar .pushbutton" as there is a non-styled element between Toolbar and Button, so PushButton is no longer a direct child of Toolbar.

  Toolbar {
      ItemStyle.class: "toolbar"
      Row {
          PushButton {
          }
      }
  }

Styling QML elements

As mentioned before, attaching the ItemStyle property to QML elements can also be used to style them. The following example styles a Text element. There's no delegate as the Text element provides the visuals needed to create a Label widget.

  .label {
      fontSize: 12;
      color: "darkgray";
  }
  .red-label {
      fontSize: 12;
      color: "red";
  }
  // Label.qml
  Text {
      font.pixelSize: (ItemStyle.style && ItemStyle.style.fontSize) ?
                        ItemStyle.style.fontSize : 12
      color: (ItemStyle.style && ItemStyle.style.color) ?
                        ItemStyle.style.color : "black"
  }

When using the component, different styles can be applied as the following code sample illustrate:

  //dark gray colored text
  Label {
      ItemStyle.class: "label"
  }

  // red colored text
  Label {
      ItemStyle.class: "red-label"
  }

Custom (private) Styles

ItemStyle has two more properties which hold the style configuration properties and the visuals. These properties are set the theme contains styling identified by the selector of the widget. These properties can also be set to styling configuration or delegates others than the ones defined in themes.

An example of a Label that uses a private style configuration defining font size to be always 14 pixels and color as blue, would look like:

  Label {
      ItemStyle.style: QtObject {
          property real fontSize: 14
          property color color: "blue"
      }
  }

A particular widget user can decide to use private styles and delegates, style from theme with private delegate, delegate from theme with private style, or just rely on the theme for both. A button that uses a private delegate but style from theme would look like

  PushButton {
      id: button
      ItemStyle.delegate: Rectangle {
          anchors.fill: parent
          color: button.pressed ? button.ItemStyle.style.pressedColor : button.ItemStyle.style.color
          radius: 15
      }
  }

QmlTheme theme file example

Finally, here are some examples of QmlTheme themes.

default.qmltheme

  @qml-mapping(.pushbutton, PushButtonStyle, PushButtonDelegate);
  @qml-import(Ubuntu.Components 0.1);

  .pushbutton {
      color: "#e3e5e8";
      shapeDark: url("artwork/ButtonShapeDark.png");
      shapeNormal: url("artwork/ButtonShape.png");
      borderIdle: url("artwork/ButtonBorderIdle.png");
      borderPressed: url("artwork/ButtonBorderPressed.png");
      borderPressed: url("artwork/ButtonBorderPressed.png");
      borderDarkIdle: url("artwork/ButtonBorderDarkIdle.png");
      borderDarkPressed: url("artwork/ButtonBorderDarkPressed.png");
  }

  .label {
      fontFamily: "Ubuntu";
      fontSize: "medium";
      color: "red";
  }

theme.qmltheme

  @import url("../default.qmltheme");
  @qml-import(themes 1.0, current:/demos);
  @qml-mapping(.frame, , TestFrameDelegate);

  .pushbutton {
      color: item.hovered ? "lightgray" : "#a3e5e8";
  }

  .pushbutton#red {
      color: "red";
  }

  .frame {
      color: "tan";
  }

QML elements supporting theming

Available through:

      import Ubuntu.Components 0.1

ItemStyle

The ItemStyle attached property contains styling properties for a particular UI element.

Theme

The Theme element provides functionality to change the current theme.