Change color with click event in python - python

I have generated a circle with graphic.py. I want the color of the circle to change with a mouse click. How can I create a mouse event for this?
def draw_circle():
#make green circle
win = GraphWin('Traffic Lights', 400,400)
win.setBackground(color_rgb(211,211,211))
pt = Point(200,200)
cir = Circle(pt,50)
cir.setFill('Green')
cir.draw(win)
win.getMouse
initial()
def click:
#action to take place
# when mouse cicked
add_mouse_click_handler(click)
main()

Related

PyGame colliders don't scale with window

I should point out that I'm a beginner with PyGame. I have made a program that displays some simple graphics on the screen using PyGame. It blits every graphic on a dummy surface and the dummy surface gets scaled and blit to a 'real' surface that gets displayed on the screen in the end. This allows the program to have a resizable window without messing the graphics and UI.
I have also made my own 'Button' class that allows me to draw clickable buttons on the screen. Here it is:
import pygame
pygame.font.init()
dfont = pygame.font.Font('font/mfdfont.ttf', 64)
#button class button(x, y, image, scale, rot, text_in, color, xoff, yoff)
class Button():
def __init__(self, x, y, image, scale = 1, rot = 0, text_in = '', color = 'WHITE', xoff = 0, yoff = 0):
self.xoff = xoff
self.yof = yoff
self.x = x
self.y = y
self.scale = scale
width = image.get_width()
height = image.get_height()
self.image = pygame.transform.rotozoom(image, rot, scale)
self.text_in = text_in
self.text = dfont.render(self.text_in, True, color)
self.text_rect = self.text.get_rect(center=(self.x +width/(2/scale) + xoff, self.y + height/(2/scale) + yoff))
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
self.clicked = False
def draw(self, surface):
action = False
#get mouse position
pos = pygame.mouse.get_pos()
#check mouseover and clicked conditions
if self.rect.collidepoint(pos):
if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False:
self.clicked = True
action = True
if pygame.mouse.get_pressed()[0] == 0:
self.clicked = False
#draw button on screen
surface.blit(self.image, (self.rect.x, self.rect.y))
surface.blit(self.text, self.text_rect)
return action
When I need to draw one of these buttons on the screen I firstly define it like this:
uparrow = button.Button(128, 1128, arrow_img, 0.5, 0, "SLEW", WHITE, 0, 128)
Then I call it's draw function like this:
if uparrow.draw(screen):
print('UP')
It works reasonably well when drawing it to a surface that doesn't get scaled. This is the problem. When I scale the dummy surface that it gets drawn to, the button's image and text scale just fine but it's collider does not. So when I click on it nothing happens, but if I click on the location of the screen the button would have been on the unscaled dummy surface it works.
Just for context, the dummy surface is 2048x1024 and the 'real' surface is much smaller, starting at 1024x512 and going up and down however the user resizes the window. The game maintains a 2:1 aspect ratio though, so any excess pixels in the game window are black. You can see this in the screenshot below:
Above is a screenshot of the game window. You can see the 'NORM' button at the top of the game screen, and the red box that roughly represents the same 'NORM' button's actual collider. It's basically where it would be on the dummy surface.
(I have previously posted a question on somewhat the same problem as this one, but at that time I didn't know the colliders actually worked and I thought my clicks just didn't register on the buttons, which is not the case).
I'd like to know what part of my button class causes this and how it should be refactored to fix this issue. Alternatively, if you think it's caused by my double surface rendering technique or anything else really, please do point me in the right direction.
In your setup you draw the buttons on an surface, scale the surface and blit that surface on the display. So you do something like the following:
dummy_surface = pygame.Surface((dummy_width, dummy_height)
while True:
# [...]
scaled_surface = pygame.transform.scale(dummy_surface, (scaled_width, scaled_height))
screen.blit(scaled_surface, (offset_x, offset_y))
For click detection to work on the original buttons, you must scale the position of the mouse pointer by the reciprocal scale and shift the mouse position by the inverse offset:
def draw(self, surface):
action = False
# get mouse position
pos = pygame.mouse.get_pos()
scale_x = scaled_width / dummy_surface.get_width()
scale_y = scaled_height / dummy_surface.get_height()
mx = int((pos[0] - offset_x) / scale_x)
my = int((pos[1] - offset_y) / scale_y)
pos = (mx, my)
# [...]

Changing the color of rectangles on click

Using the graphics.py library, Write a program to draw 3 rectangles on the screen. If the user clicks the left mouse button inside one of the rectangles change its color. Feel free to use whatever colors you like. If the user clicks inside the graphics window, but outside one of the three rectangles, close the window and exit the program.
When you click on a rectangle it changes colors then you should be able to click on the next and change its color and so on until you click outside. But as soon as I click the first rectangle and it changes colors it ends the program with object already drawn error.
Result: if self.canvas and not self.canvas.isClosed(): raise GraphicsError(OBJ_ALREADY_DRAWN)
Here is the code:
win3 = graphics.GraphWin("",500,500)
rect1 = graphics.Rectangle(graphics.Point(150,100),graphics.Point(350,200))
rect2 = graphics.Rectangle(graphics.Point(150,220),graphics.Point(350,320))
rect3 = graphics.Rectangle(graphics.Point(150,340),graphics.Point(350,440))
rect1.draw(win3)
rect2.draw(win3)
rect3.draw(win3)
def inside(point, rectangle): # checks if the click is inside of the rectangle
ll = rectangle.getP1() # lower left corner of the rectangle
ur = rectangle.getP2() # upper right corner of the rectangle
return ll.getX() < point.getX() < ur.getX() and ll.getY() < point.getY() < ur.getY()
outside = False
while outside == False:
click = win3.getMouse()
if inside(click,rect1):
rect1.setFill("Red")
rect1.draw(win3)
elif inside(click,rect2):
rect2.setFill("Green")
rect2.draw(win3)
elif inside(click,rect3):
rect3.setFill("Blue")
rect3.draw(win3)
else:
win3.close()
outside = True

Converting scroll bar coordinates to mouse coordinates

I'm trying to make a scroll bar and at the moment, the scroll works by changing the coordinates when blitting (as opposed to changing the actual rect coordinates). This means that rect collisions for buttons do not work when they are moved. I am attempting to combat this by calculating the percentage that the scroll bar has scrolled, converting that to some multiplier or screen coordinate, and then getting the mouse position.
Some notes:
Self.bar is the actual slider handle (the small thing you use to scroll)
Self.rect is the entire slider, and its height is equal to screen height
Self.total_h is the total height that the scroll bar needs to scroll, for example if it needed to scroll to 2x the screen height then total_h would equal screen_height * 2.
Some code I have tried so far:
# Calculate the distance between the top of the handle and the top of the overall bar and divide by the handle height
# (shortened from ((self.bar.rect.top - self.rect.top) / self.rect.h) * (self.rect.h / self.bar.rect.h) which makes more intuitive sense.
self.scroll_percent = ((self.bar.rect.top - self.rect.top) / self.bar.rect.h)
# These all do not work:
# pos_y = pg.mouse.get_pos()[1] * self.scroll_percent
# pos_y = pg.mouse.get_pos()[1] * (self.total_h / self.scroll_percent)
# pos_y = (self.total_h / self.scroll_percent) * pg.mouse.get_pos()[1]
# etc
The logic just doesn't make sense to me, and I've got no idea how to do this. To clarify, my goal is to allow the user to scroll the screen using a scroll bar, and depending on the scroll bar's position, we change the mouse pos accordingly.
I don't really understand why you bother with some percentage ? If I understood correctly you are only scrolling up and down so the only thing you need to know is the y offset, which is 0 when the scroll bar is at the top and then it is just the y value at which you are blitting your surface. So simply remove the y offset to your mouse y when you check for collision.
Maybe I missed something ?
If I understood corretly, here is an simple example of what to do :
(I didn't recreate the scroll bar since you said you've got this part working. I just made the surface go up automatically. I'm sure you will figure out a way to integrate this solution to your own code)
# General import
import pygame as pg
import sys
# Init
pg.init()
# Display
screen = pg.display.set_mode((500, 500))
FPS = 30
clock = pg.time.Clock()
# Surface declaration
drawing_surface = pg.Surface((screen.get_width(), screen.get_height() * 2))
drawing_surface.fill((255,0,0))
drawing_surface_y = 0
# Button
test_btn = pg.Rect(20, 400, 100, 40)
# Main functions
def update():
global drawing_surface_y
drawing_surface_y -= 1
def draw():
# Clear the screen
screen.fill((0,0,0))
# Render the button
pg.draw.rect(drawing_surface, (0,0,255), test_btn)
# Blit the drawing surface
screen.blit(drawing_surface, (0, drawing_surface_y))
def handle_input():
for evt in pg.event.get():
if evt.type == pg.QUIT:
exit()
if evt.type == pg.MOUSEBUTTONDOWN:
if evt.button == 1:
on_click()
def on_click():
mx, my = pg.mouse.get_pos()
if test_btn.collidepoint(mx, my - drawing_surface_y):
print("Test button has been clicked")
def exit():
pg.quit()
sys.exit()
# Other functions
# Main loop
if __name__ == "__main__":
while True:
handle_input()
update()
draw()
pg.display.update()
clock.tick(FPS)
Test this code and let me know if it answers your question !

How can i build this zoom screen at the side?

So, i am building a physics simulation. And whenever the left mouse button is pressed, the player can control the height the ball is at(by dragging). What i want to build is: whenever the player is dragging the ball to reposition it, a screen will appear at the side, and inside it i want a real time zoom of where the ball is(sorry if i didnt explain it too well, i think the picture explains it better).
Just to clarify, I want it all in one window only
Can anyone help me? :)
It's not that complicated. Just draw your stuff on a seperate Surface, then use subsurface and the transform module. Here's an example:
import pygame
pygame.init()
def main():
# we'll not draw an the display surface directly
screen = pygame.display.set_mode((800, 600))
# but we'll draw everything on this surface
main = screen.copy()
# this surface is the zoom window
zoom = pygame.Surface((400, 300))
# the ball and its movement vector
ball = pygame.Rect(550, 100, 40, 40)
ball_v = pygame.Vector2(0, 0)
dt = 0
clock = pygame.time.Clock()
while True:
pressed = pygame.mouse.get_pressed()
for e in pygame.event.get():
if e.type == pygame.QUIT:
return
if pressed[0]:
# dragging the ball if mouse button is pressed
ball_v = pygame.Vector2(0, 0)
ball.center = pygame.mouse.get_pos()
else:
# if not, we apply the gravity
ball_v += pygame.Vector2(0, 0.2)
# move the ball
ball.move_ip(ball_v)
# but keep it on the screen
ball.clamp_ip(main.get_rect())
# draw the main surface
main.fill(pygame.Color('white'))
w = 10
for y in range(main.get_rect().height):
pygame.draw.rect(main, pygame.Color('lightgray'), pygame.Rect(500, y, w, 1))
w = (w - 1) % 20
pygame.draw.circle(main, pygame.Color('orange'), ball.center, 20)
screen.blit(main, (0, 0))
# if the mouse button is pressed, draw the zoom window
if pressed[0]:
# the area of the main surface that should be drawn in the zoom window
rect = pygame.Rect(0, 0, 200, 150)
# center it on the ball
rect.center = ball.center
# ensure it's on the screen
rect.clamp_ip(main.get_rect())
# grab the part from the main surface
sub = main.subsurface(rect)
# scale it
zoom.blit(pygame.transform.scale2x(sub), (0, 0))
pygame.draw.rect(zoom, pygame.Color('black'), zoom.get_rect(), 4)
screen.blit(zoom, (25, 25))
pygame.display.flip()
clock.tick(120)
main()

Pygame surface location seems to be different than what it is on the screen?

I am trying to make a virtual phone kind of program with pygame, just to experiment with it, but i ran into a problem. I have loaded an image and then blitted it to the bottom left of the screen. But when i do print(imagename.get_rect()) it prints out a location at 0, 0 of the screen.
also the mouse collides with it there. what am i not understanding?
def aloitus(): #goes back to the home screen
cls() #clears the screen
tausta = pygame.image.load("./pyhelin.png") #load background
tausta = pygame.transform.scale(tausta, (360, 640)) #scale it
screen.blit(tausta, (0, 0)) #blit it
alapalkki = pygame.Surface((600, 100)) #ignore
alapalkki.set_alpha(120)
alapalkki.fill(blonk)
screen.blit(alapalkki, (0, 560))
global messenger #this is the thing!
messenger = pygame.image.load("./mese.png").convert_alpha() #load image
print(messenger.get_rect()) #print its location
messenger = pygame.transform.scale(messenger, (60,65)) #scale it to the correct size
screen.blit(messenger, (10, 570)) # blit on the screen
update() #update screen
aloitus() # at the start of the program, go to the home screen
while loop: #main loop
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
# Set the x, y postions of the mouse click
x, y = event.pos
if messenger.get_rect().collidepoint(x, y): #messenger is the image defined earlier
#do things
print("hi")
Expected result would be, when clicking on the image "hi" would be printed.
actual result is that when topleft corner is clicked hi is printed.
get_rect returns a pygame.Rect with the default top left coordinates (0, 0). You need to set the coordinates afterwards or pass them as a keyword argument to get_rect.
I suggest assigning the rect to another variable and set the coords in one of these ways:
messenger_rect = messenger.get_rect()
messenger_rect.x = 100
messenger_rect.y = 200
# or
messenger_rect.topleft = (100, 200)
# or pass the coords as an argument to `get_rect`
messenger_rect = messenger.get_rect(topleft=(100, 200))
There are even more rect attributes to which you can assign the coordinates:
x,y
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery

Categories

Resources