I have tested the start
option in find_closest
but it doesn’t work
as you said.
I hadn’t tried it, but I’m trying it now.
Here is the same example where i replaced the lines by circles intersecting. I want to ignore the blue circle. If I click at the intersection of the blue circle and the red circle, the blue circle is ignored thanks to the start option. However when i click outside of the circle but closer to the blue circle, find_closest
will return the blue circle. So, the start option only makes a difference when shapes are overlapping.
Interesting. And a bit weird.
The docs I cited actually say:
If start is specified, it names an item using a tag or id (if
by tag, it selects the bottom / first item in the display list
with the given tag). Instead of returning the topmost closest
item, this will return the topmost closest item that is below
start in the display list; if no such item exists, then it will
behave as if the start argument had not been specified. This
will only have an effect if halo is given.
I think the important bit is “if no such item exists, then it will
behave as if the start argument had not been specified”. I had not read
this closely, and assumed that it meant the next-closest object. But it
actually talks about “in the display list”, which is the order in which
items are rendered - probably the order in which they were made, by
default.
So in this mode, if the blue circle is closest, but it is the last
thing in the display list because it was made last, then “if no such
item exists, then it will behave as if the start argument had not been
specified”, giving the behaviour we see.
So I think you need a new method.
I suspect you will need to actually use find_all
to get a list of
widgets, exclude the widgets to be ignored, then sort the list using a
metric for the distance to the source point (in your example, the event
point).
So, sketched, and untested:
items = [ w for w in self.find_all() if w.id not in ignored_ids ]
items_by_distance = sorted(items, key=lambda w: distance_to(event.x, event.y, w))
closest = items_by_distance[0]
You will need to define a function distance_to(x,y,w)
to compute the
distance.
It might want to consider the shape of the widget w
. For example, the
distance to the closest point on a circle is the distance to its centre,
minus the radius of the circle, clipped to 0
if that’s a negative
value (you’re inside the circle).
For a rectangle it will be the distance to the nearest corner, again
unless you’re inside the rectangle. And so on.
This complication is why you’ll want a distinct function to compute
this.
Maybe I am using it wrong. I don’t undestand the point of having a
find_closest
method if it takes in account all the shapes on the
canvas. It seems like a really particular need to me. I think you
usually want to find the closest object of a certain type.
Well, that’s what you want. I do not know what was in the mind if the
tk author when this method was written. You can easily do the “of a
particular type” part by classifying the widgets according to your own
scheme (which might arbitrarily be “rectangles”, or blue things, or some
criterion expressed by a tag), and include/exclude those widgets.
This seems arbitrary enough to expect UI authors (yourself) to write
their own methods for that.
That said, the documented behaviour also seems a little niche, and may
reflect some draw order dependent situation in the author’s head at the
time.
Cheers,
Cameron Simpson cs@cskk.id.au