I am developing an image viewer using pyqt.
I want the image to be fixed when the box moved.
However, the image is pushed when the box tries to reach the viewer's side like this.
It was implemented using QGraphicsView, QGraphicsScene, and QGraphicsitem.
This is part of main class
self.scene_r = GraphicsScene()
self.scene_r.addPixmap(pix_resized)
self.resizedView.setScene(self.scene_r)
self.resizedView.centerOn(128,128)
This is QGraphicScene Class
class GraphicsScene(QGraphicsScene):
def __init__(self, parent=None):
QGraphicsScene.__init__(self)
rect_item = recItem(QRectF(0, 0, 100, 100))
rect_item.setFlag(QGraphicsItem.ItemIsMovable, True)
rect_item.setZValue(1)
rect_item.setPen(Qt.green)
self.addItem(rect_item)
I tried to override mouseMoveEvent() of QGraphicsRectItem class, but it failed.
That happens for two reasons:
Since you are making the item movable, you can move it freely, anywhere you want.
When the scene rectangle is smaller than the one visible in the view, the view tries to ensure that the whole scene rectangle stays visible, possibly by scrolling its contents.
Note that, unless explicitly set using setSceneRect() (on the scene or on the view, the results might differ), Qt automatically sets the scene rect implicitly to the bigger QRect that contains all visible graphics items.
There at least two possible solutions to your problem, which one to choose depends on what you need, and you can also decide to use both of them.
Explicitly set the sceneRect
You can set the sceneRect to a specific rectangle, an item, or all existing items. Note that, while in your case it won't change much if you set the rectangle for the scene or the view, in more complex cases (for example, multiple views showing the same scene) the result might differ.
# on the view
self.setSceneRect(self.scene_r.itemsBoundingRect())
# alternativaly, on the scene
self.setSceneRect(self.itemsBoundingRect())
Limit the area in which the item can be moved
In this case you can intercept the itemChange ItemPositionChange (note that the ItemSendsGeometryChanges flag must be set) and return an adjusted value before it is actually applied:
class RecItem(QGraphicsRectItem):
def __init__(self, *args):
super().__init__(*args)
self.setFlags(self.ItemSendsGeometryChanges)
def itemChange(self, change, value):
if change == self.ItemPositionChange:
sceneRect = self.scene().sceneRect()
newGeometry = self.boundingRect().translated(value)
# the item pen must be taken into account
halfPen = self.pen().width() / 2
if value.x() < sceneRect.x():
value.setX(sceneRect.x() + halfPen)
if value.y() < sceneRect.y():
value.setY(sceneRect.y() + halfPen)
if newGeometry.right() + halfPen > sceneRect.right():
value.setX(sceneRect.right() - newGeometry.width())
if newGeometry.bottom() + halfPen > sceneRect.bottom():
value.setY(sceneRect.bottom() - newGeometry.height())
return value
return super().itemChange(change, value)
Related
Is this example i have a QTreeWidget with 4 columns. The last column is filled by QFrames.
File ui.py
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
app.setStyle("Windows")
treeWidget = QtWidgets.QTreeWidget()
treeWidget.headerItem().setText(0, "Α/Α")
treeWidget.headerItem().setText(1,"Τύπος")
treeWidget.headerItem().setText(2,"Τίτλος")
treeWidget.headerItem().setText(3,"Προεπισκόπιση")
treeWidget.setStyleSheet("QTreeWidget::item{height:60px;}")
l = []
for i in range(0,30):
l.append(QtWidgets.QTreeWidgetItem(["1","1","1","1"]))
treeWidget.addTopLevelItems(l) # add everything to the tree
treeWidget.show()
right_height = treeWidget.header().height()
for el in l:
right_height += treeWidget.visualItemRect(el).height()
print(right_height)
sys.exit(app.exec_())
Output (after scrolling to the bottom of QTreeWidget):
The desired total height of ScrollArea (inside QTreeWidget) is 1823 and it's calculated as the sum of header height and height of each line.
As you can see there is empty space after last row in QTreeWidget. This problem doesn't appear after resizing QDialog manually.
Edit: This may be usefull.
After checking the code for QTreeWidget and inherited/related classes (QTreeView, QAbstractItemView, QAbstractScrollArea and QWidget, but also QAbstractSlider, used for the scroll bars), it seems clear that QTreeView does not respect the behavior shown in QTableView, which automatically scrolls the view to the bottom (without any further margin) whenever the scroll bar reaches the maximum.[1]
Note that this only happens when the (default) verticalScrollMode property is set to ScrollPerItem. For obvious reasons, whenever it is set to ScrollPerPixel, the scroll bar/area will only extend to the visible area of the viewport.
Unfortunately, the laying out of items (and related function results) of QTreeView is based on this aspect, meaning that we cannot try to just paint the tree (by overriding drawTree() and translating the painter), because in that case painting would be only partially consistent, but the behavior will not. For instance, when hovering or using drag&drop.
The above is most probably caused by optimization reasons: there is no way of knowing the whole extent of a tree, and, unless the uniformRowHeights property is True and all items actually have the same heights (which is clearly not your case), the view should always compute again the geometries of each items; while that could be feasible for a table (2d) model, that becomes quite unreasonable for an undefinite tree (3d) model, as it could theoretically block the view updates. At least, based on the default implementation of QTreeView.
There is a possibility, though: completely override the behavior of the scroll bar, and as long as you know that your model has a known and relatively limited extent.
By default, when ScrollPerItem is active, the scroll bar will always have a range that is equal to total_item_count - visible_item_count: if the viewport has x items and it can currently show y items (with y > x) in its viewport, the scroll bar maximum will be y - x (eg: with 10 visible items, if the viewport can only fully show 9, the maximum will be 1).
When the ScrollPerPixel mode is set instead, the extent will always be the maximum pixel height minus the viewport pixel size. Which means that we can know if the top left item is fully shown or not.
Now, the following requires a bit of trickery and ingenuity.
We need to consider the following aspects:
QScrollBar (based on QAbstractSlider) provides an actionTriggered signal that tells us whenever the user tries to manually change the value using the arrow buttons or by clicking on the "sub/add" page areas (the space within the "groove" that is not covered by the slider handle);
QAbstractItemView internally installs an event filter on the scroll bars, and connects to its valueChanged signals;
bonus: any well designed QObject will update its property (and emit its related changed signal) only when the new value is different from the current one, so we can normally be sure that trying to set the scroll bar value to the same one won't trigger anything;
Considering the above, we could implement a few functions in a subclass and connect them (directly or not) to user generated signals and events. The only catch is that we must use the ScrollPerPixel scroll mode for the vertical scroll bar, which will result in a slightly inconsistent display of the scroll bar handle size.
Well, we can live with that.
Here is a possible implementation that considers the above aspects:
class TreeScrollFix(QTreeWidget):
_ignoreScrollBarChange = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.verticalScrollBar().actionTriggered.connect(self.vbarTriggered)
self.verticalScrollBar().valueChanged.connect(self.fixVBarValue)
self.setVerticalScrollMode(self.ScrollPerPixel)
def vbarTriggered(self, action):
if action in (
QAbstractSlider.SliderNoAction,
QAbstractSlider.SliderToMinimum,
QAbstractSlider.SliderToMaximum,
QAbstractSlider.SliderMove,
):
# we can safely ignore the above, eventually relying on the
# fixVBarValue function
return
if action in (
QAbstractSlider.SliderSingleStepAdd,
QAbstractSlider.SliderSingleStepSub
):
delta = 1
else:
delta = QApplication.wheelScrollLines()
if not delta:
# this should not happen...
return
if action in (
QAbstractSlider.SliderSingleStepAdd,
QAbstractSlider.SliderPageStepAdd
):
func = self.indexBelow
else:
func = self.indexAbove
if self.verticalScrollBar().value() == self.verticalScrollBar().maximum():
delta -= 1
index = self.indexAt(QPoint(0, 1)) # note the extra pixel
while delta:
newIndex = func(index)
if not newIndex.isValid():
break
index = newIndex
delta -= 1
self.scrollTo(index, self.PositionAtTop)
def fixVBarValue(self, value):
vbar = self.verticalScrollBar()
if not value or vbar.maximum() == value:
return
topLeftIndex = self.indexAt(QPoint(0, 0))
topLeftRect = self.visualRect(topLeftIndex)
# adjust the theoretical value to the actual y of the item (which is
# a negative one)
value += topLeftRect.y()
showTop = topLeftRect.center().y() > 0
if not showTop:
# the item currently shown on the top left is not fully shown, and
# the visible height is less than half of its height;
# let's show the next one instead by adding that item's height
value += topLeftRect.height()
if value != vbar.value():
vbar.setValue(value)
def eventFilter(self, obj, event):
if event.type() == event.Wheel and obj == self.verticalScrollBar():
delta = event.angleDelta().y()
if delta: # delta != 0 -> no vertical scrolling
# "synthesize" the event by explicitly calling the custom
# vbarTriggered function just as it would be normally called;
# note that this is a real workaround that will never work with
# normal implicit or explicit event handling, which means that
# QApplication.postEvent and QApplication.sendEvent might be
# potentially ignored by this if another event filter exists.
self.vbarTriggered(
QAbstractSlider.SliderPageStepSub if delta > 1
else QAbstractSlider.SliderPageStepAdd
)
# the event has been handled, do not let the scroll bar handle it.
return True
return super().eventFilter(obj, event)
def scrollTo(self, index, hint=QAbstractItemView.EnsureVisible):
if hint in (self.PositionAtTop, self.PositionAtTop):
if hint == self.PositionAtBottom:
self._ignoreScrollBarChange = True
super().scrollTo(index, hint)
self._ignoreScrollBarChange = False
return
itemRect = self.visualRect(index)
viewRect = self.viewport().rect()
if hint == self.EnsureVisible and itemRect.y() < viewRect.y():
super().scrollTo(index, self.PositionAtTop)
return
vbar = self.verticalScrollBar()
if not self.indexBelow(index).isValid():
# last item
vbar.setValue(vbar.maximum())
return
self._ignoreScrollBarChange = True
if hint == self.PositionAtCenter:
super().scrollTo(index, self.PositionAtCenter)
elif itemRect.bottom() > viewRect.bottom():
super().scrollTo(index, self.PositionAtBottom)
topLeftIndex = self.indexAt(QPoint(0, 0))
topLeftRect = self.visualRect(topLeftIndex)
if topLeftRect.y() < 0:
delta = topLeftRect.height() + topLeftRect.y()
vbar.setValue(vbar.value() + delta)
self._ignoreScrollBarChange = False
And an example code to test it:
from random import randrange
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class TreeScrollFix(QTreeWidget):
# as above...
app = QApplication([])
treeWidget = TreeScrollFix()
treeWidget.setColumnCount(2)
for i in range(1, 31):
topLevel = QTreeWidgetItem(treeWidget, ["top item {}".format(i)])
for j in range(randrange(5)):
child = QTreeWidgetItem(topLevel,
['', topLevel.text(0)])
# a random vertical size hint
hint = QSize(100, randrange(30, 80))
child.setSizeHint(1, hint)
child.setText(0, 'height: {}'.format(hint.height()))
treeWidget.header().setSectionResizeMode(QHeaderView.ResizeToContents)
# expand top level indexes randomly
for i in range(randrange(5, treeWidget.topLevelItemCount())):
topIndex = randrange(treeWidget.topLevelItemCount())
treeWidget.setExpanded(treeWidget.model().index(topIndex, 0), True)
treeWidget.setStyleSheet('''
QTreeView::item {
border: 1px solid palette(highlight);
}
QTreeView::item:selected {
border-color: red;
background: palette(highlight);
color: palette(highlighted-text);
}
''')
treeWidget.resize(app.primaryScreen().size() * 2 / 3)
treeWidget.show()
app.exec_()
Note that I added an override for scrollTo(), which is always called when using keyboard navigation. Normally, the item view takes care of the top alignment when ScrollPerItem is active, but in our case the pixel scrolling could create some issues for items that do not have uniform row heights, and when scrolling to the bottom. The override takes care of that depending on the hint argument of that function, so that whenever scrolling won't show the top item in full, it automatically scrolls down to show the next item on top, otherwise it will just scroll to the bottom for the last available, not expaned item. To avoid unnecessary calls, I also used a _ignoreScrollBarChange flag that will make ignore any further and unnecessary computing in fixVBarValue(). This will also work for the internally delayed call to scrollTo() that happens when selecting any item.
Be aware that I've done some testing and it should work as expected. Unfortunately, QAbstractItemView and QTreeView use delayed item layout management, and I cannot completely be sure about these aspects. At least in one case in dozens, I got a UI freeze, but I was not able to reproduce the issue (which might have been caused by external causes). I strongly advice you to take your time to check the code above, the documentation and the Qt sources, and consider using some carefully thought test suite.
Also, for obvious reasons, if you want to use a custom QScrollBar, you'd need to properly disconnect the previous functions and connect them again to the new one.
[1] I am not sure, but it is probably related to a comment in the QTreeView code (near line 3500), which says: optimize (maybe do like QHeaderView by letting items have startposition); see the official sources or the KDAB code browser
I have the following code to place a CheckBox in the first column of a list of items in a QTableWidget.
checkboxWidget = QWidget()
checkBox = QCheckBox(checkboxWidget)
checkBox.clicked.connect(self._check_changed)
#
# If the variable is in the monitored list
# check the checkbox
#
isMonitored = False
if (self._monitored_variables != None):
if (self._monitored_variables[name]):
isMonitored = True
if (isMonitored):
checkBox.setCheckState(Qt.CheckState.Checked)
else:
checkBox.setCheckState(Qt.CheckState.Unchecked)
layoutCheckbox = QHBoxLayout(checkboxWidget)
layoutCheckbox.addWidget(checkBox)
layoutCheckbox.setAlignment(Qt.AlignCenter)
layoutCheckbox.setContentsMargins(0, 0, 0, 0)
self._variables_view.setCellWidget(row,0, checkboxWidget)
I started with the answer to this question:
How should I connect CheckBox clicked signals in Table Widgets in PyQt5?
The difference that I have is that I want the CheckBox centered in the table cell, hence the extra controls.
The click handler looks like this:
def _check_changed(self):
cb = self.sender()
print(cb.parent())
ix = self._variables_view.indexAt(cb.pos())
print(ix.row(), ix.column(), cb.isChecked())
The problem I am facing is that the row/column is not correct.
How can I recover the row/column of the CheckBox that was clicked?
It seems that my previous answer was not explicit but I am going to take this answer to take it in a generic way that will work for all the classes that inherit from QAbstractItemView and any type of widget placed through the setCellWidget or setIndexWidget methods.
General case:
The key to the solution is to obtain the position of the widget that emits the signal with respect to the viewport of the QAbstractItemView, and then use indexAt to get the QModelIndex. This can be obtained by following the following steps:
Map any relative internal position of the widget to global coordinates.
Map global coordinates to local coordinate relative to the viewport.
use indexAt() with local coordinate.
gl = widget.mapToGlobal(QtCore.QPoint())
lp = view.viewport().mapFromGlobal(gp)
ix = view.indexAt(lp)
Specific case:
In this case, just do the following:
def _check_changed(self):
widget = self.sender()
gl = widget.mapToGlobal(QtCore.QPoint())
lp = self._variables_view.viewport().mapFromGlobal(gp)
ix = self._variables_view.indexAt(lp)
It seems the answer to my issues was rather simple. In the "indexAt" method the "cb.pos()" needs to be changed to "cb.parent().pos()".
How do you force a frame to get window_height 0?
the general case where my problem occurs:
import Tkinter as Tk
class App(Tk.Frame):
def __init__(self, master):
Tk.Frame(self, master)
self.place_holder = Tk.Frame(master=self)
self.content = Tk.Frame(master=self)
self.place_holder.pack()
self.content.pack(side=Tk.RIGHT)
Tk.Button(master=self,command=self.add_something).pack(side=Tk.TOP)
self.to_destroy = []
def add_something(self):
foo = Tk.button(master=self.place_holder, command=self.destroy_last)
self.too_destroy.append(foo)
def destroy_last(self):
self.to_destroy[-1].destroy()
the problem:
As I add more elements to the place_holder, it rescales nicely.
When I remove elements from the place_holder, it rescales nicely.
EXCEPT when I remove the last element.
Before i added anything, even when i do place_holder.pack(), it will not show. But after removing the last element, the place_holder will keep the size of this last element. Is there a way to hide the place_holder again untill i add content again?
example image
The empty container at the bottom left does not contain any elements, but still has the size of the last element in it, how can i get this to disappear without removing it (i want it again in the same place)?
What is happening is that when you remove the last widget, pack no longer is managing the frame so it isn't responsible for setting the frame size.
The simplest solution is just to temporarily pack a 1x1 pixel frame, which wil cause the placeholder frame to shrink.
There's no way to make a frame of zero pixels, so this method will always result in a one pixel tall/wide area for the placeholder. If you don't want that one pixel, you can install call pack_forget on the placeholder to completely remove it from the display, and then use pack with suitable options to re-add it when you put something in it.
Example:
def destroy_last(self):
self.to_destroy.pop().destroy()
if len(self.to_destroy) == 0:
tmp = Tk.Frame(self.place_holder, width=1, height=1, borderwidth=0)
tmp.pack()
self.place_holder.update()
tmp.destroy()
I'm working on a sort of a 2D Minecraft clone for my first in depth Pyglet project and I've run across a problem. Whenever I have a decent number of blocks on screen, the frame rate drops dramatically.
Here is my rendering method:
I use a dictionary with the key being a tuple(which represents the coordinate for the block) and the item being a texture.
I loop through the entire dictionary and render each block:
for key in self.blocks:
self.blocks[key].blit(key[0] * 40 + sx,key[1] * 40+ sy)
P.S. sx and sy are coordinate offsets for screen scrolling
I would like to know if there is a way to more efficiently render each block.
I'm going to do my best to explain why and how to optemize your code without actually knowing what you code looks like.
I will assume you have something along the lines of:
self.blocks['monster001'] = pyglet.image.load('./roar.png')
This is all fine and dandy, if you want to load a static image that you don't want to do much with. However, you're making a game and you are going to use a hell of a lot more sprites and objects than just one simple image file.
Now this is where shared objects, batches and sprites come in handy.
First off, input your image into a sprite, it's a good start.
sprite = pyglet.sprite.Sprite(pyglet.image.load('./roar.png'))
sprite.draw() # This is instead of blit. Position is done via sprite.x = ...
Now, draw is a hell of a lot quicker than .blit() for numerous of reasons, but we'll skip why for now and just stick with blazing speed upgrades.
Again, this is just one small step towards successful framerates (other than having limited hardware ofc.. duh).
Anyway, back to pew pew your code with upgrades.
Now you also want to add sprites to a batch so you can simultaneously render a LOT of things on one go (read: batch) instead of manually pushing things to the graphics card. Graphic cards soul purpose was designed to be able to handle gigabits of throughput in calculations in one insanely fast go rather than handle multiple of small I/O's.
To do this, you need to create a batch container. And add "layers" to it.
It's quite simple really, all you need to do is:
main_batch = pyglet.graphics.Batch()
background = pyglet.graphics.OrderedGroup(0)
# stuff_above_background = pyglet.graphics.OrderedGroup(1)
# ...
We'll stick one with batch for now, you probably don't need more for this learning purpose.
Ok so you got your batch, now what? Well now we try our hardest to choke that living hell out of your graphics card and see if we can even buckle it under pressure (No graphic cars were harmed in this process, and please don't choke things..)
Oh one more thing, remember the note about shared objects? well, we'll create a shared image object here that we push into the sprite, instead of loading one new image every.. single... time.. monster_image we'll call it.
monster_image = pyglet.image.load('./roar.png')
for i in range(100): # We'll create 100 test monsters
self.blocks['monster'+str(i)] = pyglet.sprite.Sprite(imgage=monster_image, x=0, y=0, batch=main_batch, group=background)
Now you have 100 monsters created and added to the batch main_batch into the sub-group background. Simple as pie.
Here's the kicker, instead of calling self.blocks[key].blit() or .draw(), we can now call main_batch.draw() and it will fire away every single monster onto the graphics card and produce wonders.
Ok, so now you've optimized the speed of your code, but really that won't help you in the long run if you're making a game. Or in this case, a graphics engine for your game. What you want to do is step up into the big league and use classes. If you were amazed before you'll probably loose your marbles of how awesome your code will look once you've done with it.
Ok so first, you want to create a base class for your objects on the screen, lets called in baseSprite.
Now there are some kinks and stuff you need to work around with Pyglet, for one, when inheriting Sprite objects trying to set image will cause all sorts of iffy glitches and bugs when working with stuff so we'll set self.texture directly which is basically the same thing but we hook into the pyglet libraries variables instead ;D pew pew hehe.
class baseSprite(pyglet.sprite.Sprite):
def __init__(self, texture, x, y, batch, subgroup):
self.texture = texture
super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup)
self.x = x
self.y = y
def move(self, x, y):
""" This function is just to show you
how you could improve your base class
even further """
self.x += x
self.y += y
def _draw(self):
"""
Normally we call _draw() instead of .draw() on sprites
because _draw() will contains so much more than simply
drawing the object, it might check for interactions or
update inline data (and most likely positioning objects).
"""
self.draw()
Now that's your base, you can now create monsters by doing:
main_batch = pyglet.graphics.Batch()
background = pyglet.graphics.OrderedGroup(0)
monster_image = pyglet.image.load('./roar.png')
self.blocks['monster001'] = baseSprite(monster_image, 10, 50, main_batch, background)
self.blocks['monster002'] = baseSprite(monster_image, 70, 20, main_batch, background)
...
main_batch.draw()
How, you probably use the default #on_window_draw() example that everyone else is using and that's fine, but I find it slow, ugly and just not practical in the long run. You want to do Object Oriented Programming.. Right?
That's what it's called, I call it readable code that you like to watch all day long. RCTYLTWADL for short.
To do this, we'll need to create a class that mimics the behavior of Pyglet and call it's subsequent functions in order and poll the event handler otherwise sh** will get stuck, trust me.. Done it a couple of times and bottle necks are easy to create.
But enough of my mistakes, here's a basic main class that you can use that uses poll-based event handling and thus limiting the refresh rate to your programming rather than built in behavior in Pyglet.
class main(pyglet.window.Window):
def __init__ (self):
super(main, self).__init__(800, 800, fullscreen = False)
self.x, self.y = 0, 0
self.sprites = {}
self.batches = {}
self.subgroups = {}
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def render(self):
self.clear()
for batch_name, batch in self.batches.items():
batch.draw()
for sprite_name, sprite in self.sprites.items():
sprite._draw()
self.flip() # This updates the screen, very much important.
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze.
# Basically it flushes the event pool that otherwise
# fill up and block the buffers and hangs stuff.
event = self.dispatch_events()
x = main()
x.run()
Now this is again just a basic main class that does nothing other than render a black background and anything put into self.sprites and self.batches.
Do note! we call ._draw() on the sprites because we created our own sprite class earlier? Yea that's the awesome base sprite class that you can hook in your own stuff before draw() is done on each individual sprite.
Anywho, This all boils down to a couple of things.
Use sprites when making games, your life will be easier
Use batches, your GPU will love you and the refreshrates will be amazing
Use classes and stuff, your eyes and code mojo will love you in the end.
Here's a fully working example of all the pieces puzzled together:
import pyglet
from pyglet.gl import *
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_LINE_SMOOTH)
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE)
pyglet.clock.set_fps_limit(60)
class baseSprite(pyglet.sprite.Sprite):
def __init__(self, texture, x, y, batch, subgroup):
self.texture = texture
super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup)
self.x = x
self.y = y
def move(self, x, y):
""" This function is just to show you
how you could improve your base class
even further """
self.x += x
self.y += y
def _draw(self):
"""
Normally we call _draw() instead of .draw() on sprites
because _draw() will contains so much more than simply
drawing the object, it might check for interactions or
update inline data (and most likely positioning objects).
"""
self.draw()
class main(pyglet.window.Window):
def __init__ (self):
super(main, self).__init__(800, 800, fullscreen = False)
self.x, self.y = 0, 0
self.sprites = {}
self.batches = {}
self.subgroups = {}
self._handles = {}
self.batches['main'] = pyglet.graphics.Batch()
self.subgroups['base'] = pyglet.graphics.OrderedGroup(0)
monster_image = pyglet.image.load('./roar.png')
for i in range(100):
self._handles['monster'+str(i)] = baseSprite(monster_image, randint(0, 50), randint(0, 50), self.batches['main'], self.subgroups['base'])
# Note: We put the sprites in `_handles` because they will be rendered via
# the `self.batches['main']` batch, and placing them in `self.sprites` will render
# them twice. But we need to keep the handle so we can use `.move` and stuff
# on the items later on in the game making process ;)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def render(self):
self.clear()
for batch_name, batch in self.batches.items():
batch.draw()
for sprite_name, sprite in self.sprites.items():
sprite._draw()
self.flip() # This updates the screen, very much important.
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze.
# Basically it flushes the event pool that otherwise
# fill up and block the buffers and hangs stuff.
event = self.dispatch_events()
# Fun fact:
# If you want to limit your FPS, this is where you do it
# For a good example check out this SO link:
# http://stackoverflow.com/questions/16548833/pyglet-not-running-properly-on-amd-hd4250/16548990#16548990
x = main()
x.run()
Some bonus stuff, I added GL options that usually does some benefitial stuff for you.
I also added sa FPS limiter that you can tinker and play with.
Edit:
Batched updates
Since the sprite object can be used to do massive renderings in one go by sending it all to the graphics card, similarly you'd want to do batched updates.
For instance if you want to update every objects position, color or whatever it might be.
This is where clever programming comes into play rather than nifty little tools.
See, everything i relevant in programming.. If you want it to be.
Assume you have (at the top of your code) a variable called:
global_settings = {'player position' : (50, 50)}
# The player is at X cord 50 and Y cord 50.
In your base sprite you could simply do the following:
class baseSprite(pyglet.sprite.Sprite):
def __init__(self, texture, x, y, batch, subgroup):
self.texture = texture
super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup)
self.x = x + global_settings['player position'][0]#X
self.y = y + global_settings['player position'][1]#Y
Note that you'd have to tweak the draw() (note, not _draw() since batched rendering will call upon draw and not _draw) function a little bit to honor and update position updates per rendering sequence. That or you could create a new class that inherits baseSprite and have only those types of sprite updated:
class monster(baseSprite):
def __init__(self, monster_image, main_batch, background):
super(monster, self).__init__(imgage=monster_image, x=0, y=0, batch=main_batch, group=background)
def update(self):
self.x = x + global_settings['player position'][0]#X
self.y = y + global_settings['player position'][1]#Y
And thus only call .update() on monster type classes/sprites.
It's a bit tricky to get it optimal and there's ways to solve it and still use batched rendering, but somewhere along these lines is probably a good start.
IMPORTANT NOTE I just wrote a lot of this from the top of my head (not the first time I've written a GUI class in Pyglet) and for whatever reason this *Nix instance of mine doesn't find my X-server.. So can't test the code.
I'll give it a test in an hour when I get off work, but this gives you a general Idea of what to do and what to think for when making games in Pyglet. Remember, have fun while doing it or you're quit before you even started because games take time to make ^^
Pew pew lazors and stuff, best of luck!
How does one set the icon size for items within a Qtreewidget? So far I have tried
QTreeWidget.setIconSize(QSize(32,32))
But all that does is increase the width, not the height.
However, a
print QTreeWidget.iconSize()
shows the correct result :
PyQt4.QtCore.QSize(32, 32)
Does anyone have a better understanding of how QTreewidget works?
Jason had some of the solution. The height of the treeview's rows needs to be resized as well:
class MyDelegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
def sizeHint(self, option, index):
return QSize(32,32)
Then, elsewhere:
delegate = MyDelegate()
tree = QTreeWidget()
tree.setItemDelegate(delegate)
Not ideal as it resizes every row.
[edit] If you want to vary the size of the rows ensure the QTreeWidget/View.uniformRowHeights == False
Then mess around using the index. For me I wanted the 2nd row to bigger than the rest. I'm sure there's a better way but my sizeHint became:
def sizeHint(self,option,index):
parent = index.parent()
if parent.isValid() and not parent.parent().isValid():
return QSize(32,32)
return QSize(24,24)
However, there's another issue with this. Icons are not resizeable. Ah! That has to be done with
QTreeWidget.setIconSize(QSize(width,height))
Looks to me like you need to adjust the row height of the QTreeView to accommodate the larger icons. From what I can see in the pictures, it looks like the icons are being resized properly, but because the rows are not tall enough and the icons are being cropped. See this for how to change the row heights.