Based on a simple Line drawn using canvas.create_line
, such as this image here, three dots, based on the start, middle, and end of the line are created, example here.
I want to be able to click and drag the part of the Line where there is a dot, so I made the following MRE:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400)
canvas.pack()
line = canvas.create_line(50, 50, 350, 50, width=3)
start_x, start_y, end_x, end_y = canvas.coords(line)
start_dot = canvas.create_oval(start_x - 5, start_y - 5, start_x + 5, start_y + 5, fill='blue', tags=('start_dot',))
end_dot = canvas.create_oval(end_x - 5, end_y - 5, end_x + 5, end_y + 5, fill='red', tags=('end_dot',))
mid_x = (start_x + end_x) / 2
mid_y = start_y
mid_dot = canvas.create_oval(mid_x - 5, mid_y - 5, mid_x + 5, mid_y + 5, fill='green', tags=('mid_dot',))
def update_line(event):
global end_x, end_y, start_y, start_x, mid_y, mid_x, curve
x, y = event.x, event.y
item = canvas.find_withtag(tk.CURRENT)[0]
if item == start_dot:
canvas.coords(start_dot, x - 5, y - 5, x + 5, y + 5)
canvas.coords(line, x, y, end_x, end_y)
elif item == end_dot:
canvas.coords(end_dot, x - 5, y - 5, x + 5, y + 5)
canvas.coords(line, start_x, start_y, x, y)
elif item == mid_dot:
dx = x - (start_x + end_x) // 2
dy = y - (start_y + end_y) // 2
canvas.coords(mid_dot, mid_x + dx - 5, mid_y + dy - 5, mid_x + dx + 5, mid_y + dy + 5)
canvas.coords(line, start_x, start_y, mid_x + dx, mid_y + dy, end_x, end_y)
else:
return
test = canvas.coords(line)
if len(test) == 4:
start_x, start_y, end_x, end_y, = canvas.coords(line)
mid_x = (start_x + end_x) / 2
mid_y = (start_y + end_y) / 2
canvas.coords(mid_dot, mid_x - 5, mid_y - 5, mid_x + 5, mid_y + 5)
else:
pass
canvas.tag_bind('start_dot', '<B1-Motion>', update_line)
canvas.tag_bind('end_dot', '<B1-Motion>', update_line)
canvas.tag_bind('mid_dot', '<B1-Motion>', update_line)
root.mainloop()
This somewhat works, at least for only moving the start and end correctly. The part that doesn't work yet is updating the Line correctly after moving the middle dot and then moving either the start or end dot of the Line. Here is a gif showcasing the problem, and here the expected behavior (the rectangle can be ignored).
I noticed canvas.coords
output 5 values instead of the usual 4 that I'm used to.
How can I make the above works with the middle path/dot too?
P.S.:I use the term "polyline" but I'm not sure if this is what I'm actually doing here (it does look similar when looking on google image). Feel free to mention a better fitting term for this if there is one.
Thanks to jasonharper's comment, I managed to find a way to solve this:
Your life will be so much easier if you draw the line with three points always - rather than having to special-case having two or three points based on whether the middle point had ever been moved (which is what it would take to fix your current approach). – jasonharper Apr 8 at 3:11
First a solution that does not directly follow the wanted behavior (works for all three path/dot, but does not move the entire line when using only start and end dot):
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400)
canvas.pack()
line = canvas.create_line(50, 50, 350, 50, width=3)
start_x, start_y, end_x, end_y = canvas.coords(line)
start_dot = canvas.create_oval(start_x - 5, start_y - 5, start_x + 5, start_y + 5, fill='blue', tags=('start_dot',))
end_dot = canvas.create_oval(end_x - 5, end_y - 5, end_x + 5, end_y + 5, fill='red', tags=('end_dot',))
mid_x = (start_x + end_x) / 2
mid_y = start_y
mid_dot = canvas.create_oval(mid_x - 5, mid_y - 5, mid_x + 5, mid_y + 5, fill='green', tags=('mid_dot',))
def update_line(event):
global start_x, start_y, end_x, end_y, mid_x, mid_y
x, y = event.x, event.y
item = canvas.find_withtag(tk.CURRENT)[0]
if item == start_dot:
start_x, start_y = x, y
elif item == end_dot:
end_x, end_y = x, y
elif item == mid_dot:
mid_x, mid_y = x, y
canvas.coords(line, start_x, start_y, mid_x, mid_y, end_x, end_y)
canvas.coords(start_dot, start_x - 5, start_y - 5, start_x + 5, start_y + 5)
canvas.coords(end_dot, end_x - 5, end_y - 5, end_x + 5, end_y + 5)
canvas.coords(mid_dot, mid_x - 5, mid_y - 5, mid_x + 5, mid_y + 5)
canvas.tag_bind('start_dot', '<B1-Motion>', update_line)
canvas.tag_bind('end_dot', '<B1-Motion>', update_line)
canvas.tag_bind('mid_dot', '<B1-Motion>', update_line)
root.mainloop()
And now, a solution that both move the entire line at first (only using start and end dots), and then move it partially after the middle dot have been used at least once:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400)
canvas.pack()
line = canvas.create_line(50, 50, 350, 50, width=3)
start_x, start_y, end_x, end_y = canvas.coords(line)
start_dot = canvas.create_oval(start_x - 5, start_y - 5, start_x + 5, start_y + 5, fill='blue', tags=('start_dot',))
end_dot = canvas.create_oval(end_x - 5, end_y - 5, end_x + 5, end_y + 5, fill='red', tags=('end_dot',))
mid_x = (start_x + end_x) / 2
mid_y = start_y
mid_dot = canvas.create_oval(mid_x - 5, mid_y - 5, mid_x + 5, mid_y + 5, fill='green', tags=('mid_dot',))
has_middle_dot_moved = False
def update_line(event):
global end_x, end_y, start_y, start_x, mid_y, mid_x, has_middle_dot_moved
x, y = event.x, event.y
item = canvas.find_withtag(tk.CURRENT)[0]
if not has_middle_dot_moved:
if item == start_dot:
canvas.coords(start_dot, x - 5, y - 5, x + 5, y + 5)
canvas.coords(line, x, y, end_x, end_y)
elif item == end_dot:
canvas.coords(end_dot, x - 5, y - 5, x + 5, y + 5)
canvas.coords(line, start_x, start_y, x, y)
elif item == mid_dot:
has_middle_dot_moved = True
start_x, start_y, end_x, end_y, = canvas.coords(line)
mid_x = (start_x + end_x) / 2
mid_y = (start_y + end_y) / 2
canvas.coords(mid_dot, mid_x - 5, mid_y - 5, mid_x + 5, mid_y + 5)
else:
if item == start_dot:
start_x, start_y = x, y
elif item == end_dot:
end_x, end_y = x, y
elif item == mid_dot:
mid_x, mid_y = x, y
canvas.coords(line, start_x, start_y, mid_x, mid_y, end_x, end_y)
canvas.coords(start_dot, start_x - 5, start_y - 5, start_x + 5, start_y + 5)
canvas.coords(end_dot, end_x - 5, end_y - 5, end_x + 5, end_y + 5)
canvas.coords(mid_dot, mid_x - 5, mid_y - 5, mid_x + 5, mid_y + 5)
canvas.tag_bind('start_dot', '<B1-Motion>', update_line)
canvas.tag_bind('end_dot', '<B1-Motion>', update_line)
canvas.tag_bind('mid_dot', '<B1-Motion>', update_line)
root.mainloop()