Apr 15, 2012

QML Theming/Styling

Intro


As I mentioned in my last post, I and my team in INdT are researching on how to improve the Styling  new way of creating styled QML widgets components (Button, Sliders, ComboBox, etc.). We've been involved for such a long time in the development of Qt Components for the Harmattan, and me personally worked on the Plasma Components widget set.

We also did a some big desktop and mobile applications (such as Snowshoe and PagSeguro NFC Payment) which required us to create lots of custom widgets. Obviously there's a lot of redundant code between the applications that could belong to a common code base.

Besides the existence of the Qt Components, the styling of widgets is an issue it doesn't solves as well. Nowadays if you want a new style, you'll have to create a set of QML components from scratch or to base on an existing widget set, which would increase the development and code fragmentation. One way to style your widgets is to rely on the old and confusing QStyle classes, and there is no easy way to it purely in QML.

ps: This post was written by me and Thiago Lacerda.

Issues and Requirements


Recently, lots of discussions about a new styles API happened on the Qt development mailing list. Some of the issues as we considered were summarized by Alan Alpert in a thread on Qt Project's forum. Here are some of the points he collected from the discussion in the mailing list:

  • One source, multiple target platforms
  • Change the appearance of standard controls in my application
  • Style QWidget and QQuickItem UIs with the same code
  • Inside QML items, draw native controls without using QML

In addition to those concerns, based on our experience on desktop and mobile applications we have a few other important issues to guide us:

  • Configure current theme/style from the QML
  • Use QML for configure new Styles for widgets
  • Minimize code size for application developers
  • Wisely use SceneGraph to improve performance
  • Don't need to link with the old QtWidgets module

The current implementation of the custom components in the Desktop Components solves a few of these problems. However one of the biggest problem is depending on QtWidgets, not having an easy style change support and not properly using the SceneGraph for painting the widgets.

QtQuickStyles


To research how to solve these issues we've decided to create the QtQuickStyles module. The general idea for this module is to provide a simple way of styling QML widgets (think of Qt Components). The current research is about creating an API to allow proper platform look and feel integration without depending on QtWidgets, and easily theme your whole application with your custom style, if you want another theme besides the underneath platform look and feel. We want that the developers be able to do it all purely in QML. All the collected issues are being considered for the API.

The current work is available here:

git clone git://code.openbossa.org/projects/qtquickstyles.git

Now, our focus is to adapt the existing code base, from QStyle, for painting elements in QML as a short term target so we can have working code faster, and supporting all the same platforms and styles that QtWidgets does.

QStyle


Today, on Qt code, each style knows how to paint all the control elements, and they all follow an interface (defined by QStyle class). If you want a new style you will have to implement a new QStyle subclass. The following scheme may clear up your mind:

Current QStyle architecture


Additionally, if you take a look at the QStyle code, you can see a bunch of mixed code, taking care of drawing all type of widgets. Almost all the methods have a huge switch case, each taking care of an specific action of an specific widget. When someone put the eyes on it for the first time, thinks it is too complicated to understand and very messy (and indeed it is). Additionally, a code like this leads to a huge complexity to maintain and understand it.

So, why can’t each widget take care of its own features and painting? Why we could not have an interface that would define how a button is painted, for instance? Doing it this way, if we have some work to be done on the button (again the button example), it would only be done on this button class. Furthermore, if we have modifications on how the button with the plastique style, for instance, is painted, we have only to do it on the plastique button class. This approach leads to more understanding on the overall code and also makes it easy to maintain, isolating the problems that can appear, to the widget itself.

