JavaFX Gridpane replacement while panning/scrolling - javafx

Currently, I'm trying to implement panning on a (game) map, the visual part of which is a gridpane. Since the underlying data model may specify a huge map, I only want to show a small (say 40x40) tile map. When I pan/scroll the map, I currently set the map to null and replace it with a new gridpane containing the new content. I've noticed that this is somewhat laggy, possibly because I want it to keep replacing gridpanes as I'm dragging the map. Which means that every 50 pixels I drag reloads the entire gridpane again.
Is there a more efficient way to change the gridpane's content? I tried implementing a solution that only loads a row or column (depending on whether I'm panning north/east/south/west), but that seemed worse, or as bad, since there doesn't seem to be an efficient way of doing that in a gridpane afaik. Should I even be using a gridpane for this? Any advice is much appreciated.

Related

How to handle a huge amout of tiles in QML?

Imagine a huge rectangular grid filled with tiles. The individual tiles are not very complicated, they are svg images containing a low amount of shapes.
The number of different types of tiles in not very large, I estimate in the low hundreds. However, the grid can become very large, so the number of total tiles is huge (at least tens of thousands, maybe more).
I have to be able to smoothly scroll the grid both horizontally and vertically, as well as smoothly zoom it in and out. I also have to be able to jump to a specific position.
It would also be nice if I could populate it asynchronously, first the elements which are actually visible, and then the rest. This means that a table-handling class where I first have to add rows and columns in a loop would not be the best solution, because the starting position is not necessarily the upper left corner.
Zooming is simply achieved by having all the width and height properties of the items within a tile specified as a multiple of a scaling factor. The svg shouldn't be a problem as the number of different images is not high, it should be able to be cached. In the unlikely case svg became the bottleneck, I could just use sets of different pngs in different resolutions.
I tried (or considered) the following approaches:
Using the methods of the SameGame example, creating QML objects dynamically (Component.createObject). This works if the number of objects is small, but is very slow with a large number of objects. Even if the objects are completely empty, this method takes a very long time.
Using a Repeater inside a Flickable. The Flickable contains a Grid, which is populated by a Repater. The Grid, of course, is immense.
This method is faster than creating the objects dynamically, but still inefficient as the number of tiles grows. The QML engine keeps track of every item, even those which are not visible. Zooming is also quite slow, as the properties of every item are recalculated, not just the visible ones.
Using a GridView. This looks like the perfect solution at a first glance. The GridView inherits Flickable, and it also takes care to only render contents which are within the bounds of the view. Even a test case with millions of svg images runs reasonably fast, and it scrolls and resizes smoothly. There is only one problem: The GridView is only flickable either horizontally or vertically, but not both. There has been a feature request about this since 2012, but it still seems to be ignored.
Using a QGraphicsView directly. It is capable of displaying, scrolling and zooming the needed amount of elements, but it's not QML-based. The rest of my GUI is in QML, and I've only read horror stories about combining QML and QGraphicsView. I've never seen any reasonable examples of it.
What other solutions are there? Some horrible hack of using Javascript to add and remove rows and columns of a simple GridLayout (which is only a couple rows and columns larger than the visible area) while it is moved around in a Flickable? Or just embedding an OpenGL window and drawing everything manually?
I hope this shouldn't be an impossible task. There were strategy games written more than 20 years ago for DOS and Windows 95 which could handle this amount of tiles, while additionally having textures and animations.
Yeah, Qt is very good at ignoring community suggestions for years, even if they would be extremely useful, considered important, and happen to be the most up-voted, such as zip support.
I personally wouldn't bother "fixing" GridView, but rather implement something from scratch that suits my specific requirements, in C++ so that it is fast and efficient. And it will be very easy if your tiles are uniform squares, and it sounds like you could get away with that, even if the actual images inside are not square. This will make it very easy to determined their positions programmatically, and also determine the top left corner tile, how much tiles per line and the stride for the subsequent lines. Then as the visibility rectangle moves you iterate your container and signal to create QML elements for those which enter visibility. Easy peasy.
You don't need anything fancy, just inherit QObject, register the type to QML, then go and populate it's internal "model". You definitely do not want to have all the objects in memory, even if the scene graph is smart enough to not render them, it will still be processing them, I suspect your drop in FPS is not the product of a GPU but a CPU bottleneck.
The actual grid object can emit creation and destruction signals with their data Q_SIGNAL void create(x, y, imgPath);, so you bind custom handlers on the QML side, which will give you flexibility and ease of use, such as easily specifying the "delegate" object, it will be more elegant than doing the actual creation/destruction in C++. You can use bindings on the QML side for the few items that are visible to track when they go out of screen to self-destruct, that would minimize complexity, as you won't have to track all the "living" objects.
Component {
id: objComponent
Image {
property bool isVisible: { is in grid.visibleRect ??? }
onIsVisibleChanged: if (!isVisible) destroy()
}
}
MyGrid {
id: grid
contentX: flickable.contentX
contentY: flickable.contentY
onCreate: objComponent.createObject(flickable.contentItem, {"x" : x, "y" : y, "source" : imgPath})
}
Flickable {
id: flickable
contentWidth: grid.contentWidth
contentHeight: grid.contentHeight
}
Normally, when a user has a question, important enough to offer a bounty I'd produce working code, but unfortunately I am currently too busy. The concept is pretty simple though and should not be too problematic to implement.

ListView scrolling issue in JavaFX2

