From 3568a9dffce047a7879e526d5bd407a51ca500ef Mon Sep 17 00:00:00 2001 From: Kyle Sunden Date: Mon, 31 Oct 2022 18:39:06 -0500 Subject: [PATCH] Backport PR #24095: nb/webagg: Move mouse events to outer canvas div --- lib/matplotlib/backends/web_backend/js/mpl.js | 105 +++++++++--------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/lib/matplotlib/backends/web_backend/js/mpl.js b/lib/matplotlib/backends/web_backend/js/mpl.js index 3909f786f301..e77ba1aaf9e9 100644 --- a/lib/matplotlib/backends/web_backend/js/mpl.js +++ b/lib/matplotlib/backends/web_backend/js/mpl.js @@ -112,6 +112,7 @@ mpl.figure.prototype._init_canvas = function () { var fig = this; var canvas_div = (this.canvas_div = document.createElement('div')); + canvas_div.setAttribute('tabindex', '0'); canvas_div.setAttribute( 'style', 'border: 1px solid #ddd;' + @@ -122,7 +123,8 @@ mpl.figure.prototype._init_canvas = function () { 'outline: 0;' + 'overflow: hidden;' + 'position: relative;' + - 'resize: both;' + 'resize: both;' + + 'z-index: 2;' ); function on_keyboard_event_closure(name) { @@ -145,7 +147,13 @@ mpl.figure.prototype._init_canvas = function () { var canvas = (this.canvas = document.createElement('canvas')); canvas.classList.add('mpl-canvas'); - canvas.setAttribute('style', 'box-sizing: content-box;'); + canvas.setAttribute( + 'style', + 'box-sizing: content-box;' + + 'pointer-events: none;' + + 'position: relative;' + + 'z-index: 0;' + ); this.context = canvas.getContext('2d'); @@ -165,7 +173,12 @@ mpl.figure.prototype._init_canvas = function () { )); rubberband_canvas.setAttribute( 'style', - 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;' + 'box-sizing: content-box;' + + 'left: 0;' + + 'pointer-events: none;' + + 'position: absolute;' + + 'top: 0;' + + 'z-index: 1;' ); // Apply a ponyfill if ResizeObserver is not implemented by browser. @@ -215,10 +228,10 @@ mpl.figure.prototype._init_canvas = function () { canvas.setAttribute('width', width * fig.ratio); canvas.setAttribute('height', height * fig.ratio); } - canvas.setAttribute( - 'style', - 'width: ' + width + 'px; height: ' + height + 'px;' - ); + /* This rescales the canvas back to display pixels, so that it + * appears correct on HiDPI screens. */ + canvas.style.width = width + 'px'; + canvas.style.height = height + 'px'; rubberband_canvas.setAttribute('width', width); rubberband_canvas.setAttribute('height', height); @@ -234,34 +247,53 @@ mpl.figure.prototype._init_canvas = function () { this.resizeObserverInstance.observe(canvas_div); function on_mouse_event_closure(name) { - return function (event) { - return fig.mouse_event(event, name); - }; + /* User Agent sniffing is bad, but WebKit is busted: + * https://bugs.webkit.org/show_bug.cgi?id=144526 + * https://bugs.webkit.org/show_bug.cgi?id=181818 + * The worst that happens here is that they get an extra browser + * selection when dragging, if this check fails to catch them. + */ + var UA = navigator.userAgent; + var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA); + if(isWebKit) { + return function (event) { + /* This prevents the web browser from automatically changing to + * the text insertion cursor when the button is pressed. We + * want to control all of the cursor setting manually through + * the 'cursor' event from matplotlib */ + event.preventDefault() + return fig.mouse_event(event, name); + }; + } else { + return function (event) { + return fig.mouse_event(event, name); + }; + } } - rubberband_canvas.addEventListener( + canvas_div.addEventListener( 'mousedown', on_mouse_event_closure('button_press') ); - rubberband_canvas.addEventListener( + canvas_div.addEventListener( 'mouseup', on_mouse_event_closure('button_release') ); - rubberband_canvas.addEventListener( + canvas_div.addEventListener( 'dblclick', on_mouse_event_closure('dblclick') ); // Throttle sequential mouse events to 1 every 20ms. - rubberband_canvas.addEventListener( + canvas_div.addEventListener( 'mousemove', on_mouse_event_closure('motion_notify') ); - rubberband_canvas.addEventListener( + canvas_div.addEventListener( 'mouseenter', on_mouse_event_closure('figure_enter') ); - rubberband_canvas.addEventListener( + canvas_div.addEventListener( 'mouseleave', on_mouse_event_closure('figure_leave') ); @@ -289,7 +321,7 @@ mpl.figure.prototype._init_canvas = function () { }; // Disable right mouse context menu. - this.rubberband_canvas.addEventListener('contextmenu', function (_e) { + canvas_div.addEventListener('contextmenu', function (_e) { event.preventDefault(); return false; }); @@ -444,7 +476,7 @@ mpl.figure.prototype.handle_figure_label = function (fig, msg) { }; mpl.figure.prototype.handle_cursor = function (fig, msg) { - fig.rubberband_canvas.style.cursor = msg['cursor']; + fig.canvas_div.style.cursor = msg['cursor']; }; mpl.figure.prototype.handle_message = function (fig, msg) { @@ -556,30 +588,6 @@ mpl.figure.prototype._make_on_message_function = function (fig) { }; }; -// from https://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas -mpl.findpos = function (e) { - //this section is from http://www.quirksmode.org/js/events_properties.html - var targ; - if (!e) { - e = window.event; - } - if (e.target) { - targ = e.target; - } else if (e.srcElement) { - targ = e.srcElement; - } - if (targ.nodeType === 3) { - // defeat Safari bug - targ = targ.parentNode; - } - - // pageX,Y are the mouse positions relative to the document - var boundingRect = targ.getBoundingClientRect(); - var x = e.pageX - (boundingRect.left + document.body.scrollLeft); - var y = e.pageY - (boundingRect.top + document.body.scrollTop); - - return { x: x, y: y }; -}; /* * return a copy of an object with only non-object keys @@ -596,15 +604,15 @@ function simpleKeys(original) { } mpl.figure.prototype.mouse_event = function (event, name) { - var canvas_pos = mpl.findpos(event); - if (name === 'button_press') { this.canvas.focus(); this.canvas_div.focus(); } - var x = canvas_pos.x * this.ratio; - var y = canvas_pos.y * this.ratio; + // from https://stackoverflow.com/q/1114465 + var boundingRect = this.canvas.getBoundingClientRect(); + var x = (event.clientX - boundingRect.left) * this.ratio; + var y = (event.clientY - boundingRect.top) * this.ratio; this.send_message(name, { x: x, @@ -614,11 +622,6 @@ mpl.figure.prototype.mouse_event = function (event, name) { guiEvent: simpleKeys(event), }); - /* This prevents the web browser from automatically changing to - * the text insertion cursor when the button is pressed. We want - * to control all of the cursor setting manually through the - * 'cursor' event from matplotlib */ - event.preventDefault(); return false; };