Making plasmoids QML API better

Software

For the next iteration of Plasma, we are not only revising the user experience, but also making better the developer story.

This article is pretty long (sorry no pretty pictures this time ;), and is probably easy to just TR;DL it, but it will give some important guidelines if you will ever interested to write a plasmoid for Plasma Next, such as “how the hell can I make my plasmoid have a correct size in the panel.”

First of all I’ll start with a short personal note: I just started with a new position in Blue Systems, where I’ll work towards making the next iteration of the Plasma technologies, both as a framework and as a desktop ready for prime time, aiming to give the best experience possible on the all new Qt5, Frameworks5 based Plasma Desktop by KDE.

Plasmoid and attached properties

In QML1 plasmoids, you had a global object accessible from everywhere in the plasmoids called with much imagination, “plasmoid”
This is useful to access everything that is related with the ambient it’s loaded in, the scene and the containment, so you can read things like:

  • The formfactor: are we in the desktop? panel? is it horizontal or vertical?
  • The title/icon etc:data for the plasmoid: you can set a different title, which a containment can decide to show (such as in the new system tray) or the icon, that will be used when the plasmoid is iconified in a panel
  • Configuration: the whole mechanism for handling the configuration for plasmoids is abstracted in a neat object “plasmoid.configuration” that is usable as any JavaScript Object: configuration keys are accessible as members or as keys of an associative array. It’s possible to write or read on them, and the QML property binding mechanism will work, even when the configuration is modified by somebody else (such as the desktop scripting)
  • The compact and the full representations
  • And much more

Wait, what are “The compact and the full representations”? to find out, skip to the next paragraph 😉
In Plasma Next, besides the global plasmoid object, accessible from anywhere (any item and any qml file in the plasmoid), we have also an attached object, called “Plasmoid” (uppercase) that provides the full plasmoid api (is the same object, really) as QML attached properties of the root Item of your plasmoid, so you can have a very, very convenient way to read and write properties, and to do signal handlers.

import QtQuick 2.0
import org.kde.plasma.plasmoid 2.0 //needed to give the Plasmoid attached properties
import org.kde.plasma.core 2.0 as PlasmaCore

