From e9112385c7a7ccfd599fe9f342eee20cfb3ec092 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Sun, 19 May 2024 00:29:08 +0200 Subject: [PATCH] feature: Expose a FlameRenderer interface --- .../flamegraph/DefaultFrameRenderer.java | 268 ++++++++++++++++++ .../fireplace/flamegraph/FlamegraphImage.java | 4 +- .../flamegraph/FlamegraphRenderEngine.java | 1 + .../fireplace/flamegraph/FlamegraphView.java | 94 ++++-- .../fireplace/flamegraph/FrameRenderer.java | 254 +++-------------- .../flamegraph/FrameRenderingFlags.java | 2 +- 6 files changed, 374 insertions(+), 249 deletions(-) create mode 100644 fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/DefaultFrameRenderer.java diff --git a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/DefaultFrameRenderer.java b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/DefaultFrameRenderer.java new file mode 100644 index 00000000..e920bd0f --- /dev/null +++ b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/DefaultFrameRenderer.java @@ -0,0 +1,268 @@ +/* + * Fireplace + * + * Copyright (c) 2021, Today - Brice Dutheil + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +package io.github.bric3.fireplace.flamegraph; + +import io.github.bric3.fireplace.core.ui.StringClipper; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.awt.geom.Rectangle2D; +import java.util.Objects; + +import static io.github.bric3.fireplace.flamegraph.FrameRenderingFlags.isMinimapMode; + +/** + * Default single frame renderer. + * + * @param The type of the frame node (depends on the source of profiling data). + * @see FlamegraphView + * @see FlamegraphRenderEngine + * @see FrameRenderer + * @see FrameTextsProvider + * @see FrameFontProvider + * @see FrameColorProvider + */ +public class DefaultFrameRenderer implements FrameRenderer { + @NotNull + private FrameTextsProvider<@NotNull T> frameTextsProvider; + @NotNull + private FrameFontProvider<@NotNull T> frameFontProvider; + @NotNull + private FrameColorProvider<@NotNull T> frameColorProvider; + + /** + * The space in pixels between the frame label text and the frame's border. + */ + private static final int frameTextPadding = 2; + + /** + * The width of the border drawn around the hovered frame. + */ + private static final int frameBorderWidth = 1; + + /** + * A flag that controls whether a gap is shown at the right and bottom of each frame. + */ + private boolean drawingFrameGap = true; + + /** + * @param frameTextsProvider functions that create a label for a node + * @param frameFontProvider provides a font given a frame and some flags + * @param frameColorProvider provides foreground and background color given a frame and some flags + */ + public DefaultFrameRenderer( + @NotNull FrameTextsProvider<@NotNull T> frameTextsProvider, + @NotNull FrameColorProvider<@NotNull T> frameColorProvider, + @NotNull FrameFontProvider<@NotNull T> frameFontProvider + ) { + this.frameTextsProvider = Objects.requireNonNull(frameTextsProvider, "nodeToTextProvider"); + this.frameColorProvider = Objects.requireNonNull(frameColorProvider, "frameColorProvider"); + this.frameFontProvider = Objects.requireNonNull(frameFontProvider, "frameFontProvider"); + } + + /** + * Sets whether to draw a gap between each frame. + * @param drawingFrameGap true to draw a gap between each frame + */ + public void setDrawingFrameGap(boolean drawingFrameGap) { + this.drawingFrameGap = drawingFrameGap; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isDrawingFrameGap() { + return drawingFrameGap; + } + + /** + * {@inheritDoc} + */ + @Override + public int getFrameBoxHeight(@NotNull Graphics2D g2) { + return g2.getFontMetrics(frameFontProvider.getFont(null, 0)).getAscent() + (frameTextPadding * 2) + getFrameGapWidth() * 2; + } + + private float getFrameBoxTextOffset(@NotNull Graphics2D g2) { + return getFrameBoxHeight(g2) - (g2.getFontMetrics(frameFontProvider.getFont(null, 0)).getDescent() / 2f) - frameTextPadding - getFrameGapWidth(); + } + + /** + * {@inheritDoc} + */ + @Override + public void paintFrame( + @NotNull Graphics2D g2, + @NotNull FrameModel<@NotNull T> frameModel, + @NotNull Rectangle2D frameRect, + @NotNull FrameBox<@NotNull T> frame, + @NotNull Rectangle2D paintableIntersection, + int renderFlags + ) { + boolean minimapMode = isMinimapMode(renderFlags); + var colorModel = frameColorProvider.getColors(frame, renderFlags); + + paintFrameRectangle( + g2, + frameRect, + Objects.requireNonNull( + colorModel.background, + "colorModel.background is nullable; however, at when rendering it is not anymore allowed" + ), + minimapMode + ); + if (minimapMode) { + return; + } + + var frameFont = frameFontProvider.getFont(frame, renderFlags); + + var text = calculateFrameText( + g2, + frameFont, + paintableIntersection.getWidth() - frameTextPadding * 2 - getFrameGapWidth() * 2, + frameModel, + frame + ); + + if (text == null || text.isEmpty()) { + return; + } + + g2.setFont(frameFont); + g2.setColor(Objects.requireNonNull( + colorModel.foreground, + "colorModel.background is nullable; however, at when rendering it is not anymore allowed" + )); + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2.drawString( + text, + (float) (paintableIntersection.getX() + frameTextPadding + frameBorderWidth), + (float) (frameRect.getY() + getFrameBoxTextOffset(g2)) + ); + } + + private void paintFrameRectangle( + @NotNull Graphics2D g2, + @NotNull Rectangle2D frameRect, + @NotNull Color bgColor, + boolean minimapMode + ) { + var gapThickness = minimapMode ? + 0 : + drawingFrameGap ? getFrameGapWidth() : 0; + + var x = frameRect.getX(); + var y = frameRect.getY(); + var w = frameRect.getWidth() - gapThickness; + var h = frameRect.getHeight() - gapThickness; + frameRect.setRect(x, y, w, h); + + g2.setColor(bgColor); + g2.fill(frameRect); + } + + // layout text + private String calculateFrameText( + @NotNull Graphics2D g2, + @NotNull Font font, + double targetWidth, + @NotNull FrameModel<@NotNull T> frameModel, + @NotNull FrameBox<@NotNull T> frame + ) { + var metrics = g2.getFontMetrics(font); + + // don't use stream to avoid allocations during painting + var textCandidate = frame.isRoot() ? frameModel.title : ""; + if (frame.isRoot() && !textCandidate.isBlank()) { + var textBounds = metrics.getStringBounds(textCandidate, g2); + if (textBounds.getWidth() <= targetWidth) { + return textCandidate; + } + } else { + for (var nodeToTextCandidate : frameTextsProvider.frameToTextCandidates()) { + // While the function is supposed to return a non-null value, it is not enforced + // in byte code, so let's default to empty string + textCandidate = Objects.requireNonNullElse(nodeToTextCandidate.apply(frame), ""); + var textBounds = metrics.getStringBounds(textCandidate, g2); + if (textBounds.getWidth() <= targetWidth) { + return textCandidate; + } + } + } + // only try clip the last candidate + textCandidate = frameTextsProvider.clipStrategy().clipString( + font, + metrics, + targetWidth, + textCandidate, + StringClipper.LONG_TEXT_PLACEHOLDER + ); + var textBounds = metrics.getStringBounds(textCandidate, g2); + if (textBounds.getWidth() > targetWidth || textCandidate.length() <= StringClipper.LONG_TEXT_PLACEHOLDER.length() + 1) { + // don't draw the text, if too long or too short (like "r…") + return null; + } + return textCandidate; + } + + /** + * Set the frame text provider. + * @param frameTextsProvider the frame text provider + */ + public void setFrameTextsProvider(@NotNull FrameTextsProvider<@NotNull T> frameTextsProvider) { + this.frameTextsProvider = Objects.requireNonNull(frameTextsProvider, "frameTextsProvider"); + } + + /** + * Get the frame text provider. + * @return the frame text provider + */ + @NotNull + public FrameTextsProvider<@NotNull T> getFrameTextsProvider() { + return frameTextsProvider; + } + + /** + * Set the frame font provider. + * @param frameFontProvider the frame font provider + */ + public void setFrameFontProvider(@NotNull FrameFontProvider<@NotNull T> frameFontProvider) { + this.frameFontProvider = Objects.requireNonNull(frameFontProvider, "frameFontProvider"); + } + + /** + * Get the frame font provider. + * @return the frame font provider + */ + @NotNull + public FrameFontProvider<@NotNull T> getFrameFontProvider() { + return frameFontProvider; + } + + /** + * Set the frame color provider. + * @param frameColorProvider the frame color provider + */ + public void setFrameColorProvider(@NotNull FrameColorProvider<@NotNull T> frameColorProvider) { + this.frameColorProvider = Objects.requireNonNull(frameColorProvider, "frameColorProvider"); + } + + /** + * Get the frame color provider. + * @return the frame color provider + */ + @NotNull + public FrameColorProvider<@NotNull T> getFrameColorProvider() { + return frameColorProvider; + } +} \ No newline at end of file diff --git a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphImage.java b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphImage.java index a4568268..35f0cfe2 100644 --- a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphImage.java +++ b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FlamegraphImage.java @@ -66,7 +66,7 @@ public FlamegraphImage( @NotNull FrameColorProvider<@NotNull T> frameColorProvider, @NotNull FrameFontProvider<@NotNull T> frameFontProvider ) { - this.fre = new FlamegraphRenderEngine<>(new FrameRenderer<>( + this.fre = new FlamegraphRenderEngine<>(new DefaultFrameRenderer<>( Objects.requireNonNull(frameTextsProvider), Objects.requireNonNull(frameColorProvider), Objects.requireNonNull(frameFontProvider) @@ -74,7 +74,7 @@ public FlamegraphImage( } /** - * Make an image from the frames models. + * Make an image from the frame models. * * @param frameModel The frame model to render. * @param mode The display mode of the graph. 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 243f759a..55f76862 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 @@ -136,6 +136,7 @@ public int getVisibleDepth() { * * @param frameRenderer a configured single frame renderer. * @see #init(FrameModel) + * @see FrameRenderer */ public FlamegraphRenderEngine(@NotNull FrameRenderer<@NotNull T> frameRenderer) { this.frameRenderer = Objects.requireNonNull(frameRenderer, "frameRenderer"); 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 4aa07a31..a83709fa 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 @@ -93,7 +93,7 @@ * @see FrameTextsProvider * @see FrameFontProvider * @see FlamegraphRenderEngine - * @see FrameRenderer + * @see DefaultFrameRenderer */ public class FlamegraphView { /** @@ -429,92 +429,115 @@ public void configureCanvas(@NotNull Consumer<@NotNull JComponent> canvasConfigu /** * Replaces the frame colors provider. + * Deprecated, use {@link DefaultFrameRenderer#setFrameColorProvider(FrameColorProvider)} * * @param frameColorProvider A provider that takes a frame and returns its colors. * @see FrameColorProvider */ + @Deprecated(forRemoval = true) public void setFrameColorProvider(@NotNull FrameColorProvider<@NotNull T> frameColorProvider) { - this.canvas.getFlamegraphRenderEngine() - .getFrameRenderer() - .setFrameColorProvider(frameColorProvider); + getDefaultFrameRenderer(this.canvas) + .setFrameColorProvider(frameColorProvider); + } + + private @NotNull DefaultFrameRenderer<@NotNull T> getDefaultFrameRenderer(@NotNull FlamegraphCanvas canvas) { + var flamegraphRenderEngine = canvas.getFlamegraphRenderEngine(); + var frameRenderer = flamegraphRenderEngine.getFrameRenderer(); + + // if not a default renderer, replace it + if (!(frameRenderer instanceof DefaultFrameRenderer)) { + throw new IllegalStateException( + "Deprecated method use, FrameRender is a '" + frameRenderer.getClass() + + "' while this method operate on a DefaultFrameRenderer" + ); + } + return (DefaultFrameRenderer) frameRenderer; } /** * Returns the frame colors provider. + * Deprecated, use {@link DefaultFrameRenderer#getFrameColorProvider()} * * @return The frame colors provider, may return null if renderer not configured. */ + @Deprecated(forRemoval = true) @NotNull public FrameColorProvider<@NotNull T> getFrameColorProvider() { - return canvas.getFlamegraphRenderEngine() - .getFrameRenderer() - .getFrameColorProvider(); + return getDefaultFrameRenderer(canvas) + .getFrameColorProvider(); } /** * Replaces the frame font provider. + * Deprecated, use {@link DefaultFrameRenderer#setFrameFontProvider(FrameFontProvider)} * * @param frameFontProvider A provider that takes a frame and returns its font. * @see FrameFontProvider */ + @Deprecated(forRemoval = true) public void setFrameFontProvider(@NotNull FrameFontProvider<@NotNull T> frameFontProvider) { - this.canvas.getFlamegraphRenderEngine() - .getFrameRenderer() - .setFrameFontProvider(frameFontProvider); + getDefaultFrameRenderer(this.canvas) + .setFrameFontProvider(frameFontProvider); } /** * Returns the frane font provider. + * Deprecated, use {@link DefaultFrameRenderer#getFrameFontProvider()} * * @return The frame font provider, may return null if renderer not configured. */ + @Deprecated(forRemoval = true) @NotNull public FrameFontProvider<@NotNull T> getFrameFontProvider() { - return canvas.getFlamegraphRenderEngine() - .getFrameRenderer() - .getFrameFontProvider(); + return getDefaultFrameRenderer(canvas) + .getFrameFontProvider(); } /** * Replaces the frame to text candidates provider. + * Deprecated, use {@link DefaultFrameRenderer#setFrameTextsProvider(FrameTextsProvider)} * * @param frameTextsProvider A provider that takes a frame and returns its colors. * @see FrameTextsProvider */ + @Deprecated(forRemoval = true) public void setFrameTextsProvider(@NotNull FrameTextsProvider<@NotNull T> frameTextsProvider) { - this.canvas.getFlamegraphRenderEngine() - .getFrameRenderer() - .setFrameTextsProvider(frameTextsProvider); + getDefaultFrameRenderer(this.canvas) + .setFrameTextsProvider(frameTextsProvider); } /** * Returns the frame texts candidate provider. + * Deprecated, use {@link DefaultFrameRenderer#getFrameTextsProvider()} * * @return The frame texts candidate provider, may return null if renderer not configured. */ + @Deprecated(forRemoval = true) @NotNull public FrameTextsProvider<@NotNull T> getFrameTextsProvider() { - return canvas.getFlamegraphRenderEngine() - .getFrameRenderer() - .getFrameTextsProvider(); + return getDefaultFrameRenderer(canvas) + .getFrameTextsProvider(); } /** * Toggle the display of a gap between frames. + * Deprecated, use {@link DefaultFrameRenderer#setDrawingFrameGap(boolean)} * * @param frameGapEnabled {@code true} to show a gap between frames, {@code false} otherwise. */ + @Deprecated(forRemoval = true) public void setFrameGapEnabled(boolean frameGapEnabled) { - canvas.getFlamegraphRenderEngine() - .getFrameRenderer() - .setDrawingFrameGap(frameGapEnabled); + getDefaultFrameRenderer(canvas) + .setDrawingFrameGap(frameGapEnabled); } /** * Whether gap between frames is displayed. + * Deprecated, use {@link FrameRenderer#isDrawingFrameGap()} * * @return {@code true} if gap between frames is shown, {@code false} otherwise. */ + @Deprecated(forRemoval = true) public boolean isFrameGapEnabled() { return canvas.getFlamegraphRenderEngine() .getFrameRenderer() @@ -533,6 +556,7 @@ public void setMinimapShadeColorSupplier(@NotNull Supplier<@NotNull Color> minim /** * Returns the color supplier for the minimap shade of {@link FlamegraphView}. + * * @return The color supplier for the minimap shade, may return null if not set. */ @Nullable @@ -608,6 +632,7 @@ public void setTooltipComponentSupplier(@NotNull Supplier<@NotNull JToolTip> too /** * Returns the tooltip component supplier of {@link FlamegraphView}. + * * @return the tooltip component supplier, may return null if not set. */ @Nullable @@ -627,6 +652,7 @@ public void setPopupConsumer(@NotNull BiConsumer<@NotNull FrameBox<@NotNull T>, /** * Returns the popup consumer of {@link FlamegraphView}. + * * @return the popup consumer, may return null if not set. */ @Nullable @@ -646,6 +672,7 @@ public void setSelectedFrameConsumer(@NotNull BiConsumer<@NotNull FrameBox<@NotN /** * Returns the selected frame consumer of {@link FlamegraphView}. + * * @return the selected frame consumer, may return null if not set. */ @Nullable @@ -699,22 +726,40 @@ public void setModel(@NotNull FrameModel<@NotNull T> frameModel) { * like the type of events, their number, etc. *
  • The frame background and foreground colors.
  • * + *

    + * Deprecated, use {@link #setFrameRender(FrameRenderer)} with a ${@link DefaultFrameRenderer}. * * @param frameTextsProvider The function to display label in frames. * @param frameColorProvider The frame to background color function. * @param frameFontProvider The frame font provider. */ + @Deprecated(forRemoval = true) public void setRenderConfiguration( @NotNull FrameTextsProvider<@NotNull T> frameTextsProvider, @NotNull FrameColorProvider<@NotNull T> frameColorProvider, @NotNull FrameFontProvider<@NotNull T> frameFontProvider ) { - var flamegraphRenderEngine = new FlamegraphRenderEngine<>( - new FrameRenderer<>( + setFrameRender( + new DefaultFrameRenderer( frameTextsProvider, frameColorProvider, frameFontProvider ) + ); + } + + /** + * Configures the frame renderer of {@link FlamegraphView}. + * + *

    + * This is the main entry point to configure the rendering of the flamegraph. + *

    + * + * @param frameRenderer The frame renderer. + */ + public void setFrameRender(FrameRenderer frameRenderer) { + var flamegraphRenderEngine = new FlamegraphRenderEngine<>( + frameRenderer ).init(framesModel); canvas.setFlamegraphRenderEngine(flamegraphRenderEngine); @@ -740,6 +785,7 @@ public void setTooltipTextFunction( /** * Gets the tooltip text of {@link FlamegraphView}. + * * @return The tooltipTextFunction for a frame, may return null if not set. */ public BiFunction<@NotNull FrameModel<@NotNull T>, FrameBox<@NotNull T>, String> getTooltipTextFunction() { diff --git a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderer.java b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderer.java index a87f03b9..de59edae 100644 --- a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderer.java +++ b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderer.java @@ -1,254 +1,64 @@ -/* - * Fireplace - * - * Copyright (c) 2021, Today - Brice Dutheil - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. - */ - package io.github.bric3.fireplace.flamegraph; -import io.github.bric3.fireplace.core.ui.StringClipper; import org.jetbrains.annotations.NotNull; import java.awt.*; import java.awt.geom.Rectangle2D; -import java.util.Objects; - -import static io.github.bric3.fireplace.flamegraph.FrameRenderingFlags.isMinimapMode; /** * Single frame renderer. * * @param The type of the frame node (depends on the source of profiling data). * @see FlamegraphView - * @see FlamegraphRenderEngine */ -// TODO allow delegating the rendering to a custom renderer -class FrameRenderer { - @NotNull - private FrameTextsProvider<@NotNull T> frameTextsProvider; - @NotNull - private FrameFontProvider<@NotNull T> frameFontProvider; - @NotNull - private FrameColorProvider<@NotNull T> frameColorProvider; - - /** - * The space in pixels between the frame label text and the frame's border. - */ - private final int frameTextPadding = 2; - - /** - * A flag that controls whether a gap is shown at the right and bottom of each frame. - */ - private boolean drawingFrameGap = true; - +// TODO root frame renderer ? +public interface FrameRenderer { /** - * The size of the gap at the right and bottom of each frame. + * The size of the gap at between each side of a frame. + * + * @return the size of the gap at between each side of a frame. + * @see #isDrawingFrameGap() */ - private final int frameGapWidth = 1; + default int getFrameGapWidth() {return 1;} /** - * @param frameTextsProvider functions that create a label for a node - * @param frameFontProvider provides a font given a frame and some flags - * @param frameColorProvider provides foreground and background color given a frame and some flags + * Whether a gap is shown between each frame. + * + * @return true if a gap is shown between each frame. + * @see #getFrameGapWidth() */ - public FrameRenderer( - @NotNull FrameTextsProvider<@NotNull T> frameTextsProvider, - @NotNull FrameColorProvider<@NotNull T> frameColorProvider, - @NotNull FrameFontProvider<@NotNull T> frameFontProvider - ) { - this.frameTextsProvider = Objects.requireNonNull(frameTextsProvider, "nodeToTextProvider"); - this.frameColorProvider = Objects.requireNonNull(frameColorProvider, "frameColorProvider"); - this.frameFontProvider = Objects.requireNonNull(frameFontProvider, "frameFontProvider"); - } - - public int getFrameTextPadding() { - return frameTextPadding; - } - - public int getFrameGapWidth() { - return frameGapWidth; - } - - public void setDrawingFrameGap(boolean drawingFrameGap) { - this.drawingFrameGap = drawingFrameGap; - } - - public boolean isDrawingFrameGap() { - return drawingFrameGap; - } + default boolean isDrawingFrameGap() {return true;} /** - * The width of the border drawn around the hovered frame. + * Compute the height of the frame box according to the passed {@link Graphics2D}. + * + * @param g2 the graphics context + * @return the height of the frame box */ - public final int frameBorderWidth = 1; + int getFrameBoxHeight(@NotNull Graphics2D g2); /** - * The stroke used to draw a border around the hovered frame. - */ - @NotNull - public Stroke frameBorderStroke = new BasicStroke(frameBorderWidth); - - public int getFrameBoxHeight(@NotNull Graphics2D g2) { - return g2.getFontMetrics(frameFontProvider.getFont(null, 0)).getAscent() + (frameTextPadding * 2) + frameGapWidth * 2; - } - - public float getFrameBoxTextOffset(@NotNull Graphics2D g2) { - return getFrameBoxHeight(g2) - (g2.getFontMetrics(frameFontProvider.getFont(null, 0)).getDescent() / 2f) - frameTextPadding - frameGapWidth; - } - - /** - * Paints the frame. + * Paint the frame. + * The {@code renderFlags} parameter can be used to alter the rendering of the frame, + * this value is computed by the {@link FlamegraphRenderEngine} method, and + * depends on which settings are used, and the context of the frame. This value can be decoded by + * using the {@link FrameRenderingFlags} methods. * - * @param g2 the graphics target. + * @param g2 the graphics context + * @param frameModel the frame model * @param frameRect the frame region (may fall outside visible area). * @param frame the frame to paint * @param paintableIntersection the intersection between the frame rectangle and the visible region - * (used to position the text label). - * @param flags The rendering flags (minimap, selection, hovered, highlight). + * (can be used to position the text label). + * @param renderFlags the rendering flags (minimap, selection, hovered, highlight, etc.) + * @see FrameRenderingFlags */ void paintFrame( @NotNull Graphics2D g2, - @NotNull FrameModel<@NotNull T> frameModel, + @NotNull FrameModel frameModel, @NotNull Rectangle2D frameRect, - @NotNull FrameBox<@NotNull T> frame, + @NotNull FrameBox frame, @NotNull Rectangle2D paintableIntersection, - int flags - ) { - boolean minimapMode = isMinimapMode(flags); - var colorModel = frameColorProvider.getColors(frame, flags); - - paintFrameRectangle( - g2, - frameRect, - Objects.requireNonNull( - colorModel.background, - "colorModel.background is nullable; however, at when rendering it is not anymore allowed" - ), - minimapMode - ); - if (minimapMode) { - return; - } - - var frameFont = frameFontProvider.getFont(frame, flags); - - var text = calculateFrameText( - g2, - frameFont, - paintableIntersection.getWidth() - frameTextPadding * 2 - frameGapWidth * 2, - frameModel, - frame - ); - - if (text == null || text.isEmpty()) { - return; - } - - g2.setFont(frameFont); - g2.setColor(Objects.requireNonNull( - colorModel.foreground, - "colorModel.background is nullable; however, at when rendering it is not anymore allowed" - )); - g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g2.drawString( - text, - (float) (paintableIntersection.getX() + frameTextPadding + frameBorderWidth), - (float) (frameRect.getY() + getFrameBoxTextOffset(g2)) - ); - } - - private void paintFrameRectangle( - @NotNull Graphics2D g2, - @NotNull Rectangle2D frameRect, - @NotNull Color bgColor, - boolean minimapMode - ) { - var gapThickness = minimapMode ? - 0 : - drawingFrameGap ? frameGapWidth : 0; - - var x = frameRect.getX(); - var y = frameRect.getY(); - var w = frameRect.getWidth() - gapThickness; - var h = frameRect.getHeight() - gapThickness; - frameRect.setRect(x, y, w, h); - - g2.setColor(bgColor); - g2.fill(frameRect); - } - - // layout text - private String calculateFrameText( - @NotNull Graphics2D g2, - @NotNull Font font, - double targetWidth, - @NotNull FrameModel<@NotNull T> frameModel, - @NotNull FrameBox<@NotNull T> frame - ) { - var metrics = g2.getFontMetrics(font); - - // don't use stream to avoid allocations during painting - var textCandidate = frame.isRoot() ? frameModel.title : ""; - if (frame.isRoot() && !textCandidate.isBlank()) { - var textBounds = metrics.getStringBounds(textCandidate, g2); - if (textBounds.getWidth() <= targetWidth) { - return textCandidate; - } - } else { - for (var nodeToTextCandidate : frameTextsProvider.frameToTextCandidates()) { - // While the function is supposed to return a non-null value, it is not enforced - // in byte code, so let's default to empty string - textCandidate = Objects.requireNonNullElse(nodeToTextCandidate.apply(frame), ""); - var textBounds = metrics.getStringBounds(textCandidate, g2); - if (textBounds.getWidth() <= targetWidth) { - return textCandidate; - } - } - } - // only try clip the last candidate - textCandidate = frameTextsProvider.clipStrategy().clipString( - font, - metrics, - targetWidth, - textCandidate, - StringClipper.LONG_TEXT_PLACEHOLDER - ); - var textBounds = metrics.getStringBounds(textCandidate, g2); - if (textBounds.getWidth() > targetWidth || textCandidate.length() <= StringClipper.LONG_TEXT_PLACEHOLDER.length() + 1) { - // don't draw the text, if too long or too short (like "r…") - return null; - } - return textCandidate; - } - - public void setFrameTextsProvider(@NotNull FrameTextsProvider<@NotNull T> frameTextsProvider) { - this.frameTextsProvider = Objects.requireNonNull(frameTextsProvider, "frameTextsProvider"); - } - - @NotNull - public FrameTextsProvider<@NotNull T> getFrameTextsProvider() { - return frameTextsProvider; - } - - public void setFrameFontProvider(@NotNull FrameFontProvider<@NotNull T> frameFontProvider) { - this.frameFontProvider = Objects.requireNonNull(frameFontProvider, "frameFontProvider"); - } - - @NotNull - public FrameFontProvider<@NotNull T> getFrameFontProvider() { - return frameFontProvider; - } - - public void setFrameColorProvider(@NotNull FrameColorProvider<@NotNull T> frameColorProvider) { - this.frameColorProvider = Objects.requireNonNull(frameColorProvider, "frameColorProvider"); - } - - @NotNull - public FrameColorProvider<@NotNull T> getFrameColorProvider() { - return frameColorProvider; - } -} \ No newline at end of file + int renderFlags + ); +} diff --git a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderingFlags.java b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderingFlags.java index ef686a2d..e1906b00 100644 --- a/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderingFlags.java +++ b/fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderingFlags.java @@ -59,7 +59,7 @@ public abstract class FrameRenderingFlags { public static final int FOCUSED_FRAME = 1 << 6; /** - * The renderer is currently rendering a partial frame, e.g. it is larger + * The renderer is currently rendering a partial frame, e.g., it is larger * that the painting area. */ public static final int PARTIAL_FRAME = 1 << 7;