I'm drawing little circles on a canvas with these functions :
This is the function that will draw the circles :
class Fourmis:
def __init__(self, can, posx, posy, name, radius):
self.can = can
self.largeur_can = int(self.can.cget("width"))
self.hauteur_can = int(self.can.cget("height"))
self.posx = posx
self.posy = posy
self.name = name
self.radius = radius
self.ball1 = self.can.create_oval(self.posy, self.posx, self.posy+radius, self.posx+radius, outline=self.name, fill=self.name, width=2)
self.nx = randrange(-10,10,1)
self.nx /= 2.0
self.ny = randrange(-10,10,1)
self.ny /= 2.0
#self.can.bind("<Motion>", self.destruction, add="+")
self.statut = True
self.move()
def move(self):
if self.statut == True :
self.pos_ball = self.can.coords(self.ball1)
self.posx_ball = self.pos_ball[0]
self.posy_ball = self.pos_ball[1]
if self.posx_ball < 0 or (self.posx_ball + self.radius) > self.largeur_can:
self.nx = -self.nx
if self.posy_ball < 0 or (self.posy_ball + self.radius) > self.hauteur_can:
self.ny = -self.ny
self.can.move(self.ball1, self.nx, self.ny)
self.can.after(10, self.move)
this one creates the canvas and the circles :
class App(Frame):
def __init__(self):
self.root=Tk()
self.can=Canvas(self.root,width=800,height=600,bg="black")
self.can.pack()
self.create(50, "green")
self.create(50, "purple")
def mainloop(self):
self.root.mainloop()
def create(self, i, name):
for x in range(i):
self.x=Fourmis(self.can,100,400, name,0)
I call these lines to run the project :
jeu = App()
jeu.mainloop()
What is the correct way to execute self.create(50, "green") and self.create(50, "purple") in different threads?
I have tried the following, but could not get it to work.:
class FuncThread(threading.Thread):
def __init__(self, i, name):
self.i = i
self.name = name
threading.Thread.__init__(self)
def run(self):
App.create(self, self.i, self.name)
Is someone able to tell me how to run these threads?
When this functionality is needed, what you do is schedule the events you wish to perform by putting them in a queue shared by the threads. This way, in a given thread you specify that you want to run "create(50, ...)" by queueing it, and the main thread dequeue the event and perform it.
Here is a basic example for creating moving balls in a second thread:
import threading
import Queue
import random
import math
import time
import Tkinter
random.seed(0)
class App:
def __init__(self, queue, width=400, height=300):
self.width, self.height = width, height
self.canvas = Tkinter.Canvas(width=width, height=height, bg='black')
self.canvas.pack(fill='none', expand=False)
self._oid = []
self.canvas.after(10, self.move)
self.queue = queue
self.canvas.after(50, self.check_queue)
def check_queue(self):
try:
x, y, rad, outline = self.queue.get(block=False)
except Queue.Empty:
pass
else:
self.create_moving_ball(x, y, rad, outline)
self.canvas.after(50, self.check_queue)
def move(self):
width, height = self.width, self.height
for i, (oid, r, angle, speed, (x, y)) in enumerate(self._oid):
sx, sy = speed
dx = sx * math.cos(angle)
dy = sy * math.sin(angle)
if y + dy + r> height or y + dy - r < 0:
sy = -sy
self._oid[i][3] = (sx, sy)
if x + dx + r > width or x + dx - r < 0:
sx = -sx
self._oid[i][3] = (sx, sy)
nx, ny = x + dx, y + dy
self._oid[i][-1] = (nx, ny)
self.canvas.move(oid, dx, dy)
self.canvas.update_idletasks()
self.canvas.after(10, self.move)
def create_moving_ball(self, x=100, y=100, rad=20, outline='white'):
oid = self.canvas.create_oval(x - rad, y - rad, x + rad, y + rad,
outline=outline)
oid_angle = math.radians(random.randint(1, 360))
oid_speed = random.randint(2, 5)
self._oid.append([oid, rad, oid_angle, (oid_speed, oid_speed), (x, y)])
def queue_create(queue, running):
while running:
if random.random() < 1e-6:
print "Create a new moving ball please"
x, y = random.randint(100, 150), random.randint(100, 150)
color = random.choice(['green', 'white', 'yellow', 'blue'])
queue.put((x, y, random.randint(10, 30), color))
time.sleep(0) # Effectively yield this thread.
root = Tkinter.Tk()
running = [True]
queue = Queue.Queue()
app = App(queue)
app.create_moving_ball()
app.canvas.bind('<Destroy>', lambda x: (running.pop(), x.widget.destroy()))
thread = threading.Thread(target=queue_create, args=(queue, running))
thread.start()
root.mainloop()
Related
I am writing a script to store movements over a hexgrid using Tkinter. As part of this I want to use a mouse-click on a Tkinter canvas to first identify the click location, and then draw a line between this point and the location previously clicked.
Generally this works, except that after I've drawn a line, it become an object that qualifies for future calls off the find_closest method. This means I can still draw lines between points, but selecting the underlying Hex in the Hexgrid over times becomes nearly impossible. I was wondering if someone could help me find a solution to exclude particular objects (lines) from the find_closest method.
edit: I hope this code example is minimal enough.
import tkinter
from tkinter import *
from math import radians, cos, sin, sqrt
class App:
def __init__(self, parent):
self.parent = parent
self.c1 = Canvas(self.parent, width=int(1.5*340), height=int(1.5*270), bg='white')
self.c1.grid(column=0, row=0, sticky='nsew')
self.clickcount = 0
self.clicks = [(0,0)]
self.startx = int(20*1.5)
self.starty = int(20*1.5)
self.radius = int(20*1.5) # length of a side
self.hexagons = []
self.columns = 10
self.initGrid(self.startx, self.starty, self.radius, self.columns)
self.c1.bind("<Button-1>", self.click)
def initGrid(self, x, y, radius, cols):
"""
2d grid of hexagons
"""
radius = radius
column = 0
for j in range(cols):
startx = x
starty = y
for i in range(6):
breadth = column * (1.5 * radius)
if column % 2 == 0:
offset = 0
else:
offset = radius * sqrt(3) / 2
self.draw(startx + breadth, starty + offset, radius)
starty = starty + 2 * (radius * sqrt(3) / 2)
column = column + 1
def draw(self, x, y, radius):
start_x = x
start_y = y
angle = 60
coords = []
for i in range(6):
end_x = start_x + radius * cos(radians(angle * i))
end_y = start_y + radius * sin(radians(angle * i))
coords.append([start_x, start_y])
start_x = end_x
start_y = end_y
hex = self.c1.create_polygon(coords[0][0], coords[0][1], coords[1][0], coords[1][1], coords[2][0],
coords[2][1], coords[3][0], coords[3][1], coords[4][0], coords[4][1],
coords[5][0], coords[5][1], fill='black')
self.hexagons.append(hex)
def click(self, evt):
self.clickcount = self.clickcount + 1
x, y = evt.x, evt.y
tuple_alfa = (evt.x, evt.y)
self.clicks.append(tuple_alfa)
if self.clickcount >= 2:
start = self.clicks[self.clickcount - 1]
startx = start[0]
starty = start[1]
self.c1.create_line(evt.x, evt.y, startx, starty, fill='white')
clicked = self.c1.find_closest(x, y)[0]
print(clicked)
root = tkinter.Tk()
App(root)
root.mainloop()
New to programming. Working on a simple pong clone. Started the ball but want to make sure all sides of the window (500x500) will have the ball bounce off of it. How could I do this? Thanks!
P.S. This is my current code if needed.
import threading
import random
import time
import string
import os.path
from random import randint
from tkinter import *
class Pong:
Title = 'Pong'
Size = '500x500'
class Ball:
def __init__(self,canvas,x1,y1,x2,y2):
self.x1 =x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.canvas = canvas
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="black")
def move_ball(self):
deltax = randint(0,5)
deltay = randint(0,5)
self.canvas.move(self.ball,deltax,deltay)
self.canvas.after(50,self.move_ball)
def PongGame():
print("Moved to PongGame.")
ball1 = Ball(canvas,10,10,30,30)
ball1.move_ball()
def titleButtonClicked(event):
print("Title screen button clicked.")
btn.pack_forget()
btn.place(x=600,y=600)
msg.pack_forget()
PongGame()
root = Tk()
root.geometry(Pong.Size)
root.title(Pong.Title)
root.resizable(False,False)
msg = Label(root, text = Pong.Title, font = ("", 50))
msg.pack()
canvas = Canvas(root, width = 500, height = 500)
canvas.pack()
btn=Button(root, text = "Start")
btn.bind('<Button-1>', titleButtonClicked)
btn.place(x=220,y=300)
root.mainloop()
Collisions are not trivial; the simplest is to reverse the x or the y velocity after checking which edge of the bounding box of the ball intersects with the boundaries of the canvas.
Maybe something like this:
import random
import tkinter as tk
WIDTH, HEIGHT = 500, 500
class Ball:
radius = 10
spawn_center = (250, 100)
def __init__(self, canvas):
self.canvas = canvas
self.id = None
self.create_ball()
self.velocity = None
self.assign_random_velocity()
self.keep_moving = True
self.move()
def create_ball(self):
xc, yc = self.spawn_center
x0, y0, = xc - self.radius, yc + self.radius
x1, y1, = xc + self.radius, yc - self.radius
self.id = self.canvas.create_oval(x0, y0, x1, y1)
def assign_random_velocity(self):
dx = random.randrange(1, 5) * random.choice((1, -1))
dy = random.randrange(1, 5) * random.choice((1, -1))
self.velocity = (dx, dy)
def move(self):
if self.keep_moving is None:
return
self.check_collision()
self.canvas.move(self.id, *self.velocity)
self.keep_moving = self.canvas.after(10, self.move)
def cancel_move(self):
if self.keep_moving is not None:
self.canvas.after_cancel(self.keep_moving)
self.keep_moving = None
def check_collision(self):
x0, y0, x1, y1 = self.canvas.coords(self.id)
dx, dy = self.velocity
if x0 < 0:
x0 = 0
dx = -dx
elif x1 > WIDTH:
x1 = WIDTH
dx = -dx
if y0 < 0:
y0 = 0
dy = -dy
elif y1 > HEIGHT:
y1 = HEIGHT
dy = -dy
self.velocity = dx, dy
class PongBoard(tk.Canvas):
def __init__(self, master):
self.master = master
super().__init__(self.master)
self.ball = None
self.spawn_new_ball()
def spawn_new_ball(self):
if self.ball is not None:
self.ball.cancel_move()
self.delete(self.ball.id)
self.ball = Ball(self)
root = Tk()
root.geometry(f'{WIDTH}x{HEIGHT+20}')
board = PongBoard(root)
new_ball_btn = tk.Button(root, text='spawn new ball', command=board.spawn_new_ball)
board.pack(expand=True, fill=tk.BOTH)
new_ball_btn.pack()
root.mainloop()
This will get you started, but you will have to implement the paddles, the paddles movement, the collision checking of the ball with the paddles, and keep the score by yourself.
I have constructed a lattice of particles with tkinter and now I would like each particle in the lattice to move according to a list of x and y coordinates that I read into an array from two text files. I have tried to create a class with a function inside that defines movement with the canvas.move function but I get the error TclError: wrong # coordinates: expected 0 or 4, got 400. How to get around this?
from Tkinter import *
import random
import time
import csv
tk = Tk()
N = 100
T = 500
canvas = Canvas(tk, width=100, height=100)
tk.title("Test")
canvas.pack()
n = 5
t = 10
step1 = []
step2 = []
textFile1 = open('/Users/francislempp/Desktop/major project/C++ programs/Molecular Dynamics 2D/Molecular_Dynamics_2D-gupnvjunowwmjcfiyoursdhzytow/Build/Products/Debug/motionX', 'r')
lines = textFile1.readlines()
for line in lines:
step1.append(line.split(" "))
textFile2 = open('/Users/francislempp/Desktop/major project/C++ programs/Molecular Dynamics 2D/Molecular_Dynamics_2D-gupnvjunowwmjcfiyoursdhzytow/Build/Products/Debug/motionY', 'r')
lines = textFile2.readlines()
for line in lines:
step2.append(line.split(" "))
def moves(xspeed, yspeed):
canvas.move(xspeed, yspeed)
class Ball:
def __init__(self, x, y, color):
self.x = x
self.y = y
self.shape = canvas.create_oval((x,y,x,y), fill = color)
def move(self):
canvas.move(self.shape, self.x, self.y)
pos = canvas.coords(self.shape)
if pos[3] >= 100 or pos[1] <= 0:
self.y = -self.y
if pos[2] > 100 or pos[0] <= 0:
self.x = -self.x
def delete(self):
canvas.delete(self.shape)
balls = []
for x in range(4,100,10):
for y in range(4,100,10):
#canvas.create_oval((x,y,x,y), fill='red')
Ball(x,y,"red")
tk.update()
for i in step1:
for j in step2:
Ball(i,j,"red")
Ball.move()
tk.update()
tk.mainloop()
You have to save items on list and use this list to move
I copy example from previous question
import tkinter as tk
import random
# --- functions ---
def move():
for point_id in points:
x = random.randint(-1, 1)
y = random.randint(-1, 1)
canvas.move(point_id, x, y)
root.after(100, move)
# --- main ---
points = []
root = tk.Tk()
canvas = tk.Canvas(root, width=100, height=100)
canvas.pack()
for x in range(4, 100, 10):
for y in range(4, 100, 10):
point_id = canvas.create_oval(x, y, x, y, fill="red")
points.append(point_id)
move()
root.mainloop()
EDIT: with your class but without data from file.
I use random instead of data from files to show how to create particles and move them.
Every ball moves with own speed/direction which change when touch border.
import tkinter as tk
import random
# --- classes ---
class Ball:
def __init__(self, x, y, speed_x, speed_y, color):
self.speed_x = speed_x
self.speed_y = speed_y
self.shape = canvas.create_oval((x, y, x, y), fill=color)
def move(self):
canvas.move(self.shape, self.speed_x, self.speed_y)
pos = canvas.coords(self.shape)
# change speed/direction when touch border
if pos[3] >= 100 or pos[1] <= 0:
self.speed_y = -self.speed_y
if pos[2] > 100 or pos[0] <= 0:
self.speed_x = -self.speed_x
def delete(self):
canvas.delete(self.shape)
# --- functions ---
def move():
for ball in all_balls:
ball.move()
root.after(100, move)
# --- main ---
root = tk.Tk()
canvas = tk.Canvas(root, width=100, height=100)
canvas.pack()
all_balls = []
for x in range(4, 100, 10):
for y in range(4, 100, 10):
offset_x = random.randint(-2, 2)
offset_y = random.randint(-2, 2)
all_balls.append(Ball(x, y, offset_x, offset_y, "red"))
move()
root.mainloop()
Why is my program slow while rendering 128 particles? I think that's not enough to get less than 30 fps.
All I do is rendering 128 particles and giving them some basic gravitation
on_draw function
def on_draw(self, time=None):
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
self.particles.append(Particle())
for particle in self.particles:
particle.draw()
if particle.is_dead:
self.particles.remove(particle)
Particle class
class Particle:
def __init__(self, **kwargs):
self.acceleration = Vector2(0, 0.05)
self.velocity = Vector2(random.uniform(-1, 1), random.uniform(-1, 0))
self.position = Vector2()
self.time_to_live = 255
self.numpoints = 50
self._vertices = []
for i in range(self.numpoints):
angle = math.radians(float(i) / self.numpoints * 360.0)
x = 10 * math.cos(angle) + self.velocity[0] + 300
y = 10 * math.sin(angle) + self.velocity[1] + 400
self._vertices += [x, y]
def update(self, time=None):
self.velocity += self.acceleration
self.position -= self.velocity
self.time_to_live -= 2
def draw(self):
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glPushMatrix()
glTranslatef(self.position[0], self.position[1], 0)
pyglet.graphics.draw(self.numpoints, GL_TRIANGLE_FAN, ('v2f', self._vertices), ('c4B', self.color))
glPopMatrix()
self.update()
#property
def is_dead(self):
if self.time_to_live <= 0:
return True
return False
#property
def color(self):
return tuple(color for i in range(self.numpoints) for color in (255, 255, 255, self.time_to_live))
I'm not overly happy about using GL_TRIANGLE_FAN because it's caused a lot of odd shapes when using batched rendering. So consider moving over to GL_TRIANGLES instead and simply add all the points to the object rather than leaning on GL to close the shape for you.
That way, you can easily move over to doing batched rendering:
import pyglet
from pyglet.gl import *
from collections import OrderedDict
from time import time, sleep
from math import *
from random import randint
key = pyglet.window.key
class CustomGroup(pyglet.graphics.Group):
def set_state(self):
#pyglet.gl.glLineWidth(5)
#glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
#glColor4f(1, 0, 0, 1) #FFFFFF
#glLineWidth(1)
#glEnable(texture.target)
#glBindTexture(texture.target, texture.id)
pass
def unset_state(self):
glLineWidth(1)
#glDisable(texture.target)
class Particle():
def __init__(self, x, y, batch, particles):
self.batch = batch
self.particles = particles
self.group = CustomGroup()
self.add_point(x, y)
def add_point(self, x, y):
colors = ()#255,0,0
sides = 50
radius = 25
deg = 360/sides
points = ()#x, y # Starting point is x, y?
prev = None
for i in range(sides):
n = ((deg*i)/180)*pi # Convert degrees to radians
point = int(radius * cos(n)) + x, int(radius * sin(n)) + y
if prev:
points += x, y
points += prev
points += point
colors += (255, i*int(255/sides), 0)*3 # Add a color pair for each point (r,g,b) * points[3 points added]
prev = point
points += x, y
points += prev
points += points[2:4]
colors += (255, 0, 255)*3
self.particles[len(self.particles)] = self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
class main(pyglet.window.Window):
def __init__ (self, demo=False):
super(main, self).__init__(800, 600, fullscreen = False, vsync = True)
#print(self.context.config.sample_buffers)
self.x, self.y = 0, 0
self.sprites = OrderedDict()
self.batches = OrderedDict()
self.batches['default'] = pyglet.graphics.Batch()
self.active_batch = 'default'
for i in range(1000):
self.sprites[len(self.sprites)] = Particle(randint(0, 800), randint(0, 600), self.batches[self.active_batch], self.sprites)
self.alive = True
self.fps = 0
self.last_fps = time()
self.fps_label = pyglet.text.Label(str(self.fps) + ' fps', font_size=12, x=3, y=self.height-15)
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
#self.bg.draw()
self.batches[self.active_batch].draw()
self.fps += 1
if time()-self.last_fps > 1:
self.fps_label.text = str(self.fps) + ' fps'
self.fps = 0
self.last_fps = time()
self.fps_label.draw()
self.flip()
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
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main(demo=True)
x.run()
Bare in mind, on my nVidia 1070 I managed to get roughly 35fps out of this code, which isn't mind blowing. But it is 1000 objects * sides, give or take.
What I've changed is essentially this:
self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
and in your draw loop, you'll do:
self.batch.draw()
Instead of calling Particle.draw() for each particle object.
What this does is that it'll send all the objects to the graphics card in one gigantic batch rather than having to tell the graphics card what to render object by object.
As #thokra pointed out, your code is more CPU intensive than GPU intensive.
Hopefully this fixes it or gives you a few pointers.
Most of this code is taking from a LAN project I did with a good friend of mine a while back:
https://github.com/Torxed/pyslither/blob/master/main.py
Because I didn't have all your code, mainly the main loop. I applied your problem to my own code and "solved " it by tweaking it a bit. Again, hope it helps and steal ideas from that github project if you need to. Happy new year!
I want to move around objects in python tkinter, specifically polygons. The problem is in is_click function. I can't seem to figure out how to determine if I clicked the object. The code is not 100% complete yet, and moving around needs still need to be finished but I need to figure this out for now. I also have similar class where you can move around Circles and Rectangles, where is_click function is working, but as polygon has from 3 to 4 coordinates it is a bit more complicated. Run The classes for yourself to see what they are doing.
My code for polygons:
import tkinter
class Polygon:
def __init__(self, ax, ay, bx, by, cx, cy, dx=None, dy=None, color=None):
self.ax = ax
self.ay = ay
self.bx = bx
self.by = by
self.cx = cx
self.cy = cy
self.dx = dx
self.dy = dy
self.color = color
def is_click(self, event_x, event_y):
pass
def paint(self, g):
self.g = g
if self.dx is None:
self.id = self.g.create_polygon(self.ax,self.ay,
self.bx,self.by,
self.cx,self.cy,
fill=self.color)
else:
self.id = self.g.create_polygon(self.ax,self.ay,
self.bx,self.by,
self.cx,self.cy,
self.dx,self.dy,
fill=self.color)
def move(self, d_ax=0, d_ay=0, d_bx=0, d_by=0, d_cx=0, d_cy=0, d_dx=None, d_dy=None):
if d_dx is None:
self.ax += d_ax
self.ay += d_ay
self.bx += d_bx
self.by += d_by
self.cx += d_cx
self.cy += d_cy
self.g.move(self.id, d_ax, d_ay, d_bx, d_by, d_cx, d_cy)
else:
self.ax += d_ax
self.ay += d_ay
self.bx += d_bx
self.by += d_by
self.cx += d_cx
self.cy += d_cy
self.dx += d_dx
self.dy += d_dy
self.g.move(self.id, d_ax, d_ay, d_bx, d_by, d_cx, d_cy, d_dx, d_dy)
class Tangram:
def __init__(self):
self.array = []
self.g = tkinter.Canvas(width=800,height=800)
self.g.pack()
#all objects
self.add(Polygon(500,300,625,175,750,300, color='SeaGreen'))
self.add(Polygon(750,50,625,175,750,300, color='Tomato'))
self.add(Polygon(500,175,562.6,237.5,500,300, color='SteelBlue'))
self.add(Polygon(500,175,562.5,237.5,625,175,562.5,112.5, color='FireBrick'))
self.add(Polygon(562.5,112.5,625,175,687.5,112.5, color='DarkMagenta'))
self.add(Polygon(500,50,500,175,625,50, color='Gold'))
self.add(Polygon(562.5,112.5,687.5,112.5,750,50,625,50, color='DarkTurquoise'))
#end of all objects
self.g.bind('<Button-1>', self.event_move_start)
def add(self, Object):
self.array.append(Object)
Object.paint(self.g)
def event_move_start(self, event):
ix = len(self.array) - 1
while ix >= 0 and not self.array[ix].is_click(event.x, event.y):
ix -= 1
if ix < 0:
self.Object = None
return
self.Object = self.array[ix]
self.ex, self.ey = event.x, event.y
self.g.bind('<B1-Motion>', self.event_move)
self.g.bind('<ButtonRelease-1>', self.event_release)
def event_move(self):
pass
def event_release(self):
pass
Tangram()
and code for Circle and Rectangle moving:
import tkinter, random
class Circle:
def __init__(self, x, y, r, color='red'):
self.x = x
self.y = y
self.r = r
self.color = color
def is_click(self, x, y):
return (self.x-x)**2+(self.y-y)**2 < self.r**2
def paint(self, g):
self.g = g
self.id = self.g.create_oval(self.x-self.r,self.y-self.r,
self.x+self.r,self.y+self.r,
fill=self.color)
def move(self, dx=0, dy=0):
self.g.delete(self.id)
self.x += dx
self.y += dy
self.paint(self.g)
class Rectangle:
def __init__(self, x, y, width, height, color='red'):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def is_click(self, x, y):
return self.x<=x<self.x+self.width and self.y<=y<self.y+self.height
def paint(self, g):
self.g = g
self.id = self.g.create_rectangle(self.x,self.y,
self.x+self.width,self.y+self.height,
fill=self.color)
def move(self, dx=0, dy=0):
self.x += dx
self.y += dy
self.g.move(self.id, dx, dy)
class Program:
def __init__(self):
self.array = []
self.g = tkinter.Canvas(bg='white', width=400, height=400)
self.g.pack()
for i in range(20):
if random.randrange(2):
self.add(Circle(random.randint(50, 350),random.randint(50, 350), 20, 'blue'))
else:
self.add(Rectangle(random.randint(50, 350),random.randint(50, 350), 40, 30))
self.g.bind('<Button-1>', self.event_move_start)
def add(self, Object):
self.array.append(Object)
Object.paint(self.g)
def event_move_start(self, e):
ix = len(self.array)-1
while ix >= 0 and not self.array[ix].is_click(e.x, e.y):
ix -= 1
if ix < 0:
self.Object = None
return
self.Object = self.array[ix]
self.ex, self.ey = e.x, e.y
self.g.bind('<B1-Motion>', self.event_move)
self.g.bind('<ButtonRelease-1>', self.event_release)
def event_move(self, e):
self.Object.move(e.x-self.ex, e.y-self.ey)
self.ex, self.ey = e.x, e.y
def event_release(self, e):
self.g.unbind('<B1-Motion>')
self.g.unbind('<ButtonRelease-1>')
self.Object = None
Program()
This is not the full anwser to your questions, but its too long for a comment. One way, that I would centrally consider, is to change/amend your code so that it uses find_closes method. With this method, you can determine which widget (i.e. polygon) is clicked very easily. As a quick prof of concept, you can make the following changes in the Tangram and ploygon class:
def event_move_start(self, event):
ix = len(self.array) - 1
while ix >= 0 and not self.array[ix].is_click(event, self.g, event.x, event.y): # add event and canvas to argumetns
In polygon:
def is_click(self, event, g, event_x, event_y):
widget_id = event.widget.find_closest(event.x, event.y)
print(widget_id)
g.move(widget_id, 1, 1) # just dummy move for a clicked widget
pass
This will print the ids of clicked widgets/polygons and slightly move the clicked polygon. This can be used to select which object was clicked, and having this id object, you can for moving or whatever.
p.s. the full code that I used to test it is here.
Hope this helps.