I'm developing a application which has a ListView which contains items which needs complex cell layouts. The cells are in variable heights and some of the cells tends to be larger than the view port height.
But when the ListView is filled with items the scroll thumb tends to resize its self while scrolling, which makes it hard to hold onto the thumb while scrolling. This happens mainly when passing through different size of cells.
This is not a problem in Swing if I create a same kind of a cell render to be used with the JList. This problem is there in JavaFX 2 and JavaFX8 both.
When looking at the VirtualFlow which is responsible for layout of the ListView and handle scrolling, it seems that the scrollbar thumb side (lenghtbar) is calculated based on the cell count and the visible cell count, which is actually a problem when it comes to lists which has variable heights of cells.
So is this the future of the scroll bar behavior for Java FX list views? or is there any solution available for this problem? Or should I try to hide the scrollbar and provide a different user interaction to scroll?
This problem is already reported under https://javafx-jira.kenai.com/browse/RT-25059 and fixed in Java8 upto some extend. So if this fix is needed on JavaFx2 we have to backport the changes under commit http://hg.openjdk.java.net/openjfx/8/controls/rt/rev/81cc13fe6f96
To get this changes in JavaFX 2.2 you need to apply the required changes on to FX2.2 VirtualFlow.java class and load those changes before the jfxrt.jar is loaded. Another approach is if you don't like to mess up with the jfxrt classes is to have you own ListView which uses your own Skin and the patched VirtualFlow version may be with a different name. But this might require lot of customization compared to first solution.
More approaches are welcome :).

How to pan beyond the scrollbar range in a QGraphicsview?

I am building a node graph in a QGraphicsView and I am currently implementing panning.
I used the following question "how to pan images in QGraphicsView" to start but the panning is limited by the scrollbar range.
I also tried the translate method but it gives the same result. The view is limited to a certain rectangle.
I would like to pan without limits, the graph can becomes quite large and it is useful to be able to work in different area of the scene (one graph here, another graph over there, etc).
If you take a look at this video, at the 3 minute mark you'll see the demonstration panning the screen. The application here is one I developed and although it doesn't show it, the real estate of the board appears limitless when panning.
What I did for this was to create a QGraphicsScene of 32000 x 32000 and start the application with the view at the centre of the QGraphicsScene. The test team spent ages trying to pan to the edge of the graphics scene and everyone gave up before getting there - perhaps the scene could have been smaller!
The scroll bar policies are set to off and translation is done by moving the QGraphicsView via its translate function, passing in the delta of either touch, or mouse movement that is applied in the mouseMoveEvent.
Done this way, you need not worry about exceeding the scroll bar range and there was no problem creating a very large QGraphicsScene as it's just a coordinate space.
I came across the same issue. However, setting the scene to something big and leaving it I do not think is the best option. I have developed a dynamic way of changing the scene size so it lets you move freely. You can find it in this other stack overflow answer.
You want to plot graphs. Try out this Qt library - QCustomPlot , it will save you hours of hard work.

Creating a permanent static overlay for QGraphicsView scene

I am making an app using Qt (currently 4.8) which displays a literal map from a large number of QGraphicsScene items. I would like to annotate the view with a scale. My requirement for the scale is that it is permanently fixed w.r.t the viewport widget. It needs to be updated whenever the view scale changes (zoom in, etc). There are other possible overlay items as well (compass, etc) so I'd prefer a generic solution.
I have looked at earlier questions around this which suggest:
using the ItemIgnoresTransform
using an overlay pixmap.
I tried IgnoresTransform but that way didn't work right: I couldn't figure out how to fix it in place in (say) the bottom corner of the viewport and was having some difficulty getting the text and lines always displaying in the correct size.
I scrapped that and subclassed QGraphicsView, adding an overlay pixmap by reimplementing the paintEvent (calls original one, then paints the overlay pixmap on top), and an alignment option to indicate where it goes. Coding some pixmap paint code produces a usable scale on the view. Yay! ... but it doesn't work with scrolls - I get "shattered" renderings of the scale all over, or sometimes no scale at all. I think this is because QGraphicsView::scrollViewportBy() uses viewport()->scroll() so I wondered if switching to ViewportSmartUpdate would help, but it doesn't help enough. I'd prefer not to switch to ViewportFullUpdate as that would likely slow the app down too much (there are millions of items in the scene and that would require a full repaint just to move around).
So. Any ideas from here? Would adapting my pixmap code to write to a new mostly-transparent Widget that is overlaid on the viewport be a better way?
Thanks for any help...
Though it may not be the best way of doing this, in the past I've added custom widgets to the window that holds the QGraphicsView / QGraphicsScene, which have the same graphic style as the QGraphicObjects in the scene. When the view is then used to move objects in the scene, or the scene itself, the items on the window remain in the same place.
Hope that helps.

how to render a map defined by a matrix of cells using QT Gui?

I have to render using QT a map defined by a matrix of cells in overview mode (think GPS map). I'd like to be able to zoom on it and each cell is defined by its color and some properties (bitmaps to put on the cell). I also need, like in a GPS, to be able to move around (using the directional arrows) in the map.
Right now, I'm thinking about drawing a matrix of QImages on my screen, and loading each one of them with the info of the cells I need, but it doesn't seem like a very good solution.
Thank you for every possibility you might provide.
Your initial idea is a reasonable one, but put all the QImages and info you need into a custom QGraphicsItem and add them to a QGraphicsScene (and fix their position) - then you only need a QGraphicsView to visualise everything. This way you get the BSP painting and selection optimisations, view transformations, and nice animations (if required!) for free.

Resources