Ticket #24842: pan-reuse.patch
| File pan-reuse.patch, 24.5 KB (added by , 4 weeks ago) |
|---|
-
src/org/openstreetmap/josm/actions/mapmode/SelectAction.java
diff --git a/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java b/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java index f9ee08d..8ac948f 100644
a b import static org.openstreetmap.josm.tools.I18n.tr; 6 6 import static org.openstreetmap.josm.tools.I18n.trc; 7 7 import static org.openstreetmap.josm.tools.I18n.trn; 8 8 9 import java.awt.BasicStroke; 10 import java.awt.Color; 9 11 import java.awt.Cursor; 12 import java.awt.Graphics2D; 10 13 import java.awt.Point; 11 14 import java.awt.Rectangle; 12 15 import java.awt.event.InputEvent; 13 16 import java.awt.event.KeyEvent; 14 17 import java.awt.event.MouseEvent; 18 import java.awt.geom.Path2D; 15 19 import java.awt.geom.Point2D; 16 20 import java.util.Collection; 17 21 import java.util.Collections; … … import org.openstreetmap.josm.command.MoveCommand; 33 37 import org.openstreetmap.josm.command.RotateCommand; 34 38 import org.openstreetmap.josm.command.ScaleCommand; 35 39 import org.openstreetmap.josm.command.SequenceCommand; 40 import org.openstreetmap.josm.data.Bounds; 36 41 import org.openstreetmap.josm.data.SystemOfMeasurement; 37 42 import org.openstreetmap.josm.data.UndoRedoHandler; 38 43 import org.openstreetmap.josm.data.coor.EastNorth; … … import org.openstreetmap.josm.data.osm.Way; 44 49 import org.openstreetmap.josm.data.osm.WaySegment; 45 50 import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor; 46 51 import org.openstreetmap.josm.data.osm.visitor.paint.AbstractMapRenderer; 52 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors; 47 53 import org.openstreetmap.josm.data.preferences.BooleanProperty; 48 54 import org.openstreetmap.josm.data.preferences.CachingProperty; 49 55 import org.openstreetmap.josm.gui.ExtendedDialog; … … import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 54 60 import org.openstreetmap.josm.gui.SelectionManager; 55 61 import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded; 56 62 import org.openstreetmap.josm.gui.layer.Layer; 63 import org.openstreetmap.josm.gui.layer.MapViewPaintable; 57 64 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 58 65 import org.openstreetmap.josm.gui.util.GuiHelper; 59 66 import org.openstreetmap.josm.gui.util.KeyPressReleaseListener; … … import org.openstreetmap.josm.tools.Utils; 77 84 * On Mac OS X, Ctrl + mouse button 1 simulates right click (map move), so the 78 85 * feature "selection remove" is disabled on this platform. 79 86 */ 80 public class SelectAction extends MapMode implements ModifierExListener, KeyPressReleaseListener, SelectionEnded {87 public class SelectAction extends MapMode implements ModifierExListener, KeyPressReleaseListener, SelectionEnded, MapViewPaintable { 81 88 82 89 private static final String NORMAL = /* ICON(cursor/)*/ "normal"; 83 90 … … public class SelectAction extends MapMode implements ModifierExListener, KeyPres 197 204 * set would have to be checked. 198 205 */ 199 206 private transient OsmPrimitive currentHighlight; 207 // Whether currentHighlight was set via OsmPrimitive.setHighlighted (merge-mode drag). 208 // Only then does it need to be unset again when the highlight changes. 209 private boolean dataLayerHighlight; 200 210 201 211 /** 202 212 * Create a new SelectAction … … public class SelectAction extends MapMode implements ModifierExListener, KeyPres 218 228 mv.addMouseMotionListener(this); 219 229 mv.setVirtualNodesEnabled(Config.getPref().getInt("mappaint.node.virtual-size", 8) != 0); 220 230 drawTargetHighlight = Config.getPref().getBoolean("draw.target-highlight", true); 231 mv.addTemporaryLayer(this); // draws the hover highlight without invalidating the data layer 221 232 initialMoveDelay = Config.getPref().getInt("edit.initial-move-delay", 200); 222 233 initialMoveThreshold = Config.getPref().getInt("edit.initial-move-threshold", 5); 223 234 repeatedKeySwitchLassoOption = Config.getPref().getBoolean("mappaint.select.toggle-lasso-on-repeated-S", true); … … public class SelectAction extends MapMode implements ModifierExListener, KeyPres 242 253 map.keyDetector.removeModifierExListener(this); 243 254 map.keyDetector.removeKeyListener(this); 244 255 removeHighlighting(); 256 mv.removeTemporaryLayer(this); 245 257 virtualManager.clear(); 246 258 } 247 259 … … public class SelectAction extends MapMode implements ModifierExListener, KeyPres 271 283 * @return {@code true} if repaint is required 272 284 */ 273 285 private boolean giveUserFeedback(MouseEvent e, int modifiers) { 286 if (mv.isPanning()) { 287 // Don't hover-highlight while panning: a highlight change invalidates the 288 // data layer and forces a full re-render, breaking the pan-reuse path. 289 return false; 290 } 274 291 Optional<OsmPrimitive> c = Optional.ofNullable( 275 292 mv.getNearestNodeOrWay(e.getPoint(), mv.isSelectablePredicate, true)); 276 293 … … public class SelectAction extends MapMode implements ModifierExListener, KeyPres 380 397 if (currentHighlight == null) { 381 398 return needsRepaint; 382 399 } 383 currentHighlight.setHighlighted(false); 400 if (dataLayerHighlight) { 401 currentHighlight.setHighlighted(false); 402 dataLayerHighlight = false; 403 } 384 404 currentHighlight = null; 385 405 return true; 386 406 } … … public class SelectAction extends MapMode implements ModifierExListener, KeyPres 388 408 private boolean repaintIfRequired(OsmPrimitive newHighlight) { 389 409 if (!drawTargetHighlight || Objects.equals(currentHighlight, newHighlight)) 390 410 return false; 391 if (currentHighlight != null) { 411 // The highlighted primitive is drawn by this action's temporary layer (see paint), 412 // not via OsmPrimitive.setHighlighted, which would invalidate the data layer. 413 if (dataLayerHighlight) { 392 414 currentHighlight.setHighlighted(false); 393 } 394 if (newHighlight != null) { 395 newHighlight.setHighlighted(true); 415 dataLayerHighlight = false; 396 416 } 397 417 currentHighlight = newHighlight; 398 418 return true; 399 419 } 400 420 421 /** 422 * Draws the currently highlighted (hovered) primitive as an overlay on top of the map. 423 * This replaces the old highlight via {@link OsmPrimitive#setHighlighted}, which 424 * invalidated the data layer on every feature crossing and forced a full re-render. 425 */ 426 @Override 427 public void paint(Graphics2D g, MapView mv, Bounds bbox) { 428 OsmPrimitive p = currentHighlight; 429 if (p == null || !drawTargetHighlight) { 430 return; 431 } 432 Color highlight = PaintColors.HIGHLIGHT.get(); 433 Color transparent = new Color(highlight.getRed(), highlight.getGreen(), highlight.getBlue(), 100); 434 if (p instanceof Node) { 435 Point2D pt = mv.getPoint2D((Node) p); 436 int radius = Config.getPref().getInt("mappaint.highlight.radius", 7) + 4; 437 int x = (int) Math.round(pt.getX()); 438 int y = (int) Math.round(pt.getY()); 439 g.setColor(transparent); 440 g.fillOval(x - radius, y - radius, 2 * radius, 2 * radius); 441 } else if (p instanceof Way) { 442 Path2D path = new Path2D.Double(); 443 boolean first = true; 444 for (Node n : ((Way) p).getNodes()) { 445 if (n == null || n.isIncomplete()) { 446 continue; 447 } 448 Point2D pt = mv.getPoint2D(n); 449 if (first) { 450 path.moveTo(pt.getX(), pt.getY()); 451 first = false; 452 } else { 453 path.lineTo(pt.getX(), pt.getY()); 454 } 455 } 456 g.setColor(transparent); 457 g.setStroke(new BasicStroke(Config.getPref().getInt("mappaint.highlight.width", 4) + 4, 458 BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); 459 g.draw(path); 460 } 461 } 462 401 463 /** 402 464 * Look, whether any object is selected. If not, select the nearest node. 403 465 * If there are no nodes in the dataset, do nothing. … … public class SelectAction extends MapMode implements ModifierExListener, KeyPres 531 593 if (p != null) { 532 594 p.setHighlighted(true); 533 595 currentHighlight = p; 596 dataLayerHighlight = true; 534 597 needsRepaint = true; 535 598 } 536 599 mv.setNewCursor(getCursor(p), this); -
src/org/openstreetmap/josm/gui/MapMover.java
diff --git a/src/org/openstreetmap/josm/gui/MapMover.java b/src/org/openstreetmap/josm/gui/MapMover.java index 86cbaa8..887a7a1 100644
a b import org.openstreetmap.josm.actions.mapmode.SelectAction; 21 21 import org.openstreetmap.josm.data.coor.EastNorth; 22 22 import org.openstreetmap.josm.data.preferences.BooleanProperty; 23 23 import org.openstreetmap.josm.gui.MapViewState.MapViewPoint; 24 import org.openstreetmap.josm.gui.layer.Layer;25 24 import org.openstreetmap.josm.spi.preferences.Config; 26 25 import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent; 27 26 import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener; … … public class MapMover extends MouseAdapter implements Destroyable { 244 243 } 245 244 nc.resetCursor(this); 246 245 mousePosMoveStart = null; 247 MainApplication.getLayerManager().getLayers().forEach(Layer::invalidate); 246 // Deliberately do not invalidate the layers here: a pan only moved the view, so 247 // the last frame's composite is still valid and the pan-reuse buffer stays alive. 248 248 } 249 249 250 250 /** -
src/org/openstreetmap/josm/gui/MapView.java
diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java index f4034e7..10320f1 100644
a b import org.openstreetmap.josm.gui.layer.MapViewPaintable; 69 69 import org.openstreetmap.josm.gui.layer.MapViewPaintable.LayerPainter; 70 70 import org.openstreetmap.josm.gui.layer.MapViewPaintable.MapViewEvent; 71 71 import org.openstreetmap.josm.gui.layer.MapViewPaintable.PaintableInvalidationEvent; 72 import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer; 72 73 import org.openstreetmap.josm.gui.layer.MapViewPaintable.PaintableInvalidationListener; 73 74 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 74 75 import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker; … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 236 237 237 238 private transient BufferedImage nonChangedLayersBuffer; 238 239 private transient BufferedImage offscreenBuffer; 239 // Layers that wasn't changed since last paint240 private final transient List<Layer> nonChangedLayers = new ArrayList<>();241 240 private int lastViewID; 241 // View the composite in nonChangedLayersBuffer was painted at. Needed to reuse the 242 // buffer for a translated view; lastViewID only detects equality, not the pan delta. 243 private double lastPaintedScale; 244 private EastNorth lastPaintedCenter; 245 // The layer set the composite was painted with; hide/show/add/remove changes it. 246 private List<Layer> lastPaintedLayers; 242 247 private final AtomicBoolean paintPreferencesChanged = new AtomicBoolean(true); 243 248 private Rectangle lastClipBounds = new Rectangle(); 244 249 private transient MapMover mapMover; … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 491 496 } 492 497 } 493 498 499 /** 500 * Determines if the map is currently being panned (right-button drag in progress). 501 * @return {@code true} if a pan is in progress 502 */ 503 public boolean isPanning() { 504 return mapMover != null && mapMover.movementInProgress(); 505 } 506 494 507 /** 495 508 * Draw the component. 496 509 */ … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 551 564 } 552 565 } 553 566 554 boolean canUseBuffer = !paintPreferencesChanged.getAndSet(false) 555 && nonChangedLayers.size() <= nonChangedLayersCount 567 boolean paintPrefsChanged = paintPreferencesChanged.getAndSet(false); 568 boolean sameLayerSet = lastPaintedLayers != null && lastPaintedLayers.equals(visibleLayers); 569 boolean canUseBuffer = !paintPrefsChanged 570 && sameLayerSet 571 && nonChangedLayersCount == visibleLayers.size() 556 572 && lastViewID == getViewID() 557 && lastClipBounds.contains(g.getClipBounds()) 558 && nonChangedLayers.equals(visibleLayers.subList(0, nonChangedLayers.size())); 573 && lastClipBounds.contains(g.getClipBounds()); 574 575 // Pan-reuse: when the view moved at constant zoom, nonChangedLayersBuffer holds the 576 // complete composite of all visible layers (copied back below), so the next frame is 577 // the old one shifted by the pan delta plus the newly exposed strip. Layers 578 // invalidated since the last frame (e.g. freshly loaded imagery tiles) are only 579 // repainted inside the strip; the rest is painted when the pan stops. 580 boolean viewMoved = lastViewID != getViewID(); 581 boolean scaleOk = Utils.equalsEpsilon(getScale(), lastPaintedScale); 582 boolean lastClipFull = lastClipBounds.contains(0, 0, width, height); 583 boolean canReusePanned = viewMoved 584 && !paintPrefsChanged 585 && sameLayerSet 586 && scaleOk 587 && lastClipFull 588 && nonChangedLayersBuffer != null 589 && nonChangedLayersBuffer.getWidth() == width && nonChangedLayersBuffer.getHeight() == height; 590 double panDX = 0; 591 double panDY = 0; 592 if (canReusePanned) { 593 EastNorth center = getCenter(); 594 panDX = (lastPaintedCenter.east() - center.east()) / lastPaintedScale; 595 panDY = (center.north() - lastPaintedCenter.north()) / lastPaintedScale; 596 // Sub-pixel movements (e.g. a projection change) fall back to the normal path 597 canReusePanned = Math.abs(panDX) < getWidth() && Math.abs(panDY) < getHeight() 598 && (Math.abs(panDX) >= 0.5 || Math.abs(panDY) >= 0.5); 599 } 600 if (Config.getPref().getBoolean("mappaint.debug.pan-reuse", false)) { 601 Logging.info("PANREUSE " + (canReusePanned ? "reuse" : "full") 602 + " canUse=" + canUseBuffer 603 + " prefs=" + paintPrefsChanged 604 + " count=" + nonChangedLayersCount + '/' + visibleLayers.size() 605 + " layerSet=" + sameLayerSet 606 + " buf=" + (nonChangedLayersBuffer != null 607 ? nonChangedLayersBuffer.getWidth() + "x" + nonChangedLayersBuffer.getHeight() : "null") 608 + " size=" + width + "x" + height 609 + " uiScale=" + uiScaleX + "x" + uiScaleY 610 + " clipFull=" + lastClipFull 611 + " scaleEq=" + scaleOk 612 + " delta=" + panDX + ',' + panDY 613 + " viewID=" + getViewID()); 614 } 559 615 560 616 if (null == offscreenBuffer || offscreenBuffer.getWidth() != width || offscreenBuffer.getHeight() != height) { 561 617 offscreenBuffer = getAcceleratedImage(this, width, height); 562 618 } 563 619 564 if (!can UseBuffer || nonChangedLayersBuffer == null) {620 if (!canReusePanned && (!canUseBuffer || nonChangedLayersBuffer == null)) { 565 621 if (null == nonChangedLayersBuffer 566 622 || nonChangedLayersBuffer.getWidth() != width || nonChangedLayersBuffer.getHeight() != height) { 567 623 nonChangedLayersBuffer = getAcceleratedImage(this, width, height); … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 575 631 for (int i = 0; i < nonChangedLayersCount; i++) { 576 632 paintLayer(visibleLayers.get(i), g2); 577 633 } 578 } else {579 // Maybe there were more unchanged layers then last time - draw them to buffer580 if (nonChangedLayers.size() != nonChangedLayersCount) {581 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();582 g2.setClip(scaledClip);583 g2.setTransform(trDef);584 for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) {585 paintLayer(visibleLayers.get(i), g2);586 }587 }588 634 } 589 635 590 nonChangedLayers.clear();591 if (nonChangedLayersCount > 0)592 nonChangedLayers.addAll(visibleLayers.subList(0, nonChangedLayersCount));593 636 lastViewID = getViewID(); 594 637 lastClipBounds = g.getClipBounds(); 638 lastPaintedScale = getScale(); 639 lastPaintedCenter = getCenter(); 640 lastPaintedLayers = new ArrayList<>(visibleLayers); 595 641 596 642 Graphics2D tempG = offscreenBuffer.createGraphics(); 597 643 tempG.setClip(scaledClip); 598 644 tempG.setTransform(new AffineTransform()); 599 tempG.drawImage(nonChangedLayersBuffer, 0, 0, null); 645 if (canReusePanned) { 646 // Shift the previous composite by the pan delta and repaint the exposed strips. 647 // Strips overlap the blitted area by 1 px to hide seams; the horizontal band 648 // excludes the vertical strip's columns so the corner is painted only once. 649 int blitDX = (int) Math.round(panDX * uiScaleX); 650 int blitDY = (int) Math.round(panDY * uiScaleY); 651 tempG.setClip(0, 0, width, height); 652 tempG.drawImage(nonChangedLayersBuffer, blitDX, blitDY, null); 653 if (blitDX != 0) { 654 paintPanStrip(tempG, blitDX > 0 ? 0 : width + blitDX - 1, 0, 655 blitDX > 0 ? blitDX + 1 : 1 - blitDX, height, visibleLayers, trDef); 656 } 657 if (blitDY != 0) { 658 paintPanStrip(tempG, blitDX == 0 ? 0 : blitDX > 0 ? blitDX + 1 : 0, 659 blitDY > 0 ? 0 : height + blitDY - 1, 660 blitDX == 0 ? width : blitDX > 0 ? width - blitDX - 1 : width + blitDX - 1, 661 blitDY > 0 ? blitDY + 1 : 1 - blitDY, visibleLayers, trDef); 662 } 663 tempG.setClip(scaledClip); 664 } else { 665 tempG.drawImage(nonChangedLayersBuffer, 0, 0, null); 666 } 600 667 tempG.setTransform(trDef); 601 668 602 for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) { 603 paintLayer(visibleLayers.get(i), tempG); 669 // Layers invalidated since the last frame (e.g. freshly loaded imagery tiles) are 670 // not repainted in full during a pan: a full repaint over the shifted composite 671 // would cover the content of the layers above them. They are painted strip-only 672 // and the remaining changes appear once the pan stops (clean rebuild). 673 if (!canReusePanned) { 674 for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) { 675 paintLayer(visibleLayers.get(i), tempG); 676 } 677 } 678 // Keep the buffer a complete composite of all layers so the next pan can shift it. 679 // Only when the whole viewport was painted: a partial repaint leaves stale pixels. 680 if (g.getClipBounds().contains(0, 0, width, height)) { 681 Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); 682 g2.drawImage(offscreenBuffer, 0, 0, null); 683 g2.dispose(); 684 } 685 // Layers invalidated during the pan were only repainted inside the strip. Keep them 686 // marked so the first frame after the pan (with the view still) repaints them in 687 // full, showing the changes that were deferred. 688 if (canReusePanned) { 689 for (MapViewPaintable paintable : invalidated) { 690 invalidatedListener.invalidate(paintable); 691 } 692 } 693 694 // Imagery attribution is fixed to the viewport; painting it into the reused 695 // composite would leave copies behind while panning 696 for (Layer layer : visibleLayers) { 697 if (layer instanceof AbstractTileSourceLayer) { 698 ((AbstractTileSourceLayer<?>) layer).paintAttribution(tempG, this); 699 } 604 700 } 605 701 606 702 try { … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 663 759 } 664 760 } 665 761 762 /** 763 * Paints the layers into one of the strips exposed by a pan. The strip rectangle is 764 * in device pixels; the layers paint in logical coordinates, so the clip is set under 765 * the identity transform and {@code trDef} is applied for the painting. The strip is 766 * cleared first because layers may paint with transparency over the old content. 767 */ 768 private void paintPanStrip(Graphics2D g, int x, int y, int w, int h, List<Layer> layers, AffineTransform trDef) { 769 g.setTransform(new AffineTransform()); 770 g.setClip(new Rectangle(x, y, w, h)); 771 g.setColor(PaintColors.getBackgroundColor()); 772 g.fillRect(x, y, w, h); 773 g.setTransform(trDef); 774 for (Layer layer : layers) { 775 paintLayer(layer, g); 776 } 777 } 778 666 779 private void drawTemporaryLayers(Graphics2D tempG, Bounds box) { 667 780 synchronized (temporaryLayers) { 668 781 for (MapViewPaintable mvp : temporaryLayers) { … … LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener { 823 936 if (mapMover != null) { 824 937 mapMover.destroy(); 825 938 } 826 nonChangedLayers.clear();827 939 synchronized (temporaryLayers) { 828 940 temporaryLayers.clear(); 829 941 } -
src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java
diff --git a/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java b/src/org/openstreetmap/josm/gui/layer/AbstractTileSourceLayer.java index 510fad3..b0edb59 100644
a b import java.awt.Graphics2D; 13 13 import java.awt.GridBagConstraints; 14 14 import java.awt.GridBagLayout; 15 15 import java.awt.Image; 16 import java.awt.Rectangle; 16 17 import java.awt.Shape; 17 18 import java.awt.Toolkit; 18 19 import java.awt.event.ActionEvent; … … implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 1564 1565 // old and unused. 1565 1566 } 1566 1567 1568 /** 1569 * Paints the imagery attribution for the current view. The map view calls this on its 1570 * per-frame overlay: the attribution is fixed to the viewport, so painting it into the 1571 * reused composite would leave copies behind while panning. 1572 * @param g the overlay graphics 1573 * @param mv the map view 1574 */ 1575 public void paintAttribution(Graphics2D g, MapView mv) { 1576 if (!isVisible()) { 1577 return; 1578 } 1579 ProjectionBounds pb = mv.getState().getViewArea(new Rectangle(0, 0, mv.getWidth(), mv.getHeight())) 1580 .getProjectionBounds(); 1581 EastNorth min = pb.getMin(); 1582 EastNorth max = pb.getMax(); 1583 int zoom = getDisplaySettings().isAutoZoom() ? getBestZoom() : currentZoomLevel; 1584 attribution.paintAttribution(g, mv.getWidth(), mv.getHeight(), getShiftedCoord(min), getShiftedCoord(max), 1585 zoom, this); 1586 } 1587 1567 1588 private void drawInViewArea(Graphics2D g, MapView mv, ProjectionBounds pb) { 1568 1589 int zoom = currentZoomLevel; 1569 1590 if (getDisplaySettings().isAutoZoom()) { … … implements ImageObserver, TileLoaderListener, ZoomChangeListener, FilterChangeLi 1662 1683 this.paintTileText(t, g); 1663 1684 } 1664 1685 1665 EastNorth min = pb.getMin();1666 EastNorth max = pb.getMax();1667 attribution.paintAttribution(g, mv.getWidth(), mv.getHeight(), getShiftedCoord(min), getShiftedCoord(max),1668 displayZoomLevel, this);1669 1670 1686 g.setColor(Color.lightGray); 1671 1687 1672 1688 if (ts.tooLarge()) { -
src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java
diff --git a/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java b/src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java index faaaf50..243ee31 100644
a b public class OsmDataLayer extends AbstractOsmDataLayer 1507 1507 1508 1508 @Override 1509 1509 public void primitiveHovered(PrimitiveHoverEvent e) { 1510 List<IPrimitive> primitives = new ArrayList<>(2); 1511 primitives.add(e.getHoveredPrimitive()); 1512 primitives.add(e.getPreviousPrimitive()); 1513 primitives.removeIf(Objects::isNull); 1514 resetTiles(primitives); 1515 this.invalidate(); 1510 // The hover event does not change the rendered content, so nothing is invalidated 1511 // here; listeners such as the PropertiesDialog use it to update their sidebar. 1516 1512 } 1517 1513 1518 1514 @Override
