I would like to draw some dynamical overlay elements in a scene.
Usually I would go for the paintEvent in the view or foreground painting for doing overlays, but this time I need to interact with these elements: they are like every other item in the scene... Mostly, this choice comes from the fact that these items are somewhat complex, and they share the rendering with other QGraphicsItem(s).
The problem is that, being overlayed, they shall be treated a bit differently than any other element in the scene: rendered in view coord space, not in scene coord space, and ignore changes in the view rect, dragging, dropping, rubber band selection, and such.
The question is somewhat general: how could I use the scene for a thing like this? Is there a better option for doing overlays e.g. using multiple scenes and rendering them all, in some magic way, in a single view widget?
Related
I have a QGraphicsView on a widget which im adding a number of items to.
some of these are polygons and some are ellipses (Both QGraphicsItems) when i zoom the Graphics view
void Test::on_verticalSlider_sliderMoved(int position)
{
ui->graphicsView->scale(1.1,1.1);
}
the ellipses just get bigger and bigger but i want them to shrink so that they basically stay the same shape no matter how far zoomed in i am, so basically i want the polygons to be zoomed in on but not the points i have plotted onto these polygons...if that makes sense
dont know it makes it easier but all of the ellipse points plotted at any one time are within the same QGraphicsItemGroup so there could be a flag i can set on the entire group to do this?
anyway i would be greatful for any help with this
thanks
EDIT ------ CODE SAMPLE
item is the QGraphicsEllipseItem, and m_group_point is a QGraphicsItemGroup
item->setParentItem(m_group_point.get());
then i add the group to a scene
m_scene2->addItem(m_group_point.get());
then add that scene to the view
ui->graphicsView->setScene(m_scene2.get());
they are .get because they are shared pointers
well, ignoreTransformation indeed a proper way to go.
QGraphicsItem::ItemIgnoresTransformations: The item ignores inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). This flag is useful for keeping text label items horizontal and unscaled, so they will still be readable if the view is transformed. When set, the item's view geometry and scene geometry will be maintained separately. You must call deviceTransform() to map coordinates and detect collisions in the view. By default, this flag is disabled. This flag was introduced in Qt 4.3.
About stay in the middle, you should move ellipses within scene to proper position, so they have proper scene coordinates, and then you instruct to ignore transformation of the view, so they will ignore any zooming/rotation/etc as mentioned in documentation.
The Qt docs states this about the flag QGraphicsItem::ItemIgnoresTransformations: -
The item ignores inherited transformations (i.e., its position is
still anchored to its parent, but the parent or view rotation, zoom or
shear transformations are ignored).
Which is what you want. You've added your items to a QGraphcisItemGroup. For this, Qt help states: -
QGraphicsItemGroup ignores the ItemIgnoresTransformations flag on its
children (i.e., with respect to the geometry of the group item, the
children are treated as if they were transformable).
Reading into this, the QGraphicsItem uses the ItemIgnoreTransformations flag based on its parent, which in your case is the QGraphicsGroup, but this class ignores the flag on its children, which is likely to be causing the issue you're seeing.
Therefore, do not set the flag on the parent group, but on its children.
Try setting the ItemIgnoresTransformations flag on the objects that you want to keep constant-sized.
i came about this problem and knew it could be done better.
The problem:
When overlaying a QGLWidget (Qt OpenGL contextview) with Qt widgets, Qt redraws those widgets after every Qt frame.
Qt isn’t built to redraw entire windows with >60fps constantly, so that’s enormously slow.
My idea:
Make Qt use something other to draw upon: a transparent texture. Make OpenGL use this texture whenever it redraws and draw it on top of everything else. Make Qt redirect all interaction with the OpenGL context view to the widgets drawn onto the texture.
The advantage would be that Qt only has to redraw whenever it has to (e.g. a widget is hovered or clicked, or the text cursor in a text field blinks), and can do partial redraws which are faster.
My Question:
How to approach this? how can I tell Qt to draw to a texture? how can i redirect interaction with a widget to another one (e.g. if i move the mouse above the region in the context view where a checkbox sits in the drawn-to-texture widget, Qt should register this event to the checkbox and repaint to reflect itshovered state)
I separate my 2D and 3D rendering out for my CAD-like app for the very same reasons you have, although in my case my the 2D stuff are not widgets - but it shouldn't make a difference. This is how would approach the problem:
When your widget changes render it onto a QGLFramebufferObject, do this by using the FBO as the QPaintDevice for a QPainter in your QGLWidget::paintEvent(..) and calling myWidget->render( myQPainter, ...). Repeat this for however many widgets you have, but only onto the same FBO - don't create an FBO for each one... Remember to clear it first, like a 'normal' framebuffer.
When your current OpenGL background changes, render it onto another QGLFramebufferObject using standard OpenGL calls, in the same way.
Create a pass through vertex shader (the 'camera' will just be a unit cube), and a very simple fragment shader that can layer the two textures on top of each other.
At the end of the QGLWidget::paintEvent(..), activate your shader program, bind your framebuffers as textures for it (myFBO->texture() gets the handle), and render a unit quad. Because your camera is a unit square, and the viewport size defined the FBO size, it will fill the viewport pixel perfect.
However, that's the easy part... The hard part is the widget interaction. Because you are essentially rendering a 'proxy', you going to have to relay the interaction between the 'real' and 'proxy' widget, whilst keeping the 'real' widget invisible. Here's how would I start:
Some operating systems are a bit weird about rendering widgets without ever showing them, so you may have to show and then hide the widget after instantiation - because of the clever painting queue in Qt, it's unlikely to actually make it to the screen.
Catch all mouse events in the viewport, work out which 'proxy' widget the cursor is over (if any), and then offset it to get the relative position for the 'real' hidden widget - this value will depend on what parent object the 'real' widget has, if any. Then pass the event onto the 'real' widget before redrawing the widget framebuffer.
I should state that I also had to create a 'flagging' system to handle redraws nicely. You don't want every widget event to trigger a widget FBO redraw, because there could many simultaneous events (don't just think about the mouse) - but you would only want one redraw. So I created a system where if anything in the application could change anything in the viewport visually, then it would flag the viewport as 'dirty'. Then setup a QTimer for however many fps you are aiming for (in my situation the scene could get very heavy, so I also timed how long a frame took and then used that value +10% as the timer delay for the next check, this way the system isn't bombarded when rendering gets laggy). And then check the dirty status: if it's dirty, redraw; otherwise don't. I found life got easier with two dirty flags, one for the 3D stuff and one for the 2D - but if you need to maintain a constant draw rate for the OpenGL drawing there's probably no need for two.
I imagine what I did wasn't the easiest way to do it, but it provides plenty of scope for tuning and profiling - which makes life easier in the long run. All the answers are definitely not in this post, but hopefully it will get you on the way to a strategy.
Scene A and scene B have same background. If transition from A to B has animations (say, move in from left), the background will also take part in it. I do not want this. In other words, the background layer belongs neither to scene A or scene B, but something parallel to scenes and laid behind. Can I achieve this?
Although I don't think you can do this with scenes directly,
A plausible work around would be to do the following:
Do a scene replace with no transition
Set all sprite's positions to offscreen, then CCMove them into their proper places.
It's messy, but it could work.
Adam
This is related to one of my other questions.
If I am tiling a large image by creating a separate QGraphicsItem (with the raster data as its pixmap), how do I keep track of the QGraphicsItem's position within the scene? Obviously for raster data, it is important to keep all the tiles "touching" to make a continuous image and they also have to be in the right place so the image doesnt look jumbled.
Does each tile have to have positioning methods that move it in relation to it's neighbors on the top/left/bottom/right? This seems kind of clunky. Is there a better way to make them all move together?
In other words, if I pan the scene with scroll bars, or pick up the image and drag/move it around in the scene, I want all the tiles to also move and stay in the right position relative to each other.
What is the best approach for controlling the layout, which tiles need to be rendered (i.e. only the visible ones), and populating the data only once it is needed? Also, once a tile has been rendered, is the data from it ever dropped, and repopulated from the image file, say if it stays out of view for a while, then comes back later if someone pans to that section?
There are (more than) 2 ways of doing this:
Use QGraphicsItemGroup which
handles grouping of your tile items
for you. It moves, selects, updates
it's group members as if they are
one. I've never used it but from the
doc, it seems to work with typical
applications.
Paint the tiles yourself in the
paint() of one custom item. This
gives you total control on how to
place and draw the tiles while the
item truly acts as one item since it
is, well, one item. This is what I
do.
I subclassed QGraphicsItem and reimplemented paint.
In paint I wrote something like this for labeling the item:
painter->drawText("Test",10,40);
After some time I think It may be useful to handle labeling with seperate item. So I wrote something like this.
QGraphicsTextItem *label = new QGraphicsTextItem("TEST",this);
setPos(10,40);
But two "TEST" drawing do not appear in the same place on screen. I guess difference may be related with item coordinates - scene coordinates. I tried all mapFrom... and mapTo... combinations inside QGraphicsItem interface but no progress. I want to drawings to appear in the same place on screen.
What I miss?
I assume that you are using the same font size and type in both cases. If the difference in position is very small the reason can be the QGraphicTextItem is using some padding for the text it contains. I would try to use QGraphicsSimpleTextItem that is not going to add fancy stuff internally and see if you still have the same problem. The coordinates system is the same one if you use painter or setPost so that is not the problem. If this doesn't help I will suggest to specify the same rect for both to avoid Qt adding it owns separation spaces.