Skip to content

Figure out a last-resort interop for Swing icons #759

@rock3r

Description

While we have a pretty good mechanism in place to render icons stored in the resources, using IconKeys, this does not cover all scenarios, especially when it comes to random icons coming to the IJP.

In particular, when you are passed a random Icon in IJP, this icon can be:

  • Generated entirely at runtime by drawing into a bitmap
  • A composite icon made up of multiple images, layered on top of or next to each other
  • Or really, any other way one can get a bitmap-backed Icon

In addition, it doesn't help that the IJP changes the internals of their icons infra almost on every major release; and, while we can be relatively sure that 1p code will be somewhat diligent with which kinds of icons it loads and how, this is not always the case.

The issue is, Swing icons do change based on the current theme, density, and a number of other factors (e.g., user scale aka IDE zoom), and many times the IJP does some caching to save memory. We have zero visibility into any of this, so whatever solution we come up with will always need to be used as an absolute last resort, since it can and probably will break down in several use cases. On the other hand, plugins commonly need to display random Swing Icons they don't control (e.g., PsiFile icons, which may come from 3p plugins, or be composites, etc), so it's considered an acceptable tradeoff, assuming the user is aware of the nature of such interop.

To support all these scenarios, we need a SwingIcon composable with an API that looks somewhat like this:

@Composable
public fun SwingIcon(icon: javax.swing.Icon, modifier: Modifier = Modifier, preferredSize: DpSize = DpSize.Unspecified)

The reason for the preferredSize parameter is due to how IJP icons can be ScalableIcons, which allow for "native" scaling — particularly important considering Swing doesn't really support vector icons, which are just implemented as a "hack" in IJP — basically rendered into a (cached) bitmap under the hood when they're loaded. So, if the user wants a specific size of the icon, this mechanism allows higher quality as it's letting the IJP pick the best size variant and render it from scratch at the requested size, if possible.

On the other hand, using these images obviously requires more memory and can be very slow, as it means doing a hard copy of each icon in the JVM (see BufferedImage.toComposeImageBitmap()). So, a lot of care must be put into caching these images as much as possible, hosting an internal cache in Jewel.

However, caching is a complex topic, because we need to be very careful to always, and only, invalidate cache entries when the IJP also does. This is not trivial, and most likely we'll end up with a "good enough" system that covers things like:

  • LaF changes (theme changes, for sure; possibly granular LaF Defaults changes, too, if we can detect them)
  • Density change (e.g., window is moved from one screen to another with a different density — or system density is changed)
  • User scale change (i.e., IDE Zoom changes — which might be already covered by density changes)

As we implement this, we might find further factors to add to our caching keys, but for now this seems like it should cover most cases.

Metadata

Assignees

Labels

featureNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions