Skip to content

Commit

Permalink
Subscription replaced with binding.
Browse files Browse the repository at this point in the history
Removed `subscribe` and `unsubscribe` methods from Gadget.
Added `bind` and `unbind` methods to Gadget.
Binding achieves the same as subscription but with fewer
arguments. See `examples/binding.py`.
  • Loading branch information
salt-die committed Feb 19, 2024
1 parent 22b6bfd commit ff4b8ae
Show file tree
Hide file tree
Showing 47 changed files with 277 additions and 280 deletions.
2 changes: 1 addition & 1 deletion batgrl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""batgrl, the badass terminal graphics library."""

__version__ = "0.33.0"
__version__ = "0.34.0"
8 changes: 4 additions & 4 deletions batgrl/gadgets/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,10 @@ class Animation(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/bar_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,10 @@ class BarChart(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/box_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ class BoxImage(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/braille_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ class BrailleImage(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/braille_video_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,10 @@ class BrailleVideoPlayer(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,10 @@ class Button(Themable, ButtonBehavior, Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/color_picker.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,10 @@ class ColorPicker(Themable, Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
16 changes: 8 additions & 8 deletions batgrl/gadgets/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,10 +388,10 @@ class Console(Themable, Focusable, Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down Expand Up @@ -478,10 +478,10 @@ def update_bars():
self._container.width > self._scroll_view.port_width
)

self.subscribe(self._prompt, "size", fix_input_pos)
self.subscribe(self._prompt, "pos", fix_input_pos)
self.subscribe(self._container, "size", update_bars)
self.subscribe(self._scroll_view, "size", update_bars)
self._prompt.bind("size", fix_input_pos)
self._prompt.bind("pos", fix_input_pos)
self._container.bind("size", update_bars)
self._scroll_view.bind("size", update_bars)

self._prompt.set_text(PROMPT_1)
self._container.add_gadgets(self._output, self._prompt, self._input)
Expand Down
10 changes: 5 additions & 5 deletions batgrl/gadgets/data_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,10 +461,10 @@ class DataTable(Themable, Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down Expand Up @@ -564,7 +564,7 @@ def update_bars():
self._table.height > self._scroll_view.port_height
)

self.subscribe(self._table, "size", update_bars)
self._table.bind("size", update_bars)

if data is not None:
for label, column_data in data.items():
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/digital_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,10 +306,10 @@ class DigitalDisplay(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/file_chooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,10 @@ class FileChooser(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/flat_toggle.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,10 @@ class FlatToggle(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
83 changes: 42 additions & 41 deletions batgrl/gadgets/gadget.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections.abc import Callable, Iterator, Sequence
from dataclasses import asdict, dataclass
from functools import wraps
from itertools import count
from numbers import Real
from time import monotonic
from typing import Coroutine, Literal, Optional, TypedDict
Expand Down Expand Up @@ -35,6 +36,8 @@
"subscribable",
]

_UID = count(1)


def round_down(n: float) -> int:
"""
Expand Down Expand Up @@ -396,15 +399,14 @@ class SizeHintDict(TypedDict, total=False):

def subscribable(setter):
"""Decorate property setters to make them subscribable."""
instances = WeakKeyDictionary()
instances: WeakKeyDictionary[Gadget, Callable[[], None]] = WeakKeyDictionary()

@wraps(setter)
def wrapper(self, *args, **kwargs):
setter(self, *args, **kwargs)

if subscribers := instances.get(self):
for action in subscribers.values():
action()
if bindings := instances.get(self):
for callback in bindings.values():
callback()

wrapper.instances = instances

Expand Down Expand Up @@ -511,10 +513,10 @@ class Gadget:
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand All @@ -533,6 +535,9 @@ class Gadget:
Remove this gadget and recursively remove all its children.
"""

__bindings: dict[int, str] = {}
"""UID to property name mapping."""

def __init__(
self,
*,
Expand Down Expand Up @@ -999,49 +1004,45 @@ def ancestors(self) -> Iterator["Gadget"]:
yield self.parent
yield from self.parent.ancestors()

def subscribe(
self,
source: "Gadget",
attr: str,
action: Callable[[], None],
):
def bind(self, prop: str, callback: Callable[[], None]) -> int:
"""
Subscribe to a gadget property. When property is modified, `action` will be
called.
Bind `callback` to a gadget property. When the property is updated, `callback`
is called with no arguments.
Parameters
----------
source : Gadget
The source of the gadget property.
attr : str
prop : str
The name of the gadget property.
action : Callable[[], None]
Called when the property is updated.
"""
setter = getattr(type(source), attr).fset
subscribers = setter.instances.setdefault(source, WeakKeyDictionary())
subscribers[self] = action
callback : Callable[[], None]
Callback to bind to property.
def unsubscribe(self, source: "Gadget", attr: str) -> Callable[[], None] | None:
Returns
-------
int
A unique id used to unbind the callback.
"""
uid = next(_UID)
setter = getattr(type(self), prop).fset
bindings = setter.instances.setdefault(self, {})
bindings[uid] = callback
self.__bindings[uid] = prop
return uid

def unbind(self, uid: int) -> None:
"""
Unsubscribe to a gadget event and return the action that was subscribed to the
property or ``None`` if subscription isn't found.
Unbind a callback from a gadget property.
Parameters
----------
source : Gadget
The source of the gadget property.
attr : str
The name of the gadget property.
Returns
-------
Callable[[], None] | None
The action that was subscribed to the property or `None` if no subscription
was found.
uid : int
Unique id returned by the :meth:`bind` method.
"""
setter = getattr(type(source), attr).fset
return setter.instances[source].pop(self, None)
prop = self.__bindings.pop(uid, None)
if prop is None:
return
setter = getattr(type(self), prop).fset
if self in setter.instances:
setter.instances[self].pop(uid, None)

def dispatch_key(self, key_event: KeyEvent) -> bool | None:
"""
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/graphic_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ class GraphicParticleField(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,10 @@ class Graphics(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
8 changes: 4 additions & 4 deletions batgrl/gadgets/grid_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,10 @@ class GridLayout(Gadget):
Yield all descendents of this gadget (reverse postorder traversal).
ancestors()
Yield all ancestors of this gadget.
subscribe(source, attr, action)
Subscribe to a gadget property.
unsubscribe(source, attr)
Unsubscribe to a gadget property.
bind(prop, callback)
Bind `callback` to a gadget property.
unbind(uid)
Unbind a callback from a gadget property.
on_key(key_event)
Handle key press event.
on_mouse(mouse_event)
Expand Down
Loading

0 comments on commit ff4b8ae

Please sign in to comment.