Changing the color of rectangles on click - python

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

Related

Change color with click event in 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()

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 !

Moving surface within smaller surface doesn't show previously hidden components

I'm coding some custom GUI objects for usage in pygame menus, while coding a scrollable box I hit an error.
This box works by moving a surface (which contains the components which are moved when scrolling) within a smaller surface which acts like a window to the confined surface. The surfaces mostly display correctly: the contents of the inner surface which are visible initially (the parts which fit within the window surface) display correctly, but when the inner surface is moved to reveal previously hidden components they are not displayed, the initial visible move correctly and are displayed when they return.
I think the issue is with the outer surface's clipping area thinking that only the already revealed components should be displayed and that the others are still hidden but I don't know.
The custom GUI components always have a Rect (returns the bounding rect for that component) and Draw (blits the component to the screen) functions.
Here is the code for the scroll area (and it's parent class):
class ScrollArea(BaseComponent):
"Implements a section of screen which is operable by scroll wheel"
def __init__(self,surface,rect,colour,components):
"""surface is what this is drawn on
rect is location + size
colour is colour of screen
components is iterable of components to scroll through (they need Draw and Rect functions), this changes the objects location and surface
"""
super().__init__(surface)
self.rect = pygame.Rect(rect)
self.colour = colour
self.components = components
self.Make()
def HandleEvent(self, event):
"Pass events to this; it enables the area to react to them"
if event.type == pygame.MOUSEBUTTONDOWN and self.rect.collidepoint(event.pos) and self._scroll_rect.h > self.rect.h:
if event.button == 4: self.scroll_y = min(self.scroll_y + 15,self._scroll_y_min)
if event.button == 5: self.scroll_y = max(self.scroll_y - 15,self._scroll_y_max)
def Make(self):
"Updates the area, activates any changes made"
_pos = self.rect.topleft
self._sub_surface = pygame.Surface(self.rect.size,pygame.SRCALPHA)
self.rect = pygame.Rect(_pos,self._sub_surface.get_rect().size)
self._sub_surface.unlock()#hopefully fixes issues
self._scroll_surf = pygame.Surface(self.rect.size)
self._scroll_rect = self._scroll_surf.get_rect()
scroll_height = 5
for component in self.components:
component.surface = self._scroll_surf
component.Rect().y = scroll_height
component.Rect().x = 5
component.Draw()
scroll_height += component.Rect().h + 5
self._scroll_rect.h = max(self.rect.h,scroll_height)
self.scroll_y = 0
self._scroll_y_min = 0
self._scroll_y_max = -(self._scroll_rect.h - self.rect.h)
def Draw(self):
"Draw the area and its inner components"
self._sub_surface.fill((255, 255, 255, 0))
self._sub_surface.blit(self._scroll_surf,(0,self.scroll_y))
pygame.draw.rect(self._sub_surface,self.colour,((0,0),self.rect.size),2)
self.surface.blit(self._sub_surface,self.rect.topleft)
def Rect(self):
"Return the rect of this component"
return self.rect
class BaseComponent:
def __init__(self,surface):
"surface is what this is drawn on"
self.surface = surface
def HandleEvent(self,event):
"Pass events into this for the component to react ot them"
raise NotImplementedError()
def Make(self):
"Redo calculations on how component looks"
raise NotImplementedError()
def Draw(self):
"Draw component"
raise NotImplementedError()
def ReDraw(self):
"Call Make then draw functions of component"
self.Make()
self.Draw()
def Rect(self):
"Return the rect of this component"
raise NotImplementedError()
To test this I used this code and a label component:
screen_width = 640
screen_height = 480
font_label = pygame.font.Font("freesansbold.ttf",22)
screen = pygame.display.set_mode((screen_width,screen_height))
grey = (125,125,125)
def LoadLoop():
#objects
scroll_components = []
for i in range(20):
scroll_components.append(Components.Label(screen,(0,0),str(i),font_label,grey))
scroll_area = Components.ScrollArea(screen,Components.CenterRect(screen_width/2,3*screen_height/16 + 120,300,200),grey,scroll_components)
clock = pygame.time.Clock()
running = True
while running:
#events
for event in pygame.event.get():
scroll_area.HandleEvent(event)
if event.type == pygame.QUIT:
running = False
pygame.quit()
exit()
#graphics
screen.fill(black)
scroll_area.Draw(components)
#render
pygame.display.update()
clock.tick(60)
This is the label component's code (it basically just prints text to screen with the location given as it's center):
class Label(BaseComponent):
"Class which implements placing text on a screen"
def __init__(self,surface,center,text,font,text_colour):
"""surface is what this is drawn on
center is the coordinates of where the text is to be located
text is the text of the label
font is the font of the label
text_colour is the text's colour
"""
super().__init__(surface)
self.center = center
self.text = text
self.font = font
self.text_colour = text_colour
self.Make()
def HandleEvent(self,event):
"Labels have no events they react to,\nso this does nothing"
def Make(self):
"(Re)creates the label which is drawn,\nthis must be used if any changes to the label are to be carried out"
self._text_surf = self.font.render(self.text, True, self.text_colour)
self._text_rect = self._text_surf.get_rect()
self._text_rect.center = self.center
def Draw(self):
"Draw the label , will not react to any changes made to the label"
self.surface.blit(self._text_surf,self._text_rect)
def Rect(self):
"Return the rect of this component"
return self._text_rect
This is the window produced by this code:
Before scrolling
After scrolling
I also did it with a different size of ScrollArea, one of the Labels was positioned through the bottom and it was cut in half, when scrolled the cut remained.
Please help.
Sidenote on conventions
First, a sidenote on conventions: class names should start with an uppercase letter, function and method names should be all lowercase.
They are conventions, so you are free to not follow them, but following the conventions will make your code more readable to people used to them.
The quick fix
The error is in the ScrollArea.Make() method. Look carefully at these two lines:
self._sub_surface = pygame.Surface(self.rect.size,pygame.SRCALPHA)
self._scroll_surf = pygame.Surface(self.rect.size)
self._sub_surface is the surface of the window of the scroll area. self._scroll_surf is the scrolling surface. The latter should be higher, but you set them to the same size (same width is fine, same height not).
Obviously when you loop over your component list to blit the Label, the ones which are outside self._sub_surface are also outside self._scroll_surf and hence are not blit at all. You should make self._scroll_surf higher. Try for example:
self._scroll_surf = pygame.Surface((self.rect.width, self.rect.height*10)
Better would be to estimate the proper height to contains all your labels, which should be scroll_height, but you calculate it later in the method, so you should figure how to do properly this part.
A general advice
In general, I think you have a design problem here:
for i in range(20):
scroll_components.append(Label(screen,(0,0),str(i),font_label,grey))
scroll_area = ScrollArea(screen, pygame.Rect(screen_width/2,3*screen_height/16 + 120,300,200),grey,scroll_components)
When you create each label, you pass the screen as the reference surface where the Draw method blits.
But these labels should be blitted on the scroll_surf of your ScrollArea. But you cannot do it because you have not instantiated yet the ScrollArea, and you cannot instantiate before the scroll area because you require the Labels to be passed as an argument.
And in fact in the ScrollArea.Make() method you overwrite each label surface attribute with the _scroll_surf Surface.
I think would be better to pass to ScrollArea a list of strings, and let the ScrollArea.__init__() method to create the labels.
It will look less patched and more coherent.

Mouse callback Function

I have the following code:
drawing = False # true if mouse is pressed
mode = True # if True, draw rectangle. Press 'm' to toggle to curve
ix,iy = -1,-1
# mouse callback function
def draw_circle(event,x,y,flags,param):
global ix,iy,drawing,mode
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
ix,iy = x,y
elif event == cv2.EVENT_MOUSEMOVE:
if drawing == True:
if mode == True:
cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
else:
cv2.circle(img,(x,y),20,(0,0,255),-1)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False
if mode == True:
cv2.rectangle(img,(ix,iy),(x,y),(0,255,0),-1)
else:
cv2.circle(img,(x,y),20,(0,0,255),-1)
I don't understand the use of xi and yi on the rectangle. There is a line which stated that xi,yi = x,y. Would it be the same? How would it draw rectangle? From what I understand, to draw a rectangle, there should be two sets of coordinates. I can't see which part in the line that shows that ix,iy will be different with x and y value.
Can anyone explain?
Its pretty straightforward here is what happens in steps:
the user clicks down - current mouse position x,y is stored as ix,iy and drawing is set to true
the user moves the mouse - if drawing is true and mode is true draws a rectangle using the saved ix,iy and the new current x,y (note that ix,iy does NOT equal x,y anymore since its only saved when the mouse button down triggers)
the user releases the mouse - drawing is set to false so the mouse can be moved without drawing anything and any current images are saved

Adding button to Python graphics.py window

I have a project which is making a simple breakout game with python. I am having a problem with making a button on a graphic window.
from graphics import*
win = GraphWin("win",200,150)
def buttons():
rectangle = Rectangle(Point(30,85),Point(60,55))
rectangle2 = Rectangle(Point(170,85),Point(140,55))
rectangle.setFill("blue")
rectangle2.setFill("blue")
rectangle.draw(win)
rectangle2.draw(win)
Here, How can I make those rectangles as buttons which represent the movements "Left",& "Right"??
Below is a simple solution for a red left button, a green right button and an "Exit" button to quit the program. I've rearranged the rectangles that represent the buttons such that P1 is the lower left corner and P2 is the upper right corner. This simplifies the test to see if the clicked point was inside the button. (You can make the code more sophisticated to remove this assumption.)
from graphics import *
WINDOW_WIDTH, WINDOW_HEIGHT = 200, 150
win = GraphWin("Simple Breakout", WINDOW_WIDTH, WINDOW_HEIGHT)
def buttons():
left = Rectangle(Point(25, 55), Point(55, 85)) # points are ordered ll, ur
right = Rectangle(Point(145, 55), Point(175, 85))
quit = Rectangle(Point(85, 116), Point(115, 146))
left.setFill("red")
right.setFill("green")
text = Text(Point(100, 133), "Exit")
text.draw(win)
left.draw(win)
right.draw(win)
quit.draw(win)
return left, right, quit
def inside(point, rectangle):
""" Is point inside rectangle? """
ll = rectangle.getP1() # assume p1 is ll (lower left)
ur = rectangle.getP2() # assume p2 is ur (upper right)
return ll.getX() < point.getX() < ur.getX() and ll.getY() < point.getY() < ur.getY()
left, right, quit = buttons()
centerPoint = Point(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2)
text = Text(centerPoint, "")
text.draw(win)
while True:
clickPoint = win.getMouse()
if clickPoint is None: # so we can substitute checkMouse() for getMouse()
text.setText("")
elif inside(clickPoint, left):
text.setText("left")
elif inside(clickPoint, right):
text.setText("right")
elif inside(clickPoint, quit):
break
else:
text.setText("")
win.close()
If you click the red or green buttons, you'll get "left" or "right" printed in the center of the window, otherwise no text appears:

Categories

Resources