Item {
    id: root
    Plasmoid.title: "My custom plasmoid title"
    Plasmoid.onformFactorChanged: {
       if (plasmoid.formFactor == PlasmaCore.Types.Horizontal) {...
      // My custom JavaScript handler code to react to a formfactor change
    }
//rest of your plasmoid code
}

Compact and full representations

two important new properties of the Plasmoid object are compactRepresentation and fullRepresentation: plasmoid can collapse in an icon when they are resized to a too tiny size for their usual UI to make sense: in this case you usually get an icon that will show the full plasmoid in a popup when clicked.
The plasmoid will switch at a size that you can set with the properties

Plasmoid.switchWidth
Plasmoid.switchHeight

For those properties you should use preferably not pixels, but dpi independent measures, provided by the “Units” system, as explained here.

By default, you’ll have an icon (that’s one of the things the “icon” property of the plasmoid object is for) as the compact representation, and the whole root object is taken as the full representation.

But in some cases, an icon is not enough, you may want something more complex, like in the case of the clock.
To do that, you just have to define a cutom one.
Imagine you implemented your digital clock in a file called DigitalClock.qml:

import QtQuick 2.0
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore

Item {
    id: root
    Plasmoid.switchWidth: theme.mSize(theme.defaultFont).width * 10
    Plasmoid.switchHeight: theme.mSize(theme.defaultFont).height * 15
    Plasmoid.compactRepresentation: DigitalClock {}
//rest of your plasmoid code
}

One thing that is neat, being compactRepresentation a Component, it doesn’t actually get instantiated until you actually need it.. why wasting memory on the graphical representation of the icon if it will never be iconified for its whole lifetime?

at the same time, it’s realistic that a plasmoid can sit in your panel (or , in Plasma Next is also possible to collapse them when in the desktop) for hours before you have to open it, if at all: why it should take startup time to instantiate its contents? (and why they should always take memory if they are not used?)

In Plasma Next, you can assign the full representation too, so to redo the previous example, if you want to show a calendar when you click on the clock:

import QtQuick 2.0
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore

Item {
    id: root
    Plasmoid.switchWidth: theme.mSize(theme.defaultFont).width * 10
    Plasmoid.switchHeight: theme.mSize(theme.defaultFont).height * 15
    Plasmoid.compactRepresentation: DigitalClock {}
    Plasmoid.fullRepresentation: Calendar {}

//rest of your plasmoid code, only data model related items, there won't be any graphics object here anymore
}

Plasmoid.fullRepresentation will probably usually contain the most complicated code since it may be abig and complex UI, but when it’s in the panel it won’t get created until the user opens it, so it won’t cut on precious startup time.

Therefore, in Plasma Next, the recomended way to write a QML plasmoid is:

  • Write a root Item as simple and lightweight as possible
  • Use the default icon as compact representation when possible (to have that just don’t define any compactRepresentation, it will take the default)
  • Put all of the UI under Plasmoid.fullRepresentation
  • Under the root object, put all the dataengines and the models that you need to access from both representation, but only and always non graphical pure data model stuff.

Making your plasmoid to behave well in a layout

Since Qt 5.1, QML2 has a new set of components: the Layouts.
Those are similar to the good ol’QWidget layouts, we have linear (row and column) and grid layouts. This makes construction of plasmoids and containments way easier.
What i want to talk about here, is the size hints that those layout provide.
As the Plasmoid object that I wrote about before, is now available another attached object called Layout, and is documented here.
You can define one inside of any Item, and when/if that item will find itself in for instance a RowLayout, the layout will respect the size hints defined as attached properties (minimumWidth, maximumWidth, fillHeight etc).

We are using the same attached properties for the plasmoids themselves.
If toy define the Layout sizehints properties inside either the compactRepresentation of the fullRepresentation of the plasmoid, they will be used for several things:

  • when the fullRepresentation is in a popup, the popup will never be smaller than the Layout.minimumWidth/Layout.minimumHeight of the fullRepresentation
  • If the plasmoid is collapsed in the panel, all hints of the compactRepresentation will be respected by the panel layout
  • Same thing for the fullRepresentation: for instance the taskbar is a plasmoid that always stays in fullRepresentation, which has the Layout.fillWidth property set to true: this way the taskbar will always take all the available space there is left in the panel.

Let’s add this to the usual clock example:

import QtQuick 2.0
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore

Item {
    id: root
    Plasmoid.switchWidth: theme.mSize(theme.defaultFont).width * 10
    Plasmoid.switchHeight: theme.mSize(theme.defaultFont).height * 15
    Plasmoid.compactRepresentation: DigitalClock {
        //make sure the collapsed clock is at least 5 "M" wide in panels
        Layout.minimumWidth: theme.mSize(theme.defaultFont).width * 5
    }
    Plasmoid.fullRepresentation: Calendar {
        //make sure the popup is at least 10x10 "M"
        Layout.minimumWidth: theme.mSize(theme.defaultFont).width * 10
        Layout.minimumHeight: theme.mSize(theme.defaultFont).height * 15
    }

//rest of your plasmoid code, only data model related items, there won't be any graphics object here anymore
}

2 thoughts on “Making plasmoids QML API better

  1. Luke Parry

    Hi,

    I hope this doesn’t come off sounding lazy – so I apologise in advance. When Framework 5 does stabilise I would be interested in developing my own plasmoids to improve my own productive + share these with the community. I am a PhD research student so for example a decent note taking plasmoid would be a saviour for me – instead of just sticky notes.

    What put me off making anything with KDE 4 was the fragmentation of whether to have developed QML/QGraphicsview – the former won… The other thing that put me off, was simply not knowing where to begin and which development tool to use and test them with (i.e. Kdevelop or plasmate)

    I know there are tutorials but it’d be helpful for someone especially coming outside the KDE community if there was a ‘definitive’ guide for developing a plasmoid E.g. http://qmlbook.org/ but obviously not as comprehensive.

    Hopefully you may agree with me a bit on this and can be addressed to the plasma development team.

    Kind Wishes
    Luke

    Reply
  2. markg85

    Hi Marco,

    I’m a bit unsure about the magic numbers you used since you didn’t explain them:
    – Plasmoid.switchWidth: theme.mSize(theme.defaultFont).width * 10
    – Plasmoid.switchHeight: theme.mSize(theme.defaultFont).height * 15
    – Layout.minimumWidth: theme.mSize(theme.defaultFont).width * 5
    – Layout.minimumWidth: theme.mSize(theme.defaultFont).width * 10
    – Layout.minimumHeight: theme.mSize(theme.defaultFont).height * 15

    I hope you can explain what those numbers mean and why they have to be there in the first place?

    Last but not least: congrats on the new position at BlueSystems 🙂

    Reply

Comments are closed.