-
Notifications
You must be signed in to change notification settings - Fork 31
Add a click dispatcher and typing extensions #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Pinging @viljarjf because I think he will appreciate addition of |
Disambiguation on
|
… via `instamatic._typing`
There are 2 type[people] in this world, those who enjoy types and those who mypy 😅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me for the most part, just wondering if the NoOverwriteDict
is necessary.
"""Manages `ClickEvent` distribution across `ClickListeners`.""" | ||
|
||
def __init__(self): | ||
self.listeners: NoOverwriteDict[str, ClickListener] = NoOverwriteDict() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the NoOverwriteDict()
necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not but in my opinion it makes things more clear, see comment below.
) -> ClickListener: | ||
"""Convenience method that adds and returns a new `ClickListener`""" | ||
listener = ClickListener(name, callback) | ||
self.listeners[name] = listener |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the logic for NoOverwriteDict
could instead be here:
self.listeners[name] = listener | |
if name in self.listeners: | |
raise KeyError(f'Listener "{name}" already exists and cannot be overwritten.') | |
self.listeners[name] = listener |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It absolutely could, but then let me put the question the other way around - why would you write:
if name in self.listeners:
raise KeyError(f'Listener "{name}" already exists and cannot be overwritten.')
self.listeners[name] = listener
each time if you can just compartmentalize that don't-overwrite feature into a simple class and use anywhere you don't want overwriting, simply by writing:
self.listeners[name] = listener # and the class assets key is not overwritten
That's kind of the principle of OOP. I personally think a few of existing instamatic dicts (i.e. module, frame, camera) should never allow overwriting and should be replaced with a NoOverwriteDict
to avoid potential issues.
All in all its a matter of preference, and in my opinion this makes the logic more clear, as the "don't overwrite keys" is not very relevant to the listener, so why focus on that there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree. I'm just wondering if this isn't a case of premature optimization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will merge with the NoOverwriteDict
for the time being, I have useful objects in mind for the instamatic._collections
planned. And if they fail, it can be drop-in replaced with a (more error prone) standard dict.
Click dispatcher
Instamatic GUI is build out of multiple modules that are almost completely independent, yet can communicate thanks to the ingenuity of the
AppLoader
. This class does not onlyload
modules but also allows them to access each other viaget_module()
. Currently, this functionality is used only in three places (to define GUIself.module_io
, tosave_image
, to dynamically modify theExperimentalSED
). Here I suggest another use: to add clicking interactions with theVideoStreamFrame
.This PR adds a new
ClickDispatcher
to theVideoStreamFrame
. By addingself.panel.bind('<Button>', self.on_click)
and a few lines to a formerly-empty built-in tkinteron_click()
, theClickDispatcher
can handle clicks on the decorated frame and call callbacks without any performance overhead. Since this might sound obscure, here are some examples. Firstly, adding the following to any frame makes the console print the coordinates of clicked pixel:If we expect our listener to be active only briefly, instead of defining callback we can use it as a context to wait for clicks:
Using both callback and context at the same time is also possible for some cool interactive applications:
As shown above, listeners can be accessed via
click_dispatcher.listeners
NoOverwriteDict
(in newinstamatic.collections
). Here, I only introduce the class and showcase its potential uses - some of which I plan to include in the following PRs.Typing extensions
While adding new features is fun, improving the maintainability of existing ones it also important. For this reason, in this PR I also suggest adding
typing_extenstions
a newinstamatic
dependency.typing_extensions
is Guido von Ross's light-weight dependency-less python library (i.e. a single file) that back-ports new pythontyping
types to old python versions. While typically I'd be against adding new dependencies, this one is literally in the top ten most downloaded ones and has little drawbacks.To exemplify the usefulness of
typing_extensions
, in this PR I improved the type hints for a bunch of constructors by specifying their return type usingSelf
. TheSelf
type hint is a python 3.11 addition that says the return type of acls.method
is the same ascls
's. The Python 3.7-3.10 way of definingSelf
is rarely done well. One great use by @viljarjf is actually present insimulation.py
:While I did not modify this particular file to not cause potential merging issues for him,
Self
would simplify that to:Major changes
self.app.get_module('stream').click_dispatcher
can handle user clicking on theVideoStreamFrame
image;Code maintanance
typing_extension
dependency to vastly expand typing capabilities, improving code maintenance;instamatic._collections
is a new module for repeated design patterns, e.g. newNoOverwriteDict
.Effect on the code base
The new
ClickDispatcher
can be used onVideoStreamFrame
or other frames (if implemented) to vastly improve the level of interaction between the user and GUI. With thetyping_extension
added, the developers can now usetyping
type hints accessible to Python3.8+ which, otherwise, would break python 3.7 compatibility.