Pygame Surface Mechanics - python

I'm currently writing up some GUI code for a small project I'm working on and I've come to the point where I need to implement scroll bars and their associated containers. For ease of execution, I would love to be able to draw all elements within the "scroll box" (the window that the scroll bar will affect) to a separate surface from my main display surface. The separate surface would then be cropped as need be and then drawn to the display surface in the render loop. I'm having trouble getting this to work, however.
In the draw() method of my ScrollBox class, I have the following code.
def draw(self):
self.subSurface.blit(self.image, (x, y))
#subSurface is, naturally, a Surface, and image is a pygame.Image; x and y are whatever
self.displaySurface.blit(self.subSurface, (x,y))
As with all drawable GUI elements in my code, draw() is called every pass through the main render loop. What the above code gives me is the default filled-in black Rect and self.image is not displayed in any capacity. I tried replacing the first line with
pygame.draw.rect(self.subSurface, color, rect)
but it yielded the same results. From my reading up on other Pygame GUI libraries, it seems what I want to do is possible but I don't think I'm executing it properly. How do I attach other sources/surfaces to subSurface and then have subSurface be drawn (with the sources attached) by displaySurface?
Any help would be much appreciated. Thanks.

For people visiting this question in the future:
Remember that the dest argument for Surface.blit() is relative to the upper-left corner of the destination surface. So if you're assembling an image on a subsurface, remember to use coordinates relative to the top-left corner of the object you're assembling, rather than absolute display coordinates.
So to assemble a scrollbar and draw it somewhere:
class ScrollBar:
# ... code ...
def render(self, display, x, y):
self.subSurface.blit(self.handle_image, (0, self.handle_pos))
self.subSurface.blit(self.upbtn_image, (0, 0))
self.subSurface.blit(self.dnbtn_image, (0, self.height - self.btn_height))
# ... other rendering operations
display.blit(self.subSurface, (x, y))
Adjust all numbers and variable names to taste, but you get the idea. Notice that all the scrollbar elements are positioned in "scrollbar-local" coordinates, with only the final blit to the display surface positioned in screen/application coordinates.

Related

pygame - surface from part of another surface

how can I create a surface from a section of another surface. Reason is that I have an image as a background, and on that am displaying a clock - so when i update a new time, I need to first restore the original background back on that section before bliting the new clock text. I could do this using flip / full screen update, but want to avoid that and only update / restore a section of the screen.
You can update a section of the screen by passing a a single rectnagle or a list of rectangles to pygame.display.update:
Update portions of the screen for software displays. [...] You can pass the function a single rectangle, or a sequence of rectangles.
e.g.:
pygame.display.update(rect_region)
You can define a subsurface that is directly linked to the source surface with the subsurface method:
Returns a new Surface that shares its pixels with its new parent. The new Surface is considered a child of the original. Modifications to either Surface pixels will effect each other.
e.g.:
rect_region = (x, y, width, height)
subsurf = source_surf.subsurface(rect_region)
Alternatively the blit method allows to specify a rectangular sub-area of a source Surface:
[...] An optional area rectangle can be passed as well. This represents a smaller portion of the source Surface to draw. [...]
e.g.:
rect_region = (x, y, width, height)
traget.blit(source_surf, (posx, posy), rect_region)
See also How can I crop an image with Pygame? and Pygame - blitting from x and y cordinates of image.

How to Display Sprites in Pygame?

This is just a quick question regarding sprites in PyGame, I have my image loaded as in the code below, and I'm just wondering how to display the sprite in PyGame like drawing a rectangle or circle. I don't want to have it behave in anyway. I think I use a blit command, but I'm not sure and I'm not finding much online.
Here's my image code for loading it.
Star = pygame.image.load('WhiteStar.png').convert_alpha()
You could just provide an outline for loading a sprite. I simply want to display it.
Use blit to draw an image. Actually blit draws one Surface onto another. Hence you need to blit the image onto the Surface associated to the display.
You need to specify the position where the image is blit on the target. The position can be specified by a pair of coordinates that define the top left position. Or it can be specified by a rectangle, only taking into account the top left point of the rectangle:
screen = pygame.dispaly.set_mode((width, height))
star = pygame.image.load('WhiteStar.png').convert_alpha()
# [...]
while run:
# [...]
screen.blit(star, (x, y))
# [...]
Use a pygame.Rect when you want to place the center of a surface at a specific point. pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. The position of the rectangle can be specified by a keyword argument. For example, the center of the rectangle can be specified with the keyword argument center. These keyword argument are applied to the attributes of the pygame.Rect before it is returned (see pygame.Rect for a full list of the keyword arguments):
screen.blit(star, star.get_rect(center = (x, y)))

Tkinter canvas: Get mouse movement without actually moving the cursor (think Photoshop resize brush)

