Skip to content

Commit 4f153cf

Browse files
committed
MNT: Towards standardizing widget blitting
To have standard logical framework for blitting of widgets, we pull some logic up into AxesWidget: - Most widgets held a _useblit variable. This is now part of AxesWidget. - AxesWidget._should_use_blit() returns whether blitting should *currently* be done - i.e. the user/widget requested it and the canvas supports it. This is used to guard all blitting-related code. Note: This is a well-defined and limited step towards standardization. Not all widgets are already using it. Some still have special logic. I anticipate that migrating them will be easier when this infrastructure and logical concept is present. Changes here are limited to obvious cases. The rest will be handled in a follow-up PR.
1 parent ddd4196 commit 4f153cf

1 file changed

Lines changed: 36 additions & 26 deletions

File tree

lib/matplotlib/widgets.py

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,19 @@ class AxesWidget(Widget):
113113
The parent figure canvas for the widget.
114114
active : bool
115115
If False, the widget does not respond to events.
116+
useblit : bool
117+
Whether usage of blitting is desired. The actual usage of
118+
blitting also depends on the canvas supporting it.
119+
120+
Once set, this is read-only, as some widgets currently still
121+
depend on the state statically. They still query _useblit.
116122
"""
117123

118-
def __init__(self, ax):
124+
def __init__(self, ax, useblit=False):
119125
self.ax = ax
120126
self._cids = []
121127
self._blit_background_id = None
128+
self._useblit = useblit
122129

123130
def __del__(self):
124131
if self._blit_background_id is not None:
@@ -151,6 +158,16 @@ def _set_cursor(self, cursor):
151158
"""Update the canvas cursor."""
152159
self.ax.get_figure(root=True).canvas.set_cursor(cursor)
153160

161+
def _should_use_blit(self):
162+
"""
163+
Return whether blitting should be used.
164+
165+
All blitting-related code must be guarded by this because
166+
not all canvases support blit and canvases may be swapped
167+
out during the lifetime of the widget.
168+
"""
169+
return self._useblit and self.canvas.supports_blit
170+
154171
def _save_blit_background(self, background):
155172
"""
156173
Save a blit background.
@@ -239,7 +256,7 @@ def __init__(self, ax, label, image=None,
239256
240257
.. versionadded:: 3.7
241258
"""
242-
super().__init__(ax)
259+
super().__init__(ax, useblit=useblit)
243260

244261
if image is not None:
245262
ax.imshow(image)
@@ -248,8 +265,6 @@ def __init__(self, ax, label, image=None,
248265
horizontalalignment='center',
249266
transform=ax.transAxes)
250267

251-
self._useblit = useblit
252-
253268
self._observers = cbook.CallbackRegistry(signals=["clicked"])
254269

255270
self.connect_event('button_press_event', self._click)
@@ -285,7 +300,7 @@ def _motion(self, event):
285300
if not colors.same_color(c, self.ax.get_facecolor()):
286301
self.ax.set_facecolor(c)
287302
if self.drawon:
288-
if self._useblit and self.canvas.supports_blit:
303+
if self._should_use_blit():
289304
self.ax.draw_artist(self.ax)
290305
self.canvas.blit(self.ax.bbox)
291306
else:
@@ -1096,7 +1111,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,
10961111
10971112
.. versionadded:: 3.7
10981113
"""
1099-
super().__init__(ax)
1114+
super().__init__(ax, useblit=useblit)
11001115

11011116
_api.check_isinstance((dict, None), label_props=label_props,
11021117
frame_props=frame_props, check_props=check_props)
@@ -1108,8 +1123,6 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,
11081123
if actives is None:
11091124
actives = [False] * len(labels)
11101125

1111-
self._useblit = useblit
1112-
11131126
ys = np.linspace(1, 0, len(labels)+2)[1:-1]
11141127

11151128
label_props = _expand_text_props(label_props)
@@ -1158,7 +1171,7 @@ def _clear(self, event):
11581171
"""Internal event handler to clear the buttons."""
11591172
if self.ignore(event) or self.canvas.is_saving():
11601173
return
1161-
if self._useblit and self.canvas.supports_blit:
1174+
if self._should_use_blit():
11621175
self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox))
11631176
self.ax.draw_artist(self._checks)
11641177

@@ -1264,7 +1277,7 @@ def set_active(self, index, state=None):
12641277
self._checks.set_facecolor(facecolors)
12651278

