-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
wx backends: don't use ClientDC any more #11944
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
base: main
Are you sure you want to change the base?
Changes from all commits
39c17b0
cff5f63
b31c736
452ab5c
779efc0
4ff217d
97fccf1
5f408d7
98146f3
b86d842
ac89e8b
2f00cdc
885c7d5
24bfbed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
wx Backends | ||
----------- | ||
|
||
The internal implementation of the wx backends was changed to do all | ||
the screen painting inside the ``_OnPaint`` method which handles wx | ||
``EVT_PAINT`` events. | ||
So for a screen update due to a call to ``draw`` or for drawing a | ||
selection rubberband, the ``Refresh`` method is called to trigger | ||
a later paint event instead of drawing directly to a ``ClientDC``. | ||
|
||
The atribute ``_retinaFix`` has moved from ``NavigationToolbar2Wx`` | ||
to ``_FigureCanvasWxBase``. | ||
|
||
The method ``gui_repaint`` of all wx canvases has been removed. | ||
The ``draw`` method no longer accepts an argument ``drawDC``. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -507,6 +507,7 @@ class _FigureCanvasWxBase(FigureCanvasBase, wx.Panel): | |
wx.WXK_NUMPAD_INSERT: 'insert', | ||
wx.WXK_NUMPAD_DELETE: 'delete', | ||
} | ||
_retinaFix = 'wxMac' in wx.PlatformInfo | ||
|
||
def __init__(self, parent, id, figure): | ||
""" | ||
|
@@ -531,6 +532,8 @@ def __init__(self, parent, id, figure): | |
_log.debug("%s - __init__() - bitmap w:%d h:%d", type(self), w, h) | ||
# TODO: Add support for 'point' inspection and plot navigation. | ||
self._isDrawn = False | ||
self._rubberband = None # a selection rectangle to be drawn | ||
self._overlay = None | ||
|
||
self.Bind(wx.EVT_SIZE, self._onSize) | ||
self.Bind(wx.EVT_PAINT, self._onPaint) | ||
|
@@ -619,29 +622,6 @@ def _get_imagesave_wildcards(self): | |
wildcards = '|'.join(wildcards) | ||
return wildcards, extensions, filter_index | ||
|
||
def gui_repaint(self, drawDC=None, origin='WX'): | ||
""" | ||
Performs update of the displayed image on the GUI canvas, using the | ||
supplied wx.PaintDC device context. | ||
|
||
The 'WXAgg' backend sets origin accordingly. | ||
""" | ||
_log.debug("%s - gui_repaint()", type(self)) | ||
if self.IsShownOnScreen(): | ||
if not drawDC: | ||
# not called from OnPaint use a ClientDC | ||
drawDC = wx.ClientDC(self) | ||
|
||
# following is for 'WX' backend on Windows | ||
# the bitmap can not be in use by another DC, | ||
# see GraphicsContextWx._cache | ||
if wx.Platform == '__WXMSW__' and origin == 'WX': | ||
img = self.bitmap.ConvertToImage() | ||
bmp = img.ConvertToBitmap() | ||
drawDC.DrawBitmap(bmp, 0, 0) | ||
else: | ||
drawDC.DrawBitmap(self.bitmap, 0, 0) | ||
|
||
filetypes = { | ||
**FigureCanvasBase.filetypes, | ||
'bmp': 'Windows bitmap', | ||
|
@@ -666,12 +646,30 @@ def print_figure(self, filename, *args, **kwargs): | |
def _onPaint(self, event): | ||
"""Called when wxPaintEvt is generated.""" | ||
_log.debug("%s - _onPaint()", type(self)) | ||
drawDC = wx.PaintDC(self) | ||
if not self._isDrawn: | ||
self.draw(drawDC=drawDC) | ||
else: | ||
self.gui_repaint(drawDC=drawDC) | ||
drawDC.Destroy() | ||
self._draw() | ||
|
||
# the bitmap can not be in use by another DC | ||
img = self.bitmap.ConvertToImage() | ||
bmp = img.ConvertToBitmap() | ||
dc = wx.BufferedPaintDC(self, bmp) | ||
|
||
if not self._rubberband: | ||
return | ||
|
||
# draw rubberband / selection: a box with border and 50% transparency | ||
if not self._retinaFix: | ||
dc = wx.GCDC(dc) | ||
# Set the pen, for the box's border | ||
bc = wx.BLUE | ||
dc.SetPen(wx.Pen(colour=bc, width=1, style=wx.PENSTYLE_SOLID)) | ||
# Create a brush (for the box's interior) with the same colour, | ||
# but 50% transparency. | ||
bc = wx.Colour(bc.red, bc.green, bc.blue, 0x80) | ||
dc.SetBrush(wx.Brush(bc)) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. some comments and suggestions: a) use dc.SetLogicalFunction(wx.XOR) and a white (or light) Pen color so that the border shows on axes with different background face colors. Similarly, let the brush be more transparent. b) use the explicit c) why is Perhaps something like this:
|
||
# Draw the rectangle | ||
dc.DrawRectangle(*self._rubberband) | ||
|
||
def _onSize(self, event): | ||
""" | ||
|
@@ -824,20 +822,59 @@ def _onEnter(self, event): | |
event.Skip() | ||
FigureCanvasBase.enter_notify_event(self, guiEvent=event, xy=(x, y)) | ||
|
||
def _draw_rubberband(self, x0, y0, x1, y1): | ||
# trigger a refresh to draw a rubberband-like selection box | ||
width = abs(x1 - x0) | ||
x0 = min(x0, x1) | ||
height = abs(y1 - y0) | ||
y0 = self.figure.bbox.height - max(y0, y1) | ||
previous_rubberband = self._rubberband | ||
self._rubberband = (x0, y0, width, height) | ||
self._refresh_rubberband(previous_rubberband) | ||
|
||
def _remove_rubberband(self): | ||
# end drawing of a rubberband-like selection box | ||
if not self._rubberband: | ||
return | ||
previous_rubberband = self._rubberband | ||
self._rubberband = None | ||
self._refresh_rubberband(previous_rubberband) # trigger a later redraw | ||
|
||
def _refresh_rubberband(self, previous_rubberband=None): | ||
# initiate a refresh on the area that contains the previous and | ||
# current selection / rubberband | ||
if self._rubberband: | ||
rect = wx.Rect(self._rubberband) | ||
else: | ||
rect = None | ||
if previous_rubberband: | ||
previous_rubberband = wx.Rect(*previous_rubberband) | ||
# extend by one pixel to avoid residuals on Mac OS | ||
previous_rubberband.Inflate(1, 1) | ||
if rect: | ||
rect.Union(previous_rubberband) | ||
else: | ||
rect = previous_rubberband | ||
rect.Intersect(self.Rect) | ||
self.Refresh(False, rect) | ||
|
||
|
||
class FigureCanvasWx(_FigureCanvasWxBase): | ||
# Rendering to a Wx canvas using the deprecated Wx renderer. | ||
|
||
def draw(self, drawDC=None): | ||
def _draw(self): | ||
_log.debug("%s - _draw()", type(self)) | ||
self.renderer = RendererWx(self.bitmap, self.figure.dpi) | ||
self.figure.draw(self.renderer) | ||
self._isDrawn = True | ||
|
||
def draw(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add note re: API change. |
||
""" | ||
Render the figure using RendererWx instance renderer, or using a | ||
previously defined renderer if none is specified. | ||
""" | ||
_log.debug("%s - draw()", type(self)) | ||
self.renderer = RendererWx(self.bitmap, self.figure.dpi) | ||
self.figure.draw(self.renderer) | ||
self._isDrawn = True | ||
self.gui_repaint(drawDC=drawDC) | ||
self._draw() | ||
self.Refresh() | ||
|
||
def print_bmp(self, filename, *args, **kwargs): | ||
return self._print_image(filename, wx.BITMAP_TYPE_BMP, *args, **kwargs) | ||
|
@@ -908,7 +945,7 @@ def _print_image(self, filename, filetype, *args, **kwargs): | |
# been cleaned up. The artist contains() methods will fail | ||
# otherwise. | ||
if self._isDrawn: | ||
self.draw() | ||
self._draw() | ||
self.Refresh() | ||
|
||
|
||
|
@@ -1120,11 +1157,6 @@ def __init__(self, canvas): | |
self.canvas = canvas | ||
self._idle = True | ||
self.prevZoomRect = None | ||
# for now, use alternate zoom-rectangle drawing on all | ||
# Macs. N.B. In future versions of wx it may be possible to | ||
# detect Retina displays with window.GetContentScaleFactor() | ||
# and/or dc.GetContentScaleFactor() | ||
self.retinaFix = 'wxMac' in wx.PlatformInfo | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All these attributes need removal notices. |
||
|
||
def get_canvas(self, frame, fig): | ||
return type(self.canvas)(frame, -1, fig) | ||
|
@@ -1216,85 +1248,14 @@ def set_cursor(self, cursor): | |
self.canvas.Update() | ||
|
||
def press(self, event): | ||
if self._active == 'ZOOM': | ||
if not self.retinaFix: | ||
self.wxoverlay = wx.Overlay() | ||
else: | ||
if event.inaxes is not None: | ||
self.savedRetinaImage = self.canvas.copy_from_bbox( | ||
event.inaxes.bbox) | ||
self.zoomStartX = event.xdata | ||
self.zoomStartY = event.ydata | ||
self.zoomAxes = event.inaxes | ||
pass | ||
|
||
def release(self, event): | ||
if self._active == 'ZOOM': | ||
# When the mouse is released we reset the overlay and it | ||
# restores the former content to the window. | ||
if not self.retinaFix: | ||
self.wxoverlay.Reset() | ||
del self.wxoverlay | ||
else: | ||
del self.savedRetinaImage | ||
if self.prevZoomRect: | ||
self.prevZoomRect.pop(0).remove() | ||
self.prevZoomRect = None | ||
if self.zoomAxes: | ||
self.zoomAxes = None | ||
self.canvas._remove_rubberband() | ||
|
||
def draw_rubberband(self, event, x0, y0, x1, y1): | ||
if self.retinaFix: # On Macs, use the following code | ||
# wx.DCOverlay does not work properly on Retina displays. | ||
rubberBandColor = '#C0C0FF' | ||
if self.prevZoomRect: | ||
self.prevZoomRect.pop(0).remove() | ||
self.canvas.restore_region(self.savedRetinaImage) | ||
X0, X1 = self.zoomStartX, event.xdata | ||
Y0, Y1 = self.zoomStartY, event.ydata | ||
lineX = (X0, X0, X1, X1, X0) | ||
lineY = (Y0, Y1, Y1, Y0, Y0) | ||
self.prevZoomRect = self.zoomAxes.plot( | ||
lineX, lineY, '-', color=rubberBandColor) | ||
self.zoomAxes.draw_artist(self.prevZoomRect[0]) | ||
self.canvas.blit(self.zoomAxes.bbox) | ||
return | ||
|
||
# Use an Overlay to draw a rubberband-like bounding box. | ||
|
||
dc = wx.ClientDC(self.canvas) | ||
odc = wx.DCOverlay(self.wxoverlay, dc) | ||
odc.Clear() | ||
|
||
# Mac's DC is already the same as a GCDC, and it causes | ||
# problems with the overlay if we try to use an actual | ||
# wx.GCDC so don't try it. | ||
if 'wxMac' not in wx.PlatformInfo: | ||
dc = wx.GCDC(dc) | ||
|
||
height = self.canvas.figure.bbox.height | ||
y1 = height - y1 | ||
y0 = height - y0 | ||
|
||
if y1 < y0: | ||
y0, y1 = y1, y0 | ||
if x1 < x0: | ||
x0, x1 = x1, x0 | ||
|
||
w = x1 - x0 | ||
h = y1 - y0 | ||
rect = wx.Rect(x0, y0, w, h) | ||
|
||
rubberBandColor = '#C0C0FF' # or load from config? | ||
|
||
# Set a pen for the border | ||
color = wx.Colour(rubberBandColor) | ||
dc.SetPen(wx.Pen(color, 1)) | ||
|
||
# use the same color, plus alpha for the brush | ||
r, g, b, a = color.Get(True) | ||
color.Set(r, g, b, 0x60) | ||
dc.SetBrush(wx.Brush(color)) | ||
dc.DrawRectangle(rect) | ||
self.canvas._draw_rubberband(x0, y0, x1, y1) | ||
|
||
@cbook.deprecated("3.2") | ||
def set_status_bar(self, statbar): | ||
|
@@ -1451,91 +1412,16 @@ def set_cursor(self, cursor): | |
self._make_classic_style_pseudo_toolbar(), cursor) | ||
|
||
|
||
if 'wxMac' not in wx.PlatformInfo: | ||
# on most platforms, use overlay | ||
class RubberbandWx(backend_tools.RubberbandBase): | ||
def __init__(self, *args, **kwargs): | ||
backend_tools.RubberbandBase.__init__(self, *args, **kwargs) | ||
self.wxoverlay = None | ||
|
||
def draw_rubberband(self, x0, y0, x1, y1): | ||
# Use an Overlay to draw a rubberband-like bounding box. | ||
if self.wxoverlay is None: | ||
self.wxoverlay = wx.Overlay() | ||
dc = wx.ClientDC(self.canvas) | ||
odc = wx.DCOverlay(self.wxoverlay, dc) | ||
odc.Clear() | ||
|
||
dc = wx.GCDC(dc) | ||
|
||
height = self.canvas.figure.bbox.height | ||
y1 = height - y1 | ||
y0 = height - y0 | ||
|
||
if y1 < y0: | ||
y0, y1 = y1, y0 | ||
if x1 < x0: | ||
x0, x1 = x1, x0 | ||
|
||
w = x1 - x0 | ||
h = y1 - y0 | ||
rect = wx.Rect(x0, y0, w, h) | ||
|
||
rubberBandColor = '#C0C0FF' # or load from config? | ||
|
||
# Set a pen for the border | ||
color = wx.Colour(rubberBandColor) | ||
dc.SetPen(wx.Pen(color, 1)) | ||
class RubberbandWx(backend_tools.RubberbandBase): | ||
def __init__(self, *args, **kwargs): | ||
backend_tools.RubberbandBase.__init__(self, *args, **kwargs) | ||
|
||
# use the same color, plus alpha for the brush | ||
r, g, b, a = color.Get(True) | ||
color.Set(r, g, b, 0x60) | ||
dc.SetBrush(wx.Brush(color)) | ||
dc.DrawRectangle(rect) | ||
def draw_rubberband(self, x0, y0, x1, y1): | ||
# Use an Overlay to draw a rubberband-like bounding box. | ||
self.canvas._draw_rubberband(x0, y0, x1, y1) | ||
|
||
def remove_rubberband(self): | ||
if self.wxoverlay is None: | ||
return | ||
self.wxoverlay.Reset() | ||
self.wxoverlay = None | ||
|
||
else: | ||
# on Mac OS retina displays DCOverlay does not work | ||
# and dc.SetLogicalFunction does not have an effect on any display | ||
# the workaround is to blit the full image for remove_rubberband | ||
class RubberbandWx(backend_tools.RubberbandBase): | ||
def __init__(self, *args, **kwargs): | ||
backend_tools.RubberbandBase.__init__(self, *args, **kwargs) | ||
self._rect = None | ||
|
||
def draw_rubberband(self, x0, y0, x1, y1): | ||
dc = wx.ClientDC(self.canvas) | ||
# this would be required if the Canvas is a ScrolledWindow, | ||
# which is not the case for now | ||
# self.PrepareDC(dc) | ||
|
||
# delete old rubberband | ||
if self._rect: | ||
self.remove_rubberband(dc) | ||
|
||
# draw new rubberband | ||
dc.SetPen(wx.Pen(wx.BLACK, 1, wx.SOLID)) | ||
dc.SetBrush(wx.TRANSPARENT_BRUSH) | ||
self._rect = (x0, self.canvas._height-y0, x1-x0, -y1+y0) | ||
dc.DrawRectangle(self._rect) | ||
|
||
def remove_rubberband(self, dc=None): | ||
if not self._rect: | ||
return | ||
if self.canvas.bitmap: | ||
if dc is None: | ||
dc = wx.ClientDC(self.canvas) | ||
dc.DrawBitmap(self.canvas.bitmap, 0, 0) | ||
# for testing the method on Windows, use this code instead: | ||
# img = self.canvas.bitmap.ConvertToImage() | ||
# bmp = img.ConvertToBitmap() | ||
# dc.DrawBitmap(bmp, 0, 0) | ||
self._rect = None | ||
def remove_rubberband(self): | ||
self.canvas._remove_rubberband() | ||
|
||
|
||
class _HelpDialog(wx.Dialog): | ||
|
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.
Need to add an API change note (it's mostly an implementation detail so not going to ask a deprecation period, which probably wouldn't make sense anyways).