I've been trying to develop a "text box" class for pygame as a little personal project, and I've come across an issue that's really stumped me. I'm trying to expand on the pygame text input class found here, wrapping it in a text box class that supports multiple lines and, hopefully, scrolling capabilities.
My problem comes when trying to move the blinker up and down between the lines of text. Basically, hitting the "up" arrow once moves the blinker all the way to the top, and then it stops being responsive to move it down.
Here's the code for how I give the pygame_textbox class the events:
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
test_box.update(events)
test_box_2.update(events)
test_box.draw(screen, (100, 100))
test_box_2.draw(screen, (200, 250))
pygame.display.update()
Here's the code for the text box class (event comes from the above code):
if event.type == pl.KEYDOWN:
if self.is_active:
print("reading in text box: {}".format(event))
if event.key == pl.K_UP and self.cursor_line > 0:
self.cursor_line -= 1
print("cursor going UP to {}".format(self.cursor_line))
if event.key == pl.K_DOWN and self.cursor_line < len(self.text_input)-1:
self.cursor_line += 1
print("cursor going DOWN to {}".format(self.cursor_line))
if event.key == pl.K_RETURN:
self.text_input.insert(self.cursor_line+1, pygame_textinput.TextInput())
self.cursor_line += 1
for line in self.text_input:
print(line.input_string)
Trying to debug it seems to show that the pygame.event.get() queue is taking in way more KEYDOWN events than it's supposed to; one press of the button sends multiple (and sometimes ongoing) events. I'm new to pygame, but I'm pretty sure that's not supposed to happen with KEYDOWN events, right? There's only supposed to be one event triggered every time a key is pressed. What am I doing wrong here? Is this a bug with pygame itself?
Thanks for any help you can provide. I'm fairly new at this and I hope I formatted the question right.
Just take a look at the pygame_textinput module you linked in your question:
# Update key counters:
for key in self.keyrepeat_counters:
self.keyrepeat_counters[key][0] += self.clock.get_time() # Update clock
# Generate new key events if enough time has passed:
if self.keyrepeat_counters[key][0] >= self.keyrepeat_intial_interval_ms:
self.keyrepeat_counters[key][0] = (
self.keyrepeat_intial_interval_ms
- self.keyrepeat_interval_ms
)
event_key, event_unicode = key, self.keyrepeat_counters[key][1]
pygame.event.post(pygame.event.Event(pl.KEYDOWN, key=event_key, unicode=event_unicode))
As you can see, it's the update method of the TextInput that repeats the key events by posting them again to pygame's event queue.
Related
I am trying to implement a game using mouse position to see if the user clicks a button. Somehow the mouse position does not update for a couple seconds, and changes to a new position for another couple seconds, and repeat. I moved and pressed the mouse at different location in the screen, but the mouse position did not change at all. (Working on python3.5.1 and pygame 1.9.2, using IDE PyCharm)Any idea?
Here is my code:
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if click[0]==1:
print(mouse)
pygame.display.update()
The call
mouse = pygame.mouse.get_pos()
doesn't update the position unless the event MouseMotion is executed.
If you are executing the program in a window on a MAC, the mouse must be pressed, held, and moved (if you were to press, hold, then move the mouse, pygame.mouse.get_pos() would return the current mouse position).
There is two ways of handling input events in pygame :
State Checking
Event Handling
For better understanding of how it works :
http://www.pygame.org/docs/tut/newbieguide.html#managing-the-event-subsystem
You use both in your code, state checking :
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
Event handling :
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
If you still want to use state checking to get the mouse position you can add:
clock=pygame.time.Clock()
clock.tick(60) # 60 frames per second
So the update of the position should be better.
If the button has a rect then you can use the rect.collidepoint() method with event checking like this:
mouse_pos = pygame.mouse.get_pos()
if event.type == pygame.MOUSEBUTTONDOWN and pygame.mouse.get_pressed()[0] and button.rect.collidepoint(mouse_pos):
This drove me crazy for hours. I had a similar problem.
Using pygame in:
Mac OSX 10.13 (High Sierra)
pygame 1.9.3
python 3.6
in a virtualenv
In this setup (specifically in the virtualenv), window focus is not properly tracked. Click and dragging will generate a MOUSEMOTION event, but simply moving the mouse around will not. If the MOUSEMOTION event is not generated, calling:
pos = pygame.mouse.get_pos()
will continue report the same value until another MOUSEMOTION event occurs.
Installing pygame outside of the virtualenv, all works as expected. Not really the answer I was hoping for, but at least it explains the behavior I was seeing.
Your
mouse = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
only get the states at the time of the call. As per the documentation http://www.pygame.org/docs/ref/mouse.html:
to get all of the mouse events it is better to use either
pygame.event.wait() or pygame.event.get() and check all of those events
That is you are missing the clicks because your program isn't storing them to process. You only see it when you get lucky and the program calls the function when the button is actually down.
This shows a basic pygame mouse getting program. Simply click anywhere in the window and the mouse coordinate are printed:
import pygame as py
py.init()
white = (255,255,255)
window = (400,400)
screen = py.display.set_mode(window)
clock = py.time.Clock()
done = False
while not done:
for event in py.event.get():
if event.type == py.QUIT:
done = True
elif event.type == py.MOUSEBUTTONDOWN:
print py.mouse.get_pos()
screen.fill(white)
py.display.flip()
clock.tick(30)
py.quit()
hope this helps :)
My program does only two things :
1) It's printing word 'hello' every time i hover mouse on one of two images (anyone,in any order)
2) It's closing , when i close window by pressing ESC , or by mouse
import pygame,sys ,os,random,math
from pygame.locals import *
pygame.init()
white=(255,255,255)
black=(0,0,0)
WINDOWWIDTH = 1680
WINDOWHEIGHT =1050
mousex, mousey=0,0
screen=pygame.display.set_mode((WINDOWWIDTH,WINDOWHEIGHT))
ticket=pygame.image.load(os.path.join('imag','ticket.png'))
ticket2=pygame.image.load(os.path.join('imag','ticket2.png'))
def terminate():
pygame.quit()
sys.exit()
def checkForQuit():
for event in pygame.event.get(QUIT): # get all the QUIT events
terminate() # terminate if any QUIT events are present
for event in pygame.event.get(KEYUP): # get all the KEYUP events
if event.key == K_ESCAPE:
terminate() # terminate if the KEYUP event was for the ESc key
pygame.event.post(event)
tic1=ticket.get_rect(topleft=(50,770))
tic2=ticket2.get_rect(topleft=(220,770))
List_of_tickets=[tic1,tic2]
while 1:
screen.fill(white)
checkForQuit()
screen.blit(ticket,(50,770))
screen.blit(ticket2,(220,770))
for tic in List_of_tickets:
if tic.collidepoint(pygame.mouse.get_pos()):
print 'Hello !'
pygame.display.update()
The problem is that second part is not working !
So if i hover mouse on one ticket - everything fine . I got my 'Hello' word and i can close window if i need to .
But if i hover mouse on one ticket , and THEN on second ticket (so i got many more of my 'Hello' - and that's good) - i cannot quit program nor with ESC , nor with mouse.
It seems like quitting event ceased to be handled.
The questions are :
1) What i do not understand in handling of events in Pygame ?
2) What should i do to make my code quittin in any case ?
From the docs:
"If you are only taking specific events from the queue, be aware that the queue could eventually fill up with the events you are not interested."
Toss pygame.event.clear() in there and see what happens.
(If so, the reason it only fails if you go to both tickets is that all mouse movements add an event; moving to one ticket doesn't completely fill the queue, but moving to both does. Slightly wiggling the mouse would have the same effect.)
I haven't been able to find a straightforward answer.
key.set_repeat() works for KEYDOWN events, but not for key.get_pressed():
import sys, pygame
from pygame.locals import *
pygame.init()
Clock = pygame.time.Clock()
pygame.display.set_mode((200, 100))
pygame.key.set_repeat(1, 500)
while True:
if pygame.key.get_pressed()[K_UP]:
print('up!')
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_DOWN:
print('down!')
Clock.tick(30)
Even the slightest tap on UP explodes into at least a few up!s, only down!s are delayed. I want to use key.get_pressed() since it conveniently handles multiple inputs. Do I have to work around it with some sorta tick counter? Alternatively, is there a way to handle multiple KEYDOWN events?
Just don't mess around with key.set_repeat() if you don't have to.
Simply use key.get_pressed() to check for keys that are pressed and where keeping that key pressed down has a meaning to you, e.g. something like move left while K_LEFT is pressed.
Use event.get() / KEYDOWN when you're interessed in a single key press, e.g. something like pressing K_P pauses the game.
If you mess around with key.set_repeat(), e.g. set the delay to low, you won't be able to recognize a single key stroke, since every key stroke will create multiple KEYDOWN events.
To handle multiple KEYDOWN events, just check for the relevant events. Every key stroke will generate a KEYDOWN event, so you can simple check e.g.:
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_DOWN:
print('down!')
elif event.key == K_UP:
print('up!')
That will of course print up!/down! multiple times if the user keeps pressing the keys.
The golden rule is: If you're interessted in the KEYDOWN and the KEYUP event of a single key, better use key.get_pressed() instead.
Even the slightest tap on UP explodes into at least a few up!s, only down!s are delayed
That's because the event system and key.get_pressed() are totally different. If you get pressing K_UP over multiple frames, for each frame 'up!' is printed because the key is pressed down in every frame. key.get_pressed() simple returns which keys are pressed at the moment you call this function. In other words: you can't "delay" it.
I'm facing a problem with pygame 1.9.2 based on SDL 1.2.15: the only mean of resizing window programmatically I see is pygame.display.set_mode(...). But when I call this method my keyboard state is reset, so all currently pressed keys and modifiers send KEYUP event, repeated keys are stopped too.
When the window is resized manually by user keyboard state is perfectly normal. The event queue is paused for the time of resizing, then VIDEORESIZE and VIDEOEXPOSE events come up and keys are still pressed, if they were so.
There are few questions:
Is there a way to keep normal keyboard state while resizing window?
If not, I would like to at least keep modifier keys but I can not just use key.set_mods() because I am unable to get the moment when user releases key.
At last is there a way to invoke resizing of SDL window directly?
Example program demonstrates undesired behaviour. Left and right arrows resize window to same size, yet if they are pressed, there is only one keypress. Other keys are repeated normally if held. Output is event log for everything that happens.
import pygame
import sys
# same size all the time
size = (200, 200)
pygame.init()
pygame.display.set_mode(size, pygame.RESIZABLE)
# if left or right arrow key is held nothing will happen despite this line
pygame.key.set_repeat(500, 200)
run = True
while run:
for event in pygame.event.get():
out = '%s\t%s'%(event.type, str(event.dict))
if event.type == pygame.QUIT:
pygame.display.quit()
run = False
if event.type == pygame.KEYDOWN:
# mods are also reset on set_mode() call
out += ', mods = %i' % (pygame.key.get_mods())
if event.key == pygame.K_LEFT:
pygame.display.set_mode(size, pygame.RESIZABLE)
if event.key == pygame.K_RIGHT:
pygame.display.set_mode(size, pygame.RESIZABLE)
print(out)
pygame.time.Clock().tick(60)
Tested on Win machine but soon will report if linux fails too.
UPD: I've been digging into source of pygame and can not see why, but the documentation bundled with sources of pygame states, that Pygame can only have a single display active at any time. Creating a new one with pygame.display.set_mode() will close the previous display which implies that there is no way to resize window, only to recreate it. Seems a little strange to me.
UPD: I have changed my progam behaviour so that resize does not actually happen until user releases keyboard completely. It works well and does not break usability.
Nevertheless, it would be great to know if there exists a solution to stated problem.
One solution is, like self. has mentioned, to store the states of the keys so that instead of using only the events, you could ignore this particular key up event. This could, however, have one problem. If you are trying to create a key control to resize the screen, which you probably are doing, then you will also be ignoring the key up event that is real. Because this kind of control is rarely implemented, there does not seem to be any kind of clean solution, like a function to just change the screen, or one to suppress the false key events. However, you could find a way around the key event problem. There are really two viable solutions. The first one (and probably the worse of the two) is to create a way to identify the fake events, and selectively ignore the key up events. Probably the easiest way to do this is to resize the screen periodically during a fixed amount of time, so that you can expect a false keyboard event at this time, and ignore it. Here is an example of the code to block a resize every second (specifically, it assumes a screen update occurs every time the system time has an exact second value, no fractions of a second) for the a key:
import pygame
import datetime
a_keydown = False
for event in pygame.event.get():
if item.type == pygame.KEYDOWN and item.key == pygame.K_a:
a_keydown = True
print "keydown event"
if item.type == pygame.KEYUP and item.key == pygame.K_a:
if datetime.datetime.now().time()[4] > 15: #leaves a window of fifteen microseconds for the screen resize, this may need to be adjusted
a_keydown = False
print "key up event"
The better of the two solutions is not this, however. Assuming the keyup event created by set_mode exists soley in python, you could get key events from elsewhere, like pywin32 for example. Try using the win32api module, and something like the GetKeyState() function for what you need. This will give you the last known state of a key. Not quite as convenient as pythons event stream, but it may be your best solution, as pygame doesn't supply an elegant solution to this.
This question already has answers here:
Pygame window not responding after a few seconds
(3 answers)
Pygame unresponsive display
(1 answer)
Closed 1 year ago.
Basically I have a loop (tick, set_caption, screen_fill, event.get(), send_frame_event, flip, repeat)
When I drag the window around on windows 7, the loop stops looping, I ended up stuck in pygame.event.get(), I have tried to define certain events only for get e.g. get([pygame.QUIT]) to no avail.
Simply calling pygame.event.clear() has the same freeze effect when dragging/moving the window.
Is there a workaround?
Not full code, but should be enough:
def start(self):
self.running = True
Clock = pygame.time.Clock()
while self.running:
self.p += 25
tickFPS = Clock.tick(self.fps)
pygame.display.set_caption("Press Esc to quit. FPS: %.2f" % (Clock.get_fps()))
self.screen.fill([self.p&0xFF,(255-self.p)&0xFF,255])
self.handleEvents()
self.raiseEvent("updateFrame")
pygame.display.flip()
def handleEvents(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.running = False
full code at: http://pastie.org/private/wm5vqq3f7xe0xlffy1fq
Try placing a call to pygame.event.pump() inside your mainloop (or handleEvents function)
No idea if this will help or not:
I realize the problem is with moving the window window, not with re-sizing the window.
Perhaps there is some similarity between moving and re-sizing?
I found this in the documentation on re-sizing:
Then the display mode is set, several events are placed on the pygame event queue. pygame.QUIT is sent when the user has requested the program to shutdown. The window will receive pygame.ACTIVEEVENT events as the display gains and loses input focus. If the display is set with the pygame.RESIZABLE flag, pygame.VIDEORESIZE events will be sent when the user adjusts the window dimensions. Hardware displays that draw direct to the screen will get pygame.VIDEOEXPOSE events when portions of the window must be redrawn.
and:
Note that when the user resizes the game window, pygame does not automatically update its internal screen surface. You must call set_mode() every time VIDEORESIZE is sent. This really should be more clear in the documentation.
Perhaps when moving the game window, something similar happens?