diff --git a/platform/platform-tests/testSrc/com/intellij/util/ui/JBValueTest.java b/platform/platform-tests/testSrc/com/intellij/util/ui/JBValueTest.java new file mode 100644 index 000000000000..a67b1e13fdf2 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/util/ui/JBValueTest.java @@ -0,0 +1,95 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.util.ui; + +import org.junit.Test; + +import javax.swing.*; + +import static junit.framework.TestCase.assertEquals; + +/** + * Tests {@link JBValue}. + * + * @author tav + */ +public class JBValueTest extends TestScaleHelper { + @Test + public void testInt() { + JBUI.setUserScaleFactor(1); + + JBValue value1 = JBUI.intValue(2); + JBValue.SelfCachedInteger value2 = new JBValue.SelfCachedInteger(2); + + JBUI.setUserScaleFactor(2); + + assertEquals(JBUI.scale(2), value1.get()); + assertEquals(JBUI.scale(2), value2.get()); + } + + @Test + public void testFloat() { + JBUI.setUserScaleFactor(1); + + JBValue.Float value1 = JBUI.floatValue(2.6f); + JBValue.SelfCachedFloat value2 = new JBValue.SelfCachedFloat(2.6f); + + JBUI.setUserScaleFactor(2); + + assertEquals(Math.round(JBUI.scale(2.6f)), value1.get()); + assertEquals(Math.round(JBUI.scale(2.6f)), value2.get()); + assertEquals(JBUI.scale(2.6f), value1.getFloat()); + assertEquals(JBUI.scale(2.6f), value2.getFloat()); + } + + @Test + public void testUpdateTracker() { + JBUI.setUserScaleFactor(1); + + JBValue.UpdateTracker tracker = new JBValue.UpdateTracker(); + JBValue value1 = JBUI.intValue(1, tracker); + JBValue value2 = JBUI.intValue(2, tracker); + JBValue.Float value3 = JBUI.floatValue(3.6f, tracker); + + JBUI.setUserScaleFactor(2); + + assertEquals(JBUI.scale(1), value1.get()); + assertEquals(JBUI.scale(2), value2.get()); + assertEquals(Math.round(JBUI.scale(3.6f)), value3.get()); + assertEquals(JBUI.scale(3.6f), value3.getFloat()); + + tracker.forget((JBValue.Cacheable)value3); + + int scale = JBUI.scale(1); + JBUI.setUserScaleFactor(3); + + assertEquals(Math.round(scale * 3.6f), value3.get()); + assertEquals(scale * 3.6f, value3.getFloat()); + + tracker.dispose(); + + scale = JBUI.scale(1); + JBUI.setUserScaleFactor(1); + + assertEquals(scale, value1.get()); + assertEquals(scale * 2, value2.get()); + } + + @Test + public void testUIManagerInteger() { + JBUI.setUserScaleFactor(1); + String key = "JBValue.int"; + UIManager.put(key, 2); + + JBValue.UIManagerInteger value = new JBValue.UIManagerInteger(key); + + JBUI.setUserScaleFactor(2); + + assertEquals(JBUI.scale(2), value.get()); + + UIManager.put(key, 3); + + assertEquals(JBUI.scale(3), value.get()); + + UIManager.put(key, null); + } +} diff --git a/platform/util/src/com/intellij/util/ui/JBUI.java b/platform/util/src/com/intellij/util/ui/JBUI.java index 6a4c571a8212..0b19c0280073 100644 --- a/platform/util/src/com/intellij/util/ui/JBUI.java +++ b/platform/util/src/com/intellij/util/ui/JBUI.java @@ -487,10 +487,18 @@ public class JBUI { return new JBValue.Integer(value); } + public static JBValue.Integer intValue(int value, JBValue.UpdateTracker tracker) { + return new JBValue.CachedInteger(value, tracker); + } + public static JBValue.Float floatValue(float value) { return new JBValue.Float(value); } + public static JBValue.Float floatValue(float value, JBValue.UpdateTracker tracker) { + return new JBValue.CachedFloat(value, tracker); + } + public static JBDimension size(int width, int height) { return new JBDimension(width, height); } diff --git a/platform/util/src/com/intellij/util/ui/JBValue.java b/platform/util/src/com/intellij/util/ui/JBValue.java index 9241e86bb4f7..55f29e16a822 100644 --- a/platform/util/src/com/intellij/util/ui/JBValue.java +++ b/platform/util/src/com/intellij/util/ui/JBValue.java @@ -6,12 +6,28 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.LinkedList; +import java.util.List; + import static com.intellij.ui.paint.PaintUtil.RoundingMode.ROUND; /** - * A wrapper over an unscaled value which lazily scales it via {@link JBUI#scale}. + * A wrapper over an unscaled numeric value, auto-scaled via {@link JBUI#scale}. + *
+ * Use {@link Integer} or {@link Float} classes for on-demand value scaling. + *
+ * If the same JBValue object is used multiple times in a code block, then in order to save scaling ops,
+ * a {@link CachedInteger} or {@link CachedFloat} classes can be used instead. Either with a separate
+ * {@link UpdateTracker} (better to use for many JBValue objects), or with a dedicated one (for a single
+ * JBValue object) as in the {@link SelfCachedInteger} or {@link SelfCachedFloat} classes.
*
* @author tav
+ * @see JBUI#intValue(int)
+ * @see JBUI#intValue(int,UpdateTracker)
+ * @see JBUI#floatValue(float)
+ * @see JBUI#floatValue(float,UpdateTracker)
*/
public abstract class JBValue {
protected JBValue() {}
@@ -36,11 +52,18 @@ public abstract class JBValue {
*/
protected abstract float getUnscaled();
+ public interface Cacheable {
+ /**
+ * Scales and caches the value.
+ */
+ void cache();
+ }
+
/**
* JBValue wrapper over an integer value in {@link UIManager}.
*/
public static class UIManagerInteger extends JBValue {
- private final String key;
+ private final @NotNull String key;
public UIManagerInteger(@NotNull String key) {
this.key = key;
@@ -103,4 +126,125 @@ public abstract class JBValue {
return value;
}
}
+
+ /**
+ * Integer JBValue which caches its scaled value on JBUI.scale change.
+ */
+ public static class CachedInteger extends Integer implements Cacheable {
+ private int cachedScaledValue;
+
+ protected CachedInteger(int value) {
+ super(value);
+ }
+
+ /**
+ * @param value unscaled value
+ * @param tracker updates the value
+ * @see JBUI#intValue(int, UpdateTracker)
+ */
+ public CachedInteger(int value, @NotNull UpdateTracker tracker) {
+ super(value);
+ cachedScaledValue = JBUI.scale(value);
+ tracker.track(this);
+ }
+
+ @Override
+ public int get() {
+ return cachedScaledValue;
+ }
+
+ public void cache() {
+ cachedScaledValue = JBUI.scale((int)getUnscaled());
+ }
+ }
+
+ /**
+ * CachedInteger with a dedicated UpdateTracker.
+ */
+ public static class SelfCachedInteger extends CachedInteger {
+ private final @NotNull UpdateTracker tracker = new UpdateTracker();
+
+ public SelfCachedInteger(int value) {
+ super(value);
+ this.tracker.track(this);
+ }
+ }
+
+ /**
+ * Float JBValue which caches its scaled value on JBUI.scale change.
+ */
+ public static class CachedFloat extends Float implements Cacheable {
+ private float cachedScaledValue;
+
+ protected CachedFloat(float value) {
+ super(value);
+ }
+
+ /**
+ * @param value unscaled value
+ * @param tracker updates the value
+ * @see JBUI#floatValue(float, UpdateTracker)
+ */
+ public CachedFloat(float value, UpdateTracker tracker) {
+ super(value);
+ cachedScaledValue = JBUI.scale(value);
+ tracker.track(this);
+ }
+
+ @Override
+ public int get() {
+ return ROUND.round(cachedScaledValue);
+ }
+
+ @Override
+ public float getFloat() {
+ return cachedScaledValue;
+ }
+
+ public void cache() {
+ cachedScaledValue = JBUI.scale(getUnscaled());
+ }
+ }
+
+ /**
+ * CachedFloat with a dedicated UpdateTracker.
+ */
+ public static class SelfCachedFloat extends CachedFloat {
+ private final @NotNull UpdateTracker tracker = new UpdateTracker();
+
+ public SelfCachedFloat(float value) {
+ super(value);
+ this.tracker.track(this);
+ }
+ }
+
+ /**
+ * Tracks for a list of {@link Cacheable} values and auto-updates them on {@link JBUI#USER_SCALE_FACTOR_PROPERTY}} change.
+ */
+ public static class UpdateTracker {
+ private final List