I have a program with a canvas text element that moves around. I use tag_bind to detect when a mouse hovers and leaves the element. It fails when the element itself moves into or away from the mouse.
import mathclass Easing: @staticmethod def linear(t, b, c, d): return c*t/d + b @staticmethod def inSine(t, b, c, d): return -c * math.cos(t/d * (math.pi/2)) + c + bdef apply_easing(easing, t, beginning, destination, duration): #TODO: make this more efficient if possible return {key: easing(t, beginning[key], destination[key] - beginning[key], duration) for key in destination.keys()}class Tween: easing = Easing() _playing = [] def __init__(self, duration, subject, target, easing=easing.linear, options={"loop":"forever"}): self.duration = duration self.subject = subject self.target = target self.initial = {key: self.subject[key] for key in self.target.keys()} self.easing = easing self.loop = options.get("loop", "once") if options is not None else "once" self.playbackdirection = "forward" self.elapsed_time = 0 self.play() def play(self): if not self in Tween._playing: Tween._playing.append(self) def stop(self): Tween._playing.remove(self) def update(self, deltatime): if self.elapsed_time >= self.duration: if self.loop == "once": self.stop() if self.loop == "forever": self.playbackdirection = "backward" if self.elapsed_time <= 0: self.playbackdirection = "forward" if self.playbackdirection == "forward": self.elapsed_time += deltatime elif self.playbackdirection == "backward": self.elapsed_time -= deltatime result = apply_easing(self.easing, self.elapsed_time, self.initial, self.target, self.duration) self.subject.update(result) return resultfrom tkinter import *import timeWIDTH=640HEIGHT=480root = Tk()root.title("Minimal Example")root.geometry(f"{WIDTH}x{HEIGHT}")class InteractiveText: def __init__(self, canvas, x, y, content, default_fill='black'): self.position = {"x":x, "y":y} self.content = content self.canvas = canvas self.default_fill = default_fill self.text = canvas.create_text(self.position["x"], self.position["y"], text=self.content, font = ("monaco", 24), fill=default_fill) self.canvas.tag_bind(self.text, '<Enter>', lambda event: self.on_hover()) self.canvas.tag_bind(self.text, '<Leave>', lambda event: self.on_leave()) self.canvas.tag_bind(self.text, '<Button-1>', self.on_click) def update_position(self): self.canvas.coords(self.text, self.position["x"], self.position["y"]) def on_hover(self): self.canvas.itemconfig(self.text, fill="red") def on_leave(self): self.canvas.itemconfig(self.text, fill=self.default_fill) def on_click(self, event): print(self.content, "clicked!")class Scene: def __init__(self): self.canvas = Canvas(root, width=WIDTH, height=HEIGHT, bg="white") self.canvas.pack() self.canvas_items = [] def text(self, x, y, content): t = InteractiveText(self.canvas, x, y, content) self.canvas_items.append(t) return t def rect(self, x, y, w, h): r = MyRect(self.canvas, x, y, w, h) self.canvas_items.append(r) return rdef update(lastFrameTime): currentTime = time.time() for tw in Tween._playing: tw.update(currentTime - lastFrameTime) for obj in CURRENT_SCENE.canvas_items: obj.update_position() next_deltatime = int(round(lastFrameTime + 1/60*1000 - currentTime, 0)) root.after(next_deltatime, update, currentTime)root.after(0, update, time.time())mainmenu =Scene()CURRENT_SCENE = mainmenu# There are the buttons for the Main Menuplaytext = mainmenu.text(-100, 65, "Play game")quittext = mainmenu.text(WIDTH+150, 150, "Quit")Tween(4.99, playtext.position, {"x": WIDTH/2+25, "y":50}, Tween.easing.linear)Tween(4.99, quittext.position, {"x": WIDTH/2, "y":390}, Tween.easing.inSine)root.mainloop()I know the program is a little long for a SO quesiton but its mostly boilerplate. The important part is in the InteractiveText class, where I bind the mouse events to the canvas item. When the canvas item leaves the mouse behind on its own, it doesn't trigger a hover/leave event and thus doesn't trigger the functions on_leave and on_hover.
Expected hover to end after element coords changed so that the mouse was outside the element.