[Development] [QtQuick] Views Version 2 from Qt CS; animated layout
Alan Alpert
416365416c at gmail.com
Thu Jul 25 18:31:50 CEST 2013
On Thu, Jul 25, 2013 at 12:37 AM, Rutledge Shawn
<Shawn.Rutledge at digia.com> wrote:
>
> On 24 Jul 2013, at 5:45 PM, Alan Alpert wrote:
>
>> There's a rough outline of how we want views to look in QtQuick,
>> because the current ListView is unmaintainable and not extensible. See
>> http://qt-project.org/groups/qt-contributors-summit-2013/wiki/Qt-Quick-views-version-2
>> for details.
>
> It was already obvious for a long time that instantiation, layout and flicking behavior ought to have been separated from the beginning. (At least Flickable is inherited, so hopefully we don't have to do a lot extra to get the touch events working in the views.)
>
> Yesterday we were talking more about animating transitions between layouts. I think we might be able to get there by giving a layout manager a list of items to manage (not necessarily a public property), and adding a layout: property to Item. So if you declare
>
> Rectangle {
> Repeater {
> id: peter
> anchors.fill: parent
> layout: FlowLayout { anchors.fill: peter; spacing: … }
> model: …
> Rectangle { … }
> }
> }
>
> then the Repeater will give the FlowLayout the repeater's children as its list of items to manage. Later you could assign a different layout, and when the property changes it will give the items to the second layout to manage. The delegate Rectangle could have its own state transitions or Behavior on x and y or something like that to animate from its position as determined by the first layout to its position determined by the second. That part needs more thought.
Have you considered using the Positioner layouts for inspiration? They
have a similar set of add/move/remove transitions to the view classes,
and you don't need a special "positioner" property on Item to make use
of them. Just reparenting items to a different positioner and using an
add transition is enough for an animated change.
> The layout must have correct geometry, but it doesn't have to get it from its parent. The layout would not be required to be in the item containment hierarchy. Yet the app author can still easily see which items have which layouts, because of the layout: declaration.
Except that Layouts are not on Items, conceptually layouts are just
item group in the hierarchy. This seems like a much more confusing
approach to me (although that could just be because your example
doesn't work - Repeaters have no children). Also QtQuick can't depend
on QtQuick.Layouts, so there's a technical and conceptual problem from
that too.
> The current way is that the items must be children of the layout, so you can't switch layouts without reparenting all of the items. That way could continue to work for the simple use case: if the layout has children then it will assign them to its own list of items-to-manage.
Sounds like all you need is a convenient reparentAll() mechanism. Does
a binding on the children: property work?
> The QQuickLayoutAttached will have to continue to be universal, having properties that apply to any kind of layout. But it would be useful to have a mechanism to change the properties depending on which layout is applied, so e.g. if you switch a device from portrait to landscape, the layout can change, and the items can also change their preferred width/height etc.
>
> Yesterday I began to write a file manager to test the DnD behavior, and was thinking about how one fundamental usability feature which has been present in the Mac finder from the beginning (and missing from many other file managers) is that in the icon view you can rearrange the icons however you want, and the OS will remember those positions. (Spatial memory might help you to organize sub-parts of a project or something like that.) They are not forced to sit on a grid all the time, and yet there is a handy "cleanup" command which will arrange them to a grid on-demand. (In OS X it's been extended to have Cleanup by Name etc, which basically sorts the items at the same time, and you can also toggle "Arrange by name" etc. to keep the sorted and grid-aligned all the time.) So generalizing this idea provides some further motivation to have switchable layout managers (including switching to no layout at all), and animated position movements. Since I cannot do that yet, in my file manager I cannot use layouts; I can set the position of each icon in component.onCompleted, and the user is free to drag the icons around afterwards. The Cleanup command would have to be implemented in Javascript. (Fortunately a flow layout is easy to implement.)
>
> The file manager, along with many other types of "infinite canvas" drawing and diagramming applications, need 2-dimensional panning functionality, so the Flickable seems a natural fit. And many of those could benefit from dynamically creating and destroying delegates too, to save memory when there are a lot of objects in the model, because the Flickable could manage a really large area. So the instantiation needs to support creating and destroying delegates in both dimensions (whereas ListView, GridView and PathView only create at one end and destroy at the other, depending on the single direction of movement).
Yes, you'd also need 2D creation for TableView and GenuineGridView (as
opposed to list view with grid layout, which is a more accurate
depiction of the current GridView).
> Under Controller you have viewport and reaction to input, but Flickable takes care of those. Do you think there's anything wrong with that? And then there is the selection model. So far it has been enough in such cases for each item to have its own MouseArea and to remember its own selection state, but it would be nice to have a means to implement a lasso tool, and a means of maintaining a list of selections, although it's possible to iterate all the children and find the ones that are selected, and it's also possible to maintain a separate list in JS.
Delegates remembering their own selection state means that you lose
selection state the moment the item goes offscreen. So that's
unacceptable in a lot of cases. Selection is also about more than just
click-to-select, some usecases require selection to be managed or
controlled externally, which is where having a separate selection
model that can also be interacted with in C++ (like the existing
selection models) are ideal.
But this is another area where it should be modular, so that for the
bevy of simple usecases you can just do the MouseArea thing and not
pay the cost for coordinating with a separate selection model.
> The Instantiator name is already taken, but was it designed to be used for views?
It could be extended. We need to define the needs of an "Instantiator"
in this context better before we can know whether that fits the design
or not.
> Come to think of it, the trouble with Repeater has always been that it's in the containment hierarchy, but it's an interloper. It's similar to how the layout has a behavioral role, and therefore should not be required to be in the containment hierarchy. If you want to make a component which is something like a ListView, but you want to customize it by rebuilding it from scratch, then you might use a Repeater to instantiate the rows in the list. You will soon run into the trouble that each item's parent is the Repeater, not the list component which you were trying to create. This must be fixed.
Already done. Since time immemorial, Repeater re-parents everything it
creates to be a child of the Repeater's parent. Instantiator is even
more generic, it doesn't enforce any parenting and just creates stuff.
>So hopefully the "new view" will make the Repeater obsolete, and we need to start upholding the design pattern that the parent of a delegate or inner component should always be the useful parent component (the one the user is trying to create), not the layout or the repeater. So most of these interchangeable components should be properties. You could do something like this:
>
> Viewport {
> id: vp
> View {
> id: view
> model: ...
> delegate: Rectangle {
> id: del
> MouseArea {
> anchors.fill: parent
> onClicked: view.selection.toggle(del)
> drag.target: del
> }
> }
> instantiator: ...
> layout: FlowLayout { anchors.fill: parent; spacing ? }
> controllers: [
> MultipleSelectionArea {
> anchors.fill: view
> target: view.selection
> }
> FlickArea {
> anchors.fill: vp
> target: vp
> }
> ]
> }
> }
>
> • Every child of the view can see that its parent is the view itself: no interlopers (such as layout or repeater)
>
> • The view does NOT contain children other than the ones the user wants to see; so if you iterate the children you won't have to check the type of each
This example does not actually include QQuickItem children at all
(except that View is a child of the Viewport), making it hard to see
either point. Any Item level parent relationship is being done custom
by the View type, or not.
> • There can be multiple controllers, to handle as many behaviors as we like
How do they interact? Like if I specify two FlickAreas with different sizes?
> • A Flickable has two roles: it's a viewport, which is why it is normally the view's parent, and it also does the flicking. This can be split up further as shown, but I'm not sure we gain much from writing the extra declarations. The View could just have a Flickable as its parent. But splitting it up like this makes the design principle more consistent: the view hierarchy resembles the scene graph, while the behaviors are implemented by independent controllers which are NOT in the view hierarchy. The same logic could be extended to the MouseArea: if every Item had a controllers list (or if we had a habit of hiding controllers in the data property), we could say that a controller is never a visual child of an item.
What about interaction items, like MouseArea and MultiPointTouchArea,
which need to be in the visual hierarchy? It seems like a pointless
complication to me that "every Item" can take on certain interaction
behaviors by specifying controllers on it. It's much simpler to have
those behaviors as separate items, which conceptually simplifies Item
while also allowing the user greater manual control over the
interaction of multiple 'controllers'.
> • MultipleSelectionArea would handle mouse and touch events which fall on any of the items in its target list, so dragging one drags them all. If you drag somewhere else on its parent, then it would lasso-select items from the target.
>
> • The MultipleSelectionArea could handle slow drags, and the FlickArea could handle quick drags. They could have minimum/maximumVelocity properties. Or the MultipleSelectionArea's lasso behavior could be configured to handle mouse events only, not touch events.
How do they communicate? They don't even have a reference to each
other, is View managing all this? In which case "View" is the real
controller, and the controllers property is just a list of behaviors
for this controller to have.
> I don't think the "controller" can be just one thing, because there can be a lot of separate behaviors, done with separate gestures: flicking/panning, lasso selection, drawing new items, dragging individual items, dragging multiple selections, etc.
>
The problem with splitting them up like this has always been about how
the communication is managed between components without unacceptable
overhead. The biggest example being how the Instantiator, Layout and
ViewPort need to work together to determine which items are on screen
and need delegates. Might even need to also communicate with the
controller to preload incoming items while scrolling. If everything
goes through the View, like it seems to in your example, then it's
still a big monolithic class that knows everything and we've just
added more customizability to it (which might be enough, I don't
know).
--
Alan Alpert
More information about the Development
mailing list