diff --git a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphRenderEngine.java b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphRenderEngine.java index c3c93ff6..82007b56 100644 --- a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphRenderEngine.java +++ b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphRenderEngine.java @@ -427,8 +427,7 @@ private void paintHoveredFrameBorder( if (viewRect.intersects(frameRect)) { g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g2.setColor(frameBorderColor.get()); - // TODO use floor / ceil ? - g2.drawRect((int) x, (int) y, (int) w, (int) h); + g2.draw(frameRect); } } @@ -683,8 +682,10 @@ public ZoomTarget calculateZoomTargetFrame( * factor = ---------------------------- * frameWidthX * bounds.width * + * + * Note that to retrieve the zoom factor one should use {@code 1 / factor}. */ - private static double getScaleFactor(double visibleWidth, double canvasWidth, double frameWidthX) { + protected static double getScaleFactor(double visibleWidth, double canvasWidth, double frameWidthX) { return visibleWidth / (canvasWidth * frameWidthX); } diff --git a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphView.java b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphView.java index a11faa52..20554912 100644 --- a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphView.java +++ b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphView.java @@ -18,6 +18,7 @@ import java.awt.*; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; +import java.awt.event.HierarchyEvent; import java.awt.event.MouseEvent; import java.awt.geom.Area; import java.awt.image.BufferedImage; @@ -226,7 +227,7 @@ public FlamegraphView() { canvas = new FlamegraphCanvas<>(this); canvas.putClientProperty(OWNER_KEY, this); scrollPaneListener = new FlamegraphScrollPaneMouseInputListener<>(canvas); - var scrollPane = new JScrollPane(canvas); + var scrollPane = createScrollPane(); scrollPane.putClientProperty(OWNER_KEY, this); var layeredScrollPane = JScrollPaneWithBackButton.create( () -> { @@ -265,6 +266,105 @@ public FlamegraphView() { }); } + private JScrollPane createScrollPane() { + var jScrollPane = new JScrollPane(canvas); + var viewport = new JViewport() { + @Override + protected LayoutManager createLayoutManager() { + return new ViewportLayout() { + private final Dimension oldViewPortSize = new Dimension(); // reusable + private final Dimension flamegraphSize = new Dimension(); // reusable + private final Point flamegraphLocation = new Point(); // reusable + + @Override + public void layoutContainer(Container parent) { + // Custom layout code to handle container shrinking. + // The default view port layout asks the preferred size + // of the view. + // But that cannot work since the canvas won;t update + // its width, it receives its size from the layout container. + // + // However, the default algorithm only updates the size + // after it has received the preferred size, or if the + // viewport got bigger. + // + // This code makes the necessary query to the canvas to + // asks if it needs a new size given the viewport width change, + // in order to keep the same zoom factor. + // + // The view location is also updated. + + var vp = (JViewport) parent; + var canvas = (FlamegraphCanvas) vp.getView(); + int oldVpWidth = oldViewPortSize.width; + var vpSize = vp.getSize(oldViewPortSize); + + // view port has been resized + if (vpSize.width != oldVpWidth) { + // if old fg width == old vp width + // the scaleFactor is 1.0 + // => recompute the fg size + // if old fg width > old vp width + // the scaleFactor is > 1.0 + // => compute the scaleFactor + // => scale the fg size using the current vp width + // if old fg width < old vp width + // ==> do nothing + int oldFlamegraphWidth = flamegraphSize.width; + if (oldFlamegraphWidth == oldVpWidth) { + canvas.updateFlamegraphDimension( + flamegraphSize, + vpSize.width + ); + + // check view position ? + } else { + // compute scale factor + double scaleFactor = FlamegraphRenderEngine.getScaleFactor( + oldVpWidth, + oldFlamegraphWidth, + 1.0 + ); + + // scale the fg size with the new viewport width + canvas.updateFlamegraphDimension( + flamegraphSize, + (int) Math.round(vpSize.width / scaleFactor) + ); + + } + vp.setViewSize(flamegraphSize); + + // if view position X > 0 + // the fg is zoomed + // => compute the position ratio + // => apply ratio to the current fg width + + int oldFlamegraphX = Math.abs(flamegraphLocation.x); + if (oldFlamegraphX > 0) { + // compute scale factor + double positionRatio = (double) oldFlamegraphX / (double) oldFlamegraphWidth; + flamegraphLocation.x = Math.abs((int) Math.round(positionRatio * flamegraphSize.width)); + flamegraphLocation.y = Math.abs(flamegraphLocation.y); + + vp.setViewPosition(flamegraphLocation); + } + } else { + super.layoutContainer(parent); + // update the sizes + vp.getSize(oldViewPortSize); + canvas.getSize(flamegraphSize); + canvas.getLocation(flamegraphLocation); + } + } + }; + } + }; + jScrollPane.setViewport(viewport); + jScrollPane.setViewportView(canvas); + return jScrollPane; + } + /** * Murky workaround to propagate the background color to the canvas * since JLayer is final. @@ -435,7 +535,9 @@ public void setShowHoveredSiblings(boolean showHoveredSiblings) { * @return {@code true} if the siblings of the hovered frame are highlighted, {@code false} otherwise. */ public boolean isShowHoveredSiblings() { - return canvas.getFlamegraphRenderEngine().map(FlamegraphRenderEngine::isShowHoveredSiblings).orElse(false); + return canvas.getFlamegraphRenderEngine() + .map(FlamegraphRenderEngine::isShowHoveredSiblings) + .orElse(false); } /** @@ -595,7 +697,7 @@ public List> getFrames() { * @param value the value. * @see JComponent#putClientProperty(Object, Object) */ - public void putClientProperty(String key, Object value) { + public void putClientProperty(String key, V value) { // value can be null, it means removing the key (see putClientProperty) canvas.putClientProperty(Objects.requireNonNull(key), value); } @@ -607,8 +709,9 @@ public void putClientProperty(String key, Object value) { * @return the value * @see JComponent#getClientProperty(Object) */ - public Object getClientProperty(String key) { - return canvas.getClientProperty(Objects.requireNonNull(key)); + @SuppressWarnings("unchecked") + public V getClientProperty(String key) { + return (V) canvas.getClientProperty(Objects.requireNonNull(key)); } /** @@ -917,11 +1020,35 @@ public void updateUI() { super.updateUI(); } + @Override + public void doLayout() { + Rectangle bounds = getBounds(); + double delta = getParent().getWidth() - getVisibleRect().getWidth(); + // TODO capture position in view rect + + if(delta < 0) { + + } + Point location = getLocation(); + + + super.doLayout(); + } + @Override public void addNotify() { super.addNotify(); var fgCanvas = this; + fgCanvas.addHierarchyListener(e -> { + boolean b = (e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0; + if (b && !e.getComponent().isDisplayable()) { + fgCanvas.getParent(); + // todo + } + }); + + // Adjust the width of the canvas to the width of the view rect, when // the scroll bar is made visible, this prevents the horizontal scrollbar // from appearing on first display, see #96. @@ -1026,7 +1153,6 @@ public Dimension getPreferredSize() { flamegraphWidth, true ); - preferredSize.width = Math.max(preferredSize.width, flamegraphWidth); preferredSize.height = Math.max(preferredSize.height, flamegraphHeight); @@ -1037,6 +1163,18 @@ public Dimension getPreferredSize() { return preferredSize; } + protected Dimension updateFlamegraphDimension(Dimension dimension, int flamegraphWidth) { + var flamegraphHeight = flamegraphRenderEngine.computeVisibleFlamegraphHeight( + (Graphics2D) getGraphics(), + flamegraphWidth, + true + ); + + dimension.width = flamegraphWidth; + dimension.height = flamegraphHeight; + return dimension; + } + @Override protected void paintComponent(Graphics g) { long start = System.currentTimeMillis();