12661279
if self.drawon:
1267-
if self._useblit and self.canvas.supports_blit:
1280+
if self._should_use_blit():
12681281
background = self._load_blit_background()
12691282
if background is not None:
12701283
self.canvas.restore_region(background)
@@ -1678,7 +1691,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
16781691
16791692
.. versionadded:: 3.7
16801693
"""
1681-
super().__init__(ax)
1694+
super().__init__(ax, useblit=useblit)
16821695

16831696
_api.check_isinstance((dict, None), label_props=label_props,
16841697
radio_props=radio_props)
@@ -1705,8 +1718,6 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,
17051718

17061719
ys = np.linspace(1, 0, len(labels) + 2)[1:-1]
17071720

1708-
self._useblit = useblit
1709-
17101721
label_props = _expand_text_props(label_props)
17111722
self.labels = [
17121723
ax.text(0.25, y, label, transform=ax.transAxes,
@@ -1751,7 +1762,7 @@ def _clear(self, event):
17511762
"""Internal event handler to clear the buttons."""
17521763
if self.ignore(event) or self.canvas.is_saving():
17531764
return
1754-
if self._useblit and self.canvas.supports_blit:
1765+
if self._should_use_blit():
17551766
self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox))
17561767
self.ax.draw_artist(self._buttons)
17571768

@@ -1845,7 +1856,7 @@ def set_active(self, index):
18451856
self._buttons.set_facecolor(button_facecolors)
18461857

18471858
if self.drawon:
1848-
if self._useblit and self.canvas.supports_blit:
1859+
if self._should_use_blit():
18491860
background = self._load_blit_background()
18501861
if background is not None:
18511862
self.canvas.restore_region(background)
@@ -1989,7 +2000,7 @@ class Cursor(AxesWidget):
19892000
"""
19902001
def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False,
19912002
**lineprops):
1992-
super().__init__(ax)
2003+
super().__init__(ax, useblit=useblit)
19932004

19942005
self.connect_event('motion_notify_event', self.onmove)
19952006
self.connect_event('draw_event', self.clear)
@@ -2010,7 +2021,7 @@ def clear(self, event):
20102021
"""Internal event handler to clear the cursor."""
20112022
if self.ignore(event) or self.canvas.is_saving():
20122023
return
2013-
if self.useblit:
2024+
if self._should_use_blit():
20142025
self._save_blit_background(self.canvas.copy_from_bbox(self.ax.bbox))
20152026

20162027
@_call_with_reparented_event
@@ -2035,7 +2046,7 @@ def onmove(self, event):
20352046
if not (self.visible and (self.vertOn or self.horizOn)):
20362047
return
20372048
# Redraw.
2038-
if self.useblit:
2049+
if self._should_use_blit():
20392050
background = self._load_blit_background()
20402051
if background is not None:
20412052
self.canvas.restore_region(background)
@@ -2205,14 +2216,13 @@ class _SelectorWidget(AxesWidget):
22052216

22062217
def __init__(self, ax, onselect=None, useblit=False, button=None,
22072218
state_modifier_keys=None, use_data_coordinates=False):
2208-
super().__init__(ax)
2219+
super().__init__(ax, useblit=useblit)
22092220

22102221
self._visible = True
22112222
if onselect is None:
22122223
self.onselect = lambda *args: None
22132224
else:
22142225
self.onselect = onselect
2215-
self._useblit = useblit
22162226
self.connect_default_events()
22172227

22182228
self._state_modifier_keys = dict(move=' ', clear='escape',
@@ -2239,7 +2249,7 @@ def __init__(self, ax, onselect=None, useblit=False, button=None,
22392249
@property
22402250
def useblit(self):
22412251
"""Return whether blitting is used (requested and supported by canvas)."""
2242-
return self._useblit and self.canvas.supports_blit
2252+
return self._should_use_blit()
22432253

22442254
def set_active(self, active):
22452255
super().set_active(active)
@@ -2262,7 +2272,7 @@ def update_background(self, event):
22622272
"""Force an update of the background."""
22632273
# If you add a call to `ignore` here, you'll want to check edge case:
22642274
# `release` can call a draw event even when `ignore` is True.
2265-
if not self.useblit:
2275+
if not self._should_use_blit():
22662276
return
22672277
if self.canvas.is_saving():
22682278
return # saving does not use blitting
@@ -2323,11 +2333,11 @@ def ignore(self, event):
23232333
event.button != self._eventpress.button)
23242334

23252335
def update(self):
2326-
"""Draw using blit() or draw_idle(), depending on ``self.useblit``."""
2336+
"""Draw using blit() or draw_idle(), depending on blitting support."""
23272337
if (not self.ax.get_visible() or
23282338
self.ax.get_figure(root=True)._get_renderer() is None):
23292339
return
2330-
if self.useblit:
2340+
if self._should_use_blit():
23312341
background = self._load_blit_background()
23322342
if background is not None:
23332343
self.canvas.restore_region(background)
@@ -2500,7 +2510,7 @@ def set_props(self, **props):
25002510
artist = self._selection_artist
25012511
props = cbook.normalize_kwargs(props, artist)
25022512
artist.set(**props)
2503-
if self.useblit:
2513+
if self._should_use_blit():
25042514
self.update()
25052515

25062516
def set_handle_props(self, **handle_props):
@@ -2516,7 +2526,7 @@ def set_handle_props(self, **handle_props):
25162526
handle_props = cbook.normalize_kwargs(handle_props, artist)
25172527
for handle in self._handles_artists:
25182528
handle.set(**handle_props)
2519-
if self.useblit:
2529+
if self._should_use_blit():
25202530
self.update()
25212531
self._handle_props.update(handle_props)
25222532

0 commit comments

Comments
 (0)