Silex
Silex

Reputation: 2713

Zero width or height versus visible property in QML

Does setting an component's width or height to zero has the same effect as setting its visible property to false?

An example use-case:

I have an item, which slides into the window. The sliding happens by animating its height from 0 to x and when I dismiss this item from x to 0. Don't want to go in depth why I am not animating the position of the item instead. When the item has 0 height, should I set its visible property to false or it doesn't make any difference?

Upvotes: 3

Views: 4100

Answers (2)

derM
derM

Reputation: 13691

As dtech already pointed out, the dimensions of the root node of a component do not automatically represent the dimensions of the underlying object tree. As an example take this:

Item {
    id: root
    Text {
        id: txt
        text: 'some text produces implicit width'
    }
}

In this example the text of txt will be shown, though the dimensions of root are width: 0; height: 0.
As dtech already mentioned, you might set clip to true, but this is not advisable, as then it would be passed to the renderer, which renders the Item and its tree and finally applies clipping to it - in a seperate batch.

If you have something like that:

Item {
    Rectangle {
        anchors.fill: parent
        color: 'red'
    }
}

The renderer would do nothing extra when rendering, as it could be processed in the same batch as the rest. However as a developer it is hard to tell, whether something is visible when the size is set to 0 or not. Therefore it is adivsable to always set visible properly.

We might simply set

visible: width > 0 && height > 0 && opacity > 0

which works fine, as long as we don't animate on any of those properties or change them frequently. At least for animations we might have good knowledge, when the any of those properties might become 0 and use this information to reduce the amount of evaluations.

The nice thing about QML is, that the logical expression is evaluated from the left to the right, which means in our last example:

  • If width === 0 and height changes, it wont trigger reevaluation
  • If height === 0 and width changes, each change triggers reevaluation.

This means, we need to put the most stable condition first. This might be our information about when any of those values might change. I propose, using the animation.running property, to prevent reevaluation of the binding, while the animation is running.

Let's take a more complete example: Upon click, this Rectangle will shrink from width: 800 to width: 0 - which shall set it invisible.

Or three additional properties binding1, binding2, binding3 are bound to expressions, that we might use to set visible. When ever a particular part of the binding is reeavluated, we log this.

Rectangle {
    id: rect
    color: 'red'
    width: 800
    height: 600

    NumberAnimation {
        id: ani1
        target: rect
        property: 'width'
        from: 800
        to: 0
        duration: 3000
    }
}
MouseArea {
    anchors.fill: parent
    onClicked: ani1.running = true
}

property bool binding1: {console.log("1", !rect.width); return !rect.width}
property bool binding2: {!ani1.running && (function() { console.log("2", !rect.width); return !rect.width })()}
property bool binding3: {(function() { console.log("3", !rect.width); return !rect.width })() && !ani1.running}

// equivalent, stripped of the logging:
// property bool binding1: !rect.width
// property bool binding2: !ani1.running && !rect.width
// property bool binding3: !rect.width && !ani1.running

As we can see, binding1 is constantly reevaluated, when ever the width changes. This is not desirable.
We can see, that binding2 is only evaluated once at creation, and whenever ani stops running.
In binding3 we have it the other way around and we first evaluate the width, and then whether the ani is running. This means, we have a reevaluation whenever the width is changing.

We could also use the signal handlers ani.onStarted and ani.onStopped and explicitly set the visiblity then, but that would not be declarative and QML encourages you to always strive to stay declarativ.

Upvotes: 2

dtech
dtech

Reputation: 49289

Not really, unless you clip. And it is better to avoid clipping as much as possible.

An Item with zero size will still have its children visible.

Whereas setting visible to false will hide the entire object tree.

In your particular case it seems like it doesn't matter as long as it doesn't cause you to have unwanted visible leftovers. You certainly do not want to have a binding such as visible: height as that would needlessly execute on every step of the animation.

Just to be on the safe side, you can install handlers on the animation to toggle visibility:

// in the animation
onStarted: if (!item.height) item.visible = true // show if start at 0
onStopped: if (!item.height) item.visible = false // hide if end at 0

This will avoid the continuous reevaluations you'd get if you bind visibility to height directly, but will still ensure visibility on before your object begins expanding and off after it has finished contracting.

Upvotes: 6

Related Questions