mirror of
https://github.com/python/cpython.git
synced 2026-07-25 11:22:45 -04:00
gh-43699: Defer the drag cursor in tkinter.dnd until the pointer moves (GH-152371)
The drag cursor is now changed only once the pointer starts moving past a small threshold rather than on the initial button press, so that a plain click no longer flashes it. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -40,10 +40,12 @@ class Source:
|
||||
|
||||
|
||||
class FakeEvent:
|
||||
def __init__(self, widget, num=1):
|
||||
def __init__(self, widget, num=1, x_root=0, y_root=0):
|
||||
self.num = num
|
||||
self.widget = widget
|
||||
self.x = self.y = self.x_root = self.y_root = 0
|
||||
self.x = self.y = 0
|
||||
self.x_root = x_root
|
||||
self.y_root = y_root
|
||||
|
||||
|
||||
class DndTest(AbstractTkTest, unittest.TestCase):
|
||||
@@ -111,10 +113,16 @@ class DndTest(AbstractTkTest, unittest.TestCase):
|
||||
handler.cancel()
|
||||
|
||||
def test_drag_cursor(self):
|
||||
# The drag cursor is not shown on the initial press, only once the
|
||||
# pointer moves past the threshold, so a plain click does not flash
|
||||
# it (gh-43699). The original cursor is restored afterwards.
|
||||
self.canvas['cursor'] = 'watch'
|
||||
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
|
||||
# The drag cursor is shown while dragging, the original restored after.
|
||||
self.assertEqual(handler.save_cursor, 'watch')
|
||||
self.assertEqual(str(self.canvas['cursor']), 'watch')
|
||||
handler.on_motion(FakeEvent(self.canvas, x_root=2)) # below threshold
|
||||
self.assertEqual(str(self.canvas['cursor']), 'watch')
|
||||
handler.on_motion(FakeEvent(self.canvas, x_root=20)) # past threshold
|
||||
self.assertEqual(str(self.canvas['cursor']), 'hand2')
|
||||
handler.cancel()
|
||||
self.assertEqual(str(self.canvas['cursor']), 'watch')
|
||||
|
||||
+17
-1
@@ -120,6 +120,10 @@ class DndHandler:
|
||||
|
||||
root = None
|
||||
|
||||
# The drag cursor is shown only once the pointer has moved this many
|
||||
# pixels from the initial press, so that a plain click does not flash it.
|
||||
threshold = 3
|
||||
|
||||
def __init__(self, source, event):
|
||||
if event.num > 5:
|
||||
return
|
||||
@@ -134,11 +138,12 @@ class DndHandler:
|
||||
self.target = None
|
||||
self.initial_button = button = event.num
|
||||
self.initial_widget = widget = event.widget
|
||||
self.dragging = False
|
||||
self.x_origin, self.y_origin = event.x_root, event.y_root
|
||||
self.release_pattern = "<B%d-ButtonRelease-%d>" % (button, button)
|
||||
self.save_cursor = widget['cursor'] or ""
|
||||
widget.bind(self.release_pattern, self.on_release)
|
||||
widget.bind("<Motion>", self.on_motion)
|
||||
widget['cursor'] = "hand2"
|
||||
|
||||
def __del__(self):
|
||||
root = self.root
|
||||
@@ -175,6 +180,17 @@ class DndHandler:
|
||||
if new_target is not None:
|
||||
new_target.dnd_enter(source, event)
|
||||
self.target = new_target
|
||||
self.update_cursor(x, y)
|
||||
|
||||
def update_cursor(self, x, y):
|
||||
# Show the drag cursor only once the pointer has actually started
|
||||
# moving past the threshold, so that a plain click does not flash it.
|
||||
if not self.dragging:
|
||||
if (abs(x - self.x_origin) <= self.threshold and
|
||||
abs(y - self.y_origin) <= self.threshold):
|
||||
return
|
||||
self.dragging = True
|
||||
self.initial_widget['cursor'] = "hand2"
|
||||
|
||||
def on_release(self, event):
|
||||
self.finish(event, 1)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
The drag cursor in :mod:`tkinter.dnd` is now changed only once the pointer
|
||||
starts moving rather than on the initial button press, so that a plain
|
||||
click no longer flashes it.
|
||||
Reference in New Issue
Block a user