To do this, a component must be a control or composed of sub controls (such as the Slider's groove, handle and tickmarks) and for each control it'll use a QQuickItem that reads the widget's state and "paints" itself with as it is the style.

QtQuickStyles in use


We've done a few brainstorm for defining the current API to use the style in the components.
The following code is the example of how our API was thought to be used:


// Regular Button having the underneath platform look and feel
import QtQuick 2.0
import QtUiComponents 1.0

Button {
    width: 140
    height: 40
    text: "Native Button"
}


We are going to explain later how the style resolves the current platform being used.

Override platforms style:


// Regular Button having the plastique look and feel
import QtQuick 2.0
import QtUiComponents 1.0
import QtUiStyles 1.0

Button {
    width: 140
    height: 40
    text: "Plastique Button"

    // This use case is not implemented exported yet, but it will be
    style: PlastiqueButtonStyle { }
}


Using a user defined style:


// Regular Button
import QtQuick 2.0
import QtUiComponents 1.0

Button {
    width: 140
    height: 40
    text: "User-defined Button"
    // Accepts a QML Component
    style: Rectangle {
        color: "gray"
        Text {
            anchors.centerIn: parent
            text: button.text // button reference is injected when the internal Loader loads it
        }
    }
}


We can see, by the examples above that we can easily uses a Button component and gives it the look and feel that we want, by only playing around with the style property. In order to follow a well discussed and already implemented Component's API, we're using the Qt Components Common API specification for creating the components in the repository.

Custom Styles


To solve the issue of being easy to create a new style, we are also adding component helpers that exports properties that commonly changes from one application to another. These configurable properties were selected based on our experience of creating QML components.
The custom style API defined for the Button and Style can be found here.


// Regular Button
import QtQuick 2.0
import QtUiComponents 1.0

import QtUiStyles 1.0

Button {
    width: 140
    height: 40
    text: "Custom Style Button"
    style: CustomButtonStyle {
        background: "myButton.png"
        pressedBackground: "myPressedButton.png"
    }
}


ApplicationStyle API


As listed in the issues list, it's interesting that we had an easy way of setting the application theme/style in QML. We came to an API that let the developer change the style of all the components in the application that uses the default style. The example below shows how to play around with the application theme/style:


import QtQuick 2.0
import QtUiStyles 1.0

Item {
    ApplicationStyle {
        id: appStyle
        buttonStyle: CustomButtonStyle {
            background: "myButton.png"
            pressedBackground: "myPressedButton.png"
        }
    }

    Button {
        text: "Button with custom Style"
    }

    Button {
        text: "Button with plastique Style"
        style: PlastiqueButtonStyle { } // Theme changes doesn't affect when style property is set
    }

    Button {
        text: "Button with appStyle"
        style: appStyle.buttonStyle // Explicitly binds button style with a style
    }
}


Implementation Details


We have a global class, called QUiStyle, which (practically) has getters for each widget style who defines how it is painted. So, we would have some methods like: buttonStyle(), checkBoxStyle(), radioButtonStyle() and so on. Each specific style, e.g. Plastique, Cleanlooks, Widows, etc, will inherits from this base class and set the members that will be returned by the getters, to its specific widget style class, for instance, on the QUiPlastiqueStyle class, we will set buttonStyle member to an instance of QUiPlastiqueButton. So, it is easy for a developer to add its own Style (if he desires to make it on C++), he only has to define his new style class, inheriting from QUiStyle, and set the style widget members of QUiStyle to its own style widgets.

The following diagram can ease your understanding of the current code structure:

Proposal for a new QStyle modular architecture

For each type of widget we will have a class that calls the correct style class to paint it. For example, for the button, we have a class called QUiButtonStyle, which will ask for the QUiStyle a reference for the platform button style (calling the buttonStyle() method). Then, it will be drawn with the current platform look and feel. All the other widgets follows the same workflow.

Current implementation for the style components


In order to theme your application, we have a global object called appStyle and a QML component, called ApplicationStyle (presented in the QML example above). The ApplicationStyle component as a style property for each type of widget, e.g. buttonStyle, checkBoxStyle, radioButtonStyle, etc. These properties binds to the appStyle object analogue properties. So, if you want all your buttons of your application to have a CustomButtonStyle, you can simply do it as the example.

Development Status


Currently, we are focusing on creating a good set of working widgets for the Plastique style, but we are planning to implement another style parallelly, the CleanLooks is the favourite candidate.
Now, if you take a look at our repository, you will find the following widgets:

  • Button
  • CheckBox
  • RadioButton
  • Slider

Besides this, we have a CustomStyle for each one, e.g. CustomButtonStyle, CustomCheckBoxStyle, etc. If a user does not want to use the platform look and feel or create a style from scratch, he can use this Custom API to easily create his own styles.
The next widget that is planned to be developed is the ComboBox, also with his Custom Style.

Remarks


This first release was done using the QPainter to paint the widgets, instead of using the new SceneGraph. This was done in order to speed up our development and have a nice set of widgets working as fast as possible. In the future (close future) we will get rid of all QPainter based classes and replace them to use SceneGraph. The current custom styles already does that, but we would like to have them working for the platform styles as well, and we are already researching it.

The code in repository is mess because we are still doing a lot of changes, but we need to make it make it more stable and follow the Qt addons repository architecture until the end of the month. If you have any opinion, doubt or suggestion, please let us know.

14 comments:

  1. Finally this sounds like somebody cares about native look and feel :)

    I think you should try styles for platforms completely unsimiliar to each other - e.g. the datepicker for ios and for android
    to really prove this approach works out well :)

    ReplyDelete
  2. Just dropping by to leave an idea: what about taking SVG beside PNG? SVG have absolutely no problem with sizes and that works great for translation and line breaks.

    ReplyDelete
    Replies
    1. The current CustomStyle implementation uses a BorderImage, and it can handle SVG, so if you create your custom style with SVGs it will be rendered without such issues.

      Delete
  3. Prior to having a look at the current discussions, I'd prefer having a CSS-style (maybe QSS) solution for this one. Adherence to common practices (at least w3c practices), better re-usability and a visible path for backward compatibility.

    ReplyDelete
    Replies
    1. It's possible to make that the ApplicationStyle read a CSS file and create CustomStyles when they have a widget class and set the properties. However, it's not a priority now, but it's a point to be considered.

      Delete
  4. Hi, the code is now updated and can be downloaded from git://code.openbossa.org/projects/qtquickstyles.git
    We have found some bugs that were impeding the module to run properly. We have fixed it and integrated on the master.
    Regards

    ReplyDelete
  5. #1 What about integrating support for Qml Keys for activating/pressing currently focused components?

    #2 Regarding #1: Are there thoughts about implementing a Tab-based navigation through enabled components for a more native desktop look&feel?

    I've changed BasicButton according #1:

    [code]
    diff --git a/src/components/uicomponents/qml/BasicButton.qml b/src/components/uicomponents/qml/BasicButton.qml
    index f283272..22f427a 100644
    --- a/src/components/uicomponents/qml/BasicButton.qml
    +++ b/src/components/uicomponents/qml/BasicButton.qml
    @@ -62,4 +62,18 @@ FocusScope {
    }

    Text { id: fontPropertyHolder; visible: false; onFontChanged: root.fontChanged() } // ###: temporary solution
    +
    + Keys.onPressed: {
    + if (event.key == Qt.Key_Return || event.key == Qt.Key_Space) {
    + event.accepted = true
    + pressed = true
    + }
    + }
    +
    + Keys.onReleased: {
    + if (event.key == Qt.Key_Return || event.key == Qt.Key_Space) {
    + event.accepted = true
    + pressed = false
    + }
    + }
    }
    [/code]

    ReplyDelete
    Replies
    1. Hi Stefan,

      Surely key handling for QtComponents is needed, and will be done in the way you stated, we just would like to solve the styling before polishing the widgets behaviour.

      Delete
  6. Daker, as you are the one behind the Scrollbar component from Kde.Plasma, will there be an equivalent inside QtUiComponents in the near future?
    While experimenting with the current state of this project developing a simple QML-based desktop application, I feel the lack of a "real" deskop-like ScrollBar is the major barrier using qml on non-mobile platforms right now.

    Thanks for your time and the great efforts you and your team are doing bringing QML to desktop!

    Greetings from Germany,
    Stefan

    ReplyDelete
    Replies
    1. ScrollBar is one of the next components in the queue we're trying to build.
      Maybe we'll also have an ScrollArea and the ListItemView we have in Plasma Components. Having the ScrollBar, they're almost done.
      Thank you very much for the feedback!

      Delete
  7. I am trying to use it on windows (I have add QT+=uicomponents) but it don't find import "import QtUiComponents 1.0" .. I am using qt 5 alpha.

    ReplyDelete
    Replies
    1. We haven't tested it on Windows but it's supposed to work. It's not under Qt 5 alpha, you'll have to install it yourself.

      Delete
  8. Did you install the following?
    git://code.openbossa.org/projects/qtquickstyles.git

    I'm not familiar with qmake (pro files) at all, but I think you do not need to add "QT+=uicomponents".
    After installing the files from qtquickstyles git repository to the imports folder from your QT5 installation, QtQuick will find the module automatically.

    ReplyDelete
    Replies
    1. Actually there is a qt.pro.patch you have to apply in the qt.pro root. Doing that, you can compile qtquickstyles together with Qt5 repo.

      Delete