Fossil SCM
This check-in started as a follow-up to [55f56e91ba] to make the tooltip less hasty, and prevent it from being instantly reshown (and slightly moved) when the mouse pointer goes back from the tooltip to the owning node. The final result is a combined and simplified "mousemove" handler for both nodes and rails, with consistent tooltip lifetime: the tooltip is only closed if the mouse pointer is at a fixed point over another element for the (full) duration of the dwell timeout, or away from the owning element (and the tooltip) for the (full) duration of the close timeout. This check-in also improves positioning of the tooltip for longer dwell timeouts.
Commit
1fc616382820b859e680cfcc5f56b51686ddfa52a4109d323963cd041e177446
Parent
55f56e91badb50c…
1 file changed
+23
-34
+23
-34
| --- src/graph.js | ||
| +++ src/graph.js | ||
| @@ -116,10 +116,11 @@ | ||
| 116 | 116 | ** over a graph node. Or -1 when the mouse is not |
| 117 | 117 | ** over anything. */ |
| 118 | 118 | ixActive: -1, /* The item shown in the tooltip is tx.rowinfo[ixActive]. |
| 119 | 119 | ** ixActive is -1 if the tooltip is not visible */ |
| 120 | 120 | nodeHover: null, /* Graph node under mouse when ixHover==-2 */ |
| 121 | + idNodeActive: 0, /* Element ID of the graph node with the tooltip. */ | |
| 121 | 122 | posX: 0, posY: 0 /* The last mouse position. */ |
| 122 | 123 | }; |
| 123 | 124 | |
| 124 | 125 | /* Functions used to control the tooltip popup and its timer */ |
| 125 | 126 | function onKeyDown(event){ /* Hide the tooltip when ESC key pressed */ |
| @@ -132,10 +133,11 @@ | ||
| 132 | 133 | function hideGraphTooltip(){ /* Hide the tooltip */ |
| 133 | 134 | document.removeEventListener('keydown',onKeyDown,/* useCapture == */true); |
| 134 | 135 | stopCloseTimer(); |
| 135 | 136 | tooltipObj.style.display = "none"; |
| 136 | 137 | tooltipInfo.ixActive = -1; |
| 138 | + tooltipInfo.idNodeActive = 0; | |
| 137 | 139 | } |
| 138 | 140 | document.body.onunload = hideGraphTooltip |
| 139 | 141 | function stopDwellTimer(){ |
| 140 | 142 | if(tooltipInfo.idTimer!=0){ |
| 141 | 143 | clearTimeout(tooltipInfo.idTimer); |
| @@ -169,36 +171,11 @@ | ||
| 169 | 171 | topObj.onclick = clickOnGraph |
| 170 | 172 | topObj.ondblclick = dblclickOnGraph |
| 171 | 173 | topObj.onmousemove = function(e) { |
| 172 | 174 | var ix = findTxIndex(e); |
| 173 | 175 | topObj.style.cursor = (ix<0) ? "" : "pointer" |
| 174 | - /* Keep the already visible tooltip at a constant position, as long as the | |
| 175 | - ** mouse is over the same element. */ | |
| 176 | - if(tooltipObj.style.display != "none"){ | |
| 177 | - if(ix == tooltipInfo.ixHover) return; | |
| 178 | - } | |
| 179 | - /* The tooltip is either not visible, or the mouse is over a different | |
| 180 | - ** element, so clear the dwell timer, and record the new element id and | |
| 181 | - ** mouse position. */ | |
| 182 | - stopDwellTimer(); | |
| 183 | - if(ix >= 0){ | |
| 184 | - tooltipInfo.ixHover = ix; | |
| 185 | - tooltipInfo.posX = e.clientX; | |
| 186 | - tooltipInfo.posY = e.clientY; | |
| 187 | - stopCloseTimer(); | |
| 188 | - if(tooltipInfo.dwellTimeout>0){ | |
| 189 | - tooltipInfo.idTimer = setTimeout(function() { | |
| 190 | - tooltipInfo.idTimer = 0; | |
| 191 | - stopCloseTimer(); | |
| 192 | - showGraphTooltip(); | |
| 193 | - },tooltipInfo.dwellTimeout); | |
| 194 | - } | |
| 195 | - }else{ | |
| 196 | - /* The mouse is not over an element with a tooltip */ | |
| 197 | - tooltipInfo.ixHover = -1; | |
| 198 | - resumeCloseTimer(); | |
| 199 | - } | |
| 176 | + mouseOverGraph(e,ix,null); | |
| 200 | 177 | }; |
| 201 | 178 | topObj.onmouseleave = function(e) { |
| 202 | 179 | /* Hide the tooltip if the mouse is outside the "timelineTableN" element, |
| 203 | 180 | ** and outside the tooltip. */ |
| 204 | 181 | if(e.relatedTarget && e.relatedTarget != tooltipObj){ |
| @@ -208,18 +185,26 @@ | ||
| 208 | 185 | stopCloseTimer(); |
| 209 | 186 | } |
| 210 | 187 | }; |
| 211 | 188 | function mouseOverNode(e){ /* Invoked by mousemove events over a graph node */ |
| 212 | 189 | e.stopPropagation() |
| 213 | - if(tooltipInfo.ixHover==-2) return | |
| 214 | - tooltipInfo.ixHover = -2 | |
| 215 | - tooltipInfo.posX = e.clientX | |
| 216 | - tooltipInfo.posY = e.clientY | |
| 217 | - tooltipInfo.nodeHover = this | |
| 218 | - stopCloseTimer(); | |
| 219 | - if(tooltipInfo.dwellTimeout>0){ | |
| 220 | - tooltipInfo.idTimer = setTimeout(function() { | |
| 190 | + mouseOverGraph(e,-2,this) | |
| 191 | + } | |
| 192 | + /* Combined mousemove handler for graph nodes and rails. */ | |
| 193 | + function mouseOverGraph(e,ix,node){ | |
| 194 | + stopDwellTimer(); // Mouse movement: reset the dwell timer. | |
| 195 | + var ownTooltip = // Check if the hovered element already has the tooltip. | |
| 196 | + (ix!=-2 && ix==tooltipInfo.ixHover && ix==tooltipInfo.ixActive) || | |
| 197 | + (ix==-2 && tooltipInfo.ixHover==-2 && tooltipInfo.idNodeActive==node.id); | |
| 198 | + if(ownTooltip) stopCloseTimer(); // ownTooltip: clear the close timer. | |
| 199 | + else resumeCloseTimer(); // !ownTooltip: resume the close timer. | |
| 200 | + tooltipInfo.ixHover = ix; | |
| 201 | + tooltipInfo.nodeHover = node; | |
| 202 | + tooltipInfo.posX = e.clientX; | |
| 203 | + tooltipInfo.posY = e.clientY; | |
| 204 | + if(ix!=-1 && !ownTooltip && tooltipInfo.dwellTimeout>0){ // Go dwell timer. | |
| 205 | + tooltipInfo.idTimer = setTimeout(function(){ | |
| 221 | 206 | tooltipInfo.idTimer = 0; |
| 222 | 207 | stopCloseTimer(); |
| 223 | 208 | showGraphTooltip(); |
| 224 | 209 | },tooltipInfo.dwellTimeout); |
| 225 | 210 | } |
| @@ -605,10 +590,12 @@ | ||
| 605 | 590 | dest += tx.fileDiff ? "&m&cf=" : "&m&c=" |
| 606 | 591 | dest += encodeURIComponent(tx.rowinfo[ix].h) |
| 607 | 592 | return dest |
| 608 | 593 | } |
| 609 | 594 | function clickOnGraph(e){ |
| 595 | + stopCloseTimer(); | |
| 596 | + stopDwellTimer(); | |
| 610 | 597 | tooltipInfo.ixHover = findTxIndex(e); |
| 611 | 598 | tooltipInfo.posX = e.clientX; |
| 612 | 599 | tooltipInfo.posY = e.clientY; |
| 613 | 600 | showGraphTooltip(); |
| 614 | 601 | } |
| @@ -624,10 +611,11 @@ | ||
| 624 | 611 | html = "artifact <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>" |
| 625 | 612 | }else{ |
| 626 | 613 | html = "check-in <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>" |
| 627 | 614 | } |
| 628 | 615 | tooltipInfo.ixActive = -2; |
| 616 | + tooltipInfo.idNodeActive = tooltipInfo.nodeHover.id; | |
| 629 | 617 | }else if( tooltipInfo.ixHover>=0 ){ |
| 630 | 618 | ix = tooltipInfo.ixHover |
| 631 | 619 | var br = tx.rowinfo[ix].br |
| 632 | 620 | var dest = branchHyperlink(ix) |
| 633 | 621 | var hbr = br.replace(/&/g, "&") |
| @@ -635,10 +623,11 @@ | ||
| 635 | 623 | .replace(/>/g, ">") |
| 636 | 624 | .replace(/"/g, """) |
| 637 | 625 | .replace(/'/g, "'"); |
| 638 | 626 | html = "branch <a id=\"tooltip-link\" href=\""+dest+"\">"+hbr+"</a>" |
| 639 | 627 | tooltipInfo.ixActive = ix; |
| 628 | + tooltipInfo.idNodeActive = 0; | |
| 640 | 629 | } |
| 641 | 630 | if( html ){ |
| 642 | 631 | /* Setup while hidden, to ensure proper dimensions. */ |
| 643 | 632 | var s = getComputedStyle(document.body) |
| 644 | 633 | if( tx.rowinfo[ix].bg.length ){ |
| 645 | 634 |
| --- src/graph.js | |
| +++ src/graph.js | |
| @@ -116,10 +116,11 @@ | |
| 116 | ** over a graph node. Or -1 when the mouse is not |
| 117 | ** over anything. */ |
| 118 | ixActive: -1, /* The item shown in the tooltip is tx.rowinfo[ixActive]. |
| 119 | ** ixActive is -1 if the tooltip is not visible */ |
| 120 | nodeHover: null, /* Graph node under mouse when ixHover==-2 */ |
| 121 | posX: 0, posY: 0 /* The last mouse position. */ |
| 122 | }; |
| 123 | |
| 124 | /* Functions used to control the tooltip popup and its timer */ |
| 125 | function onKeyDown(event){ /* Hide the tooltip when ESC key pressed */ |
| @@ -132,10 +133,11 @@ | |
| 132 | function hideGraphTooltip(){ /* Hide the tooltip */ |
| 133 | document.removeEventListener('keydown',onKeyDown,/* useCapture == */true); |
| 134 | stopCloseTimer(); |
| 135 | tooltipObj.style.display = "none"; |
| 136 | tooltipInfo.ixActive = -1; |
| 137 | } |
| 138 | document.body.onunload = hideGraphTooltip |
| 139 | function stopDwellTimer(){ |
| 140 | if(tooltipInfo.idTimer!=0){ |
| 141 | clearTimeout(tooltipInfo.idTimer); |
| @@ -169,36 +171,11 @@ | |
| 169 | topObj.onclick = clickOnGraph |
| 170 | topObj.ondblclick = dblclickOnGraph |
| 171 | topObj.onmousemove = function(e) { |
| 172 | var ix = findTxIndex(e); |
| 173 | topObj.style.cursor = (ix<0) ? "" : "pointer" |
| 174 | /* Keep the already visible tooltip at a constant position, as long as the |
| 175 | ** mouse is over the same element. */ |
| 176 | if(tooltipObj.style.display != "none"){ |
| 177 | if(ix == tooltipInfo.ixHover) return; |
| 178 | } |
| 179 | /* The tooltip is either not visible, or the mouse is over a different |
| 180 | ** element, so clear the dwell timer, and record the new element id and |
| 181 | ** mouse position. */ |
| 182 | stopDwellTimer(); |
| 183 | if(ix >= 0){ |
| 184 | tooltipInfo.ixHover = ix; |
| 185 | tooltipInfo.posX = e.clientX; |
| 186 | tooltipInfo.posY = e.clientY; |
| 187 | stopCloseTimer(); |
| 188 | if(tooltipInfo.dwellTimeout>0){ |
| 189 | tooltipInfo.idTimer = setTimeout(function() { |
| 190 | tooltipInfo.idTimer = 0; |
| 191 | stopCloseTimer(); |
| 192 | showGraphTooltip(); |
| 193 | },tooltipInfo.dwellTimeout); |
| 194 | } |
| 195 | }else{ |
| 196 | /* The mouse is not over an element with a tooltip */ |
| 197 | tooltipInfo.ixHover = -1; |
| 198 | resumeCloseTimer(); |
| 199 | } |
| 200 | }; |
| 201 | topObj.onmouseleave = function(e) { |
| 202 | /* Hide the tooltip if the mouse is outside the "timelineTableN" element, |
| 203 | ** and outside the tooltip. */ |
| 204 | if(e.relatedTarget && e.relatedTarget != tooltipObj){ |
| @@ -208,18 +185,26 @@ | |
| 208 | stopCloseTimer(); |
| 209 | } |
| 210 | }; |
| 211 | function mouseOverNode(e){ /* Invoked by mousemove events over a graph node */ |
| 212 | e.stopPropagation() |
| 213 | if(tooltipInfo.ixHover==-2) return |
| 214 | tooltipInfo.ixHover = -2 |
| 215 | tooltipInfo.posX = e.clientX |
| 216 | tooltipInfo.posY = e.clientY |
| 217 | tooltipInfo.nodeHover = this |
| 218 | stopCloseTimer(); |
| 219 | if(tooltipInfo.dwellTimeout>0){ |
| 220 | tooltipInfo.idTimer = setTimeout(function() { |
| 221 | tooltipInfo.idTimer = 0; |
| 222 | stopCloseTimer(); |
| 223 | showGraphTooltip(); |
| 224 | },tooltipInfo.dwellTimeout); |
| 225 | } |
| @@ -605,10 +590,12 @@ | |
| 605 | dest += tx.fileDiff ? "&m&cf=" : "&m&c=" |
| 606 | dest += encodeURIComponent(tx.rowinfo[ix].h) |
| 607 | return dest |
| 608 | } |
| 609 | function clickOnGraph(e){ |
| 610 | tooltipInfo.ixHover = findTxIndex(e); |
| 611 | tooltipInfo.posX = e.clientX; |
| 612 | tooltipInfo.posY = e.clientY; |
| 613 | showGraphTooltip(); |
| 614 | } |
| @@ -624,10 +611,11 @@ | |
| 624 | html = "artifact <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>" |
| 625 | }else{ |
| 626 | html = "check-in <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>" |
| 627 | } |
| 628 | tooltipInfo.ixActive = -2; |
| 629 | }else if( tooltipInfo.ixHover>=0 ){ |
| 630 | ix = tooltipInfo.ixHover |
| 631 | var br = tx.rowinfo[ix].br |
| 632 | var dest = branchHyperlink(ix) |
| 633 | var hbr = br.replace(/&/g, "&") |
| @@ -635,10 +623,11 @@ | |
| 635 | .replace(/>/g, ">") |
| 636 | .replace(/"/g, """) |
| 637 | .replace(/'/g, "'"); |
| 638 | html = "branch <a id=\"tooltip-link\" href=\""+dest+"\">"+hbr+"</a>" |
| 639 | tooltipInfo.ixActive = ix; |
| 640 | } |
| 641 | if( html ){ |
| 642 | /* Setup while hidden, to ensure proper dimensions. */ |
| 643 | var s = getComputedStyle(document.body) |
| 644 | if( tx.rowinfo[ix].bg.length ){ |
| 645 |
| --- src/graph.js | |
| +++ src/graph.js | |
| @@ -116,10 +116,11 @@ | |
| 116 | ** over a graph node. Or -1 when the mouse is not |
| 117 | ** over anything. */ |
| 118 | ixActive: -1, /* The item shown in the tooltip is tx.rowinfo[ixActive]. |
| 119 | ** ixActive is -1 if the tooltip is not visible */ |
| 120 | nodeHover: null, /* Graph node under mouse when ixHover==-2 */ |
| 121 | idNodeActive: 0, /* Element ID of the graph node with the tooltip. */ |
| 122 | posX: 0, posY: 0 /* The last mouse position. */ |
| 123 | }; |
| 124 | |
| 125 | /* Functions used to control the tooltip popup and its timer */ |
| 126 | function onKeyDown(event){ /* Hide the tooltip when ESC key pressed */ |
| @@ -132,10 +133,11 @@ | |
| 133 | function hideGraphTooltip(){ /* Hide the tooltip */ |
| 134 | document.removeEventListener('keydown',onKeyDown,/* useCapture == */true); |
| 135 | stopCloseTimer(); |
| 136 | tooltipObj.style.display = "none"; |
| 137 | tooltipInfo.ixActive = -1; |
| 138 | tooltipInfo.idNodeActive = 0; |
| 139 | } |
| 140 | document.body.onunload = hideGraphTooltip |
| 141 | function stopDwellTimer(){ |
| 142 | if(tooltipInfo.idTimer!=0){ |
| 143 | clearTimeout(tooltipInfo.idTimer); |
| @@ -169,36 +171,11 @@ | |
| 171 | topObj.onclick = clickOnGraph |
| 172 | topObj.ondblclick = dblclickOnGraph |
| 173 | topObj.onmousemove = function(e) { |
| 174 | var ix = findTxIndex(e); |
| 175 | topObj.style.cursor = (ix<0) ? "" : "pointer" |
| 176 | mouseOverGraph(e,ix,null); |
| 177 | }; |
| 178 | topObj.onmouseleave = function(e) { |
| 179 | /* Hide the tooltip if the mouse is outside the "timelineTableN" element, |
| 180 | ** and outside the tooltip. */ |
| 181 | if(e.relatedTarget && e.relatedTarget != tooltipObj){ |
| @@ -208,18 +185,26 @@ | |
| 185 | stopCloseTimer(); |
| 186 | } |
| 187 | }; |
| 188 | function mouseOverNode(e){ /* Invoked by mousemove events over a graph node */ |
| 189 | e.stopPropagation() |
| 190 | mouseOverGraph(e,-2,this) |
| 191 | } |
| 192 | /* Combined mousemove handler for graph nodes and rails. */ |
| 193 | function mouseOverGraph(e,ix,node){ |
| 194 | stopDwellTimer(); // Mouse movement: reset the dwell timer. |
| 195 | var ownTooltip = // Check if the hovered element already has the tooltip. |
| 196 | (ix!=-2 && ix==tooltipInfo.ixHover && ix==tooltipInfo.ixActive) || |
| 197 | (ix==-2 && tooltipInfo.ixHover==-2 && tooltipInfo.idNodeActive==node.id); |
| 198 | if(ownTooltip) stopCloseTimer(); // ownTooltip: clear the close timer. |
| 199 | else resumeCloseTimer(); // !ownTooltip: resume the close timer. |
| 200 | tooltipInfo.ixHover = ix; |
| 201 | tooltipInfo.nodeHover = node; |
| 202 | tooltipInfo.posX = e.clientX; |
| 203 | tooltipInfo.posY = e.clientY; |
| 204 | if(ix!=-1 && !ownTooltip && tooltipInfo.dwellTimeout>0){ // Go dwell timer. |
| 205 | tooltipInfo.idTimer = setTimeout(function(){ |
| 206 | tooltipInfo.idTimer = 0; |
| 207 | stopCloseTimer(); |
| 208 | showGraphTooltip(); |
| 209 | },tooltipInfo.dwellTimeout); |
| 210 | } |
| @@ -605,10 +590,12 @@ | |
| 590 | dest += tx.fileDiff ? "&m&cf=" : "&m&c=" |
| 591 | dest += encodeURIComponent(tx.rowinfo[ix].h) |
| 592 | return dest |
| 593 | } |
| 594 | function clickOnGraph(e){ |
| 595 | stopCloseTimer(); |
| 596 | stopDwellTimer(); |
| 597 | tooltipInfo.ixHover = findTxIndex(e); |
| 598 | tooltipInfo.posX = e.clientX; |
| 599 | tooltipInfo.posY = e.clientY; |
| 600 | showGraphTooltip(); |
| 601 | } |
| @@ -624,10 +611,11 @@ | |
| 611 | html = "artifact <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>" |
| 612 | }else{ |
| 613 | html = "check-in <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>" |
| 614 | } |
| 615 | tooltipInfo.ixActive = -2; |
| 616 | tooltipInfo.idNodeActive = tooltipInfo.nodeHover.id; |
| 617 | }else if( tooltipInfo.ixHover>=0 ){ |
| 618 | ix = tooltipInfo.ixHover |
| 619 | var br = tx.rowinfo[ix].br |
| 620 | var dest = branchHyperlink(ix) |
| 621 | var hbr = br.replace(/&/g, "&") |
| @@ -635,10 +623,11 @@ | |
| 623 | .replace(/>/g, ">") |
| 624 | .replace(/"/g, """) |
| 625 | .replace(/'/g, "'"); |
| 626 | html = "branch <a id=\"tooltip-link\" href=\""+dest+"\">"+hbr+"</a>" |
| 627 | tooltipInfo.ixActive = ix; |
| 628 | tooltipInfo.idNodeActive = 0; |
| 629 | } |
| 630 | if( html ){ |
| 631 | /* Setup while hidden, to ensure proper dimensions. */ |
| 632 | var s = getComputedStyle(document.body) |
| 633 | if( tx.rowinfo[ix].bg.length ){ |
| 634 |