Hints to tooling the runtime didn't actually do
anything with those type annotations since
six seven they are actually enforced so if
you now pass a string into this add function the
string is the javascript course to it you'll be delighted but uh i had basically
no other choice i'm making using the javascript type questions there so that's
what it is and yeah Yeah, why do we use them?
As I mentioned before, so that like, A,
you yourself and anybody else who reads the code later can grasp what was the
intention of the code and the tooling can make sense of it and compile it to C++.
And of course you can also use var if you don't actually know the type,
but you should use it sparingly.
Related to that, we also have type assertions now.
They've also been around since 5.15, and it was an interesting ride.
In 5.15, they didn't do anything at runtime, like if you passed a type to this
s here, for example, with the font, that was not a font, but a rect,
it would just go on and try to get the family property from rect,
which doesn't work in this case.
But let's say you have rect and point, and you take out the x property,
and it would just work, besides not being the right type.
So since 6.2, they are actually checked, for object types at least.
So, in the second example with the getX, if you pass something else than an
item into this function,
the as expression will evaluate to null and that will result in a JavaScript
expression, no matter if the type that you actually have there has a property X or not.
And, uh, since six, six, um,
we also do things with them if
the right-hand side is value type and as somebody has pointed out to me,
uh, those were some horribly wrong things because as you know,
we have JavaScript type coercion,
we should use JavaScript type coercion there, right?
So if we can construct a font from whatever you've passed there,
we should construct a font, right?
Well, no, but luckily, it never worked.
Like most of the time, it didn't work because it accidentally fell back to the
object type coercion produced null, which it also shouldn't do.
But that's not so bad because null is close enough to undefined,
which it should actually return there.
And so since 6.8, we got that right, but we couldn't straight out fix it because
of backwards compatibility.
Therefore, if you're going to use type assertions for value types,
use this nice pragma there and avoid any surprises.
And then we have generalized group properties. Generalized group properties are quite mouthful.
And as mentioned, you don't actually have to know what it is unless you want
to implement some yourself.
Most prominently, we use this to fix property changes.
If you look at the first property changes we have there, there is a property,
a binding on opacity in there.
Now, the type property changes, as far as I know, does not have a property opacity, does it?
So, the QMAC cache game would say, no, I cannot compile this, what is that?
You don't have a property opacity there. And the way it works at runtime is,
there's a custom parser that gets not the compiled bytecode of that property
changes, but one source code, the AST.
It runs through the AST and fixes this to not point to the property change itself,
but to the target you've specified.
But that's all all custom code hidden inside the property changes implementation.
And so what we could have done in the compiler is like special case property
changes, and then special case connections and binding and state changes and anchor changes.
No, I wasn't going to go there.
And so we came up with something else.
And that is the generalist group properties, which we can use in all those places.
And generally, it's good properties is a construct where we have on the left-hand
side an ID and then a property on that.
And you can install a binding on the result.
And that is typed because now we know the left-hand side here is an ID root.
OK, we can look up that ID and the Qubit cache knows that ahead of time already.
And then we look at that and see opacity. Oh, yeah, there's an item. It has opacity.
And we know what that is, it's double, and so we can install a binding on that, and that just works.
So we have that now in binding and in property changes. And it works nice.
And it's not only easier for tooling, but it's also nicer to read, in my opinion.
And simplifies the syntax some.
And yeah, we haven't done it anywhere else because the other cases weren't so widespread and so bad.
But it should be generalized to the other cases too.
On the other hand, there's a related concept in connections.
In connections, we don't generally want to do that. Or sometimes we want to do that.
But generally, we don't want to do that because if you have a connections element,
the most common thing is you don't actually know the target in advance.
You can reassign the target.
So an ID wouldn't do that.
And as you can see in the first example with connections here,
that's basically the same thing.
There's an onExchanged signal handler here, and yeah, connections does not have a signal.
Exchanged. That does not exist. And what we did there is a.
Mechanism that loosely couples a member in the connections element that you
newly define to some functionality that's behind it.
But the member we defined there, that function on yChanged is valid already in plain QML.
You don't need a custom parser for that.
And the connections element then goes through its functions and checks,
oh, is there something that looks like a signal handler and that matches my target?
And then it detaches that.
That. And that is basically the other side.
Either you know the element you referred to in advance, then you can use generalized
group properties, or you don't, then you can use a technique like that.
And that's basically so far only implemented in connections,
but we have to do the same thing for binding and property changes, et cetera, too.
But in connections, again, that was the worst first thing because everybody
uses connections so i'm happy we we have got it in there.
And then another thing you can do in QML these days is named value types.
Now I do have a little bit of C++ code here because it's relatively unknown thing so far.
So from C++ you can register types
that are not QObjects these days and you can give them your own names so you
see this wrapped in type here and with this QML value type macro you can tell
the tooling okay this is a value type called lowercase wrapped int.
Value types have to have lowercase names for various reasons don't give them
uppercase names or it will slap you around and then and And then you can use
that like font or rect or all the other value types we already have built into Qt QML and Qt Quick.
So, for example, you can assign things to or you can read out its I member here.
And then on top of that, since 6.5, you can also have value types you can construct.
For that, we have an extra macro QML constructible value, and you have to make
the constructor QInvocable.
And then tooling and the engine will figure out, oh yeah, I can call this constructor.
It has to be a constructor of one argument.
And then basically, if you have the right kind of type on binding on it,
it will automatically construct the value
type yeah because I
didn't want to make that automatic because people might have constructors invocable
constructors on their random types around the place and that you want QL to
either like me if that's actually wanted the automatic conversion to do the
conversion or to warn about it if If you do this,
like, uh, assign it into it, magic in QML, right?
Because that's otherwise that's quite magic. So I wanted to make it explicit.
And on top of that, you can also have structured value types these days.
Structured value types work kind of like constructable value types,
but you can also construct them from a JavaScript object where just all the
properties are assigned one by one from the members of the JavaScript object.
And it doesn't have like a JavaScript object literal.
It can be anything that looks like an object in JavaScript. So it can be another value type instance.
It can be an object type. It can even be an array.
The only thing it needs to have is some kind of properties that can be extracted
to populate the other type.
So this is like, again, somewhat dangerous. and that's
why I made it a separate separate macro
too yes value types cannot have signals but my a each value type is a member
of some object type via some path of other value types types,
and so the QML runtime simulates notifiability via notifying on the.
Outermost property that's a property of an object type. So yes,
you can have onIChange there. It works.
It's somewhat, it's a bit complicated and a bit expensive, but it's not so bad.
You can change a lot to do that. To do what? The signal. Yeah.
But if you have a signal, you need identity. So in order to connect the signal
to something, you need an identity on the left side, and that needs to be stable.
But if you have value type, and that's assigned recursively to some property,
and you change whatever that is, it might get reallocated. Yes.
So that's why it doesn't work.
Now, container types. Container types
are funny in QML. As a historical artifact, we have too many of them.
We have JavaScript arrays.
That's because we need to build a JavaScript. We have various other JavaScript
things that look like arrays. I'm going to ignore those for now.
But they generally have the same API as JavaScript arrays, so it's not so bad.
And then we have our own QML list types.
There's QKML list property that in Qt 5 worked quite different.
And there is various lists,
QLists and standard vectors that are manually exposed or were manually exposed
to QML in special case in various interesting ways.
And some of them had a similar API to JavaScript array.
And some of them had some random crafts and queue object list was special and
queue variant list was special and all there was like,
yeah, I was getting lots of bugs because why can I use push on this container?
But if I use push on that container, then this happens. And so I went and fixed it.
So, JS array is the reference here.
Like everything should, because we are basically locked into JavaScript,
everything should have the same API as JavaScript array, right?
And that's what I did in 6.5.
Everything that looks like an array works like an array. Yay.
And most of the special casing is gone. You automatically get the right types registered.
If you register an object type, you get QQMList property of that object type for free.
And if you register a value type, you can get QList of that value type for free.
And since 6.0, you can also do the thing we used to do in Qt 5 with standard
vector int and standard vector bool and whatnot. You can do that yourself.
There's a macro code QML sequential container.
And that will just expose the type to QML the way all those containers are exposed.
And that latter thing can only expose anonymous containers.
So you cannot define properties of that, but if some C++ type has a property
of that or returns something like that from a method or accepts something like
that as arguments to a method,
you can pass it through and you can treat it like an array in QML.
And then I thought, yeah, we have lots of interesting things as models for various views.
Like we have a list model thing with like an interesting custom parser on list
element that I don't quite know what to do with yet.
And then we have abstract item model that can be passed through there and like
plain integers and various things.
And you'd expect that any kind of list like thing would work there.
But in Qt 5, not everything worked there. Only special things worked.
And so after fixing all the list inconsistencies, I went and fixed the model data too.
So anything that looks like a JavaScript array, anything list-like,
you can use as a model to all our views now.
So for example, here we have a list of size,
the size value type as model to repeater and then you can have a required property
width and that just works and then yes Yes.
If I do now lists as models, if I then change a single value in the list, will I have a list?
Yes. Yes, in general, yes, because you're reassigning your list then with value type lists.
With QKML list property, you don't have to reassign the whole list, but you are...
Yeah, but that won't work. you still have to signal the change somehow and you
can only signal the change for the whole list,
and so too bad yes does
delegator delegate chooser does it work on this list yeah delegate chooser is
one one step removed from this machinery so it should work unless there's a
bug somewhere i haven't i haven't checked but i tried last time and it seems to only work with,
Oh, that's interesting. Can you write me a bug report for that, please?
Thank you.
Yeah, and then the other thing I recognized is you can now have required optional
properties, as contradictory as that sounds. But you can see that here.
We can say, okay, we have a required property rect model data.
Model data is the thing that captures the whole model data passed to the delegate. get.
And since rect is a structured value type, it gets populated property by property.
And rect happens to have properties width and height.
So what you get there is a bunch of rects that are always at the 0,
0 point and have a certain width and height.
As specified by the size. So that's a pretty neat thing, and it just came for free.
Now, having done all of that, there's a lot of other stuff still left to do.
One thing to do about the whole type safety machinery is we need some way to
express function types.
So like some callables, basically, because that's what people struggle with mostly right now.
You have like several different types that
all have some function in common and you
have a qml interface that takes one or
the qml property that takes one of those types and you want to call the common
function on that duck typing basically and in order in order to resolve that
we need some kind of like a function
type you can put on the right hand side of an uh of a type of a type
assertion, so that you can still type safely call any of those functions.
That's pretty high on the list. And the same thing, some types,
basically the same thing.
You have like one of those types and my property might carry any of those,
but only like those three, not more than them. And that's also to resolve duck typing.
Very, very common thing in QML.
And then we have like those value types
and list types have a drawback as noticed just here a minute ago if you were
assigned to them you like have to assign the whole thing and for lists unfortunately
that means you have to copy the whole list and you don't want to do that all
the time, so I'm thinking about like having a year extra,
extra attribute to Q underscore property that gives an internal pointer to the
thing so that we don't have to
always change or always like copy the whole thing in order to update it.
And that especially helps with recursive data structures where you have a list
of value types that themselves have other value types as members, etc.
You can probably imagine how terrible that is if you then update some member
integer i in the innermost instance of that.
So don't do that yet. Use it for simple things.
And yeah, as mentioned before, another Another thing we need to do is finish
generalized group properties and loose coupling to cover all the various elements
we have there, and not only the worst ones.
And once we get to a fixed binding that way, we can also put in support for
bidirectional bindings.
That seems to be a very, very commonly requested feature.
And then, so far, value types can only be defined in C++, as you have seen there.
It would, of course, be very nice if you could also define them in QML,
like you can do with object types.
And those are the things that I'm pretty sure about how to do.
Then, what is next is like things people have complained about,
especially like KDE people.
And I'm not sure about how to fix that.
One of those is context properties. Yeah, we all know context properties are
kind of nice because you just mesh some object into the root context of the
engine and it shows up everywhere.
And it's so easy. Yeah, great. But QML and QSK doesn't see it,
and QMLint doesn't see it,
and QMLLS doesn't see it because you've hidden it somewhere in your main CPP
inside an inner loop of 13 like depth, yeah.
So that needs some fixing. I don't know how to do it yet, but it needs some fixing.
And another thing I don't quite know yet is writing model data from delegates.
So if you look at the example with the unification of model data here,
yeah, you can have a type model data in your delegate, but that's a property of a delegate.
And if you write to it, well, you write to it, you have a different value in
your delegate, but it doesn't propagate back to the model. And so far,
you can write back to the model by looking up the model and writing into it.
But it would, of course, be nice if you could do that directly from the delegate.
If the delegate could just write to some representation of its piece of data,
and that would propagate back to the model.
That is so far not doable in a type-safe way.
And finally, you may have noticed in my example with, where did I put that?
Here, that I had to put font or rather Qt Quick into this type namespace QQ.
And does anybody know why I did that?
Yes, to make the type font addressable here, because value types are by default
not addressable, and for a reason, because if they were addressable,
they would shadow properties,
and value types as well as properties are lowercase, so that would be disastrous.
Disastrous and therefore I have to like put it at a type namespace so that I
don't have to look it up unqualified and qualify qualified thing of the type
namespace it works but that is.
Inconvenient who oh yeah yeah um and so one thing to do about that is just redesign
the lookup hierarchy so that the types are not at the top of it,
but somewhere further down below the properties.
That is not as simple as it sounds, because between the types and the things
further down are all the contexts and all the scopes,
all the way out to the root context of the engine,
and we have to thread it in somewhere in there.
Yeah, I don't have a solution for that yet, but it needs a solution.
Yeah. And that's basically what I wanted to say here.
And now I hear we have 10 minutes for questions.
Yes?
One of the ones that we get. Okay.
It's on. One thing I've... One thing I've... Testing.
One thing I've noticed clients do is,
they often want to use models within the QML space. And sometimes those models are rather complex.
They want them to contain nested data. And I haven't found a good way to do
that and still have it be somewhat optimized by the QML compiler.
Any inputs on that?
One way you can do that is write your own abstract item model, of course.
You can put in anything there and you can return a queue object as some piece of data somewhere and,
have another model in there that gives you
the full flexibility all right and another
thing you can do is like use the new support for
lists with some care to not like copy the list too often and what i've shown
there with list of value types also works with list of object types So it might
help or with lists of JavaScript objects, but don't do that.
All right. Sounds good. Thank you.
Yes.
Okay, two questions. The first one is about the type annotations.
I know type annotations are awesome, but if you need marks in testing,
that's going to be a problem because, for example, you need an object of type,
of a specific type that maybe does network I.O.
But in testing, you don't want to actually invoke the network I.O.,
so you use a mock type instead.
Then if you annotate the type to be the concrete type,
you won't be able to use
the mock type in testing right there is
a fairly blunt way to work
around that and that would be have a different
qml module that exposes this like network type for your testing that you have
in real in production so basically write the second qml module that has a a
different network interaction type that's just mock and then test with that in the import,
And when you ship for production, you replace it with the actual module.
Okay. And another thing is about the lifetime of QObjects.
As a JavaScript programmer, I feel it's really confusing and sometimes surprising
when some things that got passed into the function can just become new at any time.
Like, even a URL list from a drop event, it can go become new at the next tick of the event loop.
Is there any plan to, say, work around this?
What do you mean, become new?
Like, so it's a URL list from a drop event.
And say we want to extract the first URL and the second URL whenever the user clicks a button.
And when the user clicks the first button, the URL list is there.
And then it disappeared.
Because it was an argument to some event
handler and uh no how does how
does it disappear this is like the url
list comes from a drop event yeah yeah
and that goes past yeah that
is an argument to some method or some and like i passed it like i passed the
url list into another function and I say display the filename when the user has clicked a button.
And when the, I guess maybe the event got destroyed.
Well, generally in JavaScript, whenever you hold onto something somewhere,
it should not be garbage collected because that's the whole point of the garbage
collector, that it marks things that are still in use, it does not collect them.
So if you found an instance where something got collected despite you still
holding a reference to it somewhere, then that's a bug. You should write me another bug report.
Wait, this is pretty common across my QML experience because if it's like a
QObject, it got destroyed when their parent is destroyed.
Ah, and it was a property of a QObject you have there.
Well, oh. Yeah, it is a property. We have to look at that in more detail.
Maybe you can show me the code and then I can see what it does.
So I have two questions. One is why is it not going via the new operator?
Like using us to create things feels a bit weird.
And the other one is are there any plans to like integrate those value types
with like type conversions?
So like I can have a type and then if I pass it into a function which expects
a string, I get the string.
Or if I use it as a number and I can do calculations.
Like think of a coffee recipe or
a value where you have like five liters and
on C++ you make like structs and std even
has like std kilo and whatnot but in
representing that in QML is very hard because QML has
no idea what this struct is it can do to string but you can't really do any
maths and numbering and stuff with it easily so first question why do we why
do we not use new for creating value types well Well, we do,
but I'm not telling you right now because it might be a bad idea to remove it again.
And second question, besides toString, there's also value of in JavaScript.
We don't support that yet as invocable function, but that would be the way to convert to numbers.
And I kind of have it on my radar to do that sometime, but yeah,
I have a lot of stuff to do. Thank you.
Okay, I guess time's up.