From 9eba4b437cd9413435fed4258ea28eb5adfaa672 Mon Sep 17 00:00:00 2001 From: Anton Tarasov Date: Wed, 11 Apr 2018 18:33:34 +0300 Subject: [PATCH] Add JBValue.UpdateTracker to track for JBUI.scale change + unit test --- .../com/intellij/util/ui/JBValueTest.java | 95 +++++++++++ .../util/src/com/intellij/util/ui/JBUI.java | 8 + .../src/com/intellij/util/ui/JBValue.java | 148 +++++++++++++++++- 3 files changed, 249 insertions(+), 2 deletions(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/util/ui/JBValueTest.java 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 list = new LinkedList(); + private final PropertyChangeListener listener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + for (Cacheable value : list) value.cache(); + } + }; + + public UpdateTracker() { + JBUI.addPropertyChangeListener(JBUI.USER_SCALE_FACTOR_PROPERTY, listener); + } + + public void track(Cacheable value) { + list.add(value); + } + + public void forget(Cacheable value) { + list.remove(value); + } + + public void dispose() { + JBUI.removePropertyChangeListener(JBUI.USER_SCALE_FACTOR_PROPERTY, listener); + list.clear(); + } + } }