I'm working on an image annotation tool using Tkinter. A rectangular bounding box is following the cursor and is placed on the image by clicking. Now I need to be able to resize the bounding box and I'd like to do it in a similar fashion as it's done in Photoshop, holding down a button and depending on where the mouse is moved, the brush changes in size. Is that possible in Tkinter?
I've come up with this, where I wanted to add the pointer distance traveled in each direction to the rectangles size:
self.canvas.bind('<Alt_L>', self.resize)
self.previousx = 0
self.previousy = 0
def resize(self, event):
x = self.canvas.canvasx(event.x) # get coordinates of the event on the canvas
y = self.canvas.canvasy(event.y)
self.rect_size[0] += (self.previousx - x) # width
self.rect_size[1] += (self.previousy - y) # height
self.motion(event)
self.previousx = self.canvas.canvasx(event.x)
self.previousy = self.canvas.canvasy(event.y)
It kind of works too but
the previous coordinates need to be initialized with a different key first which is very annoying and
when the cursor - and with it the rectangle too - changes position it is very hard to tell if the rectangle is already the proper size.
How can I keep the cursor at the same spot and still get the mouse movements?
Edit:
Bryan in the comments was right, you can't move the mouse without also moving the on-screen cursor, which is true at least for my purposes. The solution was very simple, while resizing the bounding box I stopped updating it's position, so while the mouse was still moving, the rectangle did not.
Bryan in the comments was right, you can't move the mouse without also moving the on-screen cursor, which is true at least for my purposes. The solution was very simple, while resizing the bounding box I stopped updating it's position, so while the mouse was still moving, the rectangle did not.

How to shade a box when mouse hovers in pygame?

I am making a game, with pygame, and i want it to be that when the mouse hovers over my text, the box gets shaded.
here is what I have so far:
in the event-handling loop:
(tuples in CheckToUnshadeBoxes are pairs of text-surfaces and their boxes (from get_rect method))
elif event.type == MOUSEMOTION:
CheckToShadeBoxes(event, LERect, LoadRect, PlayRect)
CheckToUnshadeBoxes(event, (LERect, LESurf),
(LoadRect, LoadSurf), (PlayRect, PlaySurf))
here is CheckToShadeBoxes:
def CheckToShadeBoxes(event, *args):
'''
shade the box the mouse is over
'''
for rect in args:
s = pg.Surface((rect.width, rect.height))
s.set_alpha(50)
print(rect.x, rect.y)
s.fill(COLOURS['gray'])
x, y = event.pos
if rect.collidepoint(x, y):
SURFACE.blit(s, (rect.x, rect.y))
and CheckToUnshadeBoxes:
def CheckToUnshadeBoxes(event, *args):
''' if the mouse moves out of the box, the box will become unshaded.'''
for (rect, TextSurf) in args:
x, y = event.pos
if not rect.collidepoint(x, y):
SURFACE.blit(TextSurf, (rect.x, rect.y))
This works fine! except that when I move the mouse inside of the box, the box will continue to get darker and darker until you cant even see the text! I know it is a small detail, but it has been bugging me for a long time and I don't know how to fix it.
by the way, if it is not evident enough, COLOURS is a dictionary with string keys and RGB tuple values, and SURFACE is my main drawing surface.
If you have any questions about my code, or anything I have done just comment!
If the rects you are passing are your own derived class, then I recommend making a .is_shaded class variable and checking to make sure the variable is false before shading it.
If these rects aren't your own class extending another, then I recommend you make one, as it would make it much simpler
The problem with your code is that you keep adding in dark rectangles when the mouse is hovered over. No wonder it gets darker and darker.
All that is needed is for your code to be shuffled around a little.
Inside your event handling, you should call a function to check if it should shade the text. This should return a Boolean value. Store this in a variable. Most of the code from the CheckToShadeBoxes function will do the trick.
In your render section, you should render just one surface on top of your text, based on whether the Boolean value is true or not. The code that creates a gray surface in the CheckToShadeBoxes function will work but make sure it is only one surface. Don't create a new surface in every iteration. Define it outside once and blit it to the screen inside the loop.
This should fix your problem!
I hope this answer helps you! If you have any further questions please feel free to post a comment below!

What is the correct sequence for bliting surfaces to the screen in pygame?

I am creating a simple mp3 player and my first task was to create a simple button that a user could press. I created a class called Button which handled this behavior and detects if a user has clicked it and then changes color. I am now trying to have a default text that the button displays and another string (pres_string) which will be displayed if the button is being pressed.
The only problem is my background surface seems to be in the wrong place and is drawing over any changes I have made.
Here is my code:
http://pastebin.com/Nh3yy01X
As you can see I've commented out the lines I described and tried it with basic variables in the main function just to test what was going wrong.
Thanks for any help.
(Feel free to change the title of the question, I wasn't sure what most accuratelydescribed my problem)
Clear the surface every loop
def draw(self):
# clear screen."
self.screen.fill( self.color_bg )
# first draw background
# Then buttons
# then any extra top level text
# update
pygame.display.flip()
tip: For colors, you can call pygame.Color() with human-names like red ( gray20 and gray80 have a nice contrast, to use for bg and text. )
from pygame import Color
text = Color('gray20')
Your button, psuedocode. Fix: moved color as an instance member.
class Button(object):
def __init__(self, text, rect=None):
self.color_bg = Color("gray20")
self.color_text = color("gray80")
if rect is None: rect = Rect(0,0,1,1)
self.rect = rect
self._render()
def _render(self):
# draw button and .render() font, cache to surface for later.
self.surface_cached = self.surface.copy()
# render text
#if size changes, save rect.size of cached surface , mantaining location
self.rect.size = cache.get_rect.size
def draw(self):
# draw cached surface
screen.blit( self.surface_cached, self.rect)
For testClick use Rect.collidepoint http://www.pygame.org/docs/ref/rect.html#Rect.collidepoint
2D bitmap-based computer graphics are like drawing or painting - you put the new ink on top of whatever was there already. So your background must be the first thing you draw each time.

Categories

Resources