@@ -108,6 +108,8 @@ def __init__(self, figure=None):
108108 self .connect ('button_press_event' , self .button_press_event )
109109 self .connect ('button_release_event' , self .button_release_event )
110110 self .connect ('configure_event' , self .configure_event )
111+ self .connect ('screen-changed' , self ._update_device_pixel_ratio )
112+ self .connect ('notify::scale-factor' , self ._update_device_pixel_ratio )
111113 self .connect ('draw' , self .on_draw_event )
112114 self .connect ('draw' , self ._post_draw )
113115 self .connect ('key_press_event' , self .key_press_event )
@@ -138,26 +140,35 @@ def set_cursor(self, cursor):
138140 context = GLib .MainContext .default ()
139141 context .iteration (True )
140142
143+ def _mouse_event_coords (self , event ):
144+ """
145+ Calculate mouse coordinates in physical pixels.
146+
147+ GTK use logical pixels, but the figure is scaled to physical pixels for
148+ rendering. Transform to physical pixels so that all of the down-stream
149+ transforms work as expected.
150+
151+ Also, the origin is different and needs to be corrected.
152+ """
153+ x = event .x * self .device_pixel_ratio
154+ # flip y so y=0 is bottom of canvas
155+ y = self .figure .bbox .height - event .y * self .device_pixel_ratio
156+ return x , y
157+
141158 def scroll_event (self , widget , event ):
142- x = event .x
143- # flipy so y=0 is bottom of canvas
144- y = self .get_allocation ().height - event .y
159+ x , y = self ._mouse_event_coords (event )
145160 step = 1 if event .direction == Gdk .ScrollDirection .UP else - 1
146161 FigureCanvasBase .scroll_event (self , x , y , step , guiEvent = event )
147162 return False # finish event propagation?
148163
149164 def button_press_event (self , widget , event ):
150- x = event .x
151- # flipy so y=0 is bottom of canvas
152- y = self .get_allocation ().height - event .y
165+ x , y = self ._mouse_event_coords (event )
153166 FigureCanvasBase .button_press_event (
154167 self , x , y , event .button , guiEvent = event )
155168 return False # finish event propagation?
156169
157170 def button_release_event (self , widget , event ):
158- x = event .x
159- # flipy so y=0 is bottom of canvas
160- y = self .get_allocation ().height - event .y
171+ x , y = self ._mouse_event_coords (event )
161172 FigureCanvasBase .button_release_event (
162173 self , x , y , event .button , guiEvent = event )
163174 return False # finish event propagation?
@@ -175,27 +186,26 @@ def key_release_event(self, widget, event):
175186 def motion_notify_event (self , widget , event ):
176187 if event .is_hint :
177188 t , x , y , state = event .window .get_device_position (event .device )
189+ # flipy so y=0 is bottom of canvas
190+ x *= self .device_pixel_ratio
191+ y = (self .get_allocation ().height - y ) * self .device_pixel_ratio
178192 else :
179- x , y = event . x , event . y
193+ x , y = self . _mouse_event_coords ( event )
180194
181- # flipy so y=0 is bottom of canvas
182- y = self .get_allocation ().height - y
183195 FigureCanvasBase .motion_notify_event (self , x , y , guiEvent = event )
184196 return False # finish event propagation?
185197
186198 def leave_notify_event (self , widget , event ):
187199 FigureCanvasBase .leave_notify_event (self , event )
188200
189201 def enter_notify_event (self , widget , event ):
190- x = event .x
191- # flipy so y=0 is bottom of canvas
192- y = self .get_allocation ().height - event .y
202+ x , y = self ._mouse_event_coords (event )
193203 FigureCanvasBase .enter_notify_event (self , guiEvent = event , xy = (x , y ))
194204
195205 def size_allocate (self , widget , allocation ):
196206 dpival = self .figure .dpi
197- winch = allocation .width / dpival
198- hinch = allocation .height / dpival
207+ winch = allocation .width * self . device_pixel_ratio / dpival
208+ hinch = allocation .height * self . device_pixel_ratio / dpival
199209 self .figure .set_size_inches (winch , hinch , forward = False )
200210 FigureCanvasBase .resize_event (self )
201211 self .draw_idle ()
@@ -217,10 +227,21 @@ def _get_key(self, event):
217227 key = f'{ prefix } +{ key } '
218228 return key
219229
230+ def _update_device_pixel_ratio (self , * args , ** kwargs ):
231+ # We need to be careful in cases with mixed resolution displays if
232+ # device_pixel_ratio changes.
233+ if self ._set_device_pixel_ratio (self .get_scale_factor ()):
234+ # The easiest way to resize the canvas is to emit a resize event
235+ # since we implement all the logic for resizing the canvas for that
236+ # event.
237+ self .queue_resize ()
238+ self .queue_draw ()
239+
220240 def configure_event (self , widget , event ):
221241 if widget .get_property ("window" ) is None :
222242 return
223- w , h = event .width , event .height
243+ w = event .width * self .device_pixel_ratio
244+ h = event .height * self .device_pixel_ratio
224245 if w < 3 or h < 3 :
225246 return # empty fig
226247 # resize the figure (in inches)
@@ -237,7 +258,8 @@ def _post_draw(self, widget, ctx):
237258 if self ._rubberband_rect is None :
238259 return
239260
240- x0 , y0 , w , h = self ._rubberband_rect
261+ x0 , y0 , w , h = (dim / self .device_pixel_ratio
262+ for dim in self ._rubberband_rect )
241263 x1 = x0 + w
242264 y1 = y0 + h
243265
0 commit comments