Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce custom viewport layout to fix container shrinking #149

Merged
merged 2 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -683,8 +682,10 @@ public ZoomTarget calculateZoomTargetFrame(
* factor = ----------------------------
* frameWidthX * bounds.width
* </pre>
*
* 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
() -> {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -595,7 +697,7 @@ public List<FrameBox<T>> getFrames() {
* @param value the value.
* @see JComponent#putClientProperty(Object, Object)
*/
public void putClientProperty(String key, Object value) {
public <V> void putClientProperty(String key, V value) {
// value can be null, it means removing the key (see putClientProperty)
canvas.putClientProperty(Objects.requireNonNull(key), value);
}
Expand All @@ -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> V getClientProperty(String key) {
return (V) canvas.getClientProperty(Objects.requireNonNull(key));
}

/**
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1026,7 +1153,6 @@ public Dimension getPreferredSize() {
flamegraphWidth,
true
);

preferredSize.width = Math.max(preferredSize.width, flamegraphWidth);
preferredSize.height = Math.max(preferredSize.height, flamegraphHeight);

Expand All @@ -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();
